Telegram
Recommended starting point — three-minute setup, streaming-capable gateway, most battle-tested provider.
🟢 Runtime-ready — the gateway ships with streaming-edit capability (no production caller wires it yet — see below) and is the provider most Minara operators start with. No external binary, no business-account approval, just a bot token and a chat id.
What you get
- Streaming edits (gateway-ready) —
TelegramGatewayposts a placeholder and edits it as text arrives, throttled at 750 ms. Drive it viacreateStreamSinkinapps/agent/src/messaging/stream-helpers.ts; thesend_messagetool itself is one-shot. - Rich text (default on) — markdown replies render as Telegram
formatting (bold, headings, tables, task lists, code blocks). Set
TELEGRAM_RICH_TEXT=falseto send plain text instead. - Attachments — images (
sendPhoto), files (sendDocument), voice notes (sendVoice, OGG/Opus required), audio (sendAudio). Source must be a file the agent generated under the sandbox (see overview page for details). - Group and 1:1 chats — same flow for both; group chat ids are negative numbers.
- 4 096-char limit per message.
send_messagerejects longer text at the tool boundary; the stream sink truncates mid-stream with a… (truncated)marker.
Setup
1. Create a bot
- Open Telegram, find @BotFather
- Send
/newbot→ follow the prompts (name, then username) - Save the token BotFather gives you (looks like
123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11)
2. Get your chat id
- Start a chat with your new bot and send any message to it — Telegram drops bot → chat messages if the chat hasn't initiated contact first
- Visit
https://api.telegram.org/bot<TOKEN>/getUpdatesin a browser and find"chat":{"id":...}in the response
For a group: add the bot to the group, send a message in the group,
then check getUpdates. Group ids are negative (e.g. -1001234567890).
3. Configure Minara
Easy way — ask the agent in chat:
"set up Telegram notifications"
Minara prompts for the token + chat id, writes them to
~/.minara/credentials.json (messaging slot), and runs a test ping.
Manual way:
minara auth messaging add telegramOr set the env vars directly in your project .env file:
TELEGRAM_BOT_TOKEN=123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11
TELEGRAM_CHAT_ID=-10012345678904. Test
minara auth messaging test telegramYou should see the ping message within a second or two.
Streaming behaviour
Telegram's editMessageText endpoint accepts up to ~30 edits per
message without hitting rate limits. The TelegramGateway exposes a
startStream() session that posts a placeholder and edits it as new
text arrives; createStreamSink throttles those edits at 750 ms
— fast enough to feel live, slow enough to stay well under the
per-chat limit. Cumulative text exceeding 4 096 characters is
truncated mid-stream with a … (truncated) marker.
Throttle and length overrides are caller-side options on
createStreamSink(gw, msg, { intervalMs, maxLength }), not
arguments on send_message. The tool path itself is one-shot — the
LLM sends a fully-formed message; streaming lives in workflow /
autopilot code (not yet wired in production).
Rich text
Replies render as Telegram rich text by default. The agent writes markdown; the gateway converts it to Telegram's HTML subset before sending:
- Headings become bold, lists keep their bullets, task lists show ☐ / ☑, tables render as aligned monospace, and code blocks keep their language label.
- If Telegram rejects the HTML (rare), the gateway retries the same content as MarkdownV2, then as plain text. A message is never dropped because of a formatting error.
- During streaming, each edit only shows fully-closed formatting, so
a half-typed
**boldnever flashes before it resolves.
Turn it off to send the agent's text verbatim:
TELEGRAM_RICH_TEXT=falseAccepted "off" values are 0, false, no, off; unset means on.
The setting is read once at startup, so restart the gateway after
changing it. You can also toggle it in the web UI under
Settings → Messaging behaviour → Rich text on Telegram.
Attachments
Attachments reference files the agent has already generated inside the
sandbox (via image_generate, audio_generate, write_file, code
execution, etc.). The LLM passes the sandbox-relative path:
send_message({
provider: "telegram",
text: "BTC/USD daily — key levels marked",
attachments: [
{ kind: "image", sandbox_path: "images/btc-2026-04-19.png" },
],
})Kind routing:
| kind | Telegram endpoint | Notes |
|---|---|---|
image | sendPhoto | caption supported |
file | sendDocument | arbitrary filetypes |
voice | sendVoice | requires OGG/Opus; non-OGG is rejected |
audio | sendAudio | mp3 / m4a / flac — music-style player UI |
Multiple attachments go as sequential messages; the first carries
msg.text as its caption if short enough (≤ 1024 chars), otherwise
the text is sent as its own leading message and attachments follow
threaded under it. See apps/agent/src/messaging/telegram.ts
for details.
Overriding the channel
Route urgent alerts to a different chat while keeping routine notifications on the default:
send_message({
provider: "telegram",
channel: "-1009876543210",
text: "Critical: position liquidation imminent",
})Troubleshooting
"Test message didn't arrive"
- Did you message the bot first? Telegram drops messages to chats that haven't initiated contact
- Check the sign on
TELEGRAM_CHAT_ID— group chats are negative - Confirm the bot is still in the group: BotFather → your bot →
Bot Settings→Group Privacy
"Streaming feels slow / jumpy"
- Expected: 750 ms baseline throttle inside
TelegramGateway. Caller code can override viacreateStreamSink(gw, msg, { intervalMs: 500 })— this is a caller-side option, not asend_messagetool argument - Very long responses get truncated at 4 096 characters — consider splitting the workflow into multiple messages
"Chat not found (400)"
- The bot was removed from the group, or the chat id is wrong
- Re-run the
getUpdatesstep after sending a fresh message in the target chat
Reference
- Env vars:
TELEGRAM_BOT_TOKEN,TELEGRAM_CHAT_ID,TELEGRAM_RICH_TEXT - Source:
apps/agent/src/messaging/telegram.ts - Bot API docs: core.telegram.org/bots/api