Skip to main content
Version: 2.0.0

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.

MethodPurpose
send(string $message, array $options = []): boolSends raw text to a channel. Without options['channel'], uses the Default Channel from Settings.
sendToChannel(string $channelHandle, string $message, array $options = []): boolShorthand for send() with an explicit channel handle.
sendEventNotification(string $eventHandle, array $variables = [], array $options = []): intDispatches 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 = []): boolConvenience 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 = []): boolThe 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
OptionAliasDescription
--message-mRequired. The message text to send.
--channel-cOptional. 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:

ToolPermissionPurpose
telegram_sendwriteSend a plain text message to a channel (optional — falls back to the default channel).
telegram_send_order_notificationwriteSend 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.
  • botToken and chatId (and messageThreadId) on a channel support environment variable references (e.g. $TELEGRAM_BOT_TOKEN), resolved via Craft's standard App::parseEnv() — the recommended way to keep bot tokens out of the database/project config.
  • A channel's notifications array 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.