> ## Documentation Index
> Fetch the complete documentation index at: https://docs.dairo.app/llms.txt
> Use this file to discover all available pages before exploring further.

# Channels

> How Dairo's channel-agnostic model works across email, agent-to-agent, Telegram, and Slack, and how a send picks its channel.

A channel is the medium a message travels over. Dairo's inboxes, messages, and threads
share one channel-agnostic model, so the same send call and the same read loop work
across channels. You learn the model once, and each new channel adds a value rather than
a new API.

Four channels carry messages today:

* **`email`** — external mail over your [verified domains](/domains/domains), delivered
  and tracked for you.
* **`a2a`** — internal delivery between Dairo inboxes. It lands as a signed
  [agent-to-agent](/receiving/agent-messages) receipt on both ends.
* **`telegram`** — external chat over a [Telegram bot](/channels/telegram) you connect.
  Messages people send the bot arrive in your mailbox; your agent replies with the same
  send call.
* **`slack`** — external chat over a [Slack app](/channels/slack) your customers install.
  Mentions and DMs arrive as messages; your agent replies the same way.

The `channel` enum also reserves values for channels that are not open yet, such as
`whatsapp`. Dairo never returns a channel that is not live, so any value you read is one
you can act on.

## The `channel` field

`channel` is a first-class field. It appears wherever the medium matters:

| Where                                      | What it means                                                                    |
| ------------------------------------------ | -------------------------------------------------------------------------------- |
| [Inbox](/receiving/inboxes)                | The channel the inbox operates on (`email`, `telegram`, or `slack`).             |
| [Message](/receiving/messages-and-threads) | The channel this message traveled over (`email`, `a2a`, `telegram`, or `slack`). |
| Thread                                     | The channel the conversation runs on.                                            |
| Send request                               | An optional delivery hint (`email` or `a2a`); omit it to let Dairo classify.     |

Because `channel` rides on the message itself, one inbox can hold a mix: an `email` reply
and an `a2a` hop live side by side in the same thread, each tagged with how it arrived.

## Send on a channel

Sending is one call to `POST /v1/messages`. For an email inbox, the `channel` field is an
optional hint (`email` or `a2a`), not a switch you must set. When you omit it, Dairo
classifies by recipient:

* Every recipient is a Dairo inbox → the internal `a2a` hop, a signed receipt on both
  ends.
* Any external (non-Dairo) recipient → `email` over your verified domain.

Setting `channel` overrides that classification:

* `channel: "email"` forces the email path even when every recipient is a Dairo inbox.
* `channel: "a2a"` forces the internal hop and requires every recipient to be a Dairo
  inbox. A send with any external recipient is rejected with `400`, so an explicit `a2a`
  request can never deliver over public email.

The send response echoes back the `channel` it actually went out on.

<Note>
  A Telegram or Slack inbox is different: it always delivers over its own channel, and the
  inbox's channel wins over any `channel` hint on the request. Those channels are driven by
  the inbox, not passed as a send hint.
</Note>

<CodeGroup>
  ```ts title="TypeScript" theme={null}
  // Omit channel → Dairo classifies. External recipient here, so it goes out over email.
  const email = await dairo.messages.send({
    inboxId: "inbox_123",
    to: "ada@example.com",
    subject: "Hello",
    text: "Sent on the email channel.",
  });
  console.log(email.channel); // "email"

  // Target another Dairo inbox over a2a explicitly.
  const hop = await dairo.messages.send({
    inboxId: "inbox_123",
    channel: "a2a",
    to: "agent@partner.dairo.app",
    subject: "Handoff",
    text: "Over to you.",
  });
  console.log(hop.channel); // "a2a"
  ```

  ```bash title="cURL" theme={null}
  curl -X POST https://api.dairo.app/v1/messages \
    -H "Authorization: Bearer $DAIRO_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "inboxId": "inbox_123",
      "to": "ada@example.com",
      "subject": "Hello",
      "text": "Sent — classified to email for an external recipient."
    }'
  ```

  ```json title="MCP · send_message" theme={null}
  {
    "inboxId": "inbox_123",
    "to": ["agent@partner.dairo.app"],
    "channel": "a2a",
    "subject": "Handoff",
    "text": "Over to you."
  }
  ```
</CodeGroup>

## channelMetadata: the per-channel extension point

Channel-specific fields ride in a `channelMetadata` bag rather than as top-level columns,
so adding a channel never reshapes the common fields. What it carries depends on the
channel:

| Channel    | `channelMetadata` carries                                                            |
| ---------- | ------------------------------------------------------------------------------------ |
| `email`    | empty (`{}`); delivery and authentication details are flat fields on the message     |
| `a2a`      | empty (`{}`); the hop's provenance lives on the receipt                              |
| `telegram` | `chatId`, `telegramMessageId`, `fromUsername` — plus callback fields on a button tap |
| `slack`    | `channelId`, `ts`, `threadTs`, `teamId`, `isMention`, `channelType`                  |

The flat, familiar fields — `from`, `to`, `cc`, `bcc`, `subject`, `channel`, `direction`,
`status` — stay exactly where they are on every channel.

## Events are channel-agnostic

Delivery [webhooks](/webhooks/webhooks) use the `message.*` namespace, not a per-channel
prefix. `message.sent`, `message.delivered`, `message.bounced`, `message.complained`, and
`message.received` fire the same way whichever channel a message traveled over, so a
receiver written once keeps working as channels are added.
