Developer API
Telegram exposes its sending functionality four ways: a registration +
runtime dispatch pattern for other plugins to define their own event
types, a plain PHP service API for direct sends, a console command
for cron/CI/deploy scripts, and MCP tools for AI-agent access. All four
ultimately go through the same MessagesService, so every send — regardless
of entry point — is logged the same way (see Usage → Logs).
Registering a custom event type
The intended integration pattern for another plugin: register event types during bootstrap, then dispatch them when the real business event happens. Once registered, an event type automatically appears in every channel's Automation tab (see Usage → Automation tab) — an admin decides which channels receive it and can override its message template per channel, without touching PHP.
1. Implement the provider interface
<?php
namespace vendor\shop\telegram;
use Yui\Telegram\interfaces\TelegramEventProviderInterface;
class ShopTelegramEventProvider implements TelegramEventProviderInterface
{
public function getTelegramEventTypes(): array
{
return [
[
'handle' => 'shop.orderCompleted',
'provider' => 'shop',
'providerLabel' => 'Shop',
'group' => 'Orders',
'label' => 'Order completed',
'description' => 'Sent when an order is completed.',
'defaultTemplate' => 'Order {{ number }} completed for {{ total }}. Items: {{ items }}',
],
[
'handle' => 'shop.orderPaid',
'provider' => 'shop',
'providerLabel' => 'Shop',
'group' => 'Payments',
'label' => 'Order paid',
'description' => 'Sent when an order payment is confirmed.',
'defaultTemplate' => 'Order {{ number }} was paid by {{ customer }} for {{ total }}.',
],
];
}
}
Always set provider, providerLabel, group, label, description, and
defaultTemplate — the Automation tab groups events by provider and then by
group, so a plugin that skips these fields shows up as an unlabeled,
ungrouped entry.
2. Register the provider
In the registering plugin's init():
use yii\base\Event;
use Yui\Telegram\events\RegisterEventTypesEvent;
use Yui\Telegram\services\EventTypesService;
use vendor\shop\telegram\ShopTelegramEventProvider;
Event::on(
EventTypesService::class,
EventTypesService::EVENT_REGISTER_EVENT_TYPES,
function(RegisterEventTypesEvent $event) {
$event->addProvider(new ShopTelegramEventProvider());
}
);
A low-level alternative exists for registering a single type without a
provider class — call $event->addType($handle, $config) with the same
fields, inside the same listener.
3. Dispatch the real event
When the actual business event happens:
use Yui\Telegram\Plugin as TelegramPlugin;
TelegramPlugin::$plugin->messages->sendEventNotification(
'shop.orderCompleted',
[
'number' => $order->reference,
'customer' => $order->email,
'total' => $order->totalPrice,
'items' => implode(', ', $itemNames),
'itemCount' => count($itemNames),
]
);
Telegram does not enforce a schema on the variables array — the registering
plugin decides which placeholders exist, and those are exactly the
{{ placeholders }} available when an admin writes a message override for
that event type. If no channel is subscribed to the event, or no template
(default or override) resolves, the dispatch is a no-op and a skipped log
row is written (subject to the Log Skipped Events setting).
MessagesService PHP API
Get the service via Yui\Telegram\Plugin::$plugin->messages.
| Method | Purpose |
|---|---|
send(string $message, array $options = []): bool | Sends raw text to a channel. Without options['channel'], uses the Default Channel from Settings. |
sendToChannel(string $channelHandle, string $message, array $options = []): bool | Shorthand for send() with an explicit channel handle. |
sendEventNotification(string $eventHandle, array $variables = [], array $options = []): int | Dispatches a registered event type (built-in or custom) to every channel subscribed to it, rendering each channel's override or the type's default template. Returns the number of channels the message was actually sent to. |
sendOrderNotification(string|int $orderNumber, array $details = [], array $options = []): bool | Convenience wrapper that formats $details as a bold Order #<number> header followed by key/value lines — no message template needed. Defaults options['prefix'] to Orders. |
sendKeyValueReport(string $title, array $values, array $options = []): bool | The lower-level helper sendOrderNotification() is built on — a bold title followed by key: value lines for arbitrary reports. |
Common $options keys accepted by send() (and passed through by the
helpers above): channel (handle to send to), parseMode (HTML or
MarkdownV2, overrides the setting default), disableWebPreview,
disableNotification, throw (rethrow the underlying exception instead of
returning false on failure), eventHandle (only used for log
attribution), prefix (overrides the channel/global prefix for this send).
use Yui\Telegram\Plugin as TelegramPlugin;
TelegramPlugin::$plugin->messages->sendOrderNotification(
$order->number,
[
'status' => 'paid',
'total' => $order->totalPriceAsCurrency,
'customer' => $order->email,
],
['channel' => 'ecommerce']
);
Console command
./craft telegram/send --message="Deploy complete" --channel=ops
| Option | Alias | Description |
|---|---|---|
--message | -m | Required. The message text to send. |
--channel | -c | Optional. Channel handle; falls back to the Default Channel from Settings. |
Exits 0 on success, non-zero with a stderr message on failure (missing
--message, an exception, or a delivery failure) — safe to use in a deploy
script or cron job and check the exit code.
MCP tools
When the yui/mcp plugin is installed, Telegram registers two tools so an
MCP client (an AI agent connected to the Craft install) can send messages
without CP or console access:
| Tool | Permission | Purpose |
|---|---|---|
telegram_send | write | Send a plain text message to a channel (optional — falls back to the default channel). |
telegram_send_order_notification | write | Send a formatted order notification: order_number (required), details (optional key/value object), channel (optional). Renders the same way as sendOrderNotification() above. |
Both tools return {"success": true|false} and go through the same
MessagesService calls described above, so failures are visible in
Logs the same way a CP or console send would be.
Plugin events
MessagesService::EVENT_BEFORE_SEND
Fired immediately before a message is sent, for every send path (raw
send(), event notifications, order/key-value reports). Listeners can
rewrite the message text or delivery options, or abort the send entirely.
use yii\base\Event;
use Yui\Telegram\services\MessagesService;
use Yui\Telegram\events\BeforeSendMessageEvent;
Event::on(
MessagesService::class,
MessagesService::EVENT_BEFORE_SEND,
function(BeforeSendMessageEvent $event) {
// Modify $event->message or $event->options
$event->message .= "\n\n#staging";
// Or abort the send entirely:
// $event->isValid = false;
}
);
EventTypesService::EVENT_REGISTER_EVENT_TYPES
Fired when Telegram collects available event types (for the Automation tab
and for sendEventNotification() lookups). This is the event used by
Registering a custom event type above —
listen to it to call $event->addProvider(...) or $event->addType(...).
Built-in automation triggers
For reference, the handle constants used by Telegram's own built-in
automation (all defined on Yui\Telegram\models\Channel):
TRIGGER_ENTRY_SAVED, TRIGGER_ENTRY_DELETED, TRIGGER_ASSET_SAVED,
TRIGGER_ASSET_DELETED, TRIGGER_CATEGORY_SAVED,
TRIGGER_CATEGORY_DELETED, TRIGGER_USER_SAVED, TRIGGER_USER_DELETED,
TRIGGER_EMAIL_SENT, TRIGGER_EMAIL_FAILED — all registered under the
core provider. A custom event type must not reuse one of these handles.
Notes for integrators
- Element automation triggers (entry/asset/category/user saved/deleted) automatically skip drafts and revisions — only "real" saves and deletes reach subscribed channels.
botTokenandchatId(andmessageThreadId) on a channel support environment variable references (e.g.$TELEGRAM_BOT_TOKEN), resolved via Craft's standardApp::parseEnv()— the recommended way to keep bot tokens out of the database/project config.- A channel's
notificationsarray is validated against the currently registered event handles on save — enabling an event type that no longer exists (e.g. a plugin was uninstalled) is rejected.