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.
Five channels carry messages today:
email— external mail over your verified domains, delivered and tracked for you.a2a— internal delivery between Dairo inboxes. It lands as a signed agent-to-agent receipt on both ends.telegram— external chat over a Telegram bot 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 your customers install. Mentions and DMs arrive as messages; your agent replies the same way.voice— a phone call one of your numbers answered. The call arrives in the number's inbox as a message carrying the summary, notes, and transcript. It is receive-only: you place calls with Making calls, not with a send.
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 | The channel the inbox operates on (email, telegram, or slack). |
| Message | The channel this message traveled over (email, a2a, telegram, slack, or voice). |
| 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
a2ahop, a signed receipt on both ends. - Any external (non-Dairo) recipient →
emailover 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 with400, so an explicita2arequest can never deliver over public email.
The send response echoes back the channel it actually went out on.
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.
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 |
voice | callId, callerNumber, dialedNumber, durationSeconds, summary, notes — an inbound call answered by your number |
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 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.