Skip to main content
Channels sends messages to end users on the channels the agent is connected to and reads the delivery record each send creates. A send addressed to a person is recorded in that person’s conversation with the agent, so the agent has the context when they reply. Available in tools, jobs, webhooks, triggers, processors, and workflow code steps. For the rules per channel, including the WhatsApp 24-hour window, see Send proactive messages. Verified against lua-cli 3.33.0.

Quick example

A send returns a delivery id; getStatus reads the receipts that arrive later.

Methods

send(input)

Sends a text message on one channel.
ChannelSendChannel
required
One of CHANNEL_SEND_CHANNELS: whatsapp, sms, email, webchat, teams, instagram, messenger, slack. These are outbound ids; the inbound value in Lua.request.channel is pop for the widget (the Channel type spells it web) and facebook for Messenger.
ChannelSendTarget
required
Exactly one of userId (any channel), phoneNumber (whatsapp and sms, E.164), email (email and slack), conversationId (teams and slack; everyone in the conversation receives the message), or identity. teams, instagram, and messenger need a prior inbound conversation, so they accept userId or conversationId only; webchat accepts userId only; slack refuses a phoneNumber.On teams, conversationId is a group chat or channel the agent is already in.identity is a UserIdentityRef, { kind, scope, value }, such as Lua.request.sender or an entry of _luaProfile.identities. It sends to the agent’s user who holds that id, as userId would: a whatsapp_bsuid on whatsapp, a slack_user on slack, a teams_user on teams, a messenger_psid on messenger, an instagram_igsid on instagram. Any other pairing is refused, and an id none of the agent’s users holds answers 404. In lua-cli’s types from the next lua-cli release.On slack, every send goes through the agent’s own Slack bot. conversationId is Lua.request.conversation.externalId from a Slack channel-thread or group-DM turn (T…:A…:C…:threadTs or T…:A…:G…, which also names the bot), a channel id C… or G… (a new top-level message), C…:threadTs (a reply in that thread, threadTs being the thread’s first message), a member id U… (their DM with the bot), or a DM channel D… or D…:threadTs. A group DM, U…,U… with 2 to 8 member ids, works once the app has been re-authorised with the mpim:write scope from the updated manifest. userId DMs that Lua user, found in the bot’s workspace by their email address in Lua; email DMs the workspace member with that address. The bot must be a member of the channel; see Post from code.
string
required
The message. ::: formatting components render the way they do in replies.
string
Pins the send to one channel configuration when the agent has several of the same type. It must belong to the agent. On slack it is the connection identifier T…-A…: an agent with more than one Slack connection must pass it unless to.conversationId is a Lua.request.conversation.externalId, or the send is refused with a list of its connections.
boolean
default:false
Sends even while a colleague holds the end user’s conversation (human takeover). Without it, send, whatsapp.sendTemplate, whatsapp.sendReaction, whatsapp.requestPhoneNumber, and email.send refuse a held conversation with CONVERSATION_HELD. In lua-cli’s types from the next lua-cli release.
'queue' | 'fail'
default:"queue"
What to do when the WhatsApp 24-hour window is closed. queue sends the platform’s message-request template, holds the text, and delivers it when the end user replies; fail rejects the send so you can send your own approved WhatsApp message template with whatsapp.sendTemplate.
Returns
ChannelSendOutput
Example
Errors — the call throws when the send is rejected. In a deployed agent the error is a ChannelSendError with code and statusCode; in lua test it is a plain Error carrying the server’s message, or Channel send failed.
  • Unknown channel: <channel>
  • to must have exactly one of userId, phoneNumber, email, conversationId, or identity
  • A <kind> identity cannot be addressed on <channel>, and None of this agent's users has that identity (HTTP 404)
  • conversationId is only supported on teams, slack
  • <channel> is a warm-only channel; address by userId or conversationId
  • webchat channel requires to.userId
  • phoneNumber cold-start not valid for <channel>; use userId
  • A closed WhatsApp window with onClosedWindow: 'fail', or a provider rejection.
  • On slack: a 400 when the agent has more than one Slack connection and no options.channelIdentifier (the message lists them); a 404 for a channel, thread, or member the bot can’t see, an email nobody in the workspace has, or a Lua user with no email address, with a hint and Slack’s code such as (Slack: channel_not_found); a 422 (VENDOR_REJECTED on the REST API) for something you can fix: the bot is not in the channel (not_in_channel), the channel is archived, the person turned off DMs from apps (messages_tab_disabled), a missing scope (re-apply the manifest and reinstall the app), or an uninstalled app or revoked token (reconnect Slack); a 503 (VENDOR_UNAVAILABLE) when Slack rate-limits the bot, about one post per second per channel, or is down. The call does not wait out a rate limit: in a deployed agent the ChannelSendError carries retryAfterSeconds, so retry after that many seconds. After an outage the post may have landed; check the delivery record before sending again.
  • A conversation a colleague holds: code is CONVERSATION_HELD and statusCode is 409. Pass options.ignoreHold to send anyway.

