Channels.send when you choose the channel and the recipient; use user.send() when you hold a User and want the channel they last wrote from. Sends to a person are recorded in their conversation.
Verified against lua-cli 3.33.0.
Before you begin
- The channel linked to the agent (Channels overview).
- For WhatsApp outside the 24-hour window, a WhatsApp message template created and approved in Meta’s WhatsApp Manager (WhatsApp).
1
Pick the channel and the address
Channels.send accepts seven channel IDs; userId works everywhere, a phone number or email address only where first contact is allowed.Slack, Front, RCS, and iMessage are inbound only; MessageBird is reachable through
user.send() alone. text may carry ::: formatting blocks (Rendering by channel).2
Send from a job
A job has no current user, so it addresses each recipient explicitly; the Leave
appointments entries come from your booking tool, which stores the Lua user ID with Data.create('appointments', { userId: user._luaProfile.userId, date, time }) (Store and search data, Identify users). On WhatsApp, onClosedWindow: 'fail' makes the send throw when the 24-hour window has closed, and the catch falls back to an approved WhatsApp message template; messageContext is recorded as what it said.src/jobs/AppointmentRemindersJob.ts
onClosedWindow unset and the platform queues the text: it sends an opt-in prompt, returns queued: true, and delivers your text when the recipient replies. Meta blocks that prompt for US (+1) numbers (the result carries a warning), so send an approved WhatsApp message template to those recipients.3
Send from a tool
Inside a conversation,
User.get() returns the end user. Channels.email.send takes a subject and one of text, html, or richBody. user.send() delivers on the channel the end user last wrote from, when that is WhatsApp, Messenger, Instagram, Teams, MessageBird, or SMS, and mirrors the text to the web widget; deployed, it resolves true whatever happened, so confirm delivery with Channels.getStatus or lua logs (under lua test it throws instead).src/skills/tools/SendReceiptTool.ts
4
Test locally
lua test runs the job or tool on your machine; the sends are real. With nothing due, the job returns its empty result.Output
lua test skill runs send_receipt as you, so the receipt goes to your own user record; until you have emailed the agent, the run ends with email send requires a recipient email, or a userId with an existing email conversation.5
Release it
lua push uploads a version and changes nothing for end users; lua version create snapshots the agent; lua version promote <n> makes that snapshot live and is also the rollback path (Release an agent to production).lua version create prints ✓ Created v<n> (staged); in a script, n=$(lua version list --limit 1 --json --ci | jq -r '.[0].version') then lua version promote "$n". lua push all exits 0 even when a primitive fails; check its output for component(s) failed to push.6
Verify
Trigger the job once and read the run.Every send returns a
deliveryId and a status: queued or accepted at return, then sent, delivered, read, failed, or expired as receipts arrive. Channels.getStatus(deliveryId) reads one record; Channels.listDeliveries({ status: 'failed', since, limit }) lists failures with category, vendor code, and remediation link (Check deliveries).Options you may need
Thread an email reply
Pass the inbound email’sMessage-ID as inReplyTo and the thread’s IDs as references; in a tool handling an email turn, it is Lua.request.webhook.payload.messageId.
Check deliveries
A webhook reads delivery records from outside a conversation.src/webhooks/DeliveryCheckWebhook.ts
If it isn’t working
The result has queued: true and the message hasn't arrived
The result has queued: true and the message hasn't arrived
Cause The recipient’s WhatsApp window is closed; the text waits for their reply to the opt-in prompt. Fix For time-sensitive messages, set
onClosedWindow: 'fail' and send an approved WhatsApp message template.The result has persisted: false and a warning
The result has persisted: false and a warning
Cause The message was delivered but not recorded in the conversation, or it went to a Teams
conversationId, which has no single recipient. Fix Nothing is thrown; log the warning if continuity matters.A send to webchat, teams, instagram, or messenger throws
A send to webchat, teams, instagram, or messenger throws
Cause Those channels deliver only to an end user who has already written, by
userId. Fix Pass to: { userId }; phoneNumber and email work only on WhatsApp, SMS, and email.Next steps
About channels
Inbound and outbound vocabularies.
Channels reference
send, whatsapp.sendTemplate, email.send, getStatus, and listDeliveries.Link the channel and get a message template approved.
Schedule a recurring job
The job that carries your outbound messages.

