Skip to main content

Channels are two-way

Your agent doesn’t only reply — it can initiate. A scheduled job can send a reminder, a webhook can confirm a payment, a tool can follow up hours later. Outbound messages flow through the Channels API, and every one is recorded to the recipient’s conversation thread so your agent stays coherent across the whole exchange.
import { Channels } from 'lua-cli';

await Channels.send({
  channel: 'whatsapp',
  to: { userId: 'user_123' },
  text: "Your appointment is confirmed for tomorrow at 3pm."
});

How continuity works

There’s no durable “wait for reply” machinery to manage. The model is simple:
  1. Your agent sends a message (from a tool, job, webhook, or trigger). The message is recorded to the recipient’s thread.
  2. Hours or days later, the user replies on that channel.
  3. The reply wakes your agent with the same thread loaded — it sees the message it sent and continues naturally.
A user is identified by their Lua userId, and one user ↔ agent pair shares one conversation thread. Send on WhatsApp, get a reply on WhatsApp — the agent has the full history either way. You don’t manage sessions or state machines for this; the thread is the memory.

Three ways to send

Pick the one that matches what you have and where you want the message to go.

Channels.send(...) — pick the channel explicitly

Choose exactly which channel and recipient. Works from any context, can reach cold recipients (a phone number or email with no prior conversation), and records to the thread. This is the general-purpose proactive send.

user.send(...) — reach the user you already have

If you’ve already loaded a User instance, user.send([...]) delivers to that user’s active conversation. Simplest when you just want to message the user you’re working with, on the channel they’re already on.

Templates.whatsapp.send(...) — bulk template send

Send an approved WhatsApp template to one or many phone numbers by channel ID. Best for campaigns and batch notifications. See the Templates API.

Which should I use?

You want to…Use
Message the current user, on the channel they’re onuser.send([...])
Choose a specific channel (e.g. always WhatsApp) for a known userChannels.send with to.userId
Reach a phone/email with no prior conversationChannels.send with to.phoneNumber / to.email
Start or re-open a WhatsApp conversation (window closed)Channels.whatsapp.sendTemplate
Blast an approved template to many numbersTemplates.whatsapp.send

The WhatsApp 24-hour window

WhatsApp only allows free-form business messages within 24 hours of the user’s last inbound message. This is a Meta rule, not a Lua one — and it shapes how proactive WhatsApp sends behave.
  • Window open (user messaged within 24h) → Channels.send({ channel: 'whatsapp', ... }) delivers immediately (delivered: true).
  • Window closed → free-form isn’t allowed. Two options:
    • Send an approved template with Channels.whatsapp.sendTemplate. Templates are allowed any time and are how you start or re-open a conversation.
    • Let Channels.send queue it (the default). The message is held, the recipient is shown a short opt-in prompt, and the queued text is delivered once they re-engage. The call returns queued: true (not yet delivered).
// Default: queue if the window is closed
const result = await Channels.send({
  channel: 'whatsapp',
  to: { userId: 'user_123' },
  text: 'Following up on your request.'
});

if (result.queued) {
  // Held until the user re-engages; not delivered yet
}

// Or fail fast and fall back to a template yourself
try {
  await Channels.send({
    channel: 'whatsapp',
    to: { userId: 'user_123' },
    text: 'Following up on your request.',
    options: { whatsapp: { onClosedWindow: 'fail' } }
  });
} catch {
  await Channels.whatsapp.sendTemplate({
    to: { userId: 'user_123' },
    templateName: 'follow_up',
    languageCode: 'en_US',
    messageContext: 'Followed up on the customer’s open request'
  });
}
You cannot send a free-form WhatsApp message to someone who has never messaged your agent, or whose window has closed. The honest cold-start path is an approved template. Plan template content for any outreach that might land outside the window.
US (+1) recipients: the opt-in prompt used by the queue path is a marketing-category template, which Meta may not deliver to US numbers. For reliable US outreach outside the window, send an approved utility template directly with Channels.whatsapp.sendTemplate.

Per-channel constraints

Windows and policies differ by channel:
ChannelProactive sendConstraint
WhatsAppFree-form (in-window) or template24-hour window; templates required outside it
SMSFree-form any timeNo window, but subject to carrier opt-out (STOP/HELP/START) and regional rules
EmailFree-form any timeNo window
Web chatFree-form to a known userDelivered to the user’s web widget
Teams / Instagram / MessengerFree-form to a known userWarm-only — the user must have an existing conversation with your agent
See Channel Capabilities for the full matrix, sender resolution, and compliance details.

Scheduled outbound

There’s no separate “campaign” primitive — scheduled outreach is just a job that calls Channels.send:
import { LuaJob, Channels } from 'lua-cli';

const reminders = new LuaJob({
  name: 'appointment-reminders',
  description: 'Send next-day appointment reminders every morning',
  schedule: {
    type: 'cron',
    expression: '0 9 * * *' // every day at 9 AM
  },
  execute: async () => {
    const due = await getTomorrowsAppointments();
    for (const appt of due) {
      await Channels.send({
        channel: 'whatsapp',
        to: { userId: appt.userId },
        text: `Reminder: your appointment is tomorrow at ${appt.time}.`
      });
    }
  }
});

export default reminders;
See the Proactive Send recipe for a complete walkthrough.

Components in proactive messages

Proactive messages support the same response formatting components (the ::: blocks) as inline replies — lists, links, images, actions — on channels that render them. The web chat widget renders the full component set; messaging channels render what their platform supports (text, media), and fall back to text otherwise.

Next steps

Channels API

Full reference for send, sendTemplate, and email.send

Channel Capabilities

Per-channel limits and sender resolution

Proactive Send Recipe

Schedule outreach with defineJob + Channels.send

Jobs API

Schedule recurring and one-off work