Every outbound action is a draft until a hash-bound approval
An action's content_digest is recomputed on every write to its subject, body, target address or payload. Approving stores that digest as approved_digest; if a later edit changes the live digest, the approval is cleared and the action is forced back to draft in the same step — not flagged, dropped. Sending checks the live digest against the approved one and refuses with a named reason if they differ.
No WhatsApp or SMS without a recorded opt-in, checked twice
Consent is checked against Wavy's own records — the same function Wavy's campaign sends use — at the moment a reminder is scheduled, and again at the moment it actually dispatches. A contact who opts out between those two moments is blocked at send, not delivered on a stale check.
Auto-send exists for exactly two message kinds, and only if you turn it on
Email, calendar-invite responses and schedule changes can never be auto-sent — that is a closed set enforced in code, not a setting. Only WhatsApp and SMS messages are even eligible, and only once a named, scoped rule is switched on explicitly for that kind.
A dry run is recorded as failed, never as sent
Without a connected WhatsApp channel, the send helper returns a synthetic dry-run result. Mitra maps that to delivered = false, marks the action failed and returns a 503 naming what is missing — the dishonesty of a fake success is exactly what this status exists to avoid.
A booking slot is re-checked at the moment it is claimed, not just when it was drawn
The list of open times on a public booking page is a snapshot. Booking one re-reads real busy blocks and re-validates the slot inside the same database transaction as the write, so two people racing for the same time cannot both win it.