whatsapp.sendTemplate(input)

Sends an approved WhatsApp message template to one person, inside or outside the 24-hour window.
{ userId?: string; phoneNumber?: string; identity?: UserIdentityRef }
required
Exactly one of userId, phoneNumber (E.164), or a whatsapp_bsuid identity held by one of the agent’s users.
string
required
The name of an approved WhatsApp message template on the agent’s WhatsApp channel. List them with Templates.whatsapp.list.
string
The template’s language, for example en_US.
Array<Record<string, unknown>>
Meta component objects with the parameter values, for example { type: 'body', parameters: [{ type: 'text', text: 'ORD-99' }] }.
string
Plain text recorded in the conversation as what the template said, so the agent remembers the outreach. Recommended whenever the template body isn’t self-explanatory.
ChannelSendOptions
As on send.
Returns — a ChannelSendOutput. Example
Errors — to must provide userId or phoneNumber, phoneNumber must contain digits, a template that isn’t approved, or a provider rejection; thrown as on send.

whatsapp.sendReaction(input)

Reacts to a WhatsApp message with one emoji, the way a person does by long-pressing it.
{ userId?: string; phoneNumber?: string; identity?: UserIdentityRef }
required
Exactly one of userId, phoneNumber (E.164), or a whatsapp_bsuid identity held by one of the agent’s users.
string
required
The provider id of the message to react to, a wamid… value from the conversation history or the channel payload. Meta accepts messages up to 30 days old.
string
required
One emoji. An empty string removes the agent’s existing reaction.
ChannelSendOptions
As on send.
Returns — a ChannelSendOutput. Example
Errors — messageId is required, emoji is required (an empty string removes the reaction), to must provide userId or phoneNumber; thrown as on send.

whatsapp.requestPhoneNumber(input)

Asks a WhatsApp user to share their phone number. WhatsApp shows text with a share-phone-number button; WhatsApp sets the button label, in the user’s language. In lua-cli’s types from the next lua-cli release.
{ userId?: string; phoneNumber?: string; identity?: UserIdentityRef }
required
Exactly one of userId, phoneNumber (E.164), or a whatsapp_bsuid identity held by one of the agent’s users.
string
required
The message shown above the button. At most 1024 characters.
ChannelSendOptions
As on send: channelIdentifier and ignoreHold.
Returns — a ChannelSendOutput. If the user taps the button and confirms, WhatsApp sends Lua their own WhatsApp number. Lua saves it on their Lua profile when the profile has no phone number yet and no other Lua user already has that number. When another Lua user already has the number, the WhatsApp account moves to that user: this message and every later one from the account run as that user, with their profile and user data, and the user who wrote before keeps their own history and data. Either way the number is in user._luaProfile.mobileNumbers on that same message, so a preprocessor sees it immediately, and the agent receives the user message Shared my WhatsApp number: +254712345678. If the user doesn’t tap the button, nothing comes back: WhatsApp sends no decline event. WhatsApp Web shows the message as one to open on the phone; the button works in the WhatsApp mobile app. When the WhatsApp account moves, Lua staff can merge the two users into one, which brings their history and data together (When two users merge). A contact card the user forwards themselves is never saved as their number. Every call sends a new request; Lua doesn’t limit repeats. To ask less often, store when you asked in user data. The user must have messaged the agent within the last 24 hours. For a recipe that asks users who hide their number, see Users who hide their phone number. Example
Errors — to must provide userId or phoneNumber, a text over 1024 characters, no WhatsApp conversation with that user (HTTP 404), a closed 24-hour window (HTTP 409); thrown as on send. The built-in agent tool requestWhatsappPhoneNumber does the same for the current user. It is available when the outbound channels feature is on and the agent has a WhatsApp channel.

