Why this matters#
Sooner or later someone asks a business to prove that a particular person agreed to a particular message: a regulator, a carrier investigating complaints, a court, or the customer. Most messaging stacks can say whether they may send today. Very few can say what they were allowed to do eighteen months ago, and why. This piece describes a design that can answer both, and that keeps working when you change messaging vendors. It assumes you already accept the idea in consent is a data model: consent is a ledger of events, not a single yes/no flag.
Interactive figure
What consent actually permits
You may send
- Order receipt
- Delivery updates
You may not send
- Promotional broadcast
- Win-back campaign
- Free-form conversation
Grant a permission and watch what becomes sendable.
The event#
One append-only table. Every row is an immutable fact that happened at a moment:
- subject: the contact identity, not the channel address. Phone numbers and email addresses change; the person does not.
- channel: sms, whatsapp, email, voice. Never "all".
- purpose: service, transactional, marketing, research. A closed list, versioned.
- action: granted, withdrawn, expired, disclosed.
- at: the time of the event itself, with time zone, not the time the row was inserted.
- mechanism: web form, keyword, verbal, imported, inferred.
- evidence_ref: a pointer to the artefact, such as a form submission id, a call recording id or an inbound message id.
- actor: who recorded it. A person, a system, an import job.
- source_system: where it originated, which matters enormously after an acquisition or a migration.
One row, appended, never updated. Everything the three queries need has to be on it, because reconstructing a missing field from application logs is the work this design exists to avoid.
What a single grant or withdrawal has to carry
- Subject
The contact identity, not the channel address. Addresses change; the person does not.
- Scope
Channel and purpose together. "Marketing on WhatsApp" is a grant. "Consent" is not.
- Direction
Grant or withdrawal. A withdrawal is a new row with the opposite sign, never a deletion.
- Mechanism
Web form, keyword, verbal, imported, inferred. Different evidentiary weight under scrutiny.
- Evidence pointer
Submission id, recording id, inbound message id. The artefact itself lives elsewhere; the pointer lives here.
- Timestamps
When the event happened and when the row was written. They differ during an import, and the difference matters.
Flag or ledger: the difference in one table#
| Question | A yes/no field on the contact | An event ledger |
|---|---|---|
| May I send this marketing message now? | Yes, if the field is current | Yes |
| What did we hold on 1 March last year? | No: the old value was overwritten | Yes: replay events up to that date |
| How was consent given, and where is the proof? | Rarely recorded | Mechanism and evidence pointer on every row |
| Can marketing be withdrawn while service messages continue? | Only with one field per purpose and channel | Built in: scope is part of each row |
| Does it survive a vendor migration? | Only if the vendor's export is complete | Yes: it lives in your systems |
The invariants#
Nothing is ever updated or deleted. A withdrawal is a new row. If your framework offers a "soft delete" here, resist it. The history is the asset. (Where a data subject's erasure request applies, handle it as a documented, separate process with legal advice, rather than as routine editing.)
No row without an evidence reference. "Imported from legacy system" is an acceptable evidence type if you record it honestly; a blank field is not.
Purpose and channel are never empty. A grant that does not say what it is for and where it applies cannot be enforced, and a system that stores it will over-apply it.
Inferred consent is marked as inferred. Some rows will come from behaviour rather than a statement. That can be legitimate under some rules and in some markets. Mixing it with explicit grants is not.
Four invariants the ledger never breaks
- Nothing is updated or deleted
A withdrawal is a new row; erasure requests are a separate, documented process
- No row without an evidence reference
"Imported from legacy system" is honest; a blank field is not
- Purpose and channel are never empty
A grant that does not say what for and where will be over-applied
- Inferred consent is marked as inferred
Never mixed with explicit grants
The three queries#
May I send? Given subject, channel, purpose and now: the latest event wins, expiry is applied, and the default is deny. This runs in the send path, so it needs an index and a cache with a short, bounded lifetime.
What did we hold, then? The same query at a past timestamp. This is the one an audit asks, and the one a flag-based model cannot answer.
Show me everything for this person. A data subject access request. If assembling it takes a week of engineering time, you do not have a consent architecture; you have logs.
What the ledger is asked, in ascending order of urgency
- 01May I send this?
Current state, one subject, one scope. Fast, in the send path, many times a second.
- 02What does this person have?
Every scope for one subject, current state. The preference centre and the subject access request read this.
- 03What did we hold, and when?
State at a past timestamp, with the evidence. Asked by a regulator, an auditor, or a lawyer.
Where it lives#
In your systems, not a vendor's. You are accountable for it, you must produce it after switching platforms, and retention obligations outlast contracts. A consent record that only exists in a console you may cancel is a liability with a renewal date.
Not in the CRM, not in the marketing platform, not in a vendor's contact list either. All three are legitimate readers of the ledger; none is a good home for it, because the ledger has to outlive each of them and answer questions about periods when a different system was in place.
Push the derived state to vendors (suppression lists, preference syncs) but keep the ledger. In practice that means a small internal service that other systems call, which is more work than a column on the contact record, and far less work than reconstructing history later.
The ledger stays home; derived state travels
- 01The consent ledger
A small internal service, in your systems
Derived state is pushed out
- 02Suppression lists and preference syncs
The copies vendors receive
Read by
- 03CRM, marketing platform, messaging vendor
Legitimate readers — none of them a home for the ledger
Disclosure belongs here too#
The EU AI Act's transparency obligations, which apply from 2 August 2026, create a fact of the same shape: this person, on this channel, at this time, was told they were interacting with an AI system. See Article 50 for messaging teams.
Same table, with an action of disclosed. The alternative, a second system with its own timestamps and its own gaps, is how organisations end up unable to answer a simple question about their own behaviour.
The migration test#
The honest way to check this design: assume you replace your messaging vendor next quarter. Can you export the ledger, import it elsewhere, and answer all three queries on day one?
If yes, the architecture is sound. If no, you have built a dependency you did not intend to buy.
You replace your messaging vendor next quarter
- 01Can you export the ledger, import it elsewhere, and answer all three queries on day one?YesThe architecture is soundNoYou have built a dependency you did not intend to buy
The trade-offs#
This design is not free. An append-only table grows forever, so you need archiving and a retention policy agreed with legal counsel. The "may I send?" query must be fast, which means caching, and caches can be briefly stale after a withdrawal; keep their lifetime short and treat withdrawals as urgent. And teams used to editing a contact record will need to learn to append instead.
What to take away#
One append-only table, six essential fields, a few invariants and three queries. It is a modest piece of work, and it is the only part of a messaging stack that answers to a regulator rather than to a vendor. Build it before the first send, because retrofitting it means reconstructing history you no longer have.
Where to go next#
- The gap nobody fills: service-to-service identity: how systems prove they are acting for a specific customer when they read or write this ledger.
- Federating systems without merging them: why "one source of truth per fact" matters when several systems share contacts.
Sources
Every claim worth checking, with somewhere to check it.
- Regulation (EU) 2016/679 (General Data Protection Regulation)EUR-Lex
- Guidelines 05/2020 on consent under Regulation 2016/679European Data Protection Board
- Article 50: Transparency obligations for providers and deployers of certain AI systemsEuropean Commission, AI Act Service Desk
- Commission starts enforcing AI Act rules and new transparency requirements on 2 AugustEuropean Commission · 31 July 2026