A customer data platform is built to forget. Behavioral events age out, anonymous visitors expire, and profiles that go quiet are eventually removed — which is exactly right for data collected to describe someone's current intent. An unsubscribe is the one record that must survive all of it.
An opt-out is not behavioral data
If consent is stored only alongside the behavioral profile, then a customer who unsubscribes and then stops visiting for a few months has their opt-out quietly garbage-collected with the rest of their record. The next time they identify themselves, they are mailable again. That is not a stale segment; it is a compliance incident.
So consent is kept in two places on purpose. The subscription state lives on the customer profile, where the audience builder can target it like any other attribute and a reach estimate can count only mailable people. The opt-out itself is also written to a permanent suppression list that retention rules never touch, and that list is the final gate before every message.
Check it twice, because time passes
Between the moment a campaign resolves its audience and the moment the last message leaves the queue, minutes or hours go by. A large send fanned out at 09:00 must not mail someone who unsubscribed at 09:05.
The practical rule is that filtering happens at both ends: the audience query excludes non-consenting profiles when the recipient list is built, and the suppression list is checked again for each individual message on the way out. If the suppression data is unavailable, the correct behavior is to fail the job rather than send — an empty list must never be read as "nobody has opted out".
Make the safe direction the default
Consent arrives from several places: a signup form, a preference centre, an API call, a bulk import, a bounce notification. When two sources disagree, the resolution should be biased towards not sending. An unsubscribe, a hard bounce or a spam complaint should never be overwritten by a subscribe arriving from an import or an integration; only an explicit, deliberate re-subscribe should lift it.
Every other part of the pipeline is allowed to be wrong in the direction of mailing fewer people.
Deliverability belongs to your domain
Mail sent through your own SMTP relay carries your reputation, not a shared pool's. That is an advantage worth protecting, and it means the platform's job is to be a careful tenant of your server:
- Validate the connection before the settings are saved, and store credentials encrypted.
- Enforce per-minute, per-hour and per-day rate limits, plus a cap on concurrent connections and messages per connection, since most relays limit harder than customers expect.
- Keep the unsubscribe link on the sending domain where possible — it reads as legitimate to both the recipient and the receiving mail server.
- Publish SPF, DKIM and DMARC records and monitor the return path before the first campaign, not after the first block.
Do not call it delivered
An SMTP relay answering with a success code means the next hop accepted the message. It does not mean the message reached an inbox; asynchronous bounces arrive later, and spam placement never announces itself at all.
Reporting should say sent and mean "accepted by your relay", then show bounced, unsubscribed, complained and failed alongside opens and clicks. A dashboard that reports a confident delivery number is the kind of metric that stays green while a sending domain is being blocked.
Consent checklist before the first campaign
- Import the existing do-not-mail list from your previous provider on day one.
- Confirm the audience reach estimate applies the consent filter, so the number is mailable people.
- Set rate limits to match what your relay actually accepts.
- Decide whether the preference page is hosted by you or by the platform.
- Verify SPF, DKIM, DMARC and the return path.
- Send a test through the real pipeline, not a preview-only render.
See how audiences, scheduling, templates and reporting fit together on the Email Campaigns page, or compare the same controls in the Mobile Push module.