The suppression list is the most underrated system in most email programs. It’s a compliance shield protecting you from CAN-SPAM, GDPR, CCPA, and CASL violations. It’s a deliverability protector — every send to an invalid or unwanted address compounds reputation damage that can take weeks to recover from. And it’s a cost lever — every suppressed send is money you didn’t spend on quota at your ESP.

Most senders treat the suppression list as a passive byproduct of their email service provider. The high performers treat it as a primary system: governed, audited, scoped across brands and streams, integrated with their CRM and CDP, and built to satisfy retention requirements that vary by jurisdiction.

The stakes rose sharply in February 2024 when Gmail and Yahoo turned on their bulk-sender requirements. One-click unsubscribe via RFC 8058 became mandatory, and the requirement to honor opt-outs within roughly two business days now lives in your suppression-write pipeline. If your suppression system can’t keep up, your inbox placement collapses.

This guide is the end-to-end playbook: what belongs in a modern suppression list across eight distinct categories, how to architect it across multiple subdomains and sub-accounts, the exact retention requirements per regulation, and — for self-hosted senders — schema and API patterns that work in production.

This article is part of our Email deliverability guide.

What is an email suppression list?

An email suppression list is a “do-not-send” registry that’s checked before every outgoing message. Matches are dropped silently and logged. Modern systems implement it as a database table with timestamps, source attribution, scope metadata, and audit trails — not a flat list.

The registry is populated through two pathways:

  • Automatic writes from bounce processors, complaint feedback loops, and unsubscribe handlers
  • Manual writes from support tickets, GDPR deletion requests, internal escalations, and legal mandates

At send time, the registry sits between your sending application and the outgoing MTA. Every recipient address is normalized, hashed, and queried against the registry in single-digit milliseconds. A hit logs the suppression event and drops the message. A miss lets the message proceed.

Application
    ↓
Compose recipient list
    ↓
Suppression check (normalize → hash → query)
    ↓                          ↓
  Hit                       Miss
    ↓                          ↓
Drop + log              Forward to MTA
                            ↓
                       Recipient inbox

The reason this matters operationally: the suppression list is the only system that exists to prevent sends. Every other piece of your stack is optimized to enable them. Treating suppression as governance rather than housekeeping is the difference between a clean program and one that flirts with Spamhaus listings.

Suppression list vs. related concepts

Three concepts get conflated with suppression lists. Sort them out before going further.

Suppression list vs. unsubscribe list

Unsubscribes are one input to suppression. The suppression list is broader — it also contains hard bounces, spam complaints, manually added contacts, legal exclusions, and pattern-based blocks. Treating “unsubscribes” and “suppression” as synonyms causes systems to drop everything else.

Suppression list vs. blocklist (custom) vs. DNS blocklist

A custom blocklist is the pattern-based portion of your suppression registry — regex rules and domain wildcards (@disposable.com, info@, support@). It’s a subset of your suppression list, not a separate system.

A DNS blocklist is something else entirely. Spamhaus, Spamcop, SURBL, and similar are external services that publish lists of bad senders. You appear on one of those if your suppression hygiene fails; you don’t operate one.

Suppression list vs. spam trap

A spam trap is an address operated by an anti-spam organization to catch senders with poor list hygiene. You don’t put traps on your suppression list — you put them on your suppression list after you identify them, to prevent sending again. The whole point of clean acquisition and validation is to keep traps off your active list in the first place.

ConceptWho maintains itPurposeWhen it fires
Suppression listYouPrevent your sends to flagged addressesSend-time check
Unsubscribe listYou (subset of above)Honor opt-out requestsAfter user clicks unsub
Custom blocklistYou (subset of above)Pattern-based blocks (regex, domain)Send-time check
DNS blocklistExternal orgs (Spamhaus, etc.)Publish bad senders to recipientsReceiver-side filtering
Spam trapAnti-spam orgsCatch bad-hygiene sendersWhen you send to one

The 8 categories every modern suppression list should hold

Most articles list three or four categories. Production-grade systems carry eight.

1. Hard bounces

Permanent delivery failures — mailbox doesn’t exist, domain is dead, recipient server rejected with a 5xx SMTP code. Suppress immediately on the first hard bounce. Continuing to send to a hard-bouncing address is a Spamhaus-grade reputation risk because anti-spam organizations recycle abandoned addresses into traps.

2. Soft bounces (after threshold)

Temporary failures — full mailbox, server timeout, greylisting. A single soft bounce isn’t suppression-worthy, but the same address bouncing 3–5 times across 30 days almost always means a permanent issue is masquerading as a temporary one. Auto-suppress on that threshold.

