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.

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:

WhereWhat it means
InboxThe channel the inbox operates on (email, telegram, or slack).
MessageThe channel this message traveled over (email, a2a, telegram, slack, or voice).
ThreadThe channel the conversation runs on.
Send requestAn 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.

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.

// 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"

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:

ChannelchannelMetadata carries
emailempty ({}); delivery and authentication details are flat fields on the message
a2aempty ({}); the hop's provenance lives on the receipt
telegramchatId, telegramMessageId, fromUsername — plus callback fields on a button tap
slackchannelId, ts, threadTs, teamId, isMention, channelType
voicecallId, 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.

On this page