Skip to main content
Smithers ships two Telegram surfaces:
  • @smthrs/telegram: bots in serverless functions, workflow tasks, or tests, with no long-lived polling process (this page’s first half).
  • @smthrs/integrations/telegram: workflows that durably wait on Telegram, listening for messages and approving gated steps from the chat via inline buttons or a Mini App (Approve from the chat).
The package mirrors Eliza’s Telegram plugin (bot lifecycle/API access, normalized messages, formatting helpers, a fake test client), using raw fetch instead of Telegraf so Vercel Functions, Cloudflare Workers, and Sandbox tasks can call the Bot API without a local polling loop.

Install

Send messages

createTelegramClient retries 429, 5xx, and transient network/read failures: 429 honors Telegram’s retry_after, others use exponential backoff. Permanent 4xx responses are not retried.

Verify webhooks

normalizeTelegramUpdate handles message, edited_message, channel_post, and edited_channel_post, ignoring bot-authored messages by default. isTelegramChatAllowed fails closed (Eliza’s access-control shape): blank TELEGRAM_ALLOWED_CHATS allows all chats, a JSON array of ids restricts to those, and malformed values block all chats until fixed.

Test with a fake

Use it in local e2e tests when production runs on real Telegram credentials.

Approve from the chat

<Telegram.Approval> turns a workflow approval into inline Approve/Reject buttons, resolved from the button press via the durable long-poll source: no web server, no gateway wiring. The decision matches core <Approval>’s shape: { approved, note, decidedBy, decidedAt }. Register the two internal node schemas with telegramApprovalSchemas, then use the component:
For a menu instead of Approve and Reject, pass mode="select" with options; the decision becomes { selected, notes }. The run only receives the press if a Telegram source is polling next to your workflow:
WaitForEvent wakes on the first callback query for the chat: run one interactive approval per chat at a time, or pass threadId (a forum topic) to isolate concurrent approvals. Any chat member can press a button, so keep approvals private or allowlisted.

Mini App approvals

For a richer decision (read a diff, pick an option, add a note), add a Mini App button: miniApp opens a web_app page in the chat alongside the plain buttons:
A Mini App exposes a signed initData string your backend must verify before trusting the user; never trust initDataUnsafe.
Verification is HMAC-SHA256 with a crossed key: secret = HMAC_SHA256(key="WebAppData", message=botToken). Data is authentic when hex(HMAC_SHA256(key=secret, message=dataCheckString)) equals hash, where dataCheckString is every field except hash, formatted key=value, sorted, and newline-joined. It runs on Web Crypto, working in both the Smithers runtime and a Cloudflare Worker. verifyTelegramWebAppInitDataSignature verifies the newer Ed25519 signature from a third party with only the numeric bot id. A runnable reference (a static Mini App page plus a Worker endpoint that verifies initData for real) ships in apps/telegram-site: site/approve.html and src/handleApprove.ts.