Telegram
Telegram Bot interface for Aleph — setup, configuration, and media handling
The Telegram interface connects Aleph to the Telegram Bot API using the teloxide framework. It supports direct messages, group chats, file attachments, inline keyboards, Markdown formatting, forum topics, and tapback-style reactions.
Capabilities
| Feature | Status |
|---|---|
| Text messages | Supported |
| Markdown (MarkdownV2) | Supported |
| Photos | Supported (send and receive) |
| Documents | Supported (send and receive) |
| Audio | Supported (send and receive) |
| Video | Supported (send and receive) |
| Voice messages | Supported (receive) |
| Inline keyboards | Supported |
| Reply threading | Supported |
| Message editing | Supported |
| Message deletion | Supported |
| Typing indicator | Supported |
| Reactions | Supported (setMessageReaction) |
| Stream protocol | EditBased |
| Max message length | 4,096 characters |
| Max attachment size | 50 MB |
Prerequisites
Create a Bot with BotFather
- Open Telegram and search for @BotFather
- Send
/newbotand follow the prompts to name your bot - BotFather will reply with your bot token (format:
123456789:ABCdefGHIjklMNOpqrsTUVwxyz) - Save this token securely — it is your bot's authentication credential
Configure Bot Settings (Optional)
While still in BotFather, you can customize your bot:
/setdescription— Set the bot's description shown in the profile/setabouttext— Set the "About" text/setuserpic— Upload a profile picture/setcommands— Define the command menu (e.g.,/start,/help)/setprivacy— Disable "Privacy Mode" if you want the bot to see all group messages (not just commands and mentions)
Configuration
Minimal Configuration
[[channels]]
id = "telegram"
channel_type = "telegram"
enabled = true
[channels.config]
bot_token = "123456789:ABCdefGHIjklMNOpqrsTUVwxyz"Full Configuration Reference
[[channels]]
id = "telegram"
channel_type = "telegram"
enabled = true
[channels.config]
# Bot token from @BotFather (required); supports ${ENV_VAR} expansion
bot_token = "${TELEGRAM_BOT_TOKEN}"
# Bot username without @ (auto-detected on connect)
bot_username = "my_aleph_bot"
# DM policy for incoming messages: open | allowlist | pairing | disabled
dm_policy = "pairing"
# Group message policy: open | allowlist | disabled
group_policy = "open"
# Allowlist of phone numbers / user ids for DMs
allow_from = [123456789, 987654321]
# Allowlist for group chats
allowed_groups = [-1001234567890]
# Require @mention in group chats (default: true)
require_mention = true
# Bot's name for mention detection in groups
bot_name = "Aleph"
# Polling interval (default: 1)
polling_interval_secs = 1
# Send typing indicator while processing (default: true)
send_typing = true
# Maximum retries for failed messages (default: 3)
max_retries = 3Never commit bot tokens to version control. Use environment variables (${TELEGRAM_BOT_TOKEN}) or a secrets manager.
Environment Variable Mode
The simplest setup uses a single environment variable:
export TELEGRAM_BOT_TOKEN="123456789:ABCdefGHIjklMNOpqrsTUVwxyz"Adapter Structure
The Telegram interface is split across a small set of focused modules under src/gateway/interfaces/telegram/:
| Module | Responsibility |
|---|---|
bot_instance.rs | One teloxide Bot per configured account |
config.rs / config_v2.rs | TelegramConfig and the multi-account TelegramConfigV2 |
config_resolver.rs | Resolves multi-account config into a single effective view |
access.rs | Coarse DM/group pre-filter (AccessController) — only drops obvious traffic |
approval.rs | TelegramChannelApprovalCapability (interactive approval buttons) |
chunking.rs | Long-message splitting |
delivery.rs | Outbound send pipeline + rate-limit handling |
error_cooldown.rs | Per-conversation error cooldown + typing circuit breaker |
handlers.rs | Bot dispatch handlers (commands, callbacks, text) |
mention.rs | @mention parsing / stripping |
offset.rs | Persistent polling offset tracker (shared with iMessage BlueBubbles) |
polling.rs | Long-polling loop |
reaction_handler.rs | Processing status reactions (👀 / 👍 / 👎) |
sticker.rs | Sticker handling |
streaming/ | EditBased streaming (lane tracker, orchestrator, telegram event emitter) |
The adapter does not own pairing state. It forwards unknown senders to the inbound router, which is the single source for access / pairing / allowlist decisions (see Interfaces Overview).
Access, Pairing, and Allowlists — Single-Sourced on the Router
AccessController::check_message() returns one of three verdicts (src/gateway/interfaces/telegram/access.rs):
Allowed— statically allowlisted, or DM policy isopen/disabledtrivially.NeedsPairing— DM policy ispairingand the sender is not yet known. The adapter hands the message to the router; the router owns pairing code minting, expiry, and approval (channel.pairing.list/approve/reject/revoke).Denied— silently dropped (group not allowed, DM policydisabled, etc.).
There is no per-channel pairing database anymore: 26.7.21 removed the channel-local pairing store. Pairing state lives in the gateway's PairingStore (SQLite-backed), and the operator resolves new pairings from the Panel via channel.pairing.approve / reject. The TelegramConfigV2 → ChannelConfig bridge feeds the router every DM/group/allowlist rule, so a change in config.toml is the same change the router enforces.
Long-Polling vs Webhook
Aleph supports two modes for receiving updates from Telegram:
Long-Polling (Default)
The bot periodically asks Telegram's servers for new updates. This is the default and works out of the box with no additional infrastructure.
Pros:
- No public URL or SSL certificate required
- Works behind NATs and firewalls
- Simple setup
Cons:
- Slightly higher latency (configurable via
polling_interval_secs) - Keeps a persistent connection to Telegram servers
Webhook Mode
Telegram pushes updates to your server via HTTPS POST requests. Enable by configuring webhook in the channel config.
Pros:
- Lower latency (instant delivery)
- More efficient for high-traffic bots
Cons:
- Requires a public HTTPS endpoint
- Needs a valid SSL certificate (or self-signed with the
certificatefield)
User and Group Allowlists
Finding User IDs
Telegram user IDs are numeric. To find a user's ID:
- Have the user send a message to @userinfobot
- Or use the
getUpdatesAPI to see raw message data
Allowlist Behavior
# Empty list = allow everyone (subject to dm_policy)
allow_from = []
# Specific users only
allow_from = [123456789, 987654321]When dm_policy = "allowlist" and the sender is not in allow_from, the message is silently dropped. When dm_policy = "pairing" and the sender is not yet approved, the router mints a pairing code.
Group Chat Behavior
When Aleph receives a message in a group:
- Check if the group ID is in
allowed_groups(or if the list is empty) - Check if the sender is in
allow_from(or if the list is empty) - If both pass, route the message to the agent
By default, Telegram bots in "Privacy Mode" only see messages that mention the bot or start with /. Disable privacy mode via BotFather (/setprivacy) if you want Aleph to see all messages in a group.
Media Handling
Receiving Media
Aleph extracts attachments from all supported Telegram media types:
| Media Type | MIME Type | Notes |
|---|---|---|
| Photo | image/jpeg | Largest available resolution is selected |
| Document | From metadata | Generic files up to 50 MB |
| Audio | From metadata or audio/mpeg | Music files with metadata |
| Video | From metadata or video/mp4 | Video files with thumbnail |
| Voice | From metadata or audio/ogg | Opus-encoded voice messages |
Captions on media messages are extracted as the message text. If a message has both a caption and media, the caption becomes the text field.
Sending Media
Outbound attachments are dispatched based on MIME type:
image/*— Sent as a photo (sendPhoto)audio/*— Sent as audio (sendAudio)video/*— Sent as video (sendVideo)- Everything else — Sent as a document (
sendDocument)
Attachments can be provided as:
- In-memory bytes (
datafield) - Local file path (
pathfield) - Remote URL (
urlfield)
Inline Keyboards
The Telegram interface supports inline keyboard buttons for interactive prompts. The approval system uses this to let users approve or deny tool executions directly from the chat:
Aleph wants to execute: shell_exec("ls -la")
[Allow Once] [Allow Always] [Deny]When a user clicks a button, a callback query is routed back through the Gateway and processed by the approval bridge (TelegramChannelApprovalCapability). The loading indicator on the button is automatically dismissed via answerCallbackQuery. The bridge is two-way: Discord replies on the same flow edit the original message in place.
Session Routing
Each Telegram conversation gets a unique session key:
| Context | Session Key |
|---|---|
| DM with user 12345 | agent:main:dm:12345 or agent:main:telegram:dm:12345 |
| Group chat -100123 | agent:main:telegram:group:-100123 |
| Forum topic | agent:main:telegram:topic:{thread_id} |
The exact format depends on your dm_scope setting (see Interfaces Overview).
Message Formatting
Aleph sends messages using Telegram's MarkdownV2 parse mode. The following formatting is supported:
| Syntax | Result |
|---|---|
*bold* | bold |
_italic_ | italic |
`code` | code |
```language\ncode``` | Code block with syntax highlighting |
[text](url) | Hyperlink |
MarkdownV2 requires escaping special characters (_, *, [, ], (, ), ~, `, >, #, +, -, =, |, {, }, ., !). Aleph handles this escaping automatically when formatting outbound messages.
Error Handling
The Telegram interface handles common failure scenarios:
| Error | Behavior |
|---|---|
| Invalid bot token | Detected at startup via getMe API call; channel enters Error state |
| Rate limiting | Telegram rate limits are respected; the registry retries on RateLimited (SendRetryPolicy, default 2 retries with bounded retry_after) |
| Network failure | Long-polling reconnects automatically; webhook mode relies on Telegram's retry |
| User not allowlisted | Dropped silently; if dm_policy = pairing the router mints a code |
| Empty message | Skipped (no text and no attachments) |
| Service messages | Ignored (join/leave notifications, pinned messages, etc.) |
| Per-conversation stall | ErrorCooldown short-circuits typing + retries on conversations in a bad state |
Validation
The bot token is validated at two stages:
- Format check (config load) — Token must be non-empty and contain a colon (format:
<bot_id>:<hash>) - API check (channel start) —
getMeis called to verify the token and retrieve the bot's username and ID
If either check fails, the channel reports a ConfigError or AuthFailed error and does not start polling.
Troubleshooting
| Problem | Solution |
|---|---|
| Bot does not respond | Check that the channel is enabled in config.toml and that the router received the message (look for the sender id in the pairing store) |
| Bot ignores group messages | Disable Privacy Mode via BotFather (/setprivacy) or ensure the bot is mentioned |
| "User not in allowlist" in logs | Add the user's numeric Telegram ID to allow_from, or approve the pending pairing code from the Panel |
| "Failed to verify bot token" | Verify your token with curl https://api.telegram.org/bot<TOKEN>/getMe |
| Messages are delayed | Decrease polling_interval_secs or switch to webhook mode |
| Formatting looks broken | Aleph uses MarkdownV2; check that special characters are not double-escaped |