Turning Complex Problems Into Confident Technology

Proving you honoured an unsubscribe

Trust and identity · Production · Inside the Workshop

The problem

Every sending platform has an unsubscribe feature, and each one keeps its own list. Use two platforms and you have two lists. Change platform and the list stays behind. The request a person made was to you, not to your current vendor, and the obligation does not migrate just because the tooling did.

The question that matters is not "can someone unsubscribe". It is "if someone says they unsubscribed eighteen months ago and you emailed them anyway, what can you show?"

Why it mattered

Because the answer was going to be an assertion. I could have said "the platform handles it", which is an answer about a system I do not control, cannot query historically, and will one day replace. That is not evidence, and under UK GDPR the accountability sits with the sender rather than with the sender's software.

What I chose, and why

A suppression ledger I own, treated as the system of record, with the sending platform demoted to a downstream consumer that gets told what to do.

  • The unsubscribe link is a signed token, not a database identifier. It carries who it is for and cannot be edited into somebody else's address, so a one-click unsubscribe needs no session and cannot be turned into a way to unsubscribe a stranger.
  • Addresses are normalised before they are compared, and stored hashed. Suppression has to survive the difference between the address as typed and the address as delivered, and a suppression list is a list of people who explicitly asked you to stop — which makes it exactly the wrong thing to hold as plaintext.
  • Eligibility is a gate, not a filter. A campaign asks the ledger whether it may contact someone; it does not receive the list and remove rows. The distinction matters because a filter that fails open still sends.
  • The link exists before the lead does. Each lead's unsubscribe URL is minted at the point it is added to the platform, so there is no window where a contactable record has no way out.

The part that took longest

Not the cryptography. The database permissions.

A ledger is only evidence if it cannot be quietly rewritten, which means the application identity that reads it must not be able to delete from it, and the retention purge that legitimately does delete must be scoped to exactly the rows it is allowed to touch and nothing else. That is object-level grants plus explicit denials on the ledger itself, applied as part of the deployment rather than clicked on once and forgotten.

Getting a deployment pipeline to apply its own permission model turned out to need the pipeline's principal to hold rights it does not need at runtime — the right to grant and deny — which is its own small design problem: the thing that installs the locks necessarily holds a key.

Working against someone else's API

The downstream platform's behaviour had to be discovered rather than read. Its documented semantics and its observed semantics differed enough that the clients were written against what it actually does, with the real behaviour recorded in the repository next to the code, and the open questions written down and sent to their support rather than guessed at.

Everything that talks to it is therefore built to be re-run: backoff, dead-lettering, a sweep that reconciles what my ledger says against what the platform reports, and an emergency freeze that stops all outbound activity without a deployment. None of that is clever. It is the standard cost of depending on a system whose failure modes you cannot enumerate in advance.

What I would do differently

I would build the ledger before the first campaign rather than alongside it. Retrofitting a system of record means reconciling history you did not design to be reconciled, and every hour of that was avoidable by ten minutes of forethought earlier.

I would also resist the temptation to describe this as "GDPR compliance". It is one mechanism that makes one obligation demonstrable. Compliance is a much larger claim and this does not make it.

Related

If this is your problem too

The same instinct — hold the evidence yourself rather than trusting a platform's word for it — drives proving a testimonial without asking anyone to trust me. For the wider question of who is accountable for what, see the privacy policy, or start a diagnosis.