Skip to content
~/robynrocha.io
cd /home
System breakdownCompliance / Outbound / Architecture

A suppression list you cache is not a suppression list

Do-not-contact is the one rule in outbound with no acceptable failure rate. Almost every implementation quietly introduces a window where it is wrong.

Most parts of an outbound system are allowed to be approximately right. A stale enrichment field is a nuisance. A slightly wrong score sends the wrong email to someone who ignores it.

Suppression is not like that. Contacting someone who asked you not to is the one failure with no acceptable rate, and it is also the failure most systems are quietly architected to produce.

The window nobody means to build

The usual implementation looks reasonable. There is a suppression list. When a campaign is built, the list is pulled and the audience filtered against it. The campaign then sends over the following hours or days.

The bug is in that last sentence. The list was consulted when the campaign was assembled, not when each message was sent. Everyone who opts out in between sits in an audience that was already filtered, and they will be contacted anyway.

Nobody designs this window on purpose. It appears because filtering at build time is the obvious place to filter, and because the gap only produces visible failures occasionally, which is worse than constantly - it never gets prioritised.

Read at send, not at build

The fix is architectural rather than clever: the store is consulted at the moment of sending, as close to the irreversible act as you can get it.

This costs something. The sending path now depends on a live store, and you cannot pre-compute an audience and forget about it. That cost is the point. A list read at build time is a snapshot; a list read at send time is a rule.

The test is simple: if someone opts out five minutes before a scheduled send, do they receive it? If yes, you have a snapshot.

Every lane, and both addresses

Every lane feeds it. An opt-out arrives however it arrives: a reply, an unsubscribe click, a request on a phone call, a message to a different address, a bounce saying the mailbox is gone, an exclusion someone adds by hand. If any one path does not reach the store, the store is wrong and everything downstream inherits it.

One opt-out covers the person, not the channel. Someone who asks to stop hearing from you has not asked to stop hearing from you by email specifically. If the phone list and the email list are separate stores, you will call someone who unsubscribed and be technically consistent and practically indefensible.

Permanence is not a setting

There is always a case for expiry. People change jobs. Companies change needs. A no from two years ago might not be a no today. The argument is not unreasonable and it is still wrong, because the cost is asymmetric: the downside is not a lost prospect, it is contacting someone who explicitly told you not to.

So permanence is not exposed as a configuration option. Not because the alternative is unthinkable, but because a rule with a settings toggle is not a rule.

What it buys

A suppression store with these properties is boring, which is the goal. Read constantly, written from everywhere, never cached, never negotiated with. Everything else in the program can be tuned and argued about. This one is allowed to be rigid, and being rigid is what lets the rest of the system move quickly without anyone getting hurt.