---
title: Telegram integration (read and reply to Telegram chats from Omniscio)
---

# Telegram integration (read and reply to Telegram chats from Omniscio)

## What it is

Omniscio's Telegram integration uses the Telegram **MTProto user-account API** (via the maintained `@mtcute/node` client library, the default since F002; the archived `gramjs` library stays selectable as a reversible opt-out) — not the bot API — so you sign in with your real Telegram account and see every chat, group, and channel you'd see in the official Telegram apps. Messages stream into the Omniscio unified inbox in real time alongside Gmail, SMS, and Slack. You reply inline; outgoing messages send as you (your account, your name) rather than as a bot. Media attachments (photos, voice notes, documents) download on demand and render inside the conversation panel.

> Looking to **talk to your agents from Telegram** instead? That is the separate [Telegram Bot channel](telegram-bot-channel.md): a BotFather bot you message, with one agent session per chat. The two can be on at the same time and never share data.

## Where to find it

**Settings → Telegram** holds the **Telegram API key**, the **Sign in** flow and the connection state, and the panel walks you through getting a key when none is configured. Once you are connected, every chat arrives as a row in the **inbox**, and opening one shows the conversation panel with its lazy-loading media. An outbound send a script makes on your behalf lands as an approval row in that same inbox.

## How it behaves

### How to use it

1. **Get API credentials.** Visit my.telegram.org → **API development tools** → register a new application. Telegram gives you an `api_id` (number) and `api_hash` (string). These identify your Omniscio build, not your account — they're tied to the app, not the user.
2. **Provide credentials.** Omniscio resolves the `api_id` / `api_hash` in this order: **the user's own key** (Settings → Telegram → **Telegram API key**, stored encrypted) → the build-time `MAIN_VITE_TELEGRAM_API_ID` / `MAIN_VITE_TELEGRAM_API_HASH` env vars (set them in your env or a local `.env` before `npm run dev` / `npm run build`) → the bundled internal default in `default-credentials.local.json` (`telegramApiId` / `telegramApiHash` — the same slot Gmail/Calendar use, but **deliberately EMPTY for Telegram**) → empty. A build with none of these still compiles; the Telegram panel then walks you through getting one instead of showing a Connect button that cannot work.
   - **The user's key is the NORMAL path, not a fallback.** No Telegram key ships and none is planned, so on a shipped install layer 1 is the only source — which is why the settings panel presents it as an ordinary ~2-minute setup step (with the my.telegram.org steps inline) rather than as a missing part. The release-credential contract lists telegram among the keys deliberately NOT required; the env + bundled layers stay wired for people who build Omniscio themselves.
   - **Why no shared key ships.** Telegram rate-limits an api_id it considers too widely published (`API_ID_PUBLISHED_FLOOD`) — the documented failure mode for any single key shipped inside a distributed app. Per-install keys mean one throttled key can never lock everyone out.
   - **Changing the key signs you out.** A Telegram session is bound to the api_id it was authorised under, so saving or clearing a key clears the saved session and you reconnect once. Your downloaded message history is kept — the api_id identifies the app, not your account.
3. **Sign in.** Settings → **Telegram** → **Sign in**. Enter your phone number; Telegram sends a login code to the official Telegram app on another device. Type the code into Omniscio. If you have two-factor auth enabled, Omniscio prompts for your cloud password next. The session blob is encrypted via `safeStorage` and stored locally — sign-out clears it.
4. **Triage from the inbox.** Once connected, every chat shows up as an inbox row. Open one to see the conversation panel: messages newest-first, scroll back to load history, reply inline. Photos and voice notes lazy-load on click.
5. **Pair with automations.** Telegram messages flow through the standard Automation/Auto-replies engine and the daily digest, so you can keyword-route or auto-summarize them like any other channel.

### Scope and limits

Telegram is fully reachable from outside Omniscio over the `127.0.0.1:19519` localhost HTTP server (the same surface that exposes Gmail, SMS, recipes, and settings). An external AI agent or script holding the Omniscio bearer token can read conversations and a thread, triage the Telegram inbox, and — approval-gated — send, edit, delete, or forward a message. The route family (20 routes) lives in [cli-server-telegram-routes.ts](/src/main/services/cli/cli-server-telegram-routes.ts), registered once at startup from [register-cli-routes.ts](/src/main/app/startup/register-cli-routes.ts), and is locked by the [telegram-cli-contract.md](/.claude/memory/contracts/telegram-cli-contract.md). The gating model splits into two tiers by blast radius:

