Skip to content
All articles

Telegram notifications for an existing Laravel app

Sep 16, 2026 · Automation · 3 min

·By Dimitri Pisarev

Telegram is the cheapest usable notification channel a Laravel app can add: register a bot, store each recipient's chat id, and let a queued job POST JSON to the sendMessage endpoint. No SDK required, no webhook needed for pure outbound delivery, and the documented limits (roughly 30 messages per second overall, one per second per chat) are queue problems with queue solutions.

What do you need before the first message?#

Two values and one consent step:

  1. A bot token from @BotFather: /newbot, sixty seconds.
  2. The recipient's chat id. A user opens the bot and presses Start; the id arrives with any update (in development, getUpdates shows it directly). Groups work the same way, and their ids are negative.
  3. The consent step is the important one: a bot cannot message a user who never started it. Telegram enforces this, and it is the right default anyway. Chat ids are opt-in personal data; store and handle them like it.

The channel in one class#

Laravel's notification system takes a custom channel in about a dozen lines:

class TelegramChannel
{
    public function send(object $notifiable, Notification $notification): void
    {
        $payload = $notification->toTelegram($notifiable);
 
        $response = Http::asJson()->post(
            'https://api.telegram.org/bot'.config('services.telegram.token').'/sendMessage',
            [
                'chat_id'              => $payload->chatId,
                'text'                 => $payload->text,
                'parse_mode'           => 'HTML',
                'link_preview_options' => ['is_disabled' => true],
            ],
        )->json();
 
        if (! ($response['ok'] ?? false)) {
            throw TelegramDelivery::failed($response['description'] ?? 'unknown error');
        }
    }
}

The notification declares the channel in via(), the notifiable model exposes routeNotificationForTelegram() returning the stored chat id, and the queue connection does the rest. With parse_mode: HTML, escaping is your job: run user data through htmlspecialchars(), or the message dies on the first angle bracket in a stack trace.

Rate limits are a queue concern, not a channel concern#

The Bot API FAQ gives the numbers: avoid more than one message per second to the same chat, and stay under about 30 per second when broadcasting. Violations come back as HTTP 429 with a retry_after value. The queue is where that value belongs:

// inside the queued job, on a 429 response
$this->release($response['parameters']['retry_after'] ?? 5);

A report loop that notifies two hundred subscribers should be two hundred queued jobs with jitter between them, not a foreach firing synchronously. The same discipline applies as for any webhook-shaped traffic: idempotent workers, bounded concurrency, retries with jitter and a ceiling, and a dead-letter path a human actually reads.

Buttons turn alerts into workflows#

sendMessage accepts an InlineKeyboardMarkup. A deploy notice with a Roll back button, an order event with Approve and Reject: the alert stops being a sticky note and becomes an action. URL buttons stay one-way and need no webhook: a deep link straight into the admin panel is often enough.

Callback buttons are a different commitment: presses arrive as callback queries, so the bot now needs a webhook and an idempotent handler for them. At that point you are building a two-way bot, and the shop bot anatomy applies before improvising one.

Note

Message length caps at 4096 characters. Split or truncate long reports deliberately in code. "Bad Request: message is too long" arriving from the API is truncation by crash, which is the worst of both.

Where this earns its keep#

I route operational signals for small systems into a Telegram group instead of a monitoring product: queue depth, failed job spikes, nightly backups, order events. The group archive becomes a searchable incident timeline, emoji reactions work as the acknowledgment mechanism, and the team already has the app installed. For a small team that is an observability budget well spent. Messages are the delivery layer, not the analysis layer: when metrics start mattering more than messages, graduate to real tooling and keep Telegram as the pager.