Telegram Setup
This page sets up the Telegram channel end-to-end: create the bot credentials, write the channel config, start the resident gateway, verify with doctor checks, and tune access policy, sessions, media, commands, and scheduled (cron) delivery.
For the broader picture of how channels fit together, see the Messaging Gateway overview. For the full list of Telegram config fields, see the Channels Reference.
Overview
Anyy connects to Telegram as a bot account and talks to the Bot API over HTTPS.
The adapter uses long polling (getUpdates), so Telegram does not need to reach your host
and you do not need a public URL, webhook, or reverse proxy. Direct messages and group
chats are both supported, along with inbound photos, documents, and voice notes, plus a
typing indicator while a turn is running.
Identity and routing follow the standard channel model: each Telegram chat maps to a
stable Anyy identity of the form telegram:<account_id>:<chat_id>, and each chat
gets its own session.
Anyy always presents itself as Anyy in conversations. The Bot API token only authenticates your bot to Telegram; it does not change the assistant's identity.
Prerequisites
- Anyy installed, with the
anyyCLI on yourPATH(anyy --version). - A Telegram account and the official Telegram app (mobile or desktop) to talk to @BotFather.
- A bot token from BotFather (created in the next section). Treat the token as a secret.
- A host where the resident gateway can run continuously — see Running as a Resident Service.
Setup
The flow is: create the bot with BotFather, write the channel config with the setup command, start the gateway, then run doctor checks.
Create credentials
- In Telegram, open a chat with @BotFather.
- Send
/newbotand follow the prompts: pick a display name, then a username ending inbot(for examplemy_anyy_bot). - BotFather replies with an HTTP API token that looks like
123456789:AA.... Copy it. This is the value you will pass as<BOT_TOKEN>below. - Optional, recommended settings (still in the BotFather chat):
- To let the bot read all group messages (not only replies/mentions), send
/setprivacy, choose your bot, and select Disable. Leave privacy Enabled if you only want the bot to act on mentions and replies in groups. - Send
/setjoingroupsto control whether the bot can be added to groups. - Use
/setcommandsto publish the slash commands you expose (see Commands).
- To let the bot read all group messages (not only replies/mentions), send
The bot token grants full control of your bot. Never paste it into chats, commit it to
version control, or include it in logs, screenshots, or bug reports. If it leaks, send
/revoke (or /token) to BotFather to rotate it, then update the stored secret. Anyy stores the token as a profile-local secret file (mode 0600) and only keeps a
secret: reference in config.yaml; the raw token is never written into the config.
Configure Anyy
Write the Telegram config block and store the token as a managed secret with one command.
Replace <BOT_TOKEN> with the token from BotFather:
anyy setup channels telegram \
--secret token='<BOT_TOKEN>' \
--write-config
What this does:
- Stores the token as a profile-local secret at
telegram/personal/tokenand records the referencetoken_ref: secret:telegram/personal/tokenin the config. - Writes a
channels.telegramblock intoconfig.yaml(because of--write-config). - Prints a JSON summary including the resolved
config_pathand the exactdoctor_commandto run next.
The generated block uses these defaults:
channels:
telegram:
enabled: true
account_id: personal
token_ref: secret:telegram/personal/token
dm_policy: pairing
group_policy: mention
allow_from: []
allow_groups: []
media_max_bytes: 5242880
Useful flags (the --help output prints only a terse usage line, so flags such as --force are accepted but not listed there):
--account-id ID— local account label (defaultpersonal). It becomes part of the identity and secret path, so changing it changes where the token is stored.--secret token=<BOT_TOKEN>— the bot token. May be repeated for other secrets, but Telegram only needstoken.--set NAME=VALUE— override or add a string config field, e.g.--set dm_policy=allowlistor--set bot_username=my_anyy_bot. May be repeated. You cannot overridetoken_refwith--set; use--secretfor credentials.--write-config— actually write the block. Without it, the command performs a dry run and only prints what it would write (the secret is still written).--force— replace an existingchannels.telegramblock instead of failing.--home PATH/ global--profile NAME— target a specific Anyy home/profile.
Running without --write-config previews only the config block — it still writes
the secret file. The --write-config flag gates the config.yaml block, not the secret;
the token is stored either way. To stage the secret first and add the config block later,
run once without --write-config, then re-run with it.
Set bot_username (via --set bot_username=... or by editing the config) to your bot's
exact username without the @. In groups under the default mention policy, this is how
Anyy recognizes that a message mentions it (@my_anyy_bot).
Start Gateway
Channels run inside the resident gateway. Start (or restart, after a config change) the gateway:
anyy gateway start
anyy gateway status
gateway status should report the service running. If the gateway is already running and
you only changed channel config, you can apply it without a full restart:
anyy channels reload
To keep the gateway alive across reboots, install it as a system service — see Running as a Resident Service.
Run doctor checks
Verify the Telegram wiring with the channel doctor:
anyy channels doctor telegram
Add --json for machine-readable output, or --home PATH to target a specific home. The
human output has three sections:
- Channel doctor — the resolved home and config file.
- Checks — per-channel configuration probes (config present, secret reference resolvable, etc.).
- Live smoke — whether the channel is configured and enabled, and whether a live smoke
run would contact the Telegram API (
Will contact). The doctor itself does not send a message; it reports the plan.
The setup command also prints the exact doctor_command (including the resolved --home)
in its JSON output, so you can copy it directly.
Access Policy
Two independent policies decide which inbound messages are accepted: dm_policy for direct
(private) chats and group_policy for groups and supergroups. Both are enforced before a
session is created, and every drop is recorded in channel status for observability.
Direct messages
dm_policy accepts one of:
open— accept DMs from anyone who messages the bot.allowlist— accept only senders whose numeric Telegram user ID is listed inallow_from.pairing— accept only already-known (paired) identities.disabled— reject all DMs.
The setup default is pairing.
Interactive pairing-token issuance is not yet wired up in this build: the /pair
command and the pairing RPC are reserved and return "pairing is not configured". Until
pairing issuance ships, use allowlist (with your numeric user ID in allow_from) or
open so your own DMs are accepted.
For a private, single-user assistant, the simplest working setup is an allowlist:
channels:
telegram:
dm_policy: allowlist
allow_from:
- "123456789" # your numeric Telegram user ID
You can find your numeric user ID by messaging a bot such as @userinfobot, or by reading
it from channel status/audit after sending a message (the drop record carries the
sender_id). After editing, run anyy channels reload.
Groups
group_policy accepts one of:
disabled— ignore all group messages.mention— respond only when the bot is mentioned (@<bot_username>) — the default.allowlist— respond only in groups whose numeric chat ID is listed inallow_groups.open— respond to every message in groups the bot is in.pairing— respond only in already-known (paired) groups (subject to the same pairing limitation noted above).
Under the default mention policy, set bot_username so mention detection works, and add
the bot to the group. Telegram's bot privacy mode also matters: with privacy enabled
(the BotFather default), the bot only receives messages that mention it or reply to it,
which pairs naturally with mention. Disable privacy in BotFather if you want open to
see every message.
channels:
telegram:
group_policy: allowlist
allow_groups:
- "-1001234567890" # numeric (super)group chat ID
Admins
There is no separate "Telegram admin" role at the channel layer; access is governed
entirely by the policies and allowlists above. Operational control (gateway lifecycle,
config, secrets, approvals) is exercised through the anyy CLI on the host, and
approval and audit semantics apply to state-changing operations regardless of which
channel initiated them.
In-chat approvals are available to anyone whose messages the policy admits: a paired or
allowlisted user can run /approve and /deny from Telegram (see
Commands). Keep dm_policy/group_policy tight if you rely on this, since it
effectively grants approval authority.
Sessions
Each Telegram conversation maps to its own Anyy session, keyed by chat:
- A DM uses the session key
anyy:telegram:dm:<chat_id>. - A group/supergroup uses
anyy:telegram:group:<chat_id>.
Sessions persist across messages, so context carries forward within a chat. Different chats
never share a session. To start fresh in a chat, send /new. Duplicate updates and the
bot's own echoes are filtered automatically, so retries and re-delivered updates do not
create duplicate turns.
Media Support
Inbound media
Anyy downloads and safely stages supported inbound attachments before the turn runs:
- Photos — the largest available size is fetched and staged as an image.
- Documents — staged as a file; image documents are validated as images.
- Voice notes — staged as a voice attachment (with duration metadata).
Images are content-sniffed and rejected on a type mismatch. A per-attachment size cap
applies: media_max_bytes (default 5242880 bytes = 5 MiB). Oversized or undownloadable
attachments are skipped; if a message had only media and none could be staged, Anyy
substitutes a short placeholder note so the turn still has context. Any caption on the media
is used as the message text.
Raise or lower the cap as needed, then reload:
channels:
telegram:
media_max_bytes: 10485760 # 10 MiB
Outbound media
Outbound replies are delivered as text messages. Long replies are automatically split
into multiple messages by character count (default chunk size 3500 characters; tune with
send_chunk_chars). While a turn is running, the bot shows the Telegram "typing…"
indicator in that chat.
Commands
Slash commands typed in a Telegram chat are interpreted by Anyy (they are independent
of any commands you publish via BotFather's /setcommands, which only populate Telegram's
UI hints). The commands available from a channel chat include:
/new— start a new session in this chat./status— show runtime status./stop— stop the running turn./approve//deny— approve or deny a pending request./voice— manage voice replies for this conversation.
Commands that manage host-level runtime (provider, skills, cron, secrets, and similar) are
exposed on the CLI/TUI rather than from channel chats. Run them with anyy on the
host.
Cron Delivery
Scheduled tasks can deliver their output back to a Telegram chat. When you create a cron
task from within a Telegram conversation (via the runtime), the task records the
originating channel and identity (telegram:<account_id>:<chat_id>) as its delivery route,
so the scheduled run's output is sent back to that same chat through the gateway.
This means the gateway must be running for scheduled deliveries to arrive, and the target chat must still be admitted by the access policy. For the delivery-route fields and how cron output binds to a channel identity, see the Channels Reference and your cron configuration.
Troubleshooting
- Doctor reports not configured / disabled — confirm
channels.telegram.enabled: trueand that you pointed at the right home (--home/--profile). Re-runanyy setup channels telegram --write-configif the block is missing. - Secret can't be resolved — the
token_refpoints at a profile secret that wasn't written. Re-run the setup command with--secret token='<BOT_TOKEN>', thenanyy channels reload. - Bot never replies to your DMs — most often
dm_policy: pairingis blocking you and pairing issuance isn't configured. Switch toallowlist(add your numeric user ID toallow_from) oropen, then reload. - Bot silent in a group — under
mentionit only answers when mentioned; verifybot_usernamematches the real username, add the bot to the group, and check BotFather privacy mode. Forallowlist, confirm the numeric chat ID is inallow_groups. - Messages received but no session/turn — check that the gateway is running
(
anyy gateway status) and that the message wasn't dropped (channel status records the policy reason, e.g.pairing_required,mention_required,sender_not_allowed). - Attachments ignored — they likely exceeded
media_max_bytesor failed validation. Raise the cap or resend a smaller file. - Config changes not taking effect — run
anyy channels reload(or restart the gateway). Reloads require a running gateway. - Token compromised — rotate it via BotFather (
/revoke), re-run setup with the new--secret token=..., then reload.