Skip to main content

Overview

The Channels API lets your agent initiate messages on the channels it’s connected to — WhatsApp, SMS, email, web chat, and more — from anywhere your code runs: a tool, a scheduled job, a webhook, or a trigger. It’s the outbound half of a two-way conversation: inbound messages wake your agent, and Channels.* sends messages back out.
Every Channels.* send is recorded to the recipient’s conversation thread with your agent. When the user replies, your agent picks up with full context — the outbound message is already part of the conversation it remembers. See Proactive Messaging for the full model.

Channels.send

Free-form text on any connected channel

Channels.email.send

Rich email — subject, HTML, cc/bcc, attachments

Channels.whatsapp.sendTemplate

Approved templates to start or re-open a conversation

Channels.whatsapp.sendReaction

React with an emoji to a specific WhatsApp message

Where you can call it

Channels.* works in any execute context. Recipient resolution differs slightly by context:
Pair Channels.send with a scheduled job for time-based outreach (reminders, follow-ups, digests) — see the Proactive Send recipe.

Channels.send()

Send a free-form text message on a connected channel.
channel
string
required
The channel to send on. One of: 'whatsapp', 'sms', 'email', 'webchat', 'teams', 'instagram', 'messenger'.
to
object
required
The recipient. Provide exactly one of the fields below.
to.userId
string
A Lua user ID. Works on every channel — the recipient’s channel address is resolved from their conversation history with your agent.
to.phoneNumber
string
A phone number in E.164 format (e.g. +14155552671). Valid for whatsapp and sms only — lets you reach a number with no prior conversation (cold start).
to.email
string
An email address. Valid for the email channel only (cold start). For richer email, prefer Channels.email.send.
text
string
required
The message text. Supports the same response formatting components (::: blocks) as inline replies, where the channel renders them.
options
object
Optional send options — see Options.
Returns: ChannelSendOutput Examples:

Channels.email.send()

Send a rich email — subject, plain-text and/or HTML body, cc/bcc, and attachments.
to
object
required
The recipient. Provide exactly one of to.userId or to.email.
to.userId
string
A Lua user ID — the email address is resolved from the user’s conversation history.
to.email
string
A literal email address (cold start).
subject
string
The email subject line.
text
string
Plain-text body, sent as-is (the plain-text MIME part). Provide at least one of text, html, or richBody.
html
string
HTML body, sent as-is — your exact markup, no template wrapper. Use this for designed / transactional emails.
richBody
string
Rich body — markdown and ::: component blocks rendered server-side into the branded email template (the same rendering your agent’s inline replies use). Use this for “send this message as a nice email.” Mutually exclusive with html.
cc
string[]
Carbon-copy recipients.
bcc
string[]
Blind-carbon-copy recipients.
attachments
EmailAttachmentInput[]
Files to attach. Each is { filename, contentType, url } — the file is fetched from the public url at send time. Combined attachment size is capped at 28 MB.
inReplyTo
string
Threads this email as a reply (standard In-Reply-To header). Pass the Message-ID of the email you’re replying to and mail clients group your message into that conversation — e.g. a thread per support ticket instead of one thread per user. When your agent handles an inbound email, the original Message-ID is available as webhookPayload.messageId; store it against your record and pass it back here on each follow-up. Honoured on the branded (existing-address) email channel.
references
string[]
The conversation’s References chain (standard threading header) — the accumulated Message-IDs of the thread. Append the latest Message-ID each turn so long threads stay correctly linked.
options
object
Optional send options — see Options.
Returns: ChannelSendOutput (with messageId set to the provider message ID where available). Example:
Or let the platform render markdown into the branded template with richBody:
Threading a reply into an existing conversation — keep follow-ups in the same email thread (e.g. one thread per ticket) by echoing the original message’s Message-ID:

Channels.whatsapp.sendTemplate()

