Automation / Automation

Event-Driven Notification & Reminder Pipeline (Transactional Outbox + Scheduled Jobs)

The background engine of the WhatsApp Business Automation Platform: every booking change records an event, and notifications, payment requests, reminders, expiries and data retention run from those events and a scheduled job runner, reliably and without an always-on worker.

Workflow

PROCESS FLOW

From “A state change in the platform (booking confirmed, rescheduled, cancelled, payment received) or a scheduled job coming due” to result

  1. 1. Trigger

    State change

    A use case changes an appointment and records a domain event.

  2. 2. Action

    Commit together

    The change, its history and the outgoing event are saved in one transaction.

  3. 3. Action

    Dispatch

    After commit, the event is delivered to its handlers; anything undelivered is picked up again on the next scheduled run.

  4. 4. Action

    Handle idempotently

    Each handler is safe to run twice, so a repeated event never sends a duplicate message.

  5. 5. Action

    Schedule reminders

    A confirmed booking schedules reminders at the clinic's chosen times before the visit.

  6. 6. Action

    Re-check before sending

    Each reminder checks the appointment is still booked at that time, so moved or cancelled bookings never get stale reminders.

  7. 7. Action

    Send within WhatsApp rules

    Inside the 24-hour window the message goes as written; outside it, the clinic's approved template is used instead.

  8. 8. Output

    Retry and retain

    Failed sends retry with back-off; a daily retention job removes old closed records on the clinic's schedule.

Problem

A booking is only useful if the patient hears about it: confirmations, payment requests, reminders before the visit, and updates when staff change something. If saving the change and sending the message are separate steps, one can succeed while the other fails, and a patient misses a message or gets one for an appointment that moved.

Architecture

Events and a transactional outbox: use cases never send messages themselves. They record events, and handlers do the sending, which keeps use cases small and makes notifications reliable. A scheduled job runner instead of a resident worker: due jobs are claimed in small batches on a short recurring tick that finishes well inside a serverless function's time limit, with back-off on failure. Daily jobs such as retention are scheduled by the tick itself, once per day. Template fallback in one place: modules send their normal message plus a template fallback, and the messaging layer decides which one WhatsApp allows at that moment.

DIAGRAM

From a state change to a message

flowchart TD
  UC[Use case changes booking] --> TX[(One transaction: state + history + event)]
  TX --> D[Dispatcher after commit]
  D --> H[Idempotent handlers]
  H --> MSG[Send message or payment request]
  H --> SCH[(Schedule reminders)]
  T([Scheduled tick]) --> J[Claim due jobs]
  J --> RC{Still booked at this time?}
  RC -- yes --> MSG
  RC -- no --> SKIP[Do nothing]
  T --> RD[Re-deliver undelivered events]
  RD --> H
The change and its event are saved together; dispatch happens after commit, and the scheduled runner re-delivers anything missed, so no message is lost or duplicated.

Input

Domain events from the appointments module and due rows in the scheduled-jobs table.

Output

Confirmations, payment requests, reminders with Confirm, Reschedule and Cancel buttons, status updates, expired requests and retention runs.

Integrations

WhatsApp Business Cloud APIPayment provider (payment links and status updates)Serverless scheduled trigger

Limitations

A tick-based runner means jobs run within about a minute of coming due, not instantly. Templates have to be approved by WhatsApp before messages can be sent outside the 24-hour window.

Security Notes

Scheduled triggers are authenticated, handlers only act on the tenant that owns the event, no personal data is written to logs, and payment records are never deleted by retention.

Learnings

Save state and its side effects together, and make every handler idempotent: that combination removes a whole class of missed or duplicated messages. Re-checking at send time is simpler and safer than cancelling jobs every time something moves.