laravel-notification-channels/telegram
Send Telegram notifications from Laravel using the Telegram Bot API. Supports text messages, inline keyboards, dice, polls, media groups, photos, documents, locations, contacts, stickers and more, plus message routing, response handling and direct client access.
TelegramRichMessage for the sendRichMessage API method (Telegram Bot API 10.1/10.2). Supports Markdown / HTML content (markdown(), html(), view()), attached media referenced with tg://photo?id= style links (media()), the is_rtl and skip_entity_detection flags, and fluent builders for every documented InputRichBlock type (paragraph(), heading(), preformatted(), footer(), divider(), math(), anchor(), blockquote(), pullquote(), details(), table(), listBlock(), thinking(), map(), photoBlock(), videoBlock(), audioBlock(), animationBlock(), voiceNoteBlock()) plus a block() escape hatch for raw blocks such as collage and slideshow.TelegramRichMessageDraft for the sendRichMessageDraft API method. Extends TelegramRichMessage (so every content and block builder is available) and adds draftId(), a send() that streams the current content into the draft, and finalize() which turns the draft into a permanent message via sendRichMessage. Useful for streaming an AI generated answer as it is produced: repeated send() calls with the same draft id animate the replacement of the content. Drafts are limited to private chats and the draft id must be non-zero.CouldNotSendNotification::invalidRichMessageDraftId() and CouldNotSendNotification::richMessageDraftIdNotProvided() exception constructors.Telegram::sendRichMessage() and Telegram::sendRichMessageDraft() client methods.style parameter ('danger', 'success', 'primary') on button(), buttonWithCallback(), and buttonWithWebApp() methods for colored inline keyboard buttons (Telegram Bot API 9.4).guzzlehttp/guzzle 8.x alongside 7.x (^7.8 || ^8.0).TelegramMessage::escapeLegacyMarkdown() helper that escapes the characters supported by the legacy Markdown parse mode (_, *, `, [).TelegramFile::fileId() and TelegramFile::url() for attaching an existing Telegram file ID or a remote URL explicitly, bypassing the local-file/URL/file-ID detection heuristics of file(). Both validate their input and throw CouldNotSendNotification::invalidFileIdentifier() when it is malformed.CouldNotSendNotification::invalidFileType() exception constructor.Telegram::DEFAULT_TIMEOUT = 30s, Telegram::DEFAULT_CONNECT_TIMEOUT = 10s), so a network stall can no longer hang a queue worker indefinitely. Configurable via the new services.telegram.http config array, which is passed through to the Guzzle client (e.g. timeout, connect_timeout, proxy).parameters.retry_after and retries once. Responses with a retry_after above 60 seconds are not retried and fail immediately.NotificationChannels\Telegram\Facades\Telegram facade for direct Bot API access (Telegram::sendMessage([...]) etc.). No alias is auto-registered to avoid clashing with other packages — import the class directly.TelegramUpdates::create() (and its constructor) accept an optional Telegram client instance, so updates can be fetched without resolving the client from the container.Breaking: escapedLine() now escapes according to the currently set parse mode. Under the default legacy Markdown mode only _, *, ` and [ are escaped (the only characters that mode supports escaping — previously the full MarkdownV2 set was escaped, which rendered stray backslashes). Under MarkdownV2 the previous full escaping is applied, and with HTML or no parse mode the line is appended untouched. Set the parse mode before calling escapedLine().
Breaking: TelegramFile::file() (and the string $type on the new fileId()/url()) now throws CouldNotSendNotification::invalidFileType() for an unknown file type string instead of silently falling back to document.
Breaking: Telegram client methods (sendMessage(), sendFile(), getUpdates(), etc.) and TelegramSenderContract::send() no longer have nullable return types; they either return a PSR-7 response (or a decoded array for chunked messages) or throw. TelegramUpdates::get() no longer returns an empty array for a missing response.
Breaking: CouldNotSendNotification::telegramRespondedWithAnError() now accepts any GuzzleHttp\Exception\BadResponseException (previously ClientException only) and no longer throws JsonException for a non-JSON error body — it falls back to no description given instead.
Server errors (5xx) from Telegram are now wrapped by CouldNotSendNotification::telegramRespondedWithAnError() with the status code and description, instead of the generic "communication failed" message.
Breaking: Removed the unused FileType::getMimeType(), FileType::getAllowedExtensions(), and FileType::isExtensionAllowed() enum helpers. They were never consulted by the package and carried incorrect data (e.g. the non-standard audio/mp3 MIME type). FileType::toArray() remains.
Breaking: Array values passed as form params to the Telegram client (e.g. via options() or direct client calls) are now JSON encoded before sending, as the Bot API expects for structured parameters such as message_ids. Previously they were passed to Guzzle's form_params and serialized in PHP's key[0]=... style, which Telegram rejects. Pre-encoded JSON strings are unaffected.
onError() now declares a static return type instead of self, preserving fluent chain types in subclasses.
Message chunking is now accurate for multibyte text: chunks are measured in UTF-8 characters (Telegram's 4096-character limit) instead of a byte/display-width mix, and the splitter prefers breaking at the last newline, then the last space, before hard-splitting. Note that a split can still land inside a Markdown entity; prefer chunk-sized paragraphs or normal() for machine-generated content.
Chunked messages no longer sleep after the final chunk (previously a fixed 1-second delay was added after every chunk, including the last one).
Allowed Pest 5 / PHPUnit 13 for the test suite (pestphp/pest and pestphp/pest-plugin-laravel ^4.0 || ^5.0). Pest 5 requires PHP 8.4+ and Laravel 13 (via Testbench 11 / PHPUnit 13), so Pest 4 is kept for the PHP 8.3 and Laravel 12 test matrix. No test or configuration changes were required.
TelegramRichMessage is no longer final and its fluent builders now return static instead of self, so subclasses (such as TelegramRichMessageDraft) keep chainability.
Telegram::decodeResponse() and CouldNotSendNotification::telegramRespondedWithAnError() now use PHP's json_decode() with JSON_THROW_ON_ERROR instead of the removed GuzzleHttp\Utils::jsonDecode(). Invalid JSON now throws JsonException instead of GuzzleHttp\Exception\InvalidArgumentException.
CouldNotSendNotification::telegramRespondedWithAnError() no longer calls ClientException::hasResponse() (removed in Guzzle 8); a ClientException always carries a response in both Guzzle 7 and 8, so the "no response body found" fallback has been dropped.
Full Changelog: https://github.com/laravel-notification-channels/telegram/compare/7.0.0...8.0.0
TelegramMessage::escapeMarkdown by @alies-dev in https://github.com/laravel-notification-channels/telegram/pull/209TelegramFile::file() now strictly normalizes inputs to PSR-7 streams and rejects unsupported types earlier. Passing arbitrary values through to Guzzle is no longer allowed.businessConnectionId(), messageThreadId(), directMessagesTopicId()protectContent(), allowPaidBroadcast(), messageEffectId()replyParameters(), suggestedPostParameters()TelegramLocation enhancements: horizontalAccuracy(), livePeriod(), heading(), proximityAlertRadius().TelegramMessage: entities(), linkPreviewOptions().TelegramFile: captionEntities(), showCaptionAboveMedia().TelegramDice, TelegramMediaGroup.sendDice, sendMediaGroup, sendChatActioneditMessageText, editMessageCaption, editMessageMedia, editMessageReplyMarkupstopPoll, deleteMessage, deleteMessagesstrict_types, improved PHPDoc shapes).Telegram::decodeResponse() using Guzzle JSON utilities.TelegramChannel recipient resolution and response handlingTelegramFile upload handling (clear remote vs local distinction)TelegramMessage chunked sending behaviorInteractsWithTelegramMediaonError() now accepts any callable.HasSharedLogic typing hardened; keyboard layouts normalized for invalid column counts.TelegramBase now accepts optional Telegram instance (better testability).TelegramUpdates::get() safely returns empty array on invalid responses.services.telegram.* and legacy keys.pest-plugin-laravel 4.level: max with stricter rules enabled.Full Changelog: https://github.com/laravel-notification-channels/telegram/compare/6.0.0...7.0.0
TelegramVenue to support sendVenue method.sticker method to the TelegramFile to send sticker file.sendWhen method to conditionally send a message.buttonWithWebApp method to open web app from a button.onError method to handle exceptions. Based of https://github.com/laravel-notification-channels/telegram/pull/201 by @Hesammousavi.sendFile to support raw data sending.escapedLine method.HasSharedLogic trait.keyboard method parameters to $requestLocation and $requestContact to be consistent.Full Changelog: https://github.com/laravel-notification-channels/telegram/compare/5.0.0...6.0.0
Full Changelog: https://github.com/laravel-notification-channels/telegram/compare/4.0.0...5.0.0
< 8.1.Full Changelog: https://github.com/laravel-notification-channels/telegram/compare/3.0.0...4.0.0
line() method (adapted from a PR) by @llabbasmkhll in https://github.com/laravel-notification-channels/telegram/pull/149.TelegramSender contract to TelegramSenderContracttoArray() method to TelegramUpdates to retrieve payload.Full Changelog: https://github.com/laravel-notification-channels/telegram/compare/2.1.0...3.0.0
Full Changelog: https://github.com/laravel-notification-channels/telegram/compare/2.0.0...2.1.0
when on methods chain. Thanks, @foremtehan.< 7.4 and Laravel < 8.Full Changelog: https://github.com/laravel-notification-channels/telegram/compare/1.0.0...2.0.0
TelegramUpdates to retrieve bot updates. Thanks, @faissaloux.Full Changelog: https://github.com/laravel-notification-channels/telegram/compare/0.9.0...1.0.0
Full Changelog: https://github.com/laravel-notification-channels/telegram/compare/0.8.0...0.9.0
chunk($limit) ) in cases where the message is too long. Closes #127.Full Changelog: https://github.com/laravel-notification-channels/telegram/compare/0.7.0...0.8.0
NotificationFailed on exception. Thanks @4irik.TelegramChannel::class in on-demand notification route.Full Changelog: https://github.com/laravel-notification-channels/telegram/compare/0.6.0...0.7.0
buttonWithCallback() method. Thanks @digislexia.Full Changelog: https://github.com/laravel-notification-channels/telegram/compare/0.5.1...0.6.0
Full Changelog: https://github.com/laravel-notification-channels/telegram/compare/0.5.0...0.5.1
ClientException when constructing CouldNotSendNotification exception. Thanks @rubendl.Full Changelog: https://github.com/laravel-notification-channels/telegram/compare/0.4.1...0.5.0
Full Changelog: https://github.com/laravel-notification-channels/telegram/compare/0.4.0...0.4.1
base_uri for web bridge.Full Changelog: https://github.com/laravel-notification-channels/telegram/compare/0.3.0...0.4.0
disableNotification method. Thanks @2618094.Full Changelog: https://github.com/laravel-notification-channels/telegram/compare/0.2.0...0.3.0
Full Changelog: https://github.com/laravel-notification-channels/telegram/compare/0.1.1...0.2.0
Full Changelog: https://github.com/laravel-notification-channels/telegram/compare/0.1.0...0.1.1
Full Changelog: https://github.com/laravel-notification-channels/telegram/compare/0.0.6...0.1.0
Full Changelog: https://github.com/laravel-notification-channels/telegram/compare/0.0.5...0.0.6
Full Changelog: https://github.com/laravel-notification-channels/telegram/compare/0.0.4...0.0.5
Full Changelog: https://github.com/laravel-notification-channels/telegram/compare/0.0.3...0.0.4
Full Changelog: https://github.com/laravel-notification-channels/telegram/compare/0.0.2...0.0.3
register method in service provider. Thanks @enniel.TelegramChannel and TelegramMessage. Thanks @bmitch.Full Changelog: https://github.com/laravel-notification-channels/telegram/compare/0.0.1...0.0.2
How can I help you explore Laravel packages today?