Telegram

Telegram channel integration behavior and troubleshooting.

Telegram is the most complete external channel: a bot token is enough to give every approved Telegram DM or group a direct line to the same Clapilot agent that answers in the web app, with shared session history, native attachments, and in-place streaming replies. Telegram can be wired directly into the native ClapilotAICore runtime; Clapilot remains the business system of record.

How it works

Default mode is polling:

  1. clapilot-agent calls Telegram getUpdates with the saved bot token.
  2. Each inbound update is processed through the native channel pipeline and stored in Postgres.
  3. Unknown DMs and groups are added to the native approval queue; until approved, Clapilot does not answer there.
  4. Approved DMs are explicitly linked to a Clapilot user. After approval, the Telegram DM reuses that user's canonical main web-chat session key.
  5. Approved groups are explicitly mapped to a Clapilot team-chat room, defaulting to clapilot-members. After approval, the Telegram group thread reuses that room's canonical team-chat session key.
  6. Approved senders/groups are routed through the native channel pipeline. If a Telegram message includes an image or document attachment, clapilot-agent downloads the file, normalizes it into native multimodal message parts, and sends it directly to the native runtime. For linked group chats, document-like queries also preload matching Clapilot document candidates into the runtime context so the agent can work against workspace documents even without an active live UI document.
  7. If the bridge is unavailable, clapilot-agent falls back to the native provider/model flow.
  8. clapilot-agent registers native Telegram bot commands so the Telegram client can suggest /model and /models directly in the composer.
  9. clapilot-agent replies back to Telegram through native preview streaming with one visible bot message that is edited in place while the answer grows.

Optional mode is webhook:

  1. Telegram sends webhook updates to Clapilot at /api/agent-runtime/channels/telegram/inbound.
  2. The Next.js app forwards the payload to the internal clapilot-agent service.
  3. clapilot-agent processes the update through the same native channel pipeline.

Prerequisites

  • Telegram bot token
  • Clapilot app + database healthy
  • clapilot-agent service running and reachable from the app container
  • public_base_url only if you intentionally switch Telegram to webhook mode

Behavior matrix

ConcernOwner / source of truth
Chat handlingnative runtime + Clapilot DB (/api/chat)
Telegram photo/document attachmentsnative multimodal channel run in clapilot-agent
File ingestionworkspace + dokumente (/api/documents/inbox)
Agent mailbox readsapp_settings (scripts/agent-email-poller.mjs)
User mailbox readsuser_profiles (/api/emails)
DM/group allowlistagent_channel_approvals managed from Settings -> ClapilotAICore
DM session mappingagent_channel_approvals.metadata.linked_user_id + canonical main-chat session key
Group session mappingagent_channel_approvals.metadata.group_room_id + canonical team-chat session key

Transport mode

Default is polling. In that mode, no Telegram webhook setup is required.

Streaming mode

Telegram preview streaming defaults to partial.

  • partial: use one visible Telegram reply message and update that same message in place while the answer grows
  • block: compatibility mode that still waits for larger chunks before editing
  • off: disable Telegram preview streaming

Example JSON channel settings:

{
  "streaming": "partial"
}

Long-running Telegram runs use a 20 minute channel run timeout by default. If your instance needs a different bound, set CLAPILOT_CHANNEL_RUN_TIMEOUT_MS; Telegram's outer polling timeout stays at least 30 seconds above that value and can be raised with CLAPILOT_TELEGRAM_INBOUND_PROCESS_TIMEOUT_MS or the legacy alias CLAPILOT_AGENT_TELEGRAM_INBOUND_TIMEOUT_MS.

Approval mapping

Telegram approvals are now explicit session mappings, not only allow/deny flags:

  • DM: choose the Clapilot user that owns this Telegram chat
  • Group: choose any non-archived Clapilot team-chat channel or group room that should back this Telegram group thread; the picker includes every mappable room available to the admin, not only #general

After approval:

  • Telegram DMs share the same runtime session key and history boundary as the mapped user's main web chat
  • Telegram groups share the same runtime session key and history boundary as the mapped team-chat room
  • Telegram groups with an explicit room mapping are additionally mirrored one-way into that team chat: participant messages appear as user messages (attributed to the linked Clapilot user when the sender also has an approved, linked DM; otherwise with the Telegram display name), photos/documents appear as real URL-backed attachments from the shared workspace, delivered agent replies appear as agent messages, and both carry a small Telegram origin badge in the team chat UI
  • the per-mapping “Team-Chat nach Telegram spiegeln” toggle enables the reverse direction through metadata.mirror_to_channel; it is off by default, prefixes user messages with [Name via Clapilot], posts unprefixed agent replies, and forwards real Team Chat images/documents after the text; base64 files are materialized under .clapilot/channel-media/outbound/<yyyy-mm>/ in the shared workspace with a 20 MB per-file cap, while failed/oversized files remain 📎 <name> placeholders (WhatsApp and Slack reverse mirrors remain placeholder-only)
  • the raw Telegram threadKey still stays stored in agent_channel_threads so delivery and audit traces remain channel-specific