email.send(input)

Sends an email with a subject, a plain-text, HTML, or rendered body, copies, attachments, and threading headers. The agent must have an email channel linked.
{ userId?: string; email?: string }
required
One of userId (resolved to the address from the end user’s history) or email.
string
The subject line.
string
Plain-text body, sent as is. Provide one of text, html, or richBody.
string
Exact HTML body, sent as is with no template around it.
string
Markdown and ::: components, rendered on the server into the branded email template that the agent’s own replies use.
string[]
Copy recipients.
string[]
Blind-copy recipients.
Array<{ filename: string; contentType: string; url: string }>
Files fetched from url at send time and attached. The combined size is limited to 28 MB.
string
The Message-ID of the email this one answers; sets In-Reply-To so mail clients thread it. The inbound email’s id is Lua.request.webhook.payload.messageId. Angle brackets are optional. Honored on the branded (existing-address) email channel. A send with inReplyTo is checked for a hold against that thread’s conversation; a send without it, against the end user’s default conversation.
string[]
The thread’s accumulated Message-IDs for the References header.
ChannelSendOptions
As on send.
Returns — a ChannelSendOutput; messageId is the provider’s message id. Example
Errors — No email channel configuration found. Ensure the agent has an email channel linked. when the agent has no email channel, email send requires to.email or to.userId, email send requires a text, html, or richBody body, Attachments exceed the 28MB limit, and CONVERSATION_HELD for a held conversation; thrown as on send.

getStatus(deliveryId)

Reads one delivery record, including receipts that arrived after the send.
string
required
The id a send returned.
Returns — a DeliveryView. status only moves forward through queued, accepted, sent, delivered, read; failed is terminal and carries error; expired is a queued WhatsApp message the end user never replied to. A late or duplicate receipt leaves the record where it is, so polling is safe. Example
Errors — throws when no delivery with that id belongs to the agent.

listDeliveries(filter?)

Lists the agent’s deliveries, newest first.
string
Only deliveries addressed to this end user.
DeliveryStatus
Only deliveries in this status.
string
A channel name such as whatsapp, email, or sms.
string | Date
Only deliveries created at or after this instant.
number
default:50
From 1 to 200.
Returns — an array of DeliveryView. Example
Errors — Delivery list failed when the platform gives no message.

Retries and idempotency

Every send endpoint accepts an X-Idempotency-Key header. In lua test the SDK adds a fresh key to each Channels call, so a transport retry inside one call can’t send twice. In a deployed agent the call is made in process with no key, so a retry of your own, such as a job that runs again after crashing, sends again. To deduplicate across runs, call the REST send endpoints with a key you derive from the thing you are messaging about. Replaying a key returns the first send’s outcome with the same deliveryId for the life of the delivery record, 180 days; a key whose first attempt failed replays the failure; keys are scoped to one agent.

Types

readonly string[]
['whatsapp', 'sms', 'email', 'webchat', 'teams', 'instagram', 'messenger', 'slack']. ChannelSendChannel is its element type.
interface
channel, to, text, options?, as on send. ChannelSendTarget and ChannelSendOptions are the shapes of to and options.
interface
The send result described under send.
interface
The inputs of whatsapp.sendTemplate, whatsapp.sendReaction, and email.send.
interface
The filter of listDeliveries.
union
queued (WhatsApp, held until the end user replies), accepted (the provider took it and returned an id), sent (handed to the recipient’s network; where a channel has no receipts, a successful send stops here), delivered, read (WhatsApp, with read receipts on), failed, expired.
interface
interface
category (a DeliveryErrorCategory), provider, code (the vendor’s own code, as a string), title, detail?, href? (a remediation link), retryable, and owner (customer, recipient, lua, or vendor: who has to act).
union
The same vocabulary for every provider.

See also

  • Send proactive messages — windows, WhatsApp message templates, and when to use User.send
  • Templates — list WhatsApp message templates and batch-send by phone number
  • Team — resolve a colleague’s shared handles before sending
  • REST channels API — the same sends with X-Idempotency-Key
  • About channels — the inbound and outbound channel vocabularies