---
title: Channels (beta) | Letta Docs
description: Connect external messaging platforms to your Letta Code agents
---

Channels are a beta feature — leave feedback and bug reports in our [Discord](https://discord.gg/letta)!

Currently Telegram, Slack, Discord, and WhatsApp are supported.

Channels let your Letta Code agent receive and respond to messages from external platforms like Telegram, Slack, Discord, and WhatsApp. Messages from the platform flow into the agent’s conversation, and the agent replies using the `MessageChannel` tool.

Channels function as an additional communication option. You can combine channels with the [Letta Code app](/letta-code/desktop-app/index.md), the [CLI](/letta-code/cli/index.md), or [chat.letta.com](https://chat.letta.com) — all connected to the same agent and memory.

Channels use Letta Cloud and `letta server`. Agents created in [Local mode](/letta-code/local-mode/index.md) stay on one machine and are not available to messaging integrations.

## Getting started

You can set up channels via the [Letta Code app](/letta-code/desktop-app/index.md) or the CLI. The app provides a visual setup flow in the **Connections** sidebar tab. The CLI still uses the `letta channels` command group, documented below.

## Telegram (CLI)

### 1. Create a bot

Create a Telegram bot via [@BotFather](https://t.me/BotFather) and copy your bot token.

### 2. Configure

```
letta channels configure telegram
```

The interactive wizard will:

- Auto-install the Telegram runtime dependencies
- Ask for your bot token (validates it against the Telegram API)
- Let you choose a DM policy (`pairing`, `allowlist`, or `open`)

Config is written to `~/.letta/channels/telegram/accounts.json`.

### 3. Start the server with channels enabled

```
letta server --channels telegram
```

You’ll see `[Telegram] Bot started as @your_bot` in the output. The bot begins long-polling for messages immediately.

### 4. Pair a Telegram chat to an agent

Send any message to your bot from Telegram. With the default `pairing` policy, the bot replies with a pairing code and instructions to complete setup in Letta Code.

You can complete the pairing from the CLI:

```
letta channels pair \
  --channel telegram \
  --code B5ZR5H \
  --agent <your-agent-id> \
  --conversation <your-conversation-id>
```

The `--agent` flag defaults to the `LETTA_AGENT_ID` env var and `--conversation` defaults to `LETTA_CONVERSATION_ID` (or `"default"`).

You can also pair from within an active Letta Code session (app, CLI, or desktop):

```
/channels telegram pair B5ZR5H
```

### 5. Chat

Messages from Telegram now flow into the agent’s conversation. The agent responds using the `MessageChannel` tool, which converts markdown to Telegram-safe HTML formatting (bold, italic, code, links, etc.).

## Slack (CLI)

### 1. Create a Slack app

1. Go to [api.slack.com/apps](https://api.slack.com/apps) and click **Create New App**
2. Choose **From a manifest**, select your workspace, paste the JSON manifest below, and create the app
3. Generate an **app-level token**: go to **Settings > Basic Information**, scroll to **App-Level Tokens**, click **Generate Token and Scopes**, give it a name (e.g. “main”), add the `connections:write` scope, and click **Generate**. Copy the token (`xapp-...`).
4. **Install the app**: go to **Settings > Install App**, click **Install to Workspace**, authorize, then copy the **Bot User OAuth Token** (`xoxb-...`)

Slack app manifest

```
{
  "display_information": {
    "name": "Letta Code",
    "description": "Slack connector for Letta Code"
  },
  "features": {
    "bot_user": {
      "display_name": "Letta Code Slack Bot",
      "always_online": true
    },
    "app_home": {
      "messages_tab_enabled": true,
      "messages_tab_read_only_enabled": false
    }
  },
  "oauth_config": {
    "scopes": {
      "bot": [
        "app_mentions:read",
        "channels:history",
        "chat:write",
        "files:read",
        "files:write",
        "groups:history",
        "im:history",
        "reactions:read",
        "reactions:write",
        "users:read"
      ]
    }
  },
  "settings": {
    "org_deploy_enabled": false,
    "socket_mode_enabled": true,
    "is_hosted": false,
    "token_rotation_enabled": false,
    "event_subscriptions": {
      "bot_events": [
        "app_mention",
        "message.channels",
        "message.groups",
        "message.im",
        "reaction_added",
        "reaction_removed"
      ]
    }
  }
}
```

This manifest pre-configures Socket Mode, all required scopes, event subscriptions, and App Home messaging.

### 2. Configure

```
letta channels configure slack
```

The wizard asks for both tokens and your DM policy (`open` or `allowlist`).

If you choose `allowlist`, you’ll need Slack user IDs. To find a user ID: open the user’s profile in Slack, click the **three-dot menu** (⋯), and select **Copy member ID**. IDs start with `U` or `W`.

### 3. Start the server

```
letta server --channels slack
```

### 4. Bind the Slack app to an agent

Unlike Telegram (which uses pairing codes), Slack requires binding the app to an agent before any messages will route:

```
letta channels bind --channel slack --agent <your-agent-id>
```

The `--agent` flag defaults to the `LETTA_AGENT_ID` env var. If you have multiple Slack accounts configured, pass `--account-id` to specify which one.

You can also bind from the [Letta Code app](/letta-code/desktop-app/index.md) under **Connections > Slack**.

### 5. Chat

Once bound, DM the app or `@mention` it in a channel to start chatting.

**Slack routing behavior:**

- **@mentions in channels**: Each mention creates a new conversation for the agent. The agent replies in-thread, and all subsequent messages in that thread are forwarded without requiring mentions.
- **DMs**: Each DM chat gets a 1:1 mapping to a conversation. Routes are auto-created on first message.
- **DM policy**: Slack defaults to `open` (recommended). `allowlist` is also supported for DMs. The `pairing` policy does not apply to Slack.

## Discord (CLI)

For a standalone Discord bot running outside `letta server` — for example a Node.js bot deployed to Railway — see the [Discord bot tutorial](/tutorials/discord-bot/index.md). The Discord channel below is the built-in, device-local integration.

### 1. Create a Discord bot

1. Go to the [Discord Developer Portal](https://discord.com/developers/applications) and click **New Application**
2. Open the **Bot** tab. Under **Privileged Gateway Intents**, enable **Message Content Intent**
3. Click **Reset Token** and copy the bot token (starts with `MT...` or similar)
4. Open the **OAuth2 > URL Generator** tab, select the `bot` scope, and grant the permissions your setup needs. Common permissions are Send Messages, Read Message History, Add Reactions, Create Public Threads, Send Messages in Threads, and Attach Files. Then open the generated URL to invite the bot to your server.

### 2. Configure

```
letta channels configure discord
```

The interactive wizard will:

- Auto-install the Discord runtime dependencies
- Ask for your bot token
- Let you choose a DM policy (`open`, `allowlist`, or `pairing`)
- Let you choose guild channel behavior (`mention-only` or `open`)
- Ask whether mentions should auto-create Discord threads
- Ask whether to enable inbound debounce, reaction acknowledgements, and audio transcription

Config is written to `~/.letta/channels/discord/accounts.json`.

### 3. Start the server with channels enabled

```
letta server --channels discord
```

You’ll see `[Discord] Bot started as YourBot#1234` in the output. The bot begins listening for guild messages and DMs immediately.

### 4. Bind the Discord bot to an agent

Like Slack, Discord binds the bot to a single agent (there’s no per-chat pairing):

```
letta channels bind --channel discord --agent <your-agent-id>
```

The `--agent` flag defaults to the `LETTA_AGENT_ID` env var. If you have multiple Discord bots configured, pass `--account-id` to specify which one.

You can also bind from the [Letta Code app](/letta-code/desktop-app/index.md) under **Connections > Discord**.

### 5. Chat

Once bound, DM the bot, `@mention` it in a channel, or send a message in a guild channel configured with `open` mode to start chatting.

**Discord routing behavior:**

- **@mentions in channels**: Each mention creates a new conversation for the agent. The agent replies in-thread, and subsequent messages in that thread are forwarded without requiring mentions.
- **Open guild channels**: Channels configured with `allowed_channels` mode `open` can route non-mention messages to the bound agent.
- **DMs**: Each DM gets a 1:1 mapping to a conversation. Routes are auto-created on first message.
- **DM policy**: Discord defaults to `pairing` (recommended) — unknown DMs receive a one-time code an operator must approve. `allowlist` and `open` are also supported.

### Restricting the bot to specific guild channels

By default, a Discord bot listens in every guild channel it can see. To narrow that down, set `allowed_channels`. DMs are unaffected.

`allowed_channels` supports two shapes:

- A legacy string array, where every listed channel runs in `mention-only` mode.
- A mode map, where each channel ID maps to `mention-only` or `open`.

`mention-only` means the bot responds when it is `@mentioned` in the channel, then continues inside the routed thread or conversation. `open` means the bot may process ambient non-mention messages in that channel. Use `open` carefully and only in channels where the bot should see every message.

Edit `~/.letta/channels/discord/accounts.json`:

```
{
  "accounts": [
    {
      "account_id": "...",
      "token": "...",
      "dm_policy": "pairing",
      "allowed_users": [],
      "allowed_channels": {
        "1234567890123456789": "mention-only",
        "9876543210987654321": "open"
      },
      "agent_id": "agent-..."
    }
  ]
}
```

You can also use the legacy array form when every allowed guild channel should stay mention-only:

```
{
  "allowed_channels": ["1234567890123456789", "9876543210987654321"]
}
```

Use `"*"` in the map as a wildcard default for visible guild channels:

```
{
  "allowed_channels": {
    "*": "mention-only",
    "9876543210987654321": "open"
  }
}
```

For thread messages, Letta Code checks the parent guild channel when available.

Or set allowed channels from the [Letta Code app](/letta-code/desktop-app/index.md) under **Connections > Discord > Manage > Bot settings > Allowed guild channels**.

To find a channel ID, enable Developer Mode in Discord (User Settings → Advanced), then right-click the channel and choose **Copy Channel ID**. Leave `allowed_channels` empty or unset to listen in every channel the bot can see.

### Advanced Discord options

The Discord account config also accepts these optional keys:

| Key                            | Behavior                                                                                                                                                                     |
| ------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `auto_thread_on_mention`       | Create a Discord thread when the bot is mentioned in a guild channel. New accounts default to `false`; older migrated accounts may keep their previous auto-thread behavior. |
| `thread_policy_by_channel`     | Per-channel overrides for auto-threading, for example `{ "123": true, "456": false }`.                                                                                       |
| `acknowledge_message_reaction` | Add lightweight reaction acknowledgements such as eyes/check marks around processing.                                                                                        |
| `inbound_debounce_ms`          | Debounce rapid open-channel messages. Values are capped at 10,000 ms.                                                                                                        |
| `transcribe_voice`             | Attempt to transcribe audio attachments when `OPENAI_API_KEY` is available.                                                                                                  |
| `remove_stale_routes`          | Allow route-cleanup flows to remove routes that no longer match `allowed_channels`; defaults to `false`.                                                                     |

## WhatsApp (CLI)

WhatsApp sends through the linked WhatsApp account. Keep self-chat only enabled for personal numbers. Disable it only for a dedicated agent number, because replies will appear as that WhatsApp number.

### 1. Choose a WhatsApp account

Use a WhatsApp account you control. The default setup is **self-chat only**, which lets you talk to the agent by messaging yourself in WhatsApp and drops messages from other chats.

If you are using a dedicated WhatsApp number for the agent, you can disable self-chat only during setup. Letta Code will ask you to confirm that replies will appear under the linked WhatsApp number before continuing.

### 2. Configure

```
letta channels configure whatsapp
```

The interactive wizard will:

- Auto-install the WhatsApp runtime dependencies
- Ask whether to keep self-chat only enabled
- Let you choose a DM policy (`pairing`, `allowlist`, or `open`)
- Optionally bind the WhatsApp account to an agent for account-scoped routing
- Let you choose group behavior (`disabled`, `mention`, or `open`)
- Ask whether to download inbound media and transcribe voice memos when `OPENAI_API_KEY` is set

Config is written to `~/.letta/channels/whatsapp/accounts.json`.

### 3. Start the server with WhatsApp enabled

```
letta server --channels whatsapp
```

On first start, the listener prints a linked-device QR code. In WhatsApp, open **Settings > Linked Devices > Link a Device** and scan the QR code. Session state is stored under `~/.letta/channels/whatsapp/auth/`.

### 4. Pair or route a chat

For the default self-chat flow, message yourself in WhatsApp. With the default `pairing` DM policy, the chat receives a one-time pairing code. Complete the pairing from the CLI:

```
letta channels pair \
  --channel whatsapp \
  --code B5ZR5H \
  --agent <your-agent-id> \
  --conversation <your-conversation-id>
```

You can also pair from within an active Letta Code session:

```
/channels whatsapp pair B5ZR5H
```

For a dedicated agent number with an account-bound agent, direct chats can route according to the configured DM policy. Group messages route only when group mode is `mention` or `open`; set `allowed_groups` to restrict which groups the agent can see.

### 5. Chat

Messages from WhatsApp now flow into the agent’s conversation. The `MessageChannel` tool supports sending messages, reacting to messages, and uploading files on WhatsApp. Inbound media is downloaded only when you enabled that option during setup.

## Channel slash commands

After a chat is connected, you can send these slash commands as normal messages in the Telegram, Slack, Discord, or WhatsApp chat. They are handled by the channel runtime before the message reaches the agent.

| Command       | Description                                                   |
| ------------- | ------------------------------------------------------------- |
| `/help`       | Show channel usage guidance                                   |
| `/status`     | Show account, listener, route, agent, and conversation state  |
| `/pause`      | Pause agent replies for the current routed chat               |
| `/resume`     | Resume agent replies for the current routed chat              |
| `/cancel`     | Cancel the in-progress agent turn for the current routed chat |
| `/chat`       | Show the Letta web chat link for the current route            |
| `/reflection` | Start a memory reflection pass for the current routed chat    |
| `/reflect`    | Alias for `/reflection`                                       |

For Slack threads, send the command in the routed thread when there may be more than one Letta Code thread in the same channel. Slack-native slash command payloads currently exist only for `/cancel`; the other commands are typed as regular chat messages.

## Architecture

```
flowchart LR
    subgraph Platform["Messaging platform"]
        TG["Telegram / Slack / Discord / WhatsApp"]
    end

    subgraph Local["Your machine (letta server)"]
        Adapter["Channel adapter<br/>(long-polling / Socket Mode / gateway)"]
        Registry["Channel registry"]
        Queue["Message queue"]
        Agent["Agent"]
        Tool["MessageChannel tool"]
    end

    TG -->|"Inbound message"| Adapter
    Adapter --> Registry
    Registry -->|"XML-wrapped message"| Queue
    Queue --> Agent
    Agent -->|"Tool call"| Tool
    Tool -->|"Outbound reply"| TG
```

1. The **adapter** receives messages from the platform (Telegram uses long-polling, Slack uses Socket Mode, Discord uses the gateway WebSocket, and WhatsApp uses a linked-device WebSocket session)
2. The **registry** checks DM policy (pairing/allowlist/open), looks up the route, and formats the message as XML
3. The message enters the agent’s **queue** as a `channel` source item
4. The agent processes it and calls the **MessageChannel** tool to reply
5. The tool converts markdown to platform-safe formatting and sends through the adapter

## DM policies

Each channel has a DM policy that controls who can message the bot:

| Policy                                                  | Behavior                                                                                                                                                                |
| ------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `pairing` (default for Telegram, Discord, and WhatsApp) | Unknown users receive a one-time pairing code. An operator must approve the code to bind the chat to an agent.                                                          |
| `allowlist`                                             | Only pre-configured user IDs can message. Others are rejected.                                                                                                          |
| `open` (default for Slack)                              | Anyone can message. For Telegram, requires a route to exist. For Slack, Discord, and WhatsApp, routes are auto-created when the channel/account routing mode allows it. |

Defaults differ by channel. Telegram, Discord, and WhatsApp default to `pairing` (explicit approval per chat). Slack defaults to `open` (anyone can DM or mention the app, routes auto-provision). WhatsApp also has self-chat and group-mode gates that can be stricter than the DM policy.

## Routing

Routes bind a platform chat ID to an agent + conversation pair. They’re stored in `~/.letta/channels/<channel>/routing.yaml`.

Routes are created automatically when pairing completes, or manually via the CLI:

```
# Add a route manually
letta channels route add \
  --channel telegram \
  --chat-id 123456789 \
  --agent agent-abc123 \
  --conversation default


# List all routes
letta channels route list


# List routes for a specific channel
letta channels route list --channel telegram


# Remove a route
letta channels route remove --channel telegram --chat-id 123456789
```

## CLI reference

| Command                                      | Description                                                                           |
| -------------------------------------------- | ------------------------------------------------------------------------------------- |
| `letta channels install <channel>`           | Install channel runtime dependencies (optional — `configure` does this automatically) |
| `letta channels configure <channel>`         | Interactive setup wizard (installs runtime deps if needed)                            |
| `letta channels status`                      | Show config, routing, and pairing state (JSON)                                        |
| `letta channels route list [--channel <ch>]` | Show routing table                                                                    |
| `letta channels route add [options]`         | Add a route binding a chat to an agent                                                |
| `letta channels route remove [options]`      | Remove a route                                                                        |
| `letta channels bind [options]`              | Bind a Slack or Discord channel account to an agent                                   |
| `letta channels pair [options]`              | Complete a pairing code and bind to agent (Telegram or WhatsApp)                      |

### Common flags

| Flag                  | Commands               | Description                                                                                          |
| --------------------- | ---------------------- | ---------------------------------------------------------------------------------------------------- |
| `--channel <name>`    | route, pair, bind      | Channel name (`telegram`, `slack`, `discord`, `whatsapp`)                                            |
| `--account-id <id>`   | route add/remove, pair | Account ID (required when multiple accounts exist for a channel, auto-resolved when only one exists) |
| `--chat-id <id>`      | route add/remove       | Platform chat/conversation ID                                                                        |
| `--agent <id>`        | route add, pair, bind  | Agent ID (defaults to `LETTA_AGENT_ID`)                                                              |
| `--conversation <id>` | route add, pair        | Conversation ID (defaults to `LETTA_CONVERSATION_ID` or `"default"`)                                 |
| `--code <code>`       | pair                   | Pairing code from the bot                                                                            |

## Headless deployment

For server/Docker deployments without interactive setup:

1. Pre-write the config files to `~/.letta/channels/<channel>/`
2. Start with the `--install-channel-runtimes` flag to auto-install dependencies:

```
letta server --channels telegram --install-channel-runtimes
```

Or install runtimes separately:

```
letta channels install telegram
```

### Environment variables

| Variable                | Description                                          |
| ----------------------- | ---------------------------------------------------- |
| `LETTA_AGENT_ID`        | Default agent ID for `pair` and `route add` commands |
| `LETTA_CONVERSATION_ID` | Default conversation ID (fallback: `"default"`)      |
| `LETTA_API_KEY`         | API key for `letta server` authentication            |

## State files

| File                                   | Description                                                         |
| -------------------------------------- | ------------------------------------------------------------------- |
| `~/.letta/channels/<ch>/accounts.json` | Channel account configuration (tokens, DM policy, account metadata) |
| `~/.letta/channels/<ch>/routing.yaml`  | Route table (chat ID to agent/conversation binding)                 |
| `~/.letta/channels/<ch>/pairing.yaml`  | Pending and approved pairings                                       |

## Connection lifecycle

Channels integrate with the `letta server` WebSocket connection:

- On **connect**: channel adapters register their message handler and flush any buffered messages
- On **disconnect**: adapters pause delivery but keep polling/listening. Messages buffer until reconnection.
- On **shutdown**: adapters stop cleanly

This means if `letta server` briefly loses its WebSocket connection, no Telegram, Slack, Discord, or WhatsApp messages are dropped — they buffer and deliver when the connection restores.

You can also configure channels on [remote devices](/letta-code/remote/index.md) — simply swap the selected device in the Connections menu of the Letta Code app.
