Skip to content

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

  1. Open a private chat with Telegram's official @BotFather account.
  2. Send /newbot and follow the prompts.
  3. 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.
  4. Send one private message to the new bot.
  5. Find your stable numeric user and chat ids from a trusted Bot API getUpdates response 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:

push doctor --config /absolute/path/to/config.toml
push --config /absolute/path/to/config.toml

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:

[voice]
openai_api_key = "your-api-key"

Restart the managed gateway to load the updated config:

push restart

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 in telegram.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.json and the audit log
  • push.db
  • the private assistant_root repository, including SOUL.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.