Skip to content
  1. Extras
  2. MiniShop3
  3. Development
  4. Events
  5. Notifications

Notification events ​

Events for the notification system: sending, modifying, registering channels.

Built-in channels ​

MiniShop3 includes two notification channels out of the box:

ChannelClassDescription
emailEmailChannelSends via MODX modMail
telegramTelegramChannelSends via Telegram Bot API

Email channel ​

Uses standard MODX modMail. Templates are configured via chunks.

Telegram channel ​

Sends messages via Telegram Bot API. Requires settings:

  • ms3_telegram_bot_token — bot token
  • ms3_telegram_manager_chat_id — recipient Chat ID

Telegram limitation

A Telegram bot cannot start a conversation with a user. The user must send the first message to the bot. So Telegram notifications only work for managers who have set their Chat ID in advance.

Sending Telegram notifications to customers ​

To send Telegram notifications to customers you need to:

  1. Get customer consent for notifications
  2. Link the customer’s Telegram account to their store profile
  3. Store the customer’s Chat ID

Step 1: Add a field for Chat ID ​

Add a field to msCustomer to store Chat ID:

sql
ALTER TABLE modx_ms3_customers ADD COLUMN telegram_chat_id VARCHAR(50) NULL;

Or use Extra Fields in MiniShop3 settings.

Step 2: Create a bot with Deep Linking ​

Use Telegram Deep Linking to link the account:

php
<?php
// Generate unique link for linking
$customerId = $msCustomer->get('id');
$token = hash('sha256', $customerId . $modx->getOption('ms3_snippet_token_secret'));
$linkCode = base64_encode($customerId . ':' . substr($token, 0, 16));

$botUsername = 'YourShopBot';
$telegramLink = "https://t.me/{$botUsername}?start={$linkCode}";

Step 3: Handle /start on the bot side ​

The bot must handle the start parameter and save the Chat ID:

php
<?php
// Bot webhook handler (simplified)
$update = json_decode(file_get_contents('php://input'), true);
$message = $update['message'] ?? null;

if ($message && str_starts_with($message['text'], '/start ')) {
    $linkCode = substr($message['text'], 7);
    $decoded = base64_decode($linkCode);
    [$customerId, $tokenPart] = explode(':', $decoded);

    $expectedToken = hash('sha256', $customerId . $modx->getOption('ms3_snippet_token_secret'));
    if (substr($expectedToken, 0, 16) === $tokenPart) {
        $customer = $modx->getObject(\MiniShop3\Model\msCustomer::class, $customerId);
        if ($customer) {
            $customer->set('telegram_chat_id', $message['chat']['id']);
            $customer->save();
            // Send confirmation
            sendTelegramMessage($message['chat']['id'], 'Notifications enabled');
        }
    }
}

Step 4: Plugin to send notifications to the customer ​

php
<?php
/**

 * Plugin: Telegram notifications to customers
 * Events: msOnChangeOrderStatus

 */

switch ($modx->event->name) {
    case 'msOnChangeOrderStatus':
        $order = $scriptProperties['order'];
        $newStatus = $scriptProperties['status'];

        $customer = $order->getOne('Customer');
        if (!$customer) return;

        $chatId = $customer->get('telegram_chat_id');
        if (empty($chatId)) return;

        $statusName = $modx->lexicon($newStatus->get('name'));
        $orderNum = $order->get('num');
        $message = "Order #{$orderNum}\nStatus: {$statusName}";

        $botToken = $modx->getOption('ms3_telegram_bot_token');
        $url = "https://api.telegram.org/bot{$botToken}/sendMessage";
        $ch = curl_init($url);
        curl_setopt_array($ch, [
            CURLOPT_POST => true,
            CURLOPT_POSTFIELDS => ['chat_id' => $chatId, 'text' => $message, 'parse_mode' => 'HTML'],
            CURLOPT_RETURNTRANSFER => true,
        ]);
        curl_exec($ch);
        curl_close($ch);
        break;
}

Add to the customer profile template:

fenom
{if $customer.telegram_chat_id}
    <div class="alert alert-success">
        Telegram notifications enabled
    </div>
{else}
    <a href="{$telegramLink}" class="btn btn-primary" target="_blank">
        Enable Telegram notifications
    </a>
{/if}

Alternative

Instead of your own bot, you can use third-party services (SendPulse, Unisender, etc.) that provide Telegram API and handle subscriptions.

msOnBeforeSendNotification ​

Fired before sending a notification. Lets you modify data or cancel sending.

Parameters ​

ParameterTypeDescription
notificationNotificationNotification instance
recipientarray (by reference)Recipient payload
recipientTypestringcustomer or manager
channelsstring[] (by reference)Channel names, e.g. ['email', 'telegram']

Aborting the operation ​

If the plugin returns non-empty output, sending is cancelled:

Use return false or return 'cancel'. The event uses EventGate::invokeRaw().

php
<?php
switch ($modx->event->name) {
    case 'msOnBeforeSendNotification':
        $recipient = $scriptProperties['recipient'];

        // Disallow sending at night
        $hour = (int)date('G');
        if ($hour >= 23 || $hour < 8) {
            return false;
        }

        // Block temporary emails
        if (!empty($recipient['email'])) {
            $domain = substr($recipient['email'], strpos($recipient['email'], '@') + 1);
            if (in_array($domain, ['tempmail.com', 'throwaway.com'], true)) {
                return 'cancel';
            }
        }
        break;
}

Modifying via returnedValues ​