3. Spam complaints

Recipients hitting the “Report Spam” button. Fed by ISP feedback loops — Microsoft JMRP for Outlook/Hotmail, Yahoo Complaint Feedback Loop (CFL), Google’s FBL via Postmaster Tools. Suppress immediately and never re-add automatically. A complainer who marks you as spam again will damage reputation enough to affect every other recipient.

4. Unsubscribes

Requests via the in-body unsubscribe link and via the RFC 8058 List-Unsubscribe and List-Unsubscribe-Post headers. Suppress within the regulatory window for each jurisdiction — CAN-SPAM gives 10 business days, but the Gmail/Yahoo bulk-sender rules effectively require ~2 days, and best practice is immediate.

5. Manual suppressions

Support requests (“please stop emailing me”), customer verbal opt-outs at point of sale, internal escalations, contacts marked by your sales team. Every manual entry needs operator attribution in your audit log — you will be asked who added an address when a customer disputes it.

6. Legal / regulatory exclusions

GDPR Article 17 right-to-erasure requests, CCPA deletion requests, CASL revocations, regulator-mandated removals. These are typically permanent and require special handling because GDPR may also require deleting the underlying contact record — see the hashing pattern in the implementation section.

7. Pattern-based suppressions

Role-based addresses (info@, support@, sales@, admin@, postmaster@), known disposable email domains (mailinator.com, guerrillamail.com, tempmail.io), competitor domains, internal/test addresses (@example.com, @test.local). Express these as wildcards or regex in your suppression engine so new matches are caught automatically.

8. Engagement-based sunset

Subscribers who haven’t opened, clicked, or otherwise engaged in 180+ days even after re-engagement attempts. These aren’t unsubscribers — they’re silent non-responders dragging your engagement rates down. The email list cleaning workflow that funnels into suppression covers the 7-step process and re-engagement framework that surfaces these contacts before they damage reputation. Suppress them and protect your sender reputation; allow them to opt back in via a different channel if they want.

CategoryTypical sourceDefault scopeDefault retention
Hard bounceBounce webhookAll streamsIndefinite (with hash)
Soft bounceBounce webhook + thresholdAll streams30 days then re-evaluate
Spam complaintFBL webhookAll streamsIndefinite
UnsubscribeList-Unsubscribe + body linkStream-scopedIndefinite
ManualSupport / admin UIPer requestIndefinite unless specified
Legal exclusionDSAR / deletion requestAll streams + all brandsIndefinite (hashed)
PatternRegex / domain ruleAll streamsUpdated on rule change
SunsetEngagement queryMarketing onlyUntil re-engagement

How suppression lists actually work under the hood

Suppression doesn’t live in isolation — it’s one layer in the email infrastructure stack the suppression registry sits inside, alongside DNS authentication, mail agents, transport security, and observability. Production systems have four moving parts. Production systems have four moving parts.

Pre-send check. Every outbound message hits the suppression registry before entering the MTA queue. Normalize the address (lowercase, strip Gmail dots and plus-tags), hash it, and query the suppression table by hash. A typical implementation completes the check in under 5 ms.

Automatic write triggers. Three event streams should write to suppression automatically:

  • Bounce webhooks from your ESP or MTA → write hard bounces immediately, soft bounces after threshold.
  • Complaint feedback loop messages (ARF — Abuse Reporting Format, RFC 5965) → write spam complaints immediately.
  • List-Unsubscribe POST and unsubscribe-link clicks → write unsubscribes within the regulatory window.

Manual write paths. API endpoints for support tools, admin UI for internal teams, CSV bulk import for migrations and legal deletion batches.

Audit log. Every write captures source (webhook:bounce, api:manual, import:csv), timestamp, reason code, scope, and — for manual writes — the operator’s identity. You will need this when responding to GDPR data-subject access requests.

Python

# Pseudocode: send-time suppression check
def can_send(recipient, stream, brand):
    normalized = normalize_email(recipient)        # lowercase, strip dots/tags
    hash_key   = sha256(normalized)
    record     = suppression_cache.get(hash_key)   # Redis or in-memory cache

    if not record:
        return True

    if record.matches_scope(stream=stream, brand=brand):
        log_suppression_hit(hash_key, record.reason)
        return False

    return True

Two operational rules that catch most teams once: webhook receivers must ACK fast and process asynchronously (a slow database can’t be allowed to drop bounce notifications), and writes must be idempotent — receiving the same bounce notification twice should refresh the timestamp, not error out.

The 2024 Yahoo & Google requirements that changed suppression

Suppression operations got an upgrade on February 1, 2024, when Gmail and Yahoo turned on their bulk-sender requirements. Three things changed for any sender shipping more than 5,000 messages per day to Gmail or Yahoo addresses.

