Skip to main content

Overview

This recipe shows how to make your agent reach out on a schedule — a daily reminder, a follow-up, a status nudge — by combining a scheduled job with the Channels API. The same pattern works from webhooks and tools.

What It Does

  • Runs every morning on a cron schedule
  • Looks up who needs a reminder
  • Sends each a WhatsApp message with Channels.send
  • Falls back to an approved template when the recipient’s 24-hour window is closed
  • Every send is recorded to the recipient’s conversation thread, so replies continue naturally

Complete Code

import { LuaJob, Channels } from 'lua-cli';

const appointmentReminders = new LuaJob({
  name: 'appointment-reminders',
  description: 'Send next-day appointment reminders every morning',
  schedule: {
    type: 'cron',
    expression: '0 9 * * *' // every day at 9:00 AM
  },
  execute: async () => {
    const appointments = await getTomorrowsAppointments();

    for (const appt of appointments) {
      try {
        const result = await Channels.send({
          channel: 'whatsapp',
          to: { userId: appt.userId },
          text: `Hi ${appt.name}! Reminder: your appointment is tomorrow at ${appt.time}. Reply here if you need to reschedule.`,
          options: { whatsapp: { onClosedWindow: 'fail' } }
        });

        console.log(`Reminded ${appt.userId} (delivered: ${result.delivered})`);
      } catch {
        // Window closed — start the conversation with an approved template
        await Channels.whatsapp.sendTemplate({
          to: { userId: appt.userId },
          templateName: 'appointment_reminder',
          languageCode: 'en_US',
          components: [
            { type: 'BODY', parameters: [{ type: 'text', text: appt.time }] }
          ],
          messageContext: `Reminded ${appt.name} about their appointment tomorrow at ${appt.time}`
        });
      }
    }
  }
});

export default appointmentReminders;

Key Concepts

Free-form WhatsApp is only allowed within 24 hours of the user’s last message. Setting onClosedWindow: 'fail' makes Channels.send throw when the window is closed, so the catch block can send an approved template instead. (Omit the option to let it queue automatically — see Proactive Messaging.)
A scheduled job runs outside any conversation, so you address recipients explicitly — here by to.userId. You can also target a raw phoneNumber or email for cold outreach.
When you send a template, messageContext is the plain-text summary recorded to the thread. When the user replies, your agent sees “Reminded … about their appointment” and continues naturally — not a blank slate.

Variation: confirm on a webhook event

The same Channels.send call works from a webhook — for example, confirming a payment the moment your payment provider fires its event:
import { LuaWebhook, Channels } from 'lua-cli';

const paymentConfirmation = new LuaWebhook({
  name: 'payment-confirmation',
  description: 'Notify the customer when their payment succeeds',
  execute: async (event) => {
    const { customerId, amount } = event.body;

    await Channels.send({
      channel: 'whatsapp',
      to: { userId: customerId },
      text: `Payment of $${amount} received — thank you! 🎉`
    });

    return { notified: true };
  }
});

export default paymentConfirmation;

Next steps

Channels API

Full reference for send, sendTemplate, and email.send

Proactive Messaging

The model behind agent-initiated messages

LuaJob

Define scheduled tasks

Webhooks

React to external events