- **Reads and triage apply immediately.** `GET /telegram/status` · `/conversations` · `/conversations/:conversationId/messages` · `/search`, plus the triage mutations `read`, `archive`/`unarchive`, `mute`/`unmute`, `snooze`/`unsnooze`, `dismiss`, `DELETE /telegram/conversations/:conversationId` (removes the local copy only), `sync`, `connect`, `disconnect` — each applies inline (bearer + the global mutation rate-limit), mirroring the in-app Telegram view, and emits a `telegram:conversation-updated` push so an open Omniscio window refreshes.
- **Sending is approval-gated.** `POST /telegram/send`, `edit`, `delete`, and `forward` are the outbound, real-person-facing actions: by default they land as pending rows in Omniscio's inbox (preview shows the conversation + message snippet) and only transmit when you click **Approve** — the same user-in-the-loop guarantee as the other approval-gated CLI capabilities in [cli-pending-actions.md](cli-pending-actions.md). You can flip `requireApprovalForCliTelegramSend` off (Settings → CLI Control → Approval requirements) to send immediately. These route through the shared `gateOrApplyCliAction` chokepoint and the `telegram-send` approval family.

What the CLI deliberately does **not** expose: the **auth flow** (phone sign-in / login-code / 2FA — a secret-adjacent, interactive, device-bound step; configure it in Settings → Telegram → Sign in) and **AI reply suggestions / thread summaries** (they call a paid Anthropic model, kept off the CLI under the same rule that keeps mind-map AI generation off the CLI).

This is the same desktop+CLI duality as SMS — see [cli-sms.md](cli-sms.md) for the parallel write-up.

- **In-app triage is deliberately keyboard-light (declared).** Like SMS, the in-app Telegram view is mouse-first with no keyboard-only actions and no scoped shortcut set — see the rationale in [set-up-sms-integration.md](set-up-sms-integration.md#keyboard-support) (audit finding F011, 2026-08-10).

## For agents

### How it works

Telegram is in [/src/main/services/telegram/telegram-service.ts](/src/main/services/telegram/telegram-service.ts) — a service that owns the connection, login state, and event subscription, sitting **above a pluggable engine seam** (`TelegramBackend`). The engine is picked by the `telegramEngine` setting: `@mtcute/node` for anything but an explicit `gramjs` opt-out. Both engines share ONE credential resolver and emit byte-identical DTOs, so everything above the seam is engine-blind — see [telegram-engine-backend-contract.md](/.claude/memory/contracts/telegram-engine-backend-contract.md). **The client-library specifics in the rest of this paragraph describe the `gramjs` engine**; the mtcute engine reaches the same DTOs by its own equivalents. The MTProto session string lives in `safeStorage`-encrypted form via `getTelegramSession` / `setTelegramSession` in [/src/main/services/config-store/accessors-integrations.ts](/src/main/services/config-store/accessors-integrations.ts). Incoming messages are wired through `client.addEventHandler(NewMessage(...))` and pushed to the renderer via `emitPush(IPC.TELEGRAM_*)`. A 60-second background-sync timer keeps history fresh when the app is foregrounded; a 60-second watchdog detects dead connections and triggers reconnects. Because a reconnect makes gramjs replay already-seen updates (`getDifference` gap-recovery), the live `NewMessage` handler de-duplicates on the stored message id before doing any side effect, so a redelivered message never re-bumps the unread badge or re-fires its push. Two-factor login uses `computeCheck` from `telegram/Password` to do the SRP password proof client-side — your password never leaves the machine. Media downloads go through [/src/main/services/telegram/telegram-media.ts](/src/main/services/telegram/telegram-media.ts), and history catch-up is in [/src/main/services/telegram/telegram-sync.ts](/src/main/services/telegram/telegram-sync.ts). Persistence: [/src/main/db/queries-telegram.ts](/src/main/db/queries-telegram.ts). Unified-inbox bridge: [/src/main/services/state-redaction.ts](/src/main/services/state-redaction.ts). Settings flag: `telegramEnabled` in [/src/shared/types.ts](/src/shared/types.ts).

## Related

- [INDEX.md](INDEX.md) — full library index
- [gmail-integration.md](gmail-integration.md) — parallel email integration with the same UX
- [automations-and-auto-replies.md](automations-and-auto-replies.md) — auto-reply rules that run against Telegram messages
