Aleph
Interfaces

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

FeatureStatus
Text messagesSupported
Markdown (MarkdownV2)Supported
PhotosSupported (send and receive)
DocumentsSupported (send and receive)
AudioSupported (send and receive)
VideoSupported (send and receive)
Voice messagesSupported (receive)
Inline keyboardsSupported
Reply threadingSupported
Message editingSupported
Message deletionSupported
Typing indicatorSupported
ReactionsSupported (setMessageReaction)
Stream protocolEditBased
Max message length4,096 characters
Max attachment size50 MB

Prerequisites

Create a Bot with BotFather

  1. Open Telegram and search for @BotFather
  2. Send /newbot and follow the prompts to name your bot
  3. BotFather will reply with your bot token (format: 123456789:ABCdefGHIjklMNOpqrsTUVwxyz)
  4. 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 = 3

Never 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/:

ModuleResponsibility
bot_instance.rsOne teloxide Bot per configured account
config.rs / config_v2.rsTelegramConfig and the multi-account TelegramConfigV2
config_resolver.rsResolves multi-account config into a single effective view
access.rsCoarse DM/group pre-filter (AccessController) — only drops obvious traffic
approval.rsTelegramChannelApprovalCapability (interactive approval buttons)
chunking.rsLong-message splitting
delivery.rsOutbound send pipeline + rate-limit handling
error_cooldown.rsPer-conversation error cooldown + typing circuit breaker
handlers.rsBot dispatch handlers (commands, callbacks, text)
mention.rs@mention parsing / stripping
offset.rsPersistent polling offset tracker (shared with iMessage BlueBubbles)
polling.rsLong-polling loop
reaction_handler.rsProcessing status reactions (👀 / 👍 / 👎)
sticker.rsSticker 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 is open / disabled trivially.
  • NeedsPairing — DM policy is pairing and 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 policy disabled, 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 certificate field)

User and Group Allowlists

Finding User IDs

Telegram user IDs are numeric. To find a user's ID:

  1. Have the user send a message to @userinfobot
  2. Or use the getUpdates API 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:

  1. Check if the group ID is in allowed_groups (or if the list is empty)
  2. Check if the sender is in allow_from (or if the list is empty)
  3. 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 TypeMIME TypeNotes
Photoimage/jpegLargest available resolution is selected
DocumentFrom metadataGeneric files up to 50 MB
AudioFrom metadata or audio/mpegMusic files with metadata
VideoFrom metadata or video/mp4Video files with thumbnail
VoiceFrom metadata or audio/oggOpus-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 (data field)
  • Local file path (path field)
  • Remote URL (url field)

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:

ContextSession Key
DM with user 12345agent:main:dm:12345 or agent:main:telegram:dm:12345
Group chat -100123agent:main:telegram:group:-100123
Forum topicagent: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:

SyntaxResult
*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:

ErrorBehavior
Invalid bot tokenDetected at startup via getMe API call; channel enters Error state
Rate limitingTelegram rate limits are respected; the registry retries on RateLimited (SendRetryPolicy, default 2 retries with bounded retry_after)
Network failureLong-polling reconnects automatically; webhook mode relies on Telegram's retry
User not allowlistedDropped silently; if dm_policy = pairing the router mints a code
Empty messageSkipped (no text and no attachments)
Service messagesIgnored (join/leave notifications, pinned messages, etc.)
Per-conversation stallErrorCooldown short-circuits typing + retries on conversations in a bad state

Validation

The bot token is validated at two stages:

  1. Format check (config load) — Token must be non-empty and contain a colon (format: <bot_id>:<hash>)
  2. API check (channel start) — getMe is 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

ProblemSolution
Bot does not respondCheck 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 messagesDisable Privacy Mode via BotFather (/setprivacy) or ensure the bot is mentioned
"User not in allowlist" in logsAdd 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 delayedDecrease polling_interval_secs or switch to webhook mode
Formatting looks brokenAleph uses MarkdownV2; check that special characters are not double-escaped

On this page