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
From “A state change in the platform (booking confirmed, rescheduled, cancelled, payment received) or a scheduled job coming due” to result
- 1. Trigger
State change
A use case changes an appointment and records a domain event.
- 2. Action
Commit together
The change, its history and the outgoing event are saved in one transaction.
- 3. Action
Dispatch
After commit, the event is delivered to its handlers; anything undelivered is picked up again on the next scheduled run.
- 4. Action
Handle idempotently
Each handler is safe to run twice, so a repeated event never sends a duplicate message.
- 5. Action
Schedule reminders
A confirmed booking schedules reminders at the clinic's chosen times before the visit.
- 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. 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. 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.
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 --> HInput
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
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.