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 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. 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

  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.

Flag or ledger: the difference in one table#

QuestionA yes/no field on the contactAn event ledger
May I send this marketing message now?Yes, if the field is currentYes
What did we hold on 1 March last year?No: the old value was overwrittenYes: replay events up to that date
How was consent given, and where is the proof?Rarely recordedMechanism and evidence pointer on every row
Can marketing be withdrawn while service messages continue?Only with one field per purpose and channelBuilt in: scope is part of each row
Does it survive a vendor migration?Only if the vendor's export is completeYes: 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

  1. 01May I send this?

    Current state, one subject, one scope. Fast, in the send path, many 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.

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

  1. 01The consent ledger

    A small internal service, in your systems

    Derived state is pushed out

  2. 02Suppression lists and preference syncs

    The copies vendors receive

    Read by

  3. 03CRM, marketing platform, messaging vendor

    Legitimate readers — none of them a home for the ledger

The ledger has to outlive each reader and answer for periods when a different system was in place.

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

  1. 01Can you export the ledger, import it elsewhere, and answer all three queries on day one?
    Yes
    The architecture is sound
    No
    You 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#

Sources

Every claim worth checking, with somewhere to check it.

  1. Regulation (EU) 2016/679 (General Data Protection Regulation)EUR-Lex
  2. Guidelines 05/2020 on consent under Regulation 2016/679European Data Protection Board
  3. Article 50: Transparency obligations for providers and deployers of certain AI systemsEuropean Commission, AI Act Service Desk
  4. Commission starts enforcing AI Act rules and new transparency requirements on 2 AugustEuropean Commission · 31 July 2026