Telegram как канал уведомлений в Laravel встраивается штатно: система нотификаций рассчитана на несколько каналов, и Telegram становится одним из них наравне с почтой. Это удобнее собственного HTTP-клиента и, что важнее, даёт очередь, повторы и единый формат из коробки.
Разберём настройку и три вещи, которые обычно упускают: экранирование текста, поведение при отказе и то, что делать с пользователями, не начавшими диалог с ботом.
Создание Telegram-бота
- Откройте BotFather в Telegram
- Отправьте /newbot
- Введите имя и username бота
- Получите токен
Добавьте в .env:
TELEGRAM_BOT_TOKEN=123456789:ABCdefGHIjklMNOpqrsTUVwxyz
TELEGRAM_ADMIN_CHAT_ID=987654321Чтобы узнать chat_id, отправьте боту сообщение, затем откройте api.telegram.org/botTOKEN/getUpdates и найдите chat.id в ответе.
Установка пакета
composer require laravel-notification-channels/telegramНастройте в config/services.php:
'telegram-bot-api' => [
'token' => env('TELEGRAM_BOT_TOKEN'),
],Простая отправка
TelegramMessage::create()
->to(config('services.telegram.admin_chat_id'))
->content('Новый заказ #123!')
->send();Notification класс
Создайте notification:
php artisan make:notification OrderCreatedNotificationПример класса:
class OrderCreatedNotification extends Notification
{
public function __construct(
public Order $order
) {}
public function via($notifiable): array
{
return [TelegramChannel::class];
}
public function toTelegram($notifiable): TelegramMessage
{
$url = route('admin.orders.show', $this->order);
return TelegramMessage::create()
->to($notifiable->telegram_chat_id)
->content("Новый заказ #{$this->order->id}\n"
. "Клиент: {$this->order->user->name}\n"
. "Сумма: {$this->order->total} руб."
)
->button('Открыть заказ', $url);
}
}Отправка уведомления
Конкретному пользователю:
$admin = User::where('role', 'admin')->first();
$admin->notify(new OrderCreatedNotification($order));On-demand (без модели):
Notification::route('telegram', config('services.telegram.admin_chat_id'))
->notify(new OrderCreatedNotification($order));Форматирование сообщений
Текст с разметкой нужно экранировать. В режимах Markdown и HTML символы _, *, [, < в тексте интерпретируются как разметка. Имя клиента вида Иван_Петров или название товара с угловой скобкой ломают сообщение — Telegram вернёт ошибку разбора и уведомление не уйдёт вовсе.
Это не гипотетический случай: любые данные, пришедшие от пользователя (имя, комментарий к заказу, название товара), рано или поздно содержат такой символ. Экранируйте подставляемые значения либо не используйте разметку там, где она не нужна.
Поддерживается Markdown:
TelegramMessage::create()
->content("*Жирный текст*\n"
. "_Курсив_\n"
. "`Моноширинный`\n"
. "[Ссылка](https://example.com)"
);Кнопки
Inline-кнопки:
TelegramMessage::create()
->content('Выберите действие:')
->button('Подтвердить', route('orders.confirm', $order))
->button('Отменить', route('orders.cancel', $order));Несколько рядов кнопок:
TelegramMessage::create()
->content('Выберите категорию:')
->button('Электроника', '/category/electronics')
->button('Одежда', '/category/clothes')
->button('Все товары', '/catalog', true) // true = новый ряд
->button('Корзина', '/cart', true);Сервисный класс
Для централизации уведомлений создайте TelegramNotifier:
class TelegramNotifier
{
private string $adminChatId;
public function __construct()
{
$this->adminChatId = config('services.telegram.admin_chat_id');
}
public function orderCreated(Order $order): void
{
$this->send(
"Новый заказ #{$order->id}\n"
. "Клиент: {$order->user->name}\n"
. "Сумма: " . $this->formatMoney($order->total),
route('admin.orders.show', $order)
);
}
public function orderPaid(Order $order): void
{
$this->send(
"Заказ #{$order->id} оплачен\n"
. "Сумма: " . $this->formatMoney($order->total)
);
}
public function errorAlert(string $message): void
{
$this->send("Ошибка на сайте\n\n" . $message);
}
private function send(string $content, string $url = null): void
{
$message = TelegramMessage::create()
->to($this->adminChatId)
->content($content);
if ($url) {
$message->button('Открыть', $url);
}
try {
$message->send();
} catch (Exception $e) {
logger()->error('Telegram notification failed');
}
}
private function formatMoney(int $cents): string
{
return number_format($cents / 100, 0, ',', ' ') . ' руб.';
}
}Использование:
app(TelegramNotifier::class)->orderCreated($order);Отправка в очередь
Добавьте интерфейс ShouldQueue:
class OrderCreatedNotification extends Notification implements ShouldQueue
{
use Queueable;
public string $queue = 'notifications';
}При настройке webhook проверяйте, что запрос пришёл от Telegram. Используйте secret token при регистрации webhook.
Что учесть в продакшене
Пользователь должен сам начать диалог с ботом. Telegram не позволяет боту написать первым: пока человек не нажал «Старт», отправка вернёт ошибку. Для уведомлений сотрудникам это решается один раз при подключении, для клиентов — превращается в отдельный сценарий подключения, и его нужно продумать до того, как обещать заказчику уведомления в Telegram.
Отказ должен быть заметен, но не критичен. Клиент заблокировал бота, токен отозван, Telegram недоступен — во всех случаях бизнес-операция (заказ, регистрация) обязана завершиться успешно. Отсюда: отправка только через очередь, обработка ошибки в failed(), и запасной канал для критичных уведомлений. Уведомление, ушедшее только в Telegram и не доставленное, равносильно неотправленному.
Ограничения частоты у Telegram реальны. Примерно 30 сообщений в секунду суммарно и заметно меньше в один и тот же чат. Массовая рассылка по базе клиентов упрётся в это ограничение, и без задержек между отправками бот получит временную блокировку. Для рассылок — отдельная очередь с ограничением частоты, а не общая.
Итоги
| Метод | Назначение |
|---|---|
| TelegramMessage::create() | Быстрая отправка |
| Notification класс | Структурированные уведомления |
| button() | Inline-кнопка с URL |
| Webhook | Обработка ответов от бота |
Чек-лист:
- Токен в
.env, не в репозитории. При утечке —/revokeу BotFather. - Отправка только через очередь с
ShouldQueue: внешний сервис не должен влиять на скорость и успешность основной операции. - Ошибки обрабатываются в
failed(), а не теряются. Отдельно стоит различать «пользователь заблокировал бота» (нужно отключить канал для этого пользователя) и «Telegram недоступен» (нужен повтор). - Подставляемые значения экранируются, если используется разметка.
- Есть запасной канал для критичных уведомлений.
- Для рассылок — отдельная очередь с ограничением частоты.
Если бот должен ещё и принимать сообщения, а не только отправлять, — проверяйте secret_token вебхука. Без этого адрес обработчика становится единственной защитой, а он не секрет: попадает в логи, в историю команд и легко подбирается.
Комментарии
Система комментариев скоро будет подключена. А пока вы можете написать мне в Telegram или на email.