Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Telegram Notifier Laravel Package

symfony/telegram-notifier

Provides Telegram integration for Symfony Notifier. Configure via a TELEGRAM_DSN with token and chat ID, send ChatMessages with TelegramOptions for parse mode, inline keyboards, and other Bot API settings, and optionally target a local API server.

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal Setup

  1. Install the package:

    composer require symfony/telegram-notifier
    
  2. Configure the DSN in .env:

    TELEGRAM_DSN=telegram://BOT_TOKEN@default?channel=CHAT_ID
    
    • Replace BOT_TOKEN with your Telegram bot token (from @BotFather).
    • Replace CHAT_ID with your target chat ID (e.g., -1001234567890 for a group or @username for a private chat).
  3. Send your first message in a Laravel controller or command:

    use Symfony\Component\Notifier\Notifier;
    use Symfony\Component\Notifier\Message\ChatMessage;
    
    $notifier = new Notifier();
    $message = new ChatMessage('Hello from Laravel!');
    $notifier->send($message);
    

First Use Case: Alerts

Use Telegram Notifier for real-time alerts (e.g., deployment notifications, errors):

$message = new ChatMessage('🚀 Deployment successful!');
$message->options((new TelegramOptions())->parseMode('MarkdownV2'));
$notifier->send($message);

Since v8.1.6: pass plain, unescaped text — the transport now escapes MarkdownV2 markup for you (see Gotchas #3).


Implementation Patterns

1. Structured Notifications

Leverage TelegramOptions for rich messages:

$options = (new TelegramOptions())
    ->parseMode('MarkdownV2')
    ->disableWebPagePreview(true)
    ->replyMarkup((new InlineKeyboardMarkup())
        ->inlineKeyboard([
            (new InlineKeyboardButton('View Logs'))->url('/logs'),
            (new InlineKeyboardButton('Retry'))->callbackData('retry'),
        ])
    );

$message = new ChatMessage('⚠️ Task failed!');
$message->options($options);
$notifier->send($message);

2. File Attachments

Send logs, screenshots, or documents:

// Public URL
$options = (new TelegramOptions())->document('https://example.com/report.pdf');

// Local file (uploaded via multipart)
$options = (new TelegramOptions())->uploadDocument(storage_path('logs/error.log'));

// File ID (from a previously uploaded file)
$options = (new TelegramOptions())->document('ABC123XYZ');

3. Interactive Workflows

Handle callback queries (e.g., buttons) in a Laravel route:

use Symfony\Component\Notifier\Bridge\Telegram\TelegramOptions;

public function handleCallback(Request $request)
{
    $callbackQueryId = $request->input('callback_query_id');
    $options = (new TelegramOptions())
        ->answerCallbackQuery($callbackQueryId, showAlert: true);

    $message = new ChatMessage('Action confirmed!');
    $message->options($options);
    $notifier->send($message);
}

4. Threaded Messages

Organize messages in threads (Symfony 7.3+):

$options = (new TelegramOptions())->messageThreadId('thread_123');
$message = new ChatMessage('Threaded update');
$message->options($options);
$notifier->send($message);

5. Conditional Notifications

Use Laravel’s service container to dynamically configure notifications:

// config/services.php
'telegram' => [
    'default_chat' => env('TELEGRAM_DEFAULT_CHAT', '-1001234567890'),
    'chats' => [
        'admin' => '-1009876543210',
        'support' => '@support_bot',
    ],
],

// In a service
public function notifyAdmins(string $message)
{
    $options = (new TelegramOptions())->chatId(config('services.telegram.chats.admin'));
    $notifier->send(new ChatMessage($message)->options($options));
}

6. Event-Driven Notifications

Integrate with Laravel events (e.g., Deployed, JobFailed):

// In EventServiceProvider
protected $listen = [
    JobFailed::class => [TelegramNotifier::class, 'handleJobFailure'],
];

// In TelegramNotifier
public function handleJobFailure(JobFailed $event)
{
    $message = new ChatMessage("Job [{$event->job}] failed: {$event->exception}");
    $message->options((new TelegramOptions())->parseMode('MarkdownV2'));
    $notifier->send($message);
}

7. User-Supplied Content in Messages

Because the transport escapes markup as of v8.1.6, it is now safe to interpolate untrusted strings (usernames, exception messages containing _, *, ., -) into a MarkdownV2 message — no manual addslashes()-style pre-escaping:

$message = new ChatMessage("User {$user->name} triggered rule {$rule->slug}");
$message->options((new TelegramOptions())->parseMode('MarkdownV2'));

Gotchas and Tips

1. DSN Configuration Pitfalls

  • Chat ID Format:
    • Groups/channels: Negative IDs (e.g., -1001234567890).
    • Private chats: Usernames (e.g., @username) or positive IDs.
    • Debugging: Use @BotFather to get your bot’s token; use getUpdates to discover chat IDs.
  • Transport availability: symfony/telegram-notifier only registers the Telegram transport. In Laravel you must wire it into a Notifier yourself (or rely on symfony/notifier's notifier.transport_factory services), otherwise Notifier::send() silently has no transport to use.

2. File Upload Quirks

  • Size Limits: Telegram enforces file size limits. For large files:
    • Upload to a CDN first, then send the URL.
    • Use upload* methods for local files (e.g., uploadDocument).
  • Stickers: Cannot be sent via file_id. Use uploadSticker or a public URL.

3. MarkdownV2 Parsing — Changed in v8.1.6

v8.1.6 ships bug #65644 — "Fix escaping of MarkdownV2 markup in TelegramTransport". The transport now performs the MarkdownV2 escaping of the message body before sending.

Practical consequences:

  • Stop hand-escaping. Passing User \*John\* \_Doe\_ will now render with literal backslashes in Telegram (double-escaping). Pass User *John* _Doe_ instead.
  • Pin the version. If you are on v8.1.4 / v8.1.5, the fix is absent and you still need to escape reserved characters (_ * [ ] ( ) ~ > # + - = | { } . !) yourself. Set "symfony/telegram-notifier": "^8.1.6"incomposer.json` for consistent behaviour across environments.
  • Symptom to recognise: messages showing \_, \., \- literally, or conversely Telegram rejecting with 400 Bad Request: can't parse entities — both point at an escaping mismatch between your code and the installed transport version.
  • Prefer no parse mode for untrusted text. If you don't need formatting, omit parseMode() entirely; plain text has no escaping rules and is the safest default for user-generated strings.
  • Test with a MarkdownV2 fixture. Add a feature test that sends a message containing _ * . - ( ) and asserts the outbound payload, since this is exactly the code path that changed.
// Correct on >= v8.1.6 — raw text, transport escapes it
$message = new ChatMessage('User *John* _Doe_ — score 9.5');
$message->options((new TelegramOptions())->parseMode('MarkdownV2'));

4. Rate Limits

  • Telegram imposes rate limits. Handle TransportExceptionInterface (Symfony\Component\Notifier\Exception\TransportExceptionInterface) gracefully:
    use Symfony\Component\Notifier\Exception\TransportExceptionInterface;
    
    try {
        $notifier->send($message);
    } catch (TransportExceptionInterface $e) {
        Log::error('Telegram send failed', ['error' => $e->getMessage()]);
        // Retry logic or fallback (e.g., email)
    }
    

5. Debugging

  • Inspect the payload: enable Symfony Notifier's logger on the transport and check storage/logs/laravel.log for the outgoing sendMessage request body — the text field shows exactly what the escaping fix produced.
  • Check API responses: a 400 with can't parse entities is a formatting problem; a 403/400 chat not found is a chat ID or bot-membership problem.

6. Extending Functionality

  • Custom Reply Markups: Extend InlineKeyboardMarkup or ReplyKeyboardMarkup for reusable buttons:
    class AdminKeyboardMarkup extends InlineKeyboardMarkup
    {
        public function __construct()
        {
            $this->inlineKeyboard([
                new InlineKeyboardButton('Approve')->callbackData('approve'),
                new InlineKeyboardButton('Reject')->callbackData('reject'),
            ]);
        }
    }
    
  • Middleware: Decorate the transport to log or modify messages before they hit the API:
    class LoggingTelegramTransport implements TransportInterface { /* delegate + log */ }
    
    This is also the right seam if you need to disable the new automatic escaping for a specific message (e.g., you already produce fully-escaped output).

7. Local Development

  • Mocking: Use a null/mock transport for testing so the suite never hits Telegram:
    $notifier = new Notifier([$mockTransport]);
    $notifier->send($message);
    $this->assertCount(1, $mockTransport->getSentMessages());
    
  • Local API Server: For testing without hitting Telegram’s API, point the DSN at a local stub server and assert on the raw text field — the best way to regression-test the MarkdownV2 escaping fix.

8. Security

  • Token Storage: Never hardcode bot tokens. Use Laravel’s .env and validate input (e.g., chat IDs).
  • Sensitive Data: Avoid sending PII or secrets in messages. Use TelegramOptions::protectContent(true) for sensitive updates.

9. Performance

  • Batch Messages: For high-volume notifications, batch messages (e.g., daily digests) to avoid rate limits.
  • Async Dispatch: Use Laravel Queues to offload notifications:
    dispatch(new TelegramNotificationJob($message));
    

10. Upgrading

  • Pin ^8.1.6 so every environment gets the MarkdownV2 escaping fix (#65644) — mixing 8.1.4/8.1.5 between local and production causes messages to render differently for the same code.
  • Audit existing escaping: grep for preg_replace, addcslashes, or manual \* / \_ sequences applied to Telegram message bodies and remove them after upgrading, or you will double-escape.
  • Symfony Version: Ensure compatibility with your Laravel version (e.g., Symfony 6.x for Laravel 9.x).
  • Breaking Changes: Check Symfony’s UPGRADE.md before jumping minor versions.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
frankkessler/guzzle-oauth2-middleware
delatbabel/site-config
delatbabel/applog
eusonlito/laravel-gettext
henzeb/housepedia-intelligence-laravel
aliqsm/laravel-authkit
itsjustvita/laravel-wp-migrator
uslanozan/laravel-cache-contract
adilazhari/laravel-trace
ulsyairil/ai-local-guardrails
dzwallet/laravel-sdk
glc36/maxloop-kernel
ikhbarnuridev/filament-mega-menu
mydnic/volet-chatbot
enadstack/laravel-localization
mydnic/volet-feature-board-filament-plugin
wmde/email-address
refinery29/test-util
dees040/laravel-api-responses
teamzac/geocoder