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 queries below are what this figure is doing underneath.

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

  1. Subject

    The contact identity, not the channel address. Addresses change; the person does not.

  2. Scope

    Channel and purpose together. "Marketing on WhatsApp" is a grant. "Consent" is not.

  3. Direction

    Grant or withdrawal. A withdrawal is a new row with the opposite sign, never a deletion.

  4. Mechanism

    Web form, keyword, verbal, imported, inferred. Different evidentiary weight under scrutiny.

  5. Evidence pointer

    Submission id, recording id, inbound message id. The artefact itself lives elsewhere; the pointer lives here.

  6. Timestamps

    When the event happened and when the row was written. They differ during an import, and the difference matters.

Six fields. A ledger missing any one of them can answer "may I send" but not "prove it", and the second question is the one that arrives with a lawyer.

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

  1. 01May I send this?

    Current state, one subject, one scope. Sub-millisecond, in the send path, thousands of times a second.

  2. 02What does this person have?

    Every scope for one subject, current state. The preference centre and the subject access request read this.

  3. 03What did we hold, and when?

    State at a past timestamp, with the evidence. Asked by a regulator, an auditor, or a lawyer.

The third query is why the ledger is append-only. It cannot be answered by a table of current values, no matter how well indexed.

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.

  1. Safer and more transparent AIEuropean Commission · 2 August 2026