One-click unsubscribe became mandatory. The RFC 8058 List-Unsubscribe-Post header tells the receiver that the unsubscribe URL accepts a single POST request — no confirmation page, no login. Gmail calls that URL on the user’s behalf when they hit the unsubscribe button in the Gmail UI, and expects the suppression to take effect within roughly two business days. Senders whose suppression pipeline runs on daily batch jobs are now in violation by design.

List-Unsubscribe: <https://example.com/u/abc123>, <mailto:unsub+abc123@example.com>
List-Unsubscribe-Post: List-Unsubscribe=One-Click

The 0.3% spam complaint threshold became a hard line. Postmaster Tools surfaces a userreportedspamratio for every sending domain. Sustained values above 0.3% trigger throttling; above 0.5% triggers severe filtering. Every complaint must funnel into suppression in real time — daily batches are no longer enough.

Suppression writes are now on the deliverability critical path. Before 2024, slow suppression hurt your reputation slowly. After 2024, it triggers throttling within days. Audit your suppression-write latency: from the moment a recipient clicks unsubscribe in Gmail to the moment your next send to that address is blocked should be measured in minutes, not hours.

Permanent vs. temporary suppression

Most production systems mix two kinds of suppression and confuse themselves about which is which.

Permanent suppression — these never auto-clear:

  • Unsubscribes (until the user re-opts-in themselves)
  • Confirmed spam complaints
  • GDPR/CCPA/CASL deletion or revocation requests
  • Confirmed hard bounces

Temporary suppression — these clear automatically on a defined timer or condition:

  • Soft-bounce holds: clear after 30 days of no further bounces.
  • Engagement-based sunset: clear if the recipient re-engages through another channel (e.g., clicks a link in a transactional message).
  • Promotional fatigue suppression: skip this contact for 14 days after a high-engagement campaign to avoid over-mailing.
  • Migration freeze: suppress an imported segment for the first 14 days while you warm the new ESP.
AutomaticManual
PermanentHard bounces, spam complaintsLegal exclusions, manual unsubscribes
TemporarySoft bounces, sunset, fatigueMigration freeze, time-limited support requests

Implement both with an expires_at column. Permanent records have expires_at = NULL; temporary records have a future timestamp and are excluded from the send-time check automatically once the timestamp passes.

Global vs. scoped suppression at scale

The architectural decision most senders never deliberately make. Get it wrong and you’ll either over-suppress (blocking transactional password resets to people who unsubscribed from marketing) or under-suppress (still mailing newsletter content to people who asked you to stop).

Global suppression

One registry that suppresses across all subdomains, sub-accounts, brands, and products. Required for legal exclusions — a GDPR deletion must propagate everywhere. Safe default for hard bounces and spam complaints.

Scoped suppression

Separate registries per stream, subdomain, or brand. Useful for the most common edge case: a customer unsubscribes from your weekly newsletter but still needs password resets and order receipts. The suppression should scope to news.example.com, not tx.example.com.

The transactional vs. marketing edge case

A marketing unsubscribe should never block a password reset. A hard bounce on a real customer should block both — the address is invalid for everyone. Your suppression schema needs at least two scope dimensions: stream type (marketing, transactional, cold, all) and reason. The send-time check evaluates both.

For senders running this in production rather than building it, Sender’s segmentation that scopes suppression by stream and brand handles the dimensions natively — marketing unsubscribes don’t propagate to transactional, brand-level segments roll up correctly, and the audit log captures every suppression event.

Multi-brand and ESP sub-account architectures

Brand-level suppression with parent-level override is the pattern that scales. A holding company with three brands runs one parent-level suppression registry for legal exclusions (cascades to all brands) plus three brand-level registries for marketing unsubscribes (don’t cascade — opting out of Brand A doesn’t opt you out of Brand B). Subdomain-level stream scoping lives inside each brand.

Parent (legal exclusions: GDPR deletions, regulator-mandated)
    ├── Brand A
    │    ├── news.brand-a.com (marketing unsubs scoped here)
    │    └── tx.brand-a.com   (transactional, only hard bounces from here)
    └── Brand B
         ├── news.brand-b.com
         └── tx.brand-b.com

Decide this on day one. Retrofitting scope into a flat suppression table after you’ve grown a multi-brand program is painful and risks silent under-suppression during the migration.

Compliance retention: how long to keep suppressed addresses

Retention requirements vary by jurisdiction. Most articles handwave this; the real requirements are specific.

