Telegram Setup and Security¶
Push supports Telegram private chats through Bot API long polling. It makes outbound HTTPS requests only. You do not need to expose a webhook or public server port. Telegram documents the polling contract in the official Bot API reference.
Create and Configure the Bot¶
- Open a private chat with Telegram's official
@BotFatheraccount. - Send
/newbotand follow the prompts. - Store the bot token in
~/.push/config.toml. Keep that file private and do not commit it, paste it into issue comments, or print it in logs. - Send one private message to the new bot.
- Find your stable numeric user and chat ids from a trusted Bot API
getUpdatesresponse or a trusted id lookup bot. Do not allowlist a Telegram username because usernames can change.
If the bot previously used a webhook, remove it before starting Push because
Telegram does not allow getUpdates while a webhook is active. The first Push
start discards pending updates, so send a new message after the gateway reports
that it is running.
Use a Telegram-only config:
channel = "telegram"
agent = "codex"
assistant_root = "~/Code/assistant"
[telegram]
bot_token = "replace-with-the-token-from-BotFather"
allow_user_ids = [123456789]
[[routes]]
thread = "telegram:dm:123456789"
agent = "claude"
push init creates a new config with owner-only mode 0600 on Unix. An
environment variable remains supported through telegram.bot_token_env, which
defaults to TELEGRAM_BOT_TOKEN. Push never prints the token. Run:
Doctor validates that a token is available without displaying its value. A
Telegram-only preflight does not open chat.db and does not require macOS or
osascript.
Voice Messages¶
Add the optional OpenAI API key to ~/.push/config.toml to enable both incoming
voice transcription and spoken replies:
Restart the managed gateway to load the updated config:
For a foreground process, stop it, run push doctor, and run push again.
See Running Push as a Service for launchd and systemd setup. Do
not start a foreground gateway while the service is running because Telegram
permits only one poller for a bot token.
OPENAI_API_KEY remains available for CI and service secret injection. A
non-empty environment value overrides the configured key.
Send the bot a Telegram voice note. Push downloads it only after the sender
passes the normal allowlist, transcribes it with gpt-4o-transcribe, sends the
transcript through the selected coding agent, then returns the answer as both
text and an Opus voice note generated by gpt-4o-mini-tts.
There is no local Whisper, FFmpeg, or other audio dependency. Audio stays in
memory and is limited to 20 MB. If neither voice.openai_api_key nor
OPENAI_API_KEY is set, normal text messages keep working and voice notes
receive an actionable text reply. API or speech generation errors also fall
back to text without stopping the gateway.
The spoken reply is AI-generated. Voice-note audio is sent to OpenAI for
processing, so review OpenAI's data controls before enabling this feature.
Voice processing is separate from the Telegram adapter. Telegram is the first channel to provide voice attachment download and upload, so another messaging channel can add those transport operations without changing the OpenAI layer.
Allowlisting and Routing¶
An incoming Telegram update reaches the agent only when all of these are true:
- it is a normal text message or voice note in a private chat
- its numeric sender id is in
telegram.allow_user_ids, or its numeric chat id is intelegram.allow_chat_ids - the message contains non-empty text or a voice attachment
Group chats, channels, group forum topics, edited messages, and other update
types are out of scope and ignored. The private-chat thread key is
telegram:dm:<chat_id>. A private-chat topic uses
telegram:dm:<chat_id>:topic:<topic_id> and replies target that topic.
A route with channel = "telegram" selects a backend for all accepted
Telegram messages. An exact thread = "telegram:dm:<chat_id>" route takes
priority over a channel route and is inherited by its private-chat topics. An
exact topic route takes priority over the parent chat route. The default
agent applies when no route matches. iMessage keys include the imessage:
prefix, so identical numeric or text identifiers cannot share Telegram session
state.
Cursor and Restart Behavior¶
state.json stores independent imessage and telegram cursors. push.db
stores channel-qualified canonical conversations, so Telegram and iMessage
history cannot collide. On the first
Telegram start, Push asks Telegram for the newest pending update and records its
id without running it. This explicit backlog skip prevents old bot messages
from unexpectedly starting agent work. Later accepted and ignored updates
advance only the Telegram cursor. Restarts continue from the next update id.
As with iMessage, a crash after delivery but before cursor persistence can
repeat a reply. Keep state.json on durable storage.
Linux and Service Mode¶
Telegram-only mode works on Linux or a VM because it does not depend on the macOS Messages database. Use the systemd example in services.md, and provide the token through the service environment or a root-readable credentials file supported by your service manager. Avoid placing the token directly in a world-readable unit file.
Protect these files as credentials or private assistant data:
- the bot token and configuration
state.jsonand the audit logpush.db- the private
assistant_rootrepository, includingSOUL.md, context, and jobs
The assistant directory is intended to be versioned in its own private Git repository. Never put bot tokens, config secrets, state files, audit logs, or databases in it. Rotate the bot token with BotFather immediately if it is exposed. Keep allowlists narrow because an allowed sender can instruct the configured agent to use its local tools and credentials.