php
<?php
switch ($modx->event->name) {
    case 'msOnBeforeSendNotification':
        $values = &$modx->event->returnedValues;
        $values['recipient'] = array_replace(
            $scriptProperties['recipient'],
            ['email' => 'ops@example.com']
        );
        $values['channels'] = ['email'];
        break;
}

msOnAfterSendNotification ​

Fired after sending a notification (success or failure).

Parameters ​

ParameterTypeDescription
notificationNotificationInterfaceNotification object
channelChannelInterfaceChannel
recipientarrayRecipient data
successboolSend result
errorstring | nullError message (if any)

Example ​

php
<?php
switch ($modx->event->name) {
    case 'msOnAfterSendNotification':
        $notification = $scriptProperties['notification'];
        $channel = $scriptProperties['channel'];
        $recipient = $scriptProperties['recipient'];
        $success = $scriptProperties['success'];
        $error = $scriptProperties['error'];

        // Log
        if ($success) {
            $modx->log(modX::LOG_LEVEL_INFO, sprintf(
                '[Notification] Sent: %s → %s (%s)',
                get_class($notification),
                $recipient['email'] ?? $recipient['phone'] ?? 'unknown',
                get_class($channel)
            ));
        } else {
            $modx->log(modX::LOG_LEVEL_ERROR, sprintf(
                '[Notification] Error: %s → %s: %s',
                get_class($notification),
                $recipient['email'] ?? $recipient['phone'] ?? 'unknown',
                $error
            ));
        }
        break;
}

Retry on failure ​

php
<?php
switch ($modx->event->name) {
    case 'msOnAfterSendNotification':
        $success = $scriptProperties['success'];

        if (!$success) {
            $notification = $scriptProperties['notification'];
            $recipient = $scriptProperties['recipient'];

            // Add to retry queue
            $queue = $modx->newObject('msNotificationQueue', [
                'notification_class' => get_class($notification),
                'notification_data' => json_encode($notification->getContext()),
                'recipient' => json_encode($recipient),
                'retry_count' => 0,
                'scheduled_at' => date('Y-m-d H:i:s', strtotime('+5 minutes')),
            ]);
            $queue->save();
        }
        break;
}

msOnRegisterNotificationChannels ​

Fired when registering notification channels. Lets you add custom channels.

Parameters ​

ParameterTypeDescription
managerNotificationManagerNotification manager

Registering a custom channel ​

php
<?php
switch ($modx->event->name) {
    case 'msOnRegisterNotificationChannels':
        /** @var \MiniShop3\Notifications\NotificationManager $manager */
        $manager = $scriptProperties['manager'];

        // Register Telegram channel
        $manager->registerChannel('telegram', function($modx) {
            return new MyTelegramChannel($modx);
        });

        // Register Push channel
        $manager->registerChannel('push', function($modx) {
            return new MyPushChannel($modx);
        });
        break;
}

Custom channel example ​

php
<?php
namespace MyComponent\Notifications;

use MiniShop3\Notifications\Channels\ChannelInterface;
use MiniShop3\Notifications\NotificationInterface;
use MODX\Revolution\modX;

class TelegramChannel implements ChannelInterface
{
    protected modX $modx;
    protected string $botToken;

    public function __construct(modX $modx)
    {
        $this->modx = $modx;
        $this->botToken = $modx->getOption('my_telegram_bot_token');
    }

    public function send(NotificationInterface $notification, array $recipient): bool
    {
        $chatId = $recipient['telegram_id'] ?? null;
        if (!$chatId) return false;

        $message = $notification->toTelegram($recipient);
        $url = "https://api.telegram.org/bot{$this->botToken}/sendMessage";
        $data = [
            'chat_id' => $chatId,
            'text' => $message,
            'parse_mode' => 'HTML',
        ];

        $ch = curl_init($url);
        curl_setopt($ch, CURLOPT_POST, true);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        $result = curl_exec($ch);
        curl_close($ch);

        return json_decode($result, true)['ok'] ?? false;
    }
}

Full example: notification analytics ​

php
<?php
/**

 * Plugin: Notification analytics
 * Events: msOnBeforeSendNotification, msOnAfterSendNotification

 */

switch ($modx->event->name) {

    case 'msOnBeforeSendNotification':
        $notification = $scriptProperties['notification'];
        $modx->eventData['notification_start'] = microtime(true);
        break;

    case 'msOnAfterSendNotification':
        $notification = $scriptProperties['notification'];
        $channel = $scriptProperties['channel'];
        $recipient = $scriptProperties['recipient'];
        $success = $scriptProperties['success'];
        $error = $scriptProperties['error'];

        $startTime = $modx->eventData['notification_start'] ?? microtime(true);
        $duration = round((microtime(true) - $startTime) * 1000, 2);

        $stats = $modx->newObject('msNotificationStats', [
            'notification_type' => get_class($notification),
            'channel' => get_class($channel),
            'recipient_type' => $recipient['type'] ?? 'unknown',
            'success' => $success ? 1 : 0,
            'error' => $error,
            'duration_ms' => $duration,
            'createdon' => date('Y-m-d H:i:s'),
        ]);
        $stats->save();

        if (!$success) {
            $errorCount = $modx->getCount('msNotificationStats', [
                'success' => 0,
                'createdon:>=' => date('Y-m-d H:i:s', strtotime('-1 hour')),
            ]);
            if ($errorCount > 10) {
                $modx->log(modX::LOG_LEVEL_ERROR,
                    "[Notifications] ALERT: {$errorCount} errors in the last hour!"
                );
            }
        }
        break;
}