Existing approved entries can later be remapped to any available public/private team-chat channel or group room from the ClapilotAICore channel settings UI, and an approved entry can also be moved back to pending if replies should stop again without deleting the approval row.

Native model commands

Telegram now supports native model commands without routing them through the LLM:

  • /model: show the current effective model for this Telegram thread
  • /models: open a native Telegram inline menu that first shows providers and then the selectable models for that provider
  • /model <provider/model>: set a thread-local model override
  • /model channel <provider/model>: persist the Telegram channel default model and write it back into ClapilotAICore channel settings
  • /model standard: clear the thread override and fall back to the Telegram channel default or the global cross-provider standard
  • /model channel standard: remove the persisted Telegram channel default so the channel falls back to the global cross-provider standard

The inline menu uses Telegram callback buttons, but the final selection still goes through the same native session-model switch path as /model <provider/model>.

This means Telegram model switching now supports both real runtime/session overrides and a persisted channel-default path, not just prompt instructions.

Important: the Telegram coding/orchestrator auto-route does not override an explicit non-Codex thread model. If the Telegram thread is pinned to an Anthropic model such as anthropic/claude-opus-4-6, Clapilot keeps that normal native runtime path instead of silently switching the thread into the Codex/OpenAI-backed orchestrator flow.

If you explicitly want webhook mode, save this in the Telegram channel JSON settings:

{
  "transport": "webhook"
}

You can also set a Telegram channel default model in the Channels settings UI. Under the hood this is stored in the JSON settings as default_model, for example:

{
  "streaming": "partial",
  "default_model": "anthropic/claude-opus-4-6"
}

Then:

  1. Save the Telegram bot token under Settings -> ClapilotAICore -> Native Runtime Channels -> Telegram.
  2. Make sure Public Base URL is configured under /admin/general.
  3. Set the Telegram webhook to: https://<your-public-base-url>/api/agent-runtime/channels/telegram/inbound

Example:

curl -X POST "https://api.telegram.org/bot<bot_token>/setWebhook" \
  -H "Content-Type: application/json" \
  -d '{"url":"https://clapilot.example.com/api/agent-runtime/channels/telegram/inbound"}'

Credential source of truth

  • agent mailbox: app_settings.agent_email_* and default IMAP/SMTP fields
  • user mailbox: user_profiles.kanzlei_email(+password)

If app chat and Telegram report different email state, compare resolver path and runtime target.

File handling contract

  • persist dokumente.file_path as relative path only
  • do not store absolute or mandanten/... prefixed paths
  • resolve downloads against /app/workspace/mandanten

Troubleshooting

File listed but download fails

Check dokumente.file_path normalization and mounted workspace root.

Telegram image or document arrives, but the agent says no attachment was present

Check these in order:

  • the inbound Telegram update actually contains photo or document
  • the bot token can call getFile and download https://api.telegram.org/file/bot...
  • the attachment stays below the native channel attachment limit
  • the Telegram message is routed through the native runtime path, not only the plain text channel-response bridge

Telegram says no email credentials

Verify the correct table (app_settings vs user_profiles) and ensure Telegram/app point to the same runtime.

Telegram bot token saved, but no messages arrive

Check these in order:

  • the Telegram channel is enabled in ClapilotAICore settings
  • the sender or group is approved in the Telegram approval list under ClapilotAICore settings
  • the Clapilot app can reach the internal agent-runtime bridge used for delegated workspace/app actions
  • polling mode is active unless settings.transport is explicitly set to webhook
  • if webhook mode is enabled:
    • public_base_url is set and points to the publicly reachable Clapilot host
    • Telegram webhook points to /api/agent-runtime/channels/telegram/inbound, not /internal/...
  • the app can reach clapilot-agent internally
  • if DM preview streaming still looks like multiple normal replies, verify settings.streaming is not pinned to block

Restart loop with disk errors

Check Docker root pressure and prune cache/images safely without deleting named volumes.