Vol. 01 — Issue 02 research.danilowoz.com May 2026

Suppressed.

Suppression · Webhooks · Hygiene Issue №02
The Deliverability Issue

A list of people you will never email again.

A field guide to the quiet, unglamorous discipline that decides whether your email reaches the inbox — or never leaves the building.

03 — INSIDE

The three suppression types every sender must know.

Hard bounces, complaints, unsubscribes — the universal trio that drives every provider's suppression model.

04 — COMPARED

SendGrid, Mailgun, Postmark & Amazon SES, side by side.

Four philosophies, four API surfaces, four sets of quirks worth knowing before you commit.

14 — FIELD MANUAL

Triggers, webhooks, and a checklist you can ship today.

Events, idempotency, signature verification, and twelve operator rules ordered by leverage.

What a suppression list actually is.

A suppression list is, in its plainest form, a register of email addresses your system is forbidden from contacting. It is not a "do not call later" list; it is a permanent, infrastructure-level firewall sitting between your application and the outside world. When your application says "send to alex@example.com," the suppression list is the bouncer who answers, "no."

The distinction worth holding onto is this: an unsubscribe list is a subset of a suppression list. Suppression captures unsubscribes, but it also captures hard bounces, spam complaints, manually-added addresses, role accounts, competitors, exes, lawyers, the CEO — anyone the business has decided is off-limits, for any reason at all. The unsubscribe list answers "who clicked the link?" The suppression list answers a bigger question: "who must we, under no circumstances, attempt to deliver to?"

Why this matters beyond the obvious legal exposure: every email you send to a known-bad address costs you reputation. Inbox providers — Gmail, Yahoo, Microsoft, Apple — score your domain on the ratio of clean delivery to garbage attempts. A suppression list is the cheapest possible insurance policy against that ratio collapsing.

It is also, when treated seriously, a competitive advantage. Senders with disciplined suppression hygiene routinely land in the inbox while their less careful peers land in promotions, spam, or nowhere at all. The list is invisible to your customers and barely visible to your team — which is precisely why it gets neglected, and precisely why fixing it is so often the highest-leverage thing a deliverability team can do.

Three doors into the list.

01 — HARD BOUNCES

A permanent delivery failure.

The mailbox doesn't exist, the domain is dead, or the recipient server has formally refused you. Suppress immediately — retrying compounds the damage.

code: 550 / 5.1.1
02 — COMPLAINTS

"This is spam," clicked.

A recipient clicked "this is spam" in Gmail, Yahoo, Outlook, or AOL. Their mail provider tells you via a feedback loop. Suppress — and treat the volume as a leading indicator of brand decay.

event: complained
03 — UNSUBSCRIBES

Explicit opt-out.

A click, a List-Unsubscribe header, an inbound reply, or a CASL-style consent expiry. Honor within two days, even though CAN-SPAM gives you ten.

action: opt-out

A fourth category, quietly

Most providers also let you add addresses manually — sometimes called the "do-not-send" or "block" list. This is where role accounts (info@, sales@, abuse@), known bad actors, and previously-litigious recipients go. It is rarely advertised on a feature page, but it is the most powerful tool in the kit because it captures human judgment that no automated trigger ever will.

Soft bounces — the awkward middle

A soft bounce is temporary: full mailbox, out-of-office server hiccup, a momentary block. You do not suppress on the first soft bounce, or even the second. The industry-standard rule, adopted by Mailgun and broadly mirrored elsewhere, is five or more consecutive soft bounces graduates the address to suppression. Below that, retry with backoff.

Rule of Thumb

2% is the safe zone. 5% is the danger zone.

2026 industry benchmarks place a healthy bounce rate below 2%. Above 5%, mailbox providers will throttle or block you. Your suppression list is the dam holding back that flood.

Three regimes, one list.

If your database includes a single contact in the EU, Canada, or California, all three frameworks apply somewhere in your stack. The operationally sound move is to default to the strictest of them — globally.

FrameworkJurisdictionOpt-Out WindowKey Implication
CAN-SPAM United States 10 business days (legal); 2 days (practical) A single global suppression list meets the minimum. Honor unsubscribe requests in every send.
GDPR European Union Immediate Consent withdrawal is not queued. Suppression must scope to the type of communication, not just the address.
CASL Canada 10 business days, with implied-consent expiry Suppression logic must track when implied consent naturally expires, not just explicit opt-outs.

The trap that catches multi-region senders is treating "unsubscribed" as a binary, single-field truth. Under GDPR, a recipient can withdraw consent from your product-updates list while remaining subscribed to transactional notifications. Under CASL, an implied-consent relationship has a clock running on it from the moment of the underlying business transaction. A flat unsubscribed = true column captures neither nuance.

If your contacts span all three jurisdictions, the right architecture is to evaluate compliance per recipient before each send — not after the complaint arrives. The pragmatic shortcut: apply the strictest of the three to everyone, and document why.

When to add, and how.

A suppression list is not a static file you import once. It's a living object updated by events flowing in from your sending platform, your application, and your humans.

  • Hard Bounce
    Add immediately, on first occurrence.

    A 5xx SMTP response from the recipient server is permanent by definition. Most providers do this automatically; if yours doesn't, listen for the bounced webhook and write to your suppression store within seconds.

  • Complaint
    Add immediately. Never retry.

    Spam complaints arrive through ISP feedback loops (Gmail's FBL, Yahoo's CFL, Microsoft's JMRP). Most providers permanently suppress on first complaint — Postmark, notably, will not let you reactivate a complaint suppression at all.

  • Unsubscribe
    Add on click, on header click, and on inbound reply.

    The List-Unsubscribe header (RFC 8058) and one-click unsubscribe are now table stakes after the Gmail/Yahoo 2024 sender requirements. Capture all three vectors and feed one suppression list.

  • Soft × 5
    Add after a streak of consecutive failures.

    Five soft bounces in a row to the same address — with no successful delivery between them — is treated as effectively hard. Reset the counter on any successful delivery.

  • Inactivity
    Add after a re-engagement attempt fails.

    The sunset policy: 90–120 days of no opens or clicks → one final re-engagement email → 7 days to respond → suppress. Industry consensus in 2026.

  • Manual
    Add via admin tooling for role accounts, abuse contacts, legal requests.

    Make this a first-class action in your internal tools, not a database query. The audit trail matters.

A representative webhook handler

In practice, most of the above resolves to one piece of code: a webhook endpoint your sending provider calls, which writes to a suppression store. Here is the shape, in idealized form:

// POST /webhooks/email-events
export async function POST(req) {
  const event = await req.json();
  const { type, data } = event;

  if (['email.bounced', 'email.complained'].includes(type)) {
    await suppress({
      email: data.to,
      reason: type,
      providerEventId: data.id,
      occurredAt: data.created_at,
    });
  }

  if (type === 'email.unsubscribed') {
    await suppress({
      email: data.to,
      reason: 'unsubscribe',
      scope: data.list ?? 'global',
    });
  }

  return new Response(null, { status: 200 });
}

The substance is in what suppress() does next: idempotent upsert, an audit log, a notification to a deliverability Slack channel if volume crosses a threshold, and — critically — a check that prevents re-adding addresses your team has consciously chosen to remove. Without idempotency you will, eventually, have a fight with your own webhook retries.

Four providers, four philosophies.

Every major sending platform implements suppression. They do not implement it the same way. Here is how SendGrid, Mailgun, Postmark, and Amazon SES each draw the lines.

SENDGRID · TWILIO · 2009

Two-tier global + groups.

Model: Global suppressions plus suppression groups (unsubscribe scoped to a category like "newsletters" vs. "billing"). How addresses land there: Subscription Tracking → Global Unsubscribe; bounces and complaints feed dedicated lists. Quirk: Groups are powerful for layered consent but invisible to senders who treat unsubscribe as a single boolean.

MAILGUN · SINCH · 2010

Three explicit lists per domain.

Model: Separate Bounces, Unsubscribes, Complaints lists, each managed independently. How addresses land there: Hard bounces (5xx) only — soft bounces and ESP rejections excluded. Quirk: When an address is on any list, Mailgun drops the message before SMTP — you'll see "Not delivering to unsubscribed address" rather than a silent failure.

POSTMARK · ACTIVECAMPAIGN · 2010

One Suppressions object per stream.

Model: A single Suppressions object per Message Stream — addresses are "active" or "suppressed." How addresses land there: Hard bounces and spam complaints auto-suppress; unsubscribes and manual additions land here too. Quirk: Spam-complaint suppressions are permanent and cannot be deleted via the API.

AMAZON SES · AWS · 2011

Global plus account-level.

Model: A global SES list (managed by AWS) and an account-level list (managed by you, overrides global). How addresses land there: Opt in via PutAccountSuppressionAttributes and choose BOUNCE, COMPLAINT, or both. Quirk: Configuration sets let one product line run strict suppression while another runs custom — useful when transactional and marketing share an account.

A note on Resend

Resend follows the spirit of the Postmark/Mailgun approach: hard bounces and spam complaints land on the suppression list automatically, and every send checks the list before attempting delivery. Suppressed messages show up in the Emails dashboard with a suppressed status and a "suggested fix" view — and addresses can return to the list automatically if the underlying issue (a typo'd domain, an aggressive recipient server) is unresolved.

Events, not queries.

Pulling a suppression list with a nightly cron job is yesterday's architecture. Modern systems are event-driven, and the suppression list is downstream of a small number of webhook event types.

Every major provider ships webhook events for the lifecycle of a message: sent, delivered, opened, clicked, bounced, complained, unsubscribed. The suppression list listens to the last three.

The shape of the integration is almost always the same. Your sending provider POSTs a JSON payload to an endpoint you own. The endpoint validates the signature, parses the event type, and writes to a suppression store — typically a row in Postgres or a row in DynamoDB, with the email address as the primary key and the reason as a column.

Three details separate competent implementations from fragile ones. First, idempotency: providers retry webhooks on failure, sometimes aggressively, so your handler must tolerate replays. Second, signature verification: every provider signs webhook payloads, and skipping verification means anyone on the internet can pollute your suppression list. Third, backpressure: during an incident — say, a misfired campaign generates 50,000 complaints — your handler should queue, not synchronously write.

The mature pattern is to enqueue every webhook event into a durable queue (SQS, BullMQ, your favorite), then have a worker pool drain the queue into your suppression store. This decouples the webhook handler's reliability from the database's reliability, and gives you a natural place to replay events when something goes wrong.

A common mistake

Don't bypass your suppression list "just this once."

Every operational team eventually faces the request: "We need to email this one VIP who's on the suppression list." Resist it. The reason they're suppressed almost always survives whatever justification accompanies the request — and the one-off send is exactly the kind of action that turns into a compliance incident.

Hygiene is a habit, not a project.

The single best predictor of long-term deliverability is not the size of your sending list — it is the engagement quality of your list. A 200,000-address list with 8% open rates is worth less than a 30,000-address list with 35% opens, both to you and to the mailbox providers grading your reputation. The suppression list is one of the two levers that controls engagement quality (the other is acquisition).

The sunset policy

The standard operating procedure in 2026 looks like this. A contact who has not opened or clicked anything in 90 to 120 days enters the "sunset phase." You send one final email — a clear, generous "we'd love to keep in touch, here's why" message. If they don't engage within seven days, you suppress them. The math is brutal but honest: a recipient who hasn't engaged in four months will not, statistically, engage in the fifth.

Suppress role-based addresses upfront

Addresses like info@, support@, sales@, and admin@ are read by teams, not individuals. Their engagement rates are predictably low and their complaint rates are predictably high. Most operators suppress them at the acquisition layer — before they ever reach the sending list.

Clean quarterly, not annually

A quarterly cadence is the right rhythm for most senders. More often than that and you're spending engineering time on something that hasn't decayed yet; less often and the rot has already cost you reputation. Run a verification pass, reconcile your suppression list with your CRM, and audit any addresses that have somehow re-entered the sending list after being suppressed.

Double opt-in for acquisition

If you're not yet on double opt-in, this is the single highest-ROI change you can make. It eliminates an enormous category of suppression-bound addresses — typos, bots, malicious sign-ups — before they ever touch your sending infrastructure. The conversion-rate concession is real (5–10% in our reading of the field), and worth it.

The operator's checklist.

If you read nothing else, read this. Twelve items, ordered by leverage. Work top-down.

  1. One suppression list, globally. Every product line, every campaign, every tool writes to the same store.
  2. Webhook-driven, not batch. Bounces and complaints flow into the list within seconds of the event.
  3. Verify webhook signatures. Anyone can POST to your endpoint; only your provider can sign it.
  4. Idempotent writes. Provider retries should never produce duplicates or audit-log noise.
  5. Capture all three unsubscribe vectors. Click-through, List-Unsubscribe header, and inbound replies.
  6. Suppress role accounts at acquisition. info@, sales@, support@, and friends never make it onto the sending list.
  7. Five-strike soft-bounce rule. Consecutive soft failures graduate to suppression; reset on any success.
  8. Sunset at 90–120 days. One re-engagement attempt, then suppression.
  9. Default to the strictest regulation. Treat every contact as if they're in the EU, even if they're not.
  10. Audit log every change. Manual additions, deletions, and provider-driven events all leave a trail.
  11. No one-off bypasses. The suppression list is non-negotiable; carve-outs are how teams end up in a settlement.
  12. Review quarterly. Reconcile your suppression list against your CRM and verify nothing has leaked back.
The Verdict

The suppression list is the cheapest possible insurance policy against the slow collapse of sender reputation — and the highest-leverage thing a deliverability team can fix. Treat it as infrastructure, not a checkbox.

— End of Report —

Sources & Further Reading