Do not store a checkbox. Store what actually happened.
290 consent events in the live store, append only, each carrying the exact wording that was on the screen when the person agreed.
A column tells you the answer. An event tells you what happened.
Consent is an append only event log, never a column. Current state is a fold over that log, and the fold sorts declined and revoked above granted and exempt, so an opt out beating an exemption is an ordering property rather than an if statement somebody can forget to write.
Illustration | 290 real consent events in the store | 2026-09-02
The words, not a reference to them
Existing wordings are preserved rather than improved, because editing text somebody agreed to orphans their evidence.
Yes to what, exactly
Every wording carries a scope. Service does not cover promotional; promotional does cover service. A grant for one purpose is not a grant for another.
Never asked is not no
Never asked, declined, granted. An unchecked box returns nothing at all rather than a decline, because the two mean different things later.
The box is not pre checked, and that is a commercial decision before it is a legal one
The control stays hidden until the phone field holds enough digits, then appears beneath it, unchecked, and the person taps it themselves. The entire pitch is that you can prove the customer said yes. A pre checked box proves only that we checked it.
The way back from an opt out
A block can only be lifted by an act that means put me back, never by an ordinary grant arriving later. A plain recency rule would mean one import, one backfill or one legacy row with a later timestamp could make somebody who replied STOP textable again with nobody having decided that. A re-subscribe has to carry evidence, cannot come from an import or a backfill, and cannot be dated behind the block it clears.
Every grant carries the wording the customer agreed to. What we have not finished is the shared wording registry, so the words on the screen matching the words in the record is something we can show you per customer and cannot yet assert as one canonical string across a capture surface.
290 consent events | 290 of 290 carry the verbatim wording | shared wording registry: 0 rows
Trust should not depend on a prompt behaving itself.
A single function decides every send, and its denial beats every approval above it. Every store error returns no, so an unreadable consent ledger blocks the message rather than waving it through.
We did not turn it on and hope. It ran in shadow for three days first, comparing its answer against the live one on real traffic. Three of the first four real decisions disagreed, and every disagreement had the same cause: the ledger had no concept of an internal number and would have blocked our own team's phones. A second check found ten of 115 rows in the old store, from claims made before the live writer existed, that had not reached the ledger yet, and flipping before that backfill would have denied real customers who had genuine consent on file. Both were fixed before the cutover on 11 August 2026.
One send path runs on the oracle. Two more, lead dispatch and the lead watcher, still run on the old check. Until they move, saying that every sender calls it would be an intention rather than a description.
cutover 2026-08-11 | shadow log opened 2026-08-08 | every decision written to an append only action log