Skip to content
YourMail

Deliverability guide

Bounce handling and suppression lists

Why use this

A suppression list is the record of addresses you must stop sending to. It is the single most important piece of deliverability machinery, because mailbox providers judge you on the ratio of bad sends to good ones, and re-sending to a known-dead address is the purest form of bad send there is.

For example

A customer closes their work account. Your billing job keeps emailing it every month for a year, every send bounces, and your bounce rate — not theirs — is what the receiving providers are measuring.

Hard bounces and soft bounces

A hard bounce is permanent. The mailbox does not exist, the domain does not resolve, the recipient is blocked. Retrying achieves nothing except telling the receiver you are not paying attention. Hard bounces suppress the address immediately.

A soft bounce is temporary — mailbox full, server unavailable, greylisting. It may well succeed later, so it does not suppress. What it does do is warrant attention if it repeats for the same address over weeks, at which point the mailbox is abandoned rather than busy.

A complaint is neither: the message arrived, and the recipient marked it as spam. It suppresses immediately and permanently. Complaints are more damaging than bounces, because a bounce says the address is wrong while a complaint says the mail is unwanted.

The distinction reaches your application. A failed message carries the bounce type, sub-type and the receiving server's diagnostic code, so "why did this fail" has an answer in the API rather than only in a log somewhere.

Why this cannot be a to-do

Bounce handling is the item that gets deferred, because on day one every address is fresh and nothing bounces. It becomes urgent about six months later, when the accumulated dead addresses in your users table are large enough to move your rate, and at that point the damage is already in the reputation the receivers hold for you.

On this platform it is not optional and not yours to build. Hard bounces and complaints are written to your suppression list as the events arrive, and the next send to that address is refused with a 422 that names the reason:

{
  "error": {
    "type": "suppressed_recipient",
    "message": "Recipient(s) suppressed. Remove an address at https://yourmail.dev/metrics?tab=deliverability, or with DELETE /v1/suppressions/{address} (full-scope key). Blocked: old@example.com (hard bounced).",
    "docs_url": "https://yourmail.dev/docs/errors#suppressed_recipient"
  }
}

The refusal is deliberately loud. Silently dropping the message would protect the sending reputation just as well and leave you believing the mail went out.

The four reasons, and what each one blocks

hard_bounce, complaint and manual block everything. unsubscribe blocks bulk sends only, and transactional mail to that address keeps sending — someone who opted out of a newsletter has not asked to be locked out of their own account. The reasoning is set out in the one-click unsubscribe guide.

If a hard reason later arrives for an address that had only unsubscribed, the existing row is upgraded rather than duplicated, and the block widens to everything.

Migrating from another provider

This is the step that gets missed, and it is expensive. Your existing provider has been suppressing bounced addresses for years. Move to a new one without bringing that list and you begin by mailing every dead address you have ever collected — a burst of hard bounces from a brand new sending domain, which is the worst possible first impression to make on a receiving provider.

Export the list and import it before the first send.

// Migrating in? Import the old provider's list BEFORE the first send.
// One address at a time, via the SDK:
for (const address of exported) {
  await yourmail.createSuppression(address);
}

An import is recorded with the reason manual — the API does not take a reason, and that is the right default here: it blocks everything, which is what you want for an address another system already judged undeliverable, and it does not claim to know whether the original cause was a bounce or a complaint.

What to do with your own database

Suppression protects your sending. It does not clean your users table, and only you can do that.

Subscribe to the bounce and complaint webhooks and mark the address in your own records. Then stop generating the send at source — a suppressed address that your application still tries to mail every month is a refused API call every month, and the useful signal (this customer has no reachable address) never reaches the part of your product that could ask them for a new one.

For the rate itself and what receivers do about it, see the bounce rate guide.