Send a pre-approved WhatsApp template. Use this to start a conversation, or to reach a user whose 24-hour messaging window has closed (where free-form sends aren’t allowed).
to
object
required
The recipient — exactly one of to.userId or to.phoneNumber (E.164).
templateName
string
required
The name of an approved template on the agent’s WhatsApp channel. List available templates with Templates.whatsapp.list.
languageCode
string
The template language, e.g. 'en_US'.
components
object[]
Meta template component objects supplying the header / body / button parameter values — e.g. { type: 'BODY', parameters: [{ type: 'text', text: 'Tuesday at 3pm' }] }. The component type is 'HEADER', 'BODY', or 'BUTTON'; the parameter type is 'text', 'image', 'video', 'document', or 'coupon_code'.
messageContext
string
A plain-text summary of what the template said. This is the text recorded to the conversation thread so your agent remembers the outreach when the user replies. Recommended whenever the template body isn’t self-explanatory.
options
object
Optional send options — see Options.
Returns: ChannelSendOutput Example:
Channels.whatsapp.sendTemplate is the canonical way to send a WhatsApp template as part of a conversation — it records the send to the recipient’s thread and respects the recipient resolution above. The lower-level Templates.whatsapp.send (batch send by channel ID and phone numbers) remains available for bulk/campaign sends.

Channels.whatsapp.sendReaction()

React with an emoji to a specific WhatsApp message — the same reaction a person leaves by tapping and holding a message. This is also how the Reaction formatting component sends on WhatsApp; call it directly when you need to react to a message outside of a normal reply (e.g. from a job or webhook).
to
object
required
The recipient — exactly one of to.userId or to.phoneNumber (E.164).
messageId
string
required
The vendor message ID of the WhatsApp message to react to (a wamid.... value). Must be no more than 30 days old. Available from the channel webhook payload or the message’s entry in conversation history.
emoji
string
required
A single emoji to react with. Pass an empty string ('') to remove the agent’s existing reaction from the message.
options
object
Optional send options — see Options.
Returns: ChannelSendOutput Example:
WhatsApp only, for now. sendReaction isn’t available on other channels — use the Reaction formatting component for the broader multi-channel path (Instagram, Facebook Messenger, Slack, iMessage, web chat), which is ignored on channels without reaction support rather than throwing.

Options

All three methods accept an optional options object:
options.channelIdentifier
string
Pin the send to a specific channel configuration (for agents with more than one channel of the same type). The configuration must belong to your agent.
options.whatsapp.onClosedWindow
'queue' | 'fail'
default:"'queue'"
What to do when a WhatsApp free-form send hits a closed 24-hour window:
  • 'queue' (default) — queue the message and deliver it after the user re-engages (the API returns queued: true).
  • 'fail' — reject the send so you can fall back to Channels.whatsapp.sendTemplate.

ChannelSendOutput

Every send method resolves to the same shape:
delivered
boolean
The channel accepted the message. Independent of persisted.
persisted
boolean
The message was recorded to the recipient’s conversation thread with your agent. A delivered-but-unpersisted send returns delivered: true, persisted: false plus a warning — it does not throw.
queued
boolean
WhatsApp only. true means the 24-hour window was closed and the message is queued — delivered stays false until the recipient re-engages, at which point the queued text is delivered and recorded. See Proactive Messaging.

Error handling

Channels.* throws when a send is rejected (invalid recipient, unconfigured channel, provider rejection, or onClosedWindow: 'fail' on a closed window). Wrap calls in try/catch where a failure should be handled gracefully. A successful call may still report partial success — delivered: true with persisted: false (plus a warning) means the message went out but couldn’t be recorded to the conversation thread. This is not an error and won’t throw; check the flag if recording matters to your flow.
WhatsApp free-form sending is time-limited. You can only send free-form WhatsApp messages within 24 hours of the recipient’s last inbound message. Outside that window, use Channels.whatsapp.sendTemplate with an approved template — or rely on the default onClosedWindow: 'queue' behavior. See Proactive Messaging and Channel Capabilities.

Channels & recipients at a glance

See Channel Capabilities for per-channel limits, sender resolution, and compliance notes.

TypeScript types

Next steps

Proactive Messaging

The full model: Channels.send vs user.send() vs templates, and per-channel windows

Channel Capabilities

Per-channel limits, sender resolution, and compliance

Proactive Send Recipe

Schedule outreach with defineJob + Channels.send

Templates API

List and batch-send approved WhatsApp templates