RegulationRegionOpt-out / deletion windowSuppression retention
CAN-SPAMUS10 business daysNo statutory minimum; FTC enforcement practice retains 5+ years
GDPR Article 17EU/EEAWithout undue delay (typically ≤30 days)Indefinite suppression of hashed identifier (see below)
CCPA / CPRACalifornia45 days for deletion requestsIndefinite for “do not sell/share” opt-out
CASLCanada10 business daysConsent records retained 3 years post-relationship
LGPDBrazilWithout undue delayIndefinite suppression of identifier
Australia Spam ActAustralia5 business daysNo specific window; practice is indefinite
UK GDPRUKAligned with EU GDPRIndefinite suppression of hashed identifier

GDPR creates an operational tension worth flagging: a full deletion of the contact record removes the suppression evidence too, which would allow accidental re-import later. The accepted pattern is to hash the email address (one-way SHA-256) and store only the hash in suppression, alongside a deletion timestamp. The plaintext email is deleted (satisfying the erasure right), but future imports can still be matched against the hash to prevent re-mailing.

Most regulators also expect documentation: source of consent for the original opt-in, source of the opt-out, timestamps for both. CASL is the strictest on this in practice; CAN-SPAM enforcement actions have demanded similar records during disputes. For Sender users, how Sender handles CAN-SPAM and GDPR compliance at the platform level documents the consent storage, audit log, and one-click unsubscribe handling built into the platform.

Building a suppression list system (developer detail)

For senders not running on a fully managed ESP, the schema and patterns that work in production:

SQL

CREATE TABLE email_suppressions (
  id              BIGINT AUTO_INCREMENT PRIMARY KEY,
  email_hash      CHAR(64) NOT NULL,           -- SHA-256 of normalized address
  email_plain     VARCHAR(320),                -- nullable for GDPR-deleted records
  reason          ENUM('hard_bounce','soft_bounce','complaint',
                       'unsubscribe','manual','legal','pattern',
                       'sunset') NOT NULL,
  scope_stream    ENUM('marketing','transactional','cold','all') NOT NULL,
  scope_brand     VARCHAR(64) NOT NULL DEFAULT 'all',
  source          VARCHAR(64) NOT NULL,        -- 'webhook:bounce', 'api:manual', ...
  operator        VARCHAR(64),                 -- who added (if manual)
  created_at      TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  expires_at      TIMESTAMP NULL,              -- NULL = permanent
  notes           TEXT,
  UNIQUE KEY uk_dedupe (email_hash, reason, scope_stream, scope_brand)
);

CREATE INDEX idx_send_lookup ON email_suppressions
  (email_hash, scope_stream, scope_brand, expires_at);

Key implementation patterns:

  • Normalize before hashing. Lowercase the entire address. For Gmail addresses, strip dots from the local part (john.doe@gmail.comjohndoe@gmail.com) and remove +tags. For other providers, strip only +tags. Inconsistent normalization is the root cause of “we suppressed this person but they got the email anyway” bugs.
  • Idempotent writes. Use INSERT ... ON DUPLICATE KEY UPDATE so repeated bounce notifications refresh the timestamp instead of erroring. Race conditions between webhook receivers are common.
  • Send-time lookup is the hot path. Keep the table cached in Redis or an in-memory store. A slow suppression check throttles your entire send rate.
  • Webhook receivers ACK fast. Receive the bounce/complaint event, write to a queue, return 200 OK in under 200ms, process the queue asynchronously. ESPs retry unacknowledged webhooks and will eventually disable webhook delivery if you’re too slow.
  • API surface area. POST /v1/suppression for writes, DELETE /v1/suppression/{hash} for removals (with audit logging — every removal is a sensitive action), GET /v1/suppression?since=... for syncing with other systems.
  • Cross-system sync. Your CRM, CDP, marketing automation, and transactional service should all share the same suppression source of truth — usually via a webhook fan-out from the central registry.

Best practices for suppression list management

The operational disciplines that separate clean programs from troubled ones:

  • Track suppression hygiene as a metric. A mature program typically has 10–25% of contacts in suppression. Above 40% suggests systemic acquisition or content problems. A near-zero rate suggests you’re not capturing bounces and complaints — the registry isn’t being written to.
  • Never “clean” the suppression list by removing suppressed contacts to remail them. This is the single fastest path to a Spamhaus listing. Suppressed contacts can only return to the active list by opting back in themselves.
  • Document your suppression policy. Who can add manually, when temporary suppressions expire, how legal exclusions are tagged, who has delete authority. The document should be referenced in your CAN-SPAM and GDPR compliance evidence.
  • Reconcile across systems quarterly. CRM, ESP, marketing automation, transactional service. Any divergence means someone is getting mail they shouldn’t or being blocked from mail they should receive.
  • Monitor suppression growth rate. A sudden spike in spam-complaint suppressions almost always traces to a single bad campaign — find the campaign, fix the content or segmentation, then resume.
  • Audit log everything. Reason, source, operator, timestamp on every manual entry. GDPR data-subject access requests will ask for this exact information.

Migrating suppression lists between ESPs

The migration failure mode is identical year over year: teams cut over to a new provider, forget to import their suppression history, and immediately re-mail contacts who unsubscribed years ago. Spamhaus listings typically follow within days.

Pre-migration. Export every suppression category from the outgoing ESP with timestamps, reason codes, and scope flags. Most major providers (SendGrid, Mailgun, Postmark, Amazon SES, Mailchimp, Mailgun, MailerSend) expose this via UI export or API. Note the export and import formats — they rarely match.

Normalize. Lowercase, strip Gmail dots and plus-tags, hash if your new system supports hashed suppressions. Reason codes rarely map 1:1 between providers — collapse unfamiliar codes into the new ESP’s available types or into your “manual” bucket, but preserve the original code in a notes field.

Import into the new ESP. Use the bulk import API where available, CSV upload as a fallback. Validate the import with a known-suppressed test address before resuming production sends — verify the address is actually blocked on the new system, not just present in the database.

Don’t repoint sending immediately. Run 90/10 traffic for the first week (90% old ESP, 10% new), then 50/50, then 10/90. This lets you catch import failures and gives you a fast rollback if the new system mis-handles suppression. Cutover-on-day-one without a graduated migration is how programs land on blocklists.

Frequently asked questions

Is a suppression list legally required?

Effectively yes. CAN-SPAM, GDPR Article 17, CCPA, and CASL all require honoring opt-out and deletion requests, which operationally requires a suppression registry. CAN-SPAM specifies the 10-business-day window; Gmail and Yahoo bulk-sender requirements effectively shorten that to ~2 days for compliant senders.

How is a suppression list different from an unsubscribe list?

Unsubscribes are one source of suppression. The list also contains hard bounces, soft-bounce thresholds, spam complaints, legal exclusions, pattern-based blocks (role addresses, disposable domains), engagement-based sunsets, and manual entries from support.

Can I remove someone from my suppression list to re-email them?

Only if the recipient re-opts-in themselves through a fresh signup flow. Removing a suppressed contact to remail them violates CAN-SPAM, GDPR, and CASL, and is the most common cause of Spamhaus listings. The audit trail will show the removal and the regulator will not be sympathetic.

How long should suppressed addresses stay on the list?

Permanent for unsubscribes, spam complaints, hard bounces, and legal exclusions. Temporary, with documented expiry timestamps, for soft bounces, engagement-based sunsets, and migration freezes. CAN-SPAM enforcement history suggests retaining 5+ years; GDPR makes it indefinite via hashed records.

Does a suppression list block transactional emails too?

It depends on how you’ve scoped it. A marketing-only suppression (someone unsubscribed from your newsletter) should not block transactional mail like password resets or order receipts. A hard bounce or legal exclusion blocks both — the address is invalid or legally protected for every stream. Scope your suppression by stream type from day one.

What percentage of my contacts should be in suppression?

For a mature program, 10–25% is normal and healthy. Above 40% suggests acquisition or content problems — too many bad addresses entering or too many recipients unsubscribing. A near-zero rate suggests your suppression-write pipeline isn’t capturing events.

Do I need a suppression list if my ESP handles it for me?

The ESP manages the registry, but you still need to understand what’s in it, audit it for compliance evidence, export it for migration, and sync it with your other tools — CRM, CDP, marketing automation, transactional service. The ESP managing the storage doesn’t relieve you of governance responsibility.

Can I import a suppression list from another ESP?

Yes — and you must, when migrating. Export with timestamps and reason codes from the outgoing provider, normalize the addresses, then bulk-import via CSV or API into the new ESP before resuming sending. Validate with a known-suppressed test address. Migrating without bringing suppression with you is the single most common cause of post-migration deliverability collapses.

Final word

The suppression list isn’t housekeeping. It’s governance. It sits between your sending application and every regulator, every mailbox provider, and every recipient who has asked you to stop — central to email deliverability that depends on governance as much as sending.

The senders who never appear in their own compliance reports aren’t lucky. They’ve just built suppression as deliberately as they’ve built any other production system — with retention policies that match jurisdiction, scopes that match brand architecture, and audit trails that survive regulator scrutiny. The work compounds. Done well from the start, suppression becomes invisible. Done poorly, it becomes the only system anyone talks about, usually during an incident.