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

Laravel Telegram Alerts Laravel Package

balazstanka/laravel-telegram-alerts

Laravel package for sending alerts and notifications to Telegram chats via the bot API, with configurable recipients and simple integration for error monitoring, system alerts, and custom messages.

View on GitHub
Deep Wiki
Context7

Getting Started

This package wires Laravel into the Telegram Bot API so you can push operational alerts (deploys, failed jobs, exceptions, form submissions) to a chat, group, or channel.

  1. Install and publish config:
    composer require balazstanka/laravel-telegram-alerts
    php artisan vendor:publish --tag="telegram-alerts-config"
    
  2. Create a bot with @BotFather, grab the token, and put it in .env:
    TELEGRAM_BOT_TOKEN=123456:ABC...
    TELEGRAM_CHAT_ID=-1001234567890
    
  3. Add the bot to the target chat and send it one message before testing — Telegram will not let a bot initiate a conversation, and getUpdates/sendMessage will silently return {"ok":false,"description":"chat not found"} otherwise.
  4. Look first at the published config/telegram-alerts.php (token/chat id keys, and any default parse mode or queue settings), then the README section referenced by v1.1.0 for addUrl().

First use case: a deploy or failed-job alert. Start with the simplest send, then add a link button so recipients can jump straight to the CI run, the failing job in Horizon, or the PR:

// v1.1.0+
TelegramAlert::message('Deploy to production finished ✅')
    ->addUrl('Open release', 'https://github.com/org/repo/releases/tag/v1.1.0')
    ->addUrl('View logs', 'https://example.com/logs/deploy-123')
    ->send();

The addUrl($text, $url) addition is the headline feature of v1.1.0 — buttons render as an inline keyboard beneath the message, so the recipient stays in Telegram and taps straight through instead of copy-pasting a URL out of message text.

Implementation Patterns

Dedicated alert classes. Rather than scattering send calls, wrap each alert in a small class (or a method on an existing service) so the message text, chat target, and buttons live in one testable place:

class DeployFailedAlert
{
    public function __construct(public Deployment $deployment) {}

    public function send(): void
    {
        TelegramAlert::message("❌ Deploy failed: {$this->deployment->branch}")
            ->addUrl('Pipeline', route('pipelines.show', $this->deployment))
            ->addUrl('Commit', $this->deployment->commitUrl)
            ->send();
    }
}

Link buttons are the main workflow win. Good button candidates are the ones people currently have to hunt for: the failing queue job, the Sentry issue, the diff, the admin record. Put one primary action in the first row and secondary actions after it; Telegram lays buttons out left-to-right in the order you add them, so call addUrl() in priority order. Two addUrl() calls is usually the sweet spot; five is noise on mobile.

Hook into Laravel's built-in failure surfaces. The highest-value integrations are Queue\Events\JobFailed, Illuminate\Foundation\Events\DiagnosingHealth, the exception handler's report()/render() for production-only alerts, and Schedule::onFailure(). Keep alerts out of the happy path of HTTP requests — see the queuing note below.

Queue the send. Telegram calls are outbound HTTP; do not do them in the request cycle of a user-facing action. Either dispatch the alert class as a queued job or wrap the call in dispatch(fn () => ...) after the response (dispatchAfterResponse). If the package exposes a queueable alert/job, use it and set a dedicated queue so a Telegram outage never backs up real work.

Environment-aware routing. Point TELEGRAM_CHAT_ID at a private dev chat in local/staging and the ops channel in production — either via per-environment .env files or by overriding the config value in a service provider. Nothing is worse than a staging deploy alert paging the on-call channel at 3am.

Testing. Fake the HTTP layer and assert on the outgoing payload, so you check both the text and the reply_markup structure rather than guessing:

Http::fake();

(new DeployFailedAlert($deployment))->send();

Http::assertSent(fn ($request) =>
    str_contains($request['text'], 'Deploy failed')
    && $request['reply_markup']['inline_keyboard'][0][0]['url'] === $deployment->commitUrl
);

This is also the cheapest way to pin down the exact JSON shape addUrl() produces for your version.

Gotchas and Tips

  • Chat ID format. Groups and channels use negative IDs (-100…); a bare username (@mychannel) also works for public channels. If alerts vanish with no error in your logs, chat not found or bot was blocked by the user is almost always the cause — log the decoded response body, not just the status code.
  • A 200 OK does not mean success. The Telegram API returns HTTP 200 with {"ok": false, "description": "..."} for most errors. Make sure your error handling inspects the ok field, or you will ship silently broken alerting.
  • addUrl() accepts URLs only. Inline keyboard URL buttons require an http:// or https:// URL (with tg:// for Telegram deep links). Localhost, relative paths, and long query strings with unencoded characters will be rejected or silently fail; run route() output through url()->to() rather than passing a raw path.
  • No callback handling in this release. v1.1.0 adds URL buttons only. There is no callback_data support and therefore no way to handle a button press back in your app — don't design an "Approve / Reject" flow on top of this yet. If you need interactivity, that's the extension point to watch (and it requires a webhook endpoint plus setWebhook).
  • Rate limits. Roughly 30 messages/second globally and about 20/minute per group. A noisy failure mode — a queue that fails 500 jobs — will get you throttled (429 with retry_after) and then muted. Always aggregate: one alert per incident, or a "5 jobs failed in the last minute" summary, plus a cooldown/dedupe cache key.
  • Message length. Text is capped at 4096 characters; inline keyboard button labels at ~64 characters. Truncate exception messages and stack traces (Str::limit) before sending, or the API rejects the whole message.
  • Parse mode escaping. If you send HTML or MarkdownV2, any stray _, *, [, or < in a branch name, exception message, or URL breaks the whole message. Prefer plain text for alert bodies with URLs in buttons, or escape rigorously.
  • config:cache will bite you. Token and chat ID are read from config; after changing .env in a deployed environment, re-run php artisan config:cache. Alerts continuing to go to the old chat after a config change is this, not a package bug.
  • Don't retry blindly. A failed alert should not fail the job that triggered it. Wrap sends in try/catch, log at warning level, and never let alerting exceptions escape into the queue worker's failure handler (which can cause recursive alerts).
  • Version-sensitive API. If you are upgrading from 1.0.0, addUrl() is additive — no breaking changes reported — but confirm your installed constraint (^1.1) and re-run your fake-HTTP assertions, since the reply_markup payload only appears from this version onward.
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.
diephp/sequences
users-au/socialite-provider
twidpay/twid-logger
mrnewport/laravel-plaid
mrnewport/laravel-repo
mrnewport/laravel-docsign
mrnewport/laravel-stow
mrnewport/laravel-flow
mrnewport/laravel-priceable
mrnewport/laravel-nestedterms
fredmeng/laravel-apidoc-generator
itstructure/laravel-multilingual-tools
pdustdar/elasticsearch
markupai/php-sdk
rlacerda83/lumem-microservice-shipment
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle