Channels / Mainstream messaging

Telegram

Production-ready for bot DMs and groups via grammY. Long polling is the default mode; webhook mode is optional.

Quick setup

Create the bot token in BotFather

Open Telegram and chat with @BotFather (confirm the handle is exactly @BotFather).

Run /newbot, follow prompts, and save the token.

Configure token and DM policy

{
  channels: {
    telegram: {
      enabled: true,
      botToken: "123:abc",
      dmPolicy: "pairing",
      groups: { "*": { requireMention: true } },
    },
  },
}

Env fallback: TELEGRAM_BOT_TOKEN=... (default account only). Telegram does not use genesis channels login telegram; configure token in config/env, then start gateway.

Start gateway and approve first DM

genesis gateway
genesis pairing list telegram
genesis pairing approve telegram <CODE>

Pairing codes expire after 1 hour.

Add the bot to a group

Add the bot to your group, then set channels.telegram.groups and groupPolicy to match your access model.

Token resolution order is account-aware. In practice, config values win over env fallback, and TELEGRAM_BOT_TOKEN only applies to the default account.

Telegram side settings

Privacy mode and group visibility

Telegram bots default to Privacy Mode, which limits what group messages they receive.

If the bot must see all group messages, either:

  • disable privacy mode via /setprivacy, or
  • make the bot a group admin.

When toggling privacy mode, remove + re-add the bot in each group so Telegram applies the change.

Group permissions

Admin status is controlled in Telegram group settings.

Admin bots receive all group messages, which is useful for always-on group behavior.

Helpful BotFather toggles

  • /setjoingroups to allow/deny group adds
  • /setprivacy for group visibility behavior

Access control and activation

DM policy

channels.telegram.dmPolicy controls direct message access:

  • pairing (default)
  • allowlist (requires at least one sender ID in allowFrom)
  • open (requires allowFrom to include "*")
  • disabled

channels.telegram.allowFrom accepts numeric Telegram user IDs. telegram: / tg: prefixes are accepted and normalized. dmPolicy: "allowlist" with empty allowFrom blocks all DMs and is rejected by config validation. Setup asks for numeric user IDs only. If you upgraded and your config contains @username allowlist entries, run genesis doctor --fix to resolve them (best-effort; requires a Telegram bot token). If you previously relied on pairing-store allowlist files, genesis doctor --fix can recover entries into channels.telegram.allowFrom in allowlist flows (for example when dmPolicy: "allowlist" has no explicit IDs yet).

For one-owner bots, prefer dmPolicy: "allowlist" with explicit numeric allowFrom IDs to keep access policy durable in config (instead of depending on previous pairing approvals).

Common confusion: DM pairing approval does not mean "this sender is authorized everywhere". Pairing grants DM access only. Group sender authorization still comes from explicit config allowlists. If you want "I am authorized once and both DMs and group commands work", put your numeric Telegram user ID in channels.telegram.allowFrom.

Finding your Telegram user ID

Safer (no third-party bot):

  1. DM your bot.
  2. Run genesis logs --follow.
  3. Read from.id.

Official Bot API method:

curl "https://api.telegram.org/bot<bot_token>/getUpdates"

Third-party method (less private): @userinfobot or @getidsbot.

Group policy and allowlists

Two controls apply together:

  1. Which groups are allowed (channels.telegram.groups)

    • no groups config:
      • with groupPolicy: "open": any group can pass group-ID checks
      • with groupPolicy: "allowlist" (default): groups are blocked until you add groups entries (or "*")
    • groups configured: acts as allowlist (explicit IDs or "*")
  2. Which senders are allowed in groups (channels.telegram.groupPolicy)

    • open
    • allowlist (default)
    • disabled

groupAllowFrom is used for group sender filtering. If not set, Telegram falls back to allowFrom. groupAllowFrom entries should be numeric Telegram user IDs (telegram: / tg: prefixes are normalized). Do not put Telegram group or supergroup chat IDs in groupAllowFrom. Negative chat IDs belong under channels.telegram.groups. Non-numeric entries are ignored for sender authorization. Security boundary (2026.2.25+): group sender auth does not inherit DM pairing-store approvals. Pairing stays DM-only. For groups, set groupAllowFrom or per-group/per-topic allowFrom. If groupAllowFrom is unset, Telegram falls back to config allowFrom, not the pairing store. Practical pattern for one-owner bots: set your user ID in channels.telegram.allowFrom, leave groupAllowFrom unset, and allow the target groups under channels.telegram.groups. Runtime note: if channels.telegram is completely missing, runtime defaults to fail-closed groupPolicy="allowlist" unless channels.defaults.groupPolicy is explicitly set.

Example: allow any member in one specific group:

{
  channels: {
    telegram: {
      groups: {
        "-1001234567890": {
          groupPolicy: "open",
          requireMention: false,
        },
      },
    },
  },
}

Example: allow only specific users inside one specific group:

{
  channels: {
    telegram: {
      groups: {
        "-1001234567890": {
          requireMention: true,
          allowFrom: ["8734062810", "745123456"],
        },
      },
    },
  },
}

Common mistake: groupAllowFrom is not a Telegram group allowlist.

  • Put negative Telegram group or supergroup chat IDs like -1001234567890 under channels.telegram.groups.
  • Put Telegram user IDs like 8734062810 under groupAllowFrom when you want to limit which people inside an allowed group can trigger the bot.
  • Use groupAllowFrom: ["*"] only when you want any member of an allowed group to be able to talk to the bot.

Mention behavior

Group replies require mention by default.

Mention can come from:

  • native @botusername mention, or
  • mention patterns in:
    • agents.list[].groupChat.mentionPatterns
    • messages.groupChat.mentionPatterns

Session-level command toggles:

  • /activation always
  • /activation mention

These update session state only. Use config for persistence.

Persistent config example:

{
  channels: {
    telegram: {
      groups: {
        "*": { requireMention: false },
      },
    },
  },
}

Getting the group chat ID:

  • forward a group message to @userinfobot / @getidsbot
  • or read chat.id from genesis logs --follow
  • or inspect Bot API getUpdates

Runtime behavior

  • Telegram is owned by the gateway process.
  • Routing is deterministic: Telegram inbound replies back to Telegram (the model does not pick channels).
  • Inbound messages normalize into the shared channel envelope with reply metadata and media placeholders.
  • Group sessions are isolated by group ID. Forum topics append :topic:<threadId> to keep topics isolated.
  • DM messages can carry message_thread_id; Genesis routes them with thread-aware session keys and preserves thread ID for replies.
  • Long polling uses grammY runner with per-chat/per-thread sequencing. Overall runner sink concurrency uses agents.defaults.maxConcurrent.
  • Long-polling watchdog restarts trigger after 120 seconds without completed getUpdates liveness by default. Increase channels.telegram.pollingStallThresholdMs only if your deployment still sees false polling-stall restarts during long-running work. The value is in milliseconds and is allowed from 30000 to 600000; per-account overrides are supported.
  • Telegram Bot API has no read-receipt support (sendReadReceipts does not apply).

Feature reference

Live stream preview (message edits)

Genesis can stream partial replies in real time:

  • direct chats: preview message + editMessageText
  • groups/topics: preview message + editMessageText

Requirement:

  • channels.telegram.streaming is off | partial | block | progress (default: partial)
  • progress maps to partial on Telegram (compat with cross-channel naming)
  • streaming.preview.toolProgress controls whether tool/progress updates reuse the same edited preview message (default: true when preview streaming is active)
  • legacy channels.telegram.streamMode and boolean streaming values are auto-mapped

Tool-progress preview updates are the short "Working..." lines shown while tools run, for example command execution, file reads, planning updates, or patch summaries. Telegram keeps these enabled by default to match released Genesis behavior from v2026.4.22 and later. To keep the edited preview for answer text but hide tool-progress lines, set:

{
  "channels": {
    "telegram": {
      "streaming": {
        "mode": "partial",
        "preview": {
          "toolProgress": false
        }
      }
    }
  }
}

Use streaming.mode: "off" only when you want to disable Telegram preview edits entirely. Use streaming.preview.toolProgress: false when you only want to disable the tool-progress status lines.

For text-only replies:

  • DM: Genesis keeps the same preview message and performs a final edit in place (no second message)
  • group/topic: Genesis keeps the same preview message and performs a final edit in place (no second message)

For complex replies (for example media payloads), Genesis falls back to normal final delivery and then cleans up the preview message.

Preview streaming is separate from block streaming. When block streaming is explicitly enabled for Telegram, Genesis skips the preview stream to avoid double-streaming.

If native draft transport is unavailable/rejected, Genesis automatically falls back to sendMessage + editMessageText.

Telegram-only reasoning stream:

  • /reasoning stream sends reasoning to the live preview while generating
  • final answer is sent without reasoning text

Formatting and HTML fallback

Outbound text uses Telegram parse_mode: "HTML".

  • Markdown-ish text is rendered to Telegram-safe HTML.
  • Raw model HTML is escaped to reduce Telegram parse failures.
  • If Telegram rejects parsed HTML, Genesis retries as plain text.

Link previews are enabled by default and can be disabled with channels.telegram.linkPreview: false.

Native commands and custom commands

Telegram command menu registration is handled at startup with setMyCommands.

Native command defaults:

  • commands.native: "auto" enables native commands for Telegram

Add custom command menu entries:

{
  channels: {
    telegram: {
      customCommands: [
        { command: "backup", description: "Git backup" },
        { command: "generate", description: "Create an image" },
      ],
    },
  },
}

Rules:

  • names are normalized (strip leading /, lowercase)
  • valid pattern: a-z, 0-9, _, length 1..32
  • custom commands cannot override native commands
  • conflicts/duplicates are skipped and logged

Notes:

  • custom commands are menu entries only; they do not auto-implement behavior
  • plugin/skill commands can still work when typed even if not shown in Telegram menu

If native commands are disabled, built-ins are removed. Custom/plugin commands may still register if configured.

Common setup failures:

  • setMyCommands failed with BOT_COMMANDS_TOO_MUCH means the Telegram menu still overflowed after trimming; reduce plugin/skill/custom commands or disable channels.telegram.commands.native.
  • setMyCommands failed with network/fetch errors usually means outbound DNS/HTTPS to api.telegram.org is blocked.

Device pairing commands (device-pair plugin)

When the device-pair plugin is installed:

  1. /pair generates setup code
  2. paste code in iOS app
  3. /pair pending lists pending requests (including role/scopes)
  4. approve the request:
    • /pair approve <requestId> for explicit approval
    • /pair approve when there is only one pending request
    • /pair approve latest for most recent

The setup code carries a short-lived bootstrap token. Built-in bootstrap handoff keeps the primary node token at scopes: []; any handed-off operator token stays bounded to operator.approvals, operator.read, operator.talk.secrets, and operator.write. Bootstrap scope checks are role-prefixed, so that operator allowlist only satisfies operator requests; non-operator roles still need scopes under their own role prefix.

If a device retries with changed auth details (for example role/scopes/public key), the previous pending request is superseded and the new request uses a different requestId. Re-run /pair pending before approving.

More details: Pairing.

Inline buttons

Configure inline keyboard scope:

{
  channels: {
    telegram: {
      capabilities: {
        inlineButtons: "allowlist",
      },
    },
  },
}

Per-account override:

{
  channels: {
    telegram: {
      accounts: {
        main: {
          capabilities: {
            inlineButtons: "allowlist",
          },
        },
      },
    },
  },
}

Scopes:

  • off
  • dm
  • group
  • all
  • allowlist (default)

Legacy capabilities: ["inlineButtons"] maps to inlineButtons: "all".

Message action example:

{
  action: "send",
  channel: "telegram",
  to: "123456789",
  message: "Choose an option:",
  buttons: [
    [
      { text: "Yes", callback_data: "yes" },
      { text: "No", callback_data: "no" },
    ],
    [{ text: "Cancel", callback_data: "cancel" }],
  ],
}

Callback clicks are passed to the agent as text: callback_data: <value>

Telegram message actions for agents and automation

Telegram tool actions include:

  • sendMessage (to, content, optional mediaUrl, replyToMessageId, messageThreadId)
  • react (chatId, messageId, emoji)
  • deleteMessage (chatId, messageId)
  • editMessage (chatId, messageId, content)
  • createForumTopic (chatId, name, optional iconColor, iconCustomEmojiId)

Channel message actions expose ergonomic aliases (send, react, delete, edit, sticker, sticker-search, topic-create).

Gating controls:

  • channels.telegram.actions.sendMessage
  • channels.telegram.actions.deleteMessage
  • channels.telegram.actions.reactions
  • channels.telegram.actions.sticker (default: disabled)

Note: edit and topic-create are currently enabled by default and do not have separate channels.telegram.actions.* toggles. Runtime sends use the active config/secrets snapshot (startup/reload), so action paths do not perform ad-hoc SecretRef re-resolution per send.

Reaction removal semantics: /tools/reactions

Reply threading tags

Telegram supports explicit reply threading tags in generated output:

  • [[reply_to_current]] replies to the triggering message
  • [[reply_to:<id>]] replies to a specific Telegram message ID

channels.telegram.replyToMode controls handling:

  • off (default)
  • first
  • all

Note: off disables implicit reply threading. Explicit [[reply_to_*]] tags are still honored.

Forum topics and thread behavior

Forum supergroups:

  • topic session keys append :topic:<threadId>
  • replies and typing target the topic thread
  • topic config path: channels.telegram.groups.<chatId>.topics.<threadId>

General topic (threadId=1) special-case:

  • message sends omit message_thread_id (Telegram rejects sendMessage(...thread_id=1))
  • typing actions still include message_thread_id

Topic inheritance: topic entries inherit group settings unless overridden (requireMention, allowFrom, skills, systemPrompt, enabled, groupPolicy). agentId is topic-only and does not inherit from group defaults.

Per-topic agent routing: Each topic can route to a different agent by setting agentId in the topic config. This gives each topic its own isolated workspace, memory, and session. Example:

{
  channels: {
    telegram: {
      groups: {
        "-1001234567890": {
          topics: {
            "1": { agentId: "main" },      // General topic → main agent
            "3": { agentId: "zu" },        // Dev topic → zu agent
            "5": { agentId: "coder" }      // Code review → coder agent
          }
        }
      }
    }
  }
}

Each topic then has its own session key: agent:zu:telegram:group:-1001234567890:topic:3

Persistent ACP topic binding: Forum topics can pin ACP harness sessions through top-level typed ACP bindings (bindings[] with type: "acp" and match.channel: "telegram", peer.kind: "group", and a topic-qualified id like -1001234567890:topic:42). Currently scoped to forum topics in groups/supergroups. See ACP Agents.

Thread-bound ACP spawn from chat: /acp spawn <agent> --thread here|auto binds the current topic to a new ACP session; follow-ups route there directly. Genesis pins the spawn confirmation in-topic. Requires channels.telegram.threadBindings.spawnAcpSessions=true.

Template context exposes MessageThreadId and IsForum. DM chats with message_thread_id keep DM routing but use thread-aware session keys.

Audio, video, and stickers

Audio messages

Telegram distinguishes voice notes vs audio files.

  • default: audio file behavior
  • tag [[audio_as_voice]] in agent reply to force voice-note send

Message action example:

{
  action: "send",
  channel: "telegram",
  to: "123456789",
  media: "https://example.com/voice.ogg",
  asVoice: true,
}

Video messages

Telegram distinguishes video files vs video notes.

Message action example:

{
  action: "send",
  channel: "telegram",
  to: "123456789",
  media: "https://example.com/video.mp4",
  asVideoNote: true,
}

Video notes do not support captions; provided message text is sent separately.

Stickers

Inbound sticker handling:

  • static WEBP: downloaded and processed (placeholder <media:sticker>)
  • animated TGS: skipped
  • video WEBM: skipped

Sticker context fields:

  • Sticker.emoji
  • Sticker.setName
  • Sticker.fileId
  • Sticker.fileUniqueId
  • Sticker.cachedDescription

Sticker cache file:

  • ~/.genesis/telegram/sticker-cache.json

Stickers are described once (when possible) and cached to reduce repeated vision calls.

Enable sticker actions:

{
  channels: {
    telegram: {
      actions: {
        sticker: true,
      },
    },
  },
}

Send sticker action:

{
  action: "sticker",
  channel: "telegram",
  to: "123456789",
  fileId: "CAACAgIAAxkBAAI...",
}

Search cached stickers:

{
  action: "sticker-search",
  channel: "telegram",
  query: "cat waving",
  limit: 5,
}

Reaction notifications

Telegram reactions arrive as message_reaction updates (separate from message payloads).

When enabled, Genesis enqueues system events like:

  • Telegram reaction added: 👍 by Alice (@alice) on msg 42

Config:

  • channels.telegram.reactionNotifications: off | own | all (default: own)
  • channels.telegram.reactionLevel: off | ack | minimal | extensive (default: minimal)

Notes:

  • own means user reactions to bot-sent messages only (best-effort via sent-message cache).
  • Reaction events still respect Telegram access controls (dmPolicy, allowFrom, groupPolicy, groupAllowFrom); unauthorized senders are dropped.
  • Telegram does not provide thread IDs in reaction updates.
    • non-forum groups route to group chat session
    • forum groups route to the group general-topic session (:topic:1), not the exact originating topic

allowed_updates for polling/webhook include message_reaction automatically.

Ack reactions

ackReaction sends an acknowledgement emoji while Genesis is processing an inbound message.

Resolution order:

  • channels.telegram.accounts.<accountId>.ackReaction
  • channels.telegram.ackReaction
  • messages.ackReaction
  • agent identity emoji fallback (agents.list[].identity.emoji, else "👀")

Notes:

  • Telegram expects unicode emoji (for example "👀").
  • Use "" to disable the reaction for a channel or account.

Config writes from Telegram events and commands

Channel config writes are enabled by default (configWrites !== false).

Telegram-triggered writes include:

  • group migration events (migrate_to_chat_id) to update channels.telegram.groups
  • /config set and /config unset (requires command enablement)

Disable:

{
  channels: {
    telegram: {
      configWrites: false,
    },
  },
}

Long polling vs webhook

Default is long polling. For webhook mode set channels.telegram.webhookUrl and channels.telegram.webhookSecret; optional webhookPath, webhookHost, webhookPort (defaults /telegram-webhook, 127.0.0.1, 8787).

The local listener binds to 127.0.0.1:8787. For public ingress, either put a reverse proxy in front of the local port or set webhookHost: "0.0.0.0" intentionally.

Limits, retry, and CLI targets

  • channels.telegram.textChunkLimit default is 4000.
  • channels.telegram.chunkMode="newline" prefers paragraph boundaries (blank lines) before length splitting.
  • channels.telegram.mediaMaxMb (default 100) caps inbound and outbound Telegram media size.
  • channels.telegram.timeoutSeconds overrides Telegram API client timeout (if unset, grammY default applies).
  • channels.telegram.pollingStallThresholdMs defaults to 120000; tune between 30000 and 600000 only for false-positive polling-stall restarts.
  • group context history uses channels.telegram.historyLimit or messages.groupChat.historyLimit (default 50); 0 disables.
  • reply/quote/forward supplemental context is currently passed as received.
  • Telegram allowlists primarily gate who can trigger the agent, not a full supplemental-context redaction boundary.
  • DM history controls:
    • channels.telegram.dmHistoryLimit
    • channels.telegram.dms["<user_id>"].historyLimit
  • channels.telegram.retry config applies to Telegram send helpers (CLI/tools/actions) for recoverable outbound API errors.

CLI send target can be numeric chat ID or username:

genesis message send --channel telegram --target 123456789 --message "hi"
genesis message send --channel telegram --target @name --message "hi"

Telegram polls use genesis message poll and support forum topics:

genesis message poll --channel telegram --target 123456789 \
  --poll-question "Ship it?" --poll-option "Yes" --poll-option "No"
genesis message poll --channel telegram --target -1001234567890:topic:42 \
  --poll-question "Pick a time" --poll-option "10am" --poll-option "2pm" \
  --poll-duration-seconds 300 --poll-public

Telegram-only poll flags:

  • --poll-duration-seconds (5-600)
  • --poll-anonymous
  • --poll-public
  • --thread-id for forum topics (or use a :topic: target)

Telegram send also supports:

  • --presentation with buttons blocks for inline keyboards when channels.telegram.capabilities.inlineButtons allows it
  • --pin or --delivery '{"pin":true}' to request pinned delivery when the bot can pin in that chat
  • --force-document to send outbound images and GIFs as documents instead of compressed photo or animated-media uploads

Action gating:

  • channels.telegram.actions.sendMessage=false disables outbound Telegram messages, including polls
  • channels.telegram.actions.poll=false disables Telegram poll creation while leaving regular sends enabled

Exec approvals in Telegram

Telegram supports exec approvals in approver DMs and can optionally post prompts in the originating chat or topic. Approvers must be numeric Telegram user IDs.

Config path:

  • channels.telegram.execApprovals.enabled (auto-enables when at least one approver is resolvable)
  • channels.telegram.execApprovals.approvers (falls back to numeric owner IDs from allowFrom / defaultTo)
  • channels.telegram.execApprovals.target: dm (default) | channel | both
  • agentFilter, sessionFilter

Channel delivery shows the command text in the chat; only enable channel or both in trusted groups/topics. When the prompt lands in a forum topic, Genesis preserves the topic for the approval prompt and the follow-up. Exec approvals expire after 30 minutes by default.

Inline approval buttons also require channels.telegram.capabilities.inlineButtons to allow the target surface (dm, group, or all). Approval IDs prefixed with plugin: resolve through plugin approvals; others resolve through exec approvals first.

See Exec approvals.

Error reply controls

When the agent encounters a delivery or provider error, Telegram can either reply with the error text or suppress it. Two config keys control this behavior:

Key Values Default Description
channels.telegram.errorPolicy reply, silent reply reply sends a friendly error message to the chat. silent suppresses error replies entirely.
channels.telegram.errorCooldownMs number (ms) 60000 Minimum time between error replies to the same chat. Prevents error spam during outages.

Per-account, per-group, and per-topic overrides are supported (same inheritance as other Telegram config keys).

{
  channels: {
    telegram: {
      errorPolicy: "reply",
      errorCooldownMs: 120000,
      groups: {
        "-1001234567890": {
          errorPolicy: "silent", // suppress errors in this group
        },
      },
    },
  },
}

Troubleshooting

Bot does not respond to non mention group messages

  • If requireMention=false, Telegram privacy mode must allow full visibility.
    • BotFather: /setprivacy -> Disable
    • then remove + re-add bot to group
  • genesis channels status warns when config expects unmentioned group messages.
  • genesis channels status --probe can check explicit numeric group IDs; wildcard "*" cannot be membership-probed.
  • quick session test: /activation always.

Bot not seeing group messages at all

  • when channels.telegram.groups exists, group must be listed (or include "*")
  • verify bot membership in group
  • review logs: genesis logs --follow for skip reasons

Commands work partially or not at all

  • authorize your sender identity (pairing and/or numeric allowFrom)
  • command authorization still applies even when group policy is open
  • setMyCommands failed with BOT_COMMANDS_TOO_MUCH means the native menu has too many entries; reduce plugin/skill/custom commands or disable native menus
  • setMyCommands failed with network/fetch errors usually indicates DNS/HTTPS reachability issues to api.telegram.org

Polling or network instability

  • Node 22+ + custom fetch/proxy can trigger immediate abort behavior if AbortSignal types mismatch.
  • Some hosts resolve api.telegram.org to IPv6 first; broken IPv6 egress can cause intermittent Telegram API failures.
  • If logs include TypeError: fetch failed or Network request for 'getUpdates' failed!, Genesis now retries these as recoverable network errors.
  • If logs include Polling stall detected, Genesis restarts polling and rebuilds the Telegram transport after 120 seconds without completed long-poll liveness by default.
  • Increase channels.telegram.pollingStallThresholdMs only when long-running getUpdates calls are healthy but your host still reports false polling-stall restarts. Persistent stalls usually point to proxy, DNS, IPv6, or TLS egress issues between the host and api.telegram.org.
  • On VPS hosts with unstable direct egress/TLS, route Telegram API calls through channels.telegram.proxy:
channels:
  telegram:
    proxy: socks5://<user>:<password>@proxy-host:1080
  • Node 22+ defaults to autoSelectFamily=true (except WSL2) and dnsResultOrder=ipv4first.
  • If your host is WSL2 or explicitly works better with IPv4-only behavior, force family selection:
channels:
  telegram:
    network:
      autoSelectFamily: false
  • RFC 2544 benchmark-range answers (198.18.0.0/15) are already allowed for Telegram media downloads by default. If a trusted fake-IP or transparent proxy rewrites api.telegram.org to some other private/internal/special-use address during media downloads, you can opt in to the Telegram-only bypass:
channels:
  telegram:
    network:
      dangerouslyAllowPrivateNetwork: true
  • The same opt-in is available per account at channels.telegram.accounts.<accountId>.network.dangerouslyAllowPrivateNetwork.
  • If your proxy resolves Telegram media hosts into 198.18.x.x, leave the dangerous flag off first. Telegram media already allows the RFC 2544 benchmark range by default.

channels.telegram.network.dangerouslyAllowPrivateNetwork weakens Telegram media SSRF protections. Use it only for trusted operator-controlled proxy environments such as Clash, Mihomo, or Surge fake-IP routing when they synthesize private or special-use answers outside the RFC 2544 benchmark range. Leave it off for normal public internet Telegram access.

  • Environment overrides (temporary):
    • GENESIS_TELEGRAM_DISABLE_AUTO_SELECT_FAMILY=1
    • GENESIS_TELEGRAM_ENABLE_AUTO_SELECT_FAMILY=1
    • GENESIS_TELEGRAM_DNS_RESULT_ORDER=ipv4first
  • Validate DNS answers:
dig +short api.telegram.org A
dig +short api.telegram.org AAAA

More help: Channel troubleshooting.

Configuration reference

Primary reference: Configuration reference - Telegram.

High-signal Telegram fields

  • startup/auth: enabled, botToken, tokenFile, accounts.* (tokenFile must point to a regular file; symlinks are rejected)
  • access control: dmPolicy, allowFrom, groupPolicy, groupAllowFrom, groups, groups.*.topics.*, top-level bindings[] (type: "acp")
  • exec approvals: execApprovals, accounts.*.execApprovals
  • command/menu: commands.native, commands.nativeSkills, customCommands
  • threading/replies: replyToMode
  • streaming: streaming (preview), streaming.preview.toolProgress, blockStreaming
  • formatting/delivery: textChunkLimit, chunkMode, linkPreview, responsePrefix
  • media/network: mediaMaxMb, timeoutSeconds, pollingStallThresholdMs, retry, network.autoSelectFamily, network.dangerouslyAllowPrivateNetwork, proxy
  • webhook: webhookUrl, webhookSecret, webhookPath, webhookHost
  • actions/capabilities: capabilities.inlineButtons, actions.sendMessage|editMessage|deleteMessage|reactions|sticker
  • reactions: reactionNotifications, reactionLevel
  • errors: errorPolicy, errorCooldownMs
  • writes/history: configWrites, historyLimit, dmHistoryLimit, dms.*.historyLimit

Multi-account precedence: when two or more account IDs are configured, set channels.telegram.defaultAccount (or include channels.telegram.accounts.default) to make default routing explicit. Otherwise Genesis falls back to the first normalized account ID and genesis doctor warns. Named accounts inherit channels.telegram.allowFrom / groupAllowFrom, but not accounts.default.* values.