This piece assumes you already accept that consent is a ledger rather than a flag. It describes what to put in the ledger.
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. 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 — timestamp, with timezone, of the event itself, not of the row's insertion.
- mechanism — web form, keyword, verbal, imported, inferred.
- evidence_ref — a pointer to the artefact: form submission id, recording id, 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.
The invariants#
Nothing is ever updated or deleted. A withdrawal is a new row. If your ORM offers a soft delete here, resist it — the history is the asset.
No row without an evidence reference. "Imported from legacy" is an acceptable evidence type if you record it honestly; a blank field is not.
Purpose and channel are never null. 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 is often legitimate. Mixing it with explicit grants is not.
The three queries#
May I send? Given subject, channel, purpose, and now: latest event wins, expiry applied, default 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 with a timestamp. This is the one an audit asks, and the one a flag-based model simply cannot answer.
Show me everything for this person. A 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. Sub-millisecond, in the send path, thousands of 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.
Push the derived state to vendors — suppression lists, preference syncs — but keep the ledger.
Disclosure belongs here too#
The AI transparency obligation that took effect on 2 August 2026 creates a fact of the same shape: this person, on this channel, at this time, was told they were interacting with an AI system.
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.
What to take away#
One append-only table, six fields, three invariants, three queries. It is a week 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.
Sources
Every claim worth checking, with somewhere to check it.
- Safer and more transparent AIEuropean Commission · 2 August 2026