The business story
Why this system exists
I ran a production broadcast engine on a company's real inbound WhatsApp number. That single detail sets the entire design: the number that receives customer enquiries is the same number the campaign sends from, so a bug does not cost you a campaign — it costs you the channel. WhatsApp enforces its Business Messaging Policy with quality-rating downgrades and number bans, and a banned number takes every legitimate conversation down with it.
What I actually built and ran
A scheduled broadcaster with a rate limit of one message per second, a hard bulk cap per day, state restored between runs so a contact is never messaged twice, opt-out suppression, a dry-run switch, and a separate emergency-stop workflow that required typing STOP to confirm — it cancelled in-flight runs and disabled the scheduler outright. Safety was not a feature added later; it was the reason the thing was allowed to exist on that number at all.
The judgment calls
- Dry run is the default, not an option. The dangerous direction is sending when you meant to preview. Making the safe path the default costs one extra click and removes an entire class of incident.
- Opt-out is evaluated before the daily cap. That ordering looks arbitrary until it bites: if capacity is checked first, a suppressed contact gets reported as "cap reached" and the operator never learns the list needs purging. A compliance failure must never be able to hide behind a capacity message.
- The kill switch must leave resumable state. Halting is easy; halting without losing track of what already went out is the part that matters. A stop that corrupts state turns one incident into two, because the recovery run double-sends.
- Every exclusion carries a reason. A system that silently drops contacts cannot be audited after the fact, and "why did this person not get it?" is the question you will actually be asked.
What went wrong, and what I'd change
The rail I would add next is a pre-send diff: show the operator exactly which contacts changed cohort since the last run before anything goes out, because the failure I worry about most is not a crash — it is a silently widened audience that nobody reviewed. I would also track quality-rating movement against send volume over time, so the ceiling is discovered from data rather than from a suspension notice.