---
title: "Telegram"
description: "Deliver investigation findings to a Telegram chat or channel."
---

OpenSRE's Telegram integration delivers investigation findings to any chat your bot has been added to — useful for mobile-first on-call rotations and personal alerting.

Start the interactive shell with `opensre` (no subcommand). Slash commands below are run from that REPL.

---

## Prerequisites

- A Telegram account.
- The Telegram mobile or desktop app, signed in.
- The chat (group, channel, or direct message) where you want to receive findings.

---

## Step 1: Create a bot with BotFather

[BotFather](https://t.me/BotFather) is Telegram's official bot for creating other bots.

1. Open Telegram and search for `@BotFather`. Open the chat and tap **Start**.
2. Send `/newbot`.
3. When prompted, send a **display name** for your bot (e.g. `OpenSRE Alerts`).
4. Send a **username** that ends in `bot` (e.g. `opensre_alerts_bot`). It must be globally unique.
5. BotFather replies with an **HTTP API token** of the form `<numeric-id>:<token-secret>`. Copy it — it is your **bot token**. Treat it like a password. Anyone holding it can send messages as your bot.

<Tip>
You can change the bot name, picture, and description later by sending `/mybots` to BotFather and selecting your bot.
</Tip>

---

## Step 2: Add the bot to a chat

The bot can deliver to three kinds of destinations. Pick the one that fits your team:

<Tabs>
  <Tab title="Group chat">
    1. Open the group where you want findings to land.
    2. Tap the group name → **Add members** → search for your bot username → **Add**.
    3. By default, bots in groups only see messages addressed to them, which is fine for delivery-only.
  </Tab>
  <Tab title="Channel">
    1. Open your channel and tap its name.
    2. Tap **Administrators → Add Administrator**, search for your bot, and add it.
    3. Grant the **Post Messages** permission. No other admin permissions are required.
  </Tab>
  <Tab title="Direct message">
    Open a chat with your bot directly (search its username) and send `/start`. The bot will not reply, but Telegram registers the chat so the bot can message you.
  </Tab>
</Tabs>

---

## Step 3: Find your `chat_id`

The **chat ID** identifies where the bot should post.

1. Send any message in the destination chat — for a channel, post anything; for a DM, send `/start` to your bot.
2. In a browser, open:

   ```
   https://api.telegram.org/bot<YOUR_BOT_TOKEN>/getUpdates
   ```

   (replace `<YOUR_BOT_TOKEN>` with the token from Step 1)

3. In the JSON response, look for a `chat.id` field. The value depends on the chat type:

   | Chat type | Format | Example |
   |---|---|---|
   | Direct message with a user | Positive integer | `123456789` |
   | Group | Negative integer | `-987654321` |
   | Large group or channel | Negative integer starting with `-100` | `-1001234567890` |

   Copy the entire value, **including the leading minus sign** for groups and channels.

<Note>
If `getUpdates` returns an empty array, post a fresh message in the chat and reload — Telegram only buffers recent updates.
</Note>

---

## Step 4: Configure the integration

### Option A: Onboarding wizard (recommended)

Interactive shell:

```text
/onboard
```

CLI:

```bash
opensre onboard
```

Choose **Telegram** from the integration list. The wizard prompts for:

- **Bot token** — stored in the system keyring (not plain `.env`)
- **Default chat ID** — written to `.env` as `TELEGRAM_DEFAULT_CHAT_ID`

Credentials are also saved to `~/.opensre/integrations.json` via `upsert_integration("telegram", ...)`.

Non-interactive setup:

Interactive shell:

```text
/integrations setup telegram
```

CLI:

```bash
opensre integrations setup telegram
```

### Option B: Environment variables

Set in `.env` (bot token can also live in the keyring after wizard setup):

```bash
TELEGRAM_BOT_TOKEN=<numeric-id>:<token-secret>
TELEGRAM_DEFAULT_CHAT_ID=<chat-id>
```

| Variable | Description |
|---|---|
| `TELEGRAM_BOT_TOKEN` | Bot HTTP API token from BotFather. Required. |
| `TELEGRAM_DEFAULT_CHAT_ID` | Default delivery destination. Required for delivery to work. |

OpenSRE picks these up at startup and registers Telegram as an active integration.

<Note>
**Credential resolution.** Every Telegram delivery surface — investigations, the
scheduler, `/watchdog`, `/hermes watch`, and `/watch` — resolves the bot token
in the same order: integration store → `TELEGRAM_BOT_TOKEN` env → system keyring;
and the chat id as `--chat-id` → store `default_chat_id` →
`TELEGRAM_DEFAULT_CHAT_ID` env. Either setup option above works for all of them.
</Note>

---

## Step 5: Verify

Interactive shell:

```text
/integrations verify telegram
```

Or:

```text
/verify telegram
```

CLI:

```bash
opensre integrations verify telegram
```

This calls Telegram's [`getMe`](https://core.telegram.org/bots/api#getme) endpoint. On success it reports the bot `@username`. On failure it reports the Telegram API error message verbatim.

<Note>
**Verify only checks that your bot token is valid.** It does not start a listening process and does not test two-way communication. If you DM your bot at this point, it will appear online but not reply. To receive and respond to messages from Telegram, complete Step 6 below.
</Note>

You can also trigger a real delivery test against a bundled fixture:

Interactive shell:

```text
/investigate tests/e2e/kubernetes/fixtures/datadog_k8s_alert.json
```

CLI:

```bash
opensre investigate --input tests/e2e/kubernetes/fixtures/datadog_k8s_alert.json
```

Findings should appear in the configured chat. Long reports are truncated to Telegram's 4,096-character message limit.

---

## Step 6: Enable two-way chat (DM the agent from Telegram)

> **Skip this step if you only need outbound delivery** (alerts, cron reports, investigation findings posted to a chat). Steps 1–5 are sufficient for that.

After verify succeeds, you have outbound delivery but the bot will not reply to your messages yet. Two extra steps are required:

### 6a — Allow your Telegram user

Find your **numeric user id** with [@userinfobot](https://t.me/userinfobot), then:

Interactive shell:

```text
/messaging allow -p telegram -u 123456789
```

CLI:

```bash
opensre messaging allow -p telegram -u 123456789
```

Or set in `.env`:

```bash
TELEGRAM_ALLOWED_USERS=123456789   # comma-separated for multiple users
```

<Warning>
  Use the **numeric user id** (Telegram's `from.id`), not a `@username` and not
  the bot handle. Inbound authorization only ever matches the numeric id, so a
  `@handle` produces an allow-list entry that can never match a real sender — you
  will see `User <id> is not in the allowed users list` for every message. The CLI
  now rejects non-numeric Telegram ids, but older entries may still be wrong; check
  with `/messaging status -p telegram` (or `opensre messaging status -p telegram`)
  and re-add with the numeric id.
</Warning>

### 6b — Start the gateway daemon

```bash
opensre gateway start
```

This runs the background daemon that listens for inbound Telegram DMs and routes them to the OpenSRE agent. Once it is running, DM your bot from Telegram — use `/new` for a fresh session or `/help` for built-in commands.

Useful follow-up commands:

| Command | What it does |
|---|---|
| `opensre gateway status` | Show daemon and component state |
| `opensre gateway logs -f` | Follow live gateway logs |
| `opensre gateway stop` | Stop the daemon |

<Note>
The gateway uses long polling — no public HTTPS URL or port forwarding is required for local use. See the [Two-way chat gateway](#two-way-chat-gateway-dm-text) section below for deployment and remote-host setup.
</Note>

---

## Programmatic messaging: `telegram_send_message` tool

When the Telegram integration is configured, OpenSRE exposes a
`telegram_send_message` tool. The tool can send user-requested action messages,
incident notifications, or follow-up updates to the configured default chat, or
to an explicit `chat_id` when one is supplied. Ask in plain language from the
interactive shell (for example: "send a Telegram message to the team that DB CPU
is back below 70%").

```json
{
  "name": "telegram_send_message",
  "arguments": {
    "chat_id": "-1001234567890",
    "message": "DB CPU is back below 70%; keeping the incident open for 10 minutes."
  }
}
```

The tool resolves the bot token from the same credential chain as the watchdog:
integration store, then `TELEGRAM_BOT_TOKEN`, then the system keyring.
If `chat_id` is omitted, it sends through the configured `default_chat_id`, so a
user can ask OpenSRE to send a message through the configured Telegram bot
without knowing or exposing the bot token.

Delivery is an external side effect. Its result includes a stable `status`,
`sent`, `error_type`, `chat_id`, `reply_to_message_id`, and `message_length`
shape so follow-up tool calls can tell configuration failures from Telegram
delivery failures.

---

## Two-way chat gateway (DM text)

OpenSRE can also run a **Telegram messaging gateway** so you can chat with the agent from your phone in a private DM. v1 supports **text-only direct messages** (no groups, voice, or attachments).

### Allow your Telegram user

Find your **numeric user id** with [@userinfobot](https://t.me/userinfobot), then:

Interactive shell:

```text
/messaging allow -p telegram -u 123456789
```

CLI:

```bash
opensre messaging allow -p telegram -u 123456789
```

or set `TELEGRAM_ALLOWED_USERS=123456789` in `.env`.

<Warning>
  Use the **numeric user id** (Telegram's `from.id`), not a `@username` and not
  the bot handle. Inbound authorization only ever matches the numeric id, so a
  `@handle` produces an allow-list entry that can never match a real sender — you
  will see `User <id> is not in the allowed users list` for every message. The CLI
  now rejects non-numeric Telegram ids, but older entries may still be wrong; check
  with `/messaging status -p telegram` (or `opensre messaging status -p telegram`)
  and re-add with the numeric id.
</Warning>

### DM pairing (optional)

Same policy as other messaging platforms. Generate a code:

Interactive shell:

```text
/messaging pair -p telegram
```

CLI:

```bash
opensre messaging pair -p telegram
```

Then open Telegram, DM your bot, and send:

```text
/pair <code>
```

Check pairing status:

Interactive shell:

```text
/messaging status -p telegram
```

CLI:

```bash
opensre messaging status -p telegram
```

Revoke access for a user:

Interactive shell:

```text
/messaging revoke -p telegram -u 123456789
```

CLI:

```bash
opensre messaging revoke -p telegram -u 123456789
```

### Long polling (local and production)

No public HTTPS URL required. The gateway is OpenSRE's **background agent daemon** —
one process that runs the Telegram chat worker, the web health app, and the
cron task scheduler. It runs in the background by default:

| Command                            | What it does                                          |
| ---------------------------------- | ----------------------------------------------------- |
| `opensre gateway start`            | Start the daemon (web, Telegram chat, task scheduler) |
| `opensre gateway start -f`         | Run attached to the terminal instead                  |
| `opensre gateway status`           | Show the daemon and each component's state            |
| `opensre gateway logs [-n N] [-f]` | Print (or follow) the daemon logs                     |
| `opensre gateway stop`             | Stop the daemon                                       |

```bash
export TELEGRAM_BOT_TOKEN=<your-bot-token>
uv run opensre gateway start
```

Starting prints the log location; logs are stored in `~/.opensre/gateway/gateway.log`.
If Telegram is not configured the daemon still runs the other components and
`opensre gateway status` shows `telegram: not configured`.

The same controls are available inside the interactive shell via `/gateway`:

```text
/gateway start     # start the background daemon
/gateway status    # daemon + per-component state
/gateway logs 50   # last 50 log lines
/gateway stop      # stop it
```

Send a DM to your bot. Use `/new` to start a fresh session; `/help` for built-in commands.

The gateway uses the same headless agent harness as the interactive shell, with Telegram-specific wiring:

- **Prompt grounding** — CLI reference, AGENTS.md, integration list, and investigation flow context
- **Read-only evidence tools** — live integration queries (GitHub, logs, metrics, etc.) via the gather pass when integrations are configured
- **Action tools** — `shell_run` and `investigation_start`

Investigation delivery, watchdog alerts, and cron still use the outbound-only paths documented above — they do not require the gateway process.

### Deploying the gateway to a remote host

Running the gateway on a server with `make deploy` (EC2) is different from local
use in one important way: **the remote host cannot read your local machine's
keychain**. Guided setup stores the bot token (and your LLM API key) in the system
keyring, which is perfect locally but does not travel to the deployed instance.

So `make deploy` validates that the required secrets are present as **plaintext
env vars** in `.env`, and aborts if they are only in the keychain:

```bash
TELEGRAM_BOT_TOKEN=<numeric-id>:<token-secret>
TELEGRAM_ALLOWED_USERS=123456789        # numeric user id(s), comma-separated
ANTHROPIC_API_KEY=...                    # or your provider's key env
```

If `make deploy` reports `MISSING: TELEGRAM_BOT_TOKEN — API key not set` or
`MISSING: OPENAI_API_KEY — API key not set` (or your provider's key env) even
though local setup succeeded, this is
why — copy the values into `.env` (or `.env.deploy.example`) for the deploy. A
"missing" here means "not in the deploy env", not "not configured".

---

## Scheduled and watchdog delivery

### Cron (recurring reports)

Interactive shell:

```text
/cron add --kind daily_summary --cron "0 9 * * 1-5" --tz Asia/Kolkata --provider telegram --chat-id <chat_id>
/cron list
/cron run <task_id>
/cron logs <task_id>
```

CLI:

```bash
opensre cron add --kind daily_summary --cron "0 9 * * 1-5" \
  --tz Asia/Kolkata --provider telegram --chat-id <chat_id>
opensre cron list
opensre cron run <task_id>
opensre cron logs <task_id>
```

See [Cron](/cron) for task kinds and scheduler daemon setup.

### Watchdog (process threshold alarms)

Configure Telegram first (Steps 4–5), then:

Interactive shell:

```text
/watch <pid> [--max-cpu N] [--max-runtime D] [--max-rss S] [--cooldown D] [--interval N] [--once]
/watches
/unwatch <task_id>
```

Or:

```text
/watchdog --pid <pid> [--max-cpu N] [--max-runtime D] [--max-rss S]
```

CLI:

```bash
opensre watchdog --pid <pid> [--max-cpu N] [--max-runtime D] [--max-rss S]
```

### Hermes incident escalation

Interactive shell:

```text
/hermes watch
```

CLI:

```bash
opensre hermes watch
```

See [Hermes](/hermes) for log tailing and incident classification setup.

---

## Troubleshooting

`/integrations verify telegram`, `/verify telegram`, and `opensre integrations verify telegram` only call Telegram's `getMe` endpoint, so they surface token-validity errors but cannot detect chat-routing problems. Delivery-time errors only show up when an investigation actually posts.

### Errors from verify

**`Missing bot_token`**

`TELEGRAM_BOT_TOKEN` is empty. Re-check `.env` and restart any long-running OpenSRE process so it re-reads the file.

**`Telegram API check failed: 401 Client Error: Unauthorized for url: …`**

The bot token is invalid or has been revoked. Generate a new one in BotFather (`/mybots → your bot → API Token → Revoke current token`) and update `.env`.

<Warning>
The verifier currently does not redact secrets from this error: the `for url:` portion of the message includes the bot token (Telegram's API endpoint embeds the token in the path). Redact the token before sharing this error in bug reports or chat. The verifier-side redaction fix is tracked separately from the wizard work — file a new issue if one does not yet exist.
</Warning>

### Errors that only surface during delivery

These are Telegram API responses that come back when OpenSRE actually tries to post a finding. `verify` only calls `getMe`, so it cannot catch them. They appear in OpenSRE logs as `[telegram] post message failed: <description>` with the Telegram description copied verbatim.

**`description: chat not found`**

The bot is not in the chat, or `TELEGRAM_DEFAULT_CHAT_ID` is wrong. Re-add the bot and re-fetch `chat_id` from `getUpdates`.

**`description: bot was kicked from the large group …` (or similar)**

Re-add the bot. For channels, the bot must be an administrator with **Post Messages** permission.

**Findings never arrive, but `verify` passes**

`getMe` only confirms the token is valid; it does not test delivery. Send a fresh message in the destination chat and re-fetch `chat_id` from `getUpdates` — your `chat_id` may have changed (for example, if a group was upgraded and now uses a `-100` prefix).

**Gateway: `User <id> is not in the allowed users list`**

Run `/messaging status -p telegram` and confirm the sender's numeric user id is listed. Re-add with `/messaging allow -p telegram -u <user_id>` or complete `/messaging pair -p telegram` pairing.
