A campaign as a state machine, not a send list
- Organisation
- tmrwX · Family Hub campaign engine
- Role
- Product architecture, workflow design and delivery
- Period
- 2026 · Malaysia
Rebuilding a flat sequential campaign builder into an event-driven engine that pauses the moment a real person replies, and hands the relationship to an adviser with a timer on it.
- dispatched after the migration
- No duplicatesdispatched after the migration
- reconstructable from the event ledger
- Full journeyreconstructable from the event ledger
- handoffs on the adviser dashboard
- SLA-timedhandoffs on the adviser dashboard
How the system actually worked

Scroll the figure sideways to read it, or turn your phone.
Scroll the figure sideways to read it, or turn your phone.
- Every transition runs through one deterministic handler with worker-aware locking. Two workers evaluating the same enrolment cannot both dispatch.
- The ledger is append-only. Enrolled, sent, opened, clicked, replied, booked, attended, opted out — the whole journey is reconstructable from it.
- A human signal pauses automation instantly. Someone who replies does not receive tomorrow's scheduled nudge.
- Handoff starts a clock. The adviser dashboard shows the timer, so a warm lead going cold is visible rather than discovered late.







Context
The campaign engine was a sequential builder: pick an audience, queue a series of sends. It worked right up until real customers started replying in the middle of a sequence.
What was actually needed
Campaigns that react to what the customer does. Someone who replies on WhatsApp should not receive tomorrow’s scheduled nudge. Someone who books should move onto a confirmation path, not a persuasion path. And an adviser needs to know the moment a lead becomes theirs, not the next morning.
What I did technically
I specified the migration to an event-driven state machine. Programs define the objective and content; enrolments carry each customer’s current state and automation status; an append-only ledger records everything that happened — enrolled, sent, opened, clicked, replied, booked, attended, opted out. Every transition runs through one deterministic handler with worker-aware locking. Automation pauses the instant a human signal arrives and ownership transfers to the assigned adviser with an SLA timer attached.
How the migration was handled
The old flat campaign builder was kept as a read-only historical facade rather than deleted, so past reporting stayed intact while new creation paths were blocked. Migrations that throw away history get reversed the first time someone asks what happened last quarter.
Decisions that mattered
Idempotency and locking before features. The failure that actually costs trust is not a missing capability — it is the same customer receiving the same WhatsApp message three times because two workers evaluated the same enrolment at the same moment.
What changed
Duplicate dispatches stopped. Any customer’s journey can be reconstructed from the event ledger. Advisers get real-time handoff notifications with a timer, rather than discovering late that a warm lead went cold.
The insight I kept
Marketing automation is a distributed systems problem that happens to send messages. Once I started treating it as states, transitions, locks and an event log, the operational complaints stopped arriving.
Stack & practices
- Express
- TypeScript
- PostgreSQL
- Socket.IO
- Infobip
- Twilio
- React