Suppressions
Automatic protection for your sender reputation.
The suppression list protects your sender reputation — and your SES account — by making sure you never repeatedly mail an address that hard-bounced or marked you as spam.
How addresses get suppressed
- Hard bounce — the receiving server permanently rejected the address.
- Complaint — the recipient marked a message as spam.
- Unsubscribe — the recipient opted out of all marketing email, through
the one-click header or the hosted preference page. On an email sent with
a topic, the header opts out of that topic only, and the page's
Unsubscribe from all is the global opt-out. Besides flagging the
contact, the opt-out is retained here so it survives deleting and
re-importing the contact; only an explicit
PATCH /contacts/{id}withunsubscribed: falseclears it. Unlike the other origins it covers marketing mail only (see below). - Manual — you added the address, in the dashboard or via the API.
Bounces and complaints arrive as SES events and suppress the address automatically. Suppressions are per team.
When a receiving server answers that a mailbox is inactive, the address is
suppressed at once. When it answers twice, a day to 30 days apart, that a
contact's mailbox is full, the address is suppressed too. Both are listed as
hard bounces (origin: "bounce") but don't count toward your bounce rate.
What suppression does
- Transactional sends (
POST /emailsand the SMTP relay, without atopic_id): recipients suppressed for a bounce, a complaint or a manual entry are stripped fromto/cc/bcc. If everytorecipient is suppressed, the send is rejected with422 all_recipients_suppressed(messageAll recipients are suppressed; the SMTP relay answers550with that message). An unsubscribe entry does not apply here: someone who opted out of marketing still receives password resets, receipts and other account mail — the same meaning Resend givesunsubscribed("unsubscribed from all Broadcasts"). - Topic sends (
POST /emailswith atopic_id) and broadcasts: every entry applies, unsubscribes included, plus the recipient's opt-out of that topic; broadcasts skip such contacts during fan-out.
A list imported with origin: "unsubscribe" therefore blocks topic sends and
broadcasts only. Import with manual to block every send.
When the suppression list alone leaves a POST /emails with no to
recipient, with or without a topic_id, a metadata-only email with status
suppressed is kept (no content, not counted toward your quota), and each
retry with the same Idempotency-Key keeps another. Each email kept this way
fires email.suppressed.
Nothing is kept when topic opt-outs empty to, for a batch item, or on the
SMTP relay.
An email that waits before it goes out, such as a scheduled one, is checked
again when it does. Recipients suppressed in the meantime are dropped, and on
a topic send or a broadcast, so are those who opted out of the topic. If no
to recipient is left, the email ends suppressed without being sent and
fires email.suppressed.
Every change to the list publishes a suppression.added or
suppression.removed webhook event.
Reviewing and removing
The dashboard lists every suppressed address with the reason and date. You can remove an address to allow sending again — do this only when you know the cause is fixed (e.g. a mailbox that existed all along but was rejected by a misconfigured server). Re-suppression is automatic on the next bounce or complaint.
SES's own suppression list
Besides this per-team list, Amazon SES keeps an account-level suppression
list per region, shared by every team on the instance. The setup wizard sets
it to bounces only: a hard-bounced mailbox is dead for everyone, so SES may
refuse it account-wide, but a spam report is about one sender's mail and
stays on that team's list here. A send SES refuses because of its own list
shows as a permanent bounce with the subtype OnAccountSuppressionList, and
only the SES console can remove such an entry. SES never tried to deliver
such a send, so it doesn't count as a hard bounce in your rates, the same as
in SES's own bounce rate. The same goes for any bounce or complaint SES
reports about mail it didn't attempt.
API
The /suppressions endpoints mirror Resend's suppressions surface, so the
Resend SDK's suppressions.* methods work as-is. Each entry reads as
{ id, email, origin, source_id, created_at }, where origin is bounce,
complaint, manual or unsubscribe and source_id is the email whose
bounce or complaint created it.
GET /suppressions?origin=bounce— keyset-paginated list, newest first, optionally filtered by origin.GET /suppressions/{id}andDELETE /suppressions/{id}— the path segment is the suppression id or the email address.POST /suppressionswith{ "email": "...", "origin": "manual" }— blocks the address.originis optional (bounce,complaint,manualorunsubscribe, defaultmanual) and lets an import from another provider keep its bounce and complaint history, or a migrated opt-out list keep its reason. Idempotent: an address already suppressed for any reason keeps its entry and origin, and its existing id is returned.POST /suppressions/batch/addwith{ "emails": [...], "origin": "bounce" }andPOST /suppressions/batch/removewith{ "emails": [...] }or{ "ids": [...] }— up to 1000 entries per call (Resend caps at 100). Add applies the one optionaloriginto every row it creates and returns one id per distinct address in input order; remove lists only the rows actually removed.
Three MillionSend specifics: origin on add is accepted (Resend's SDK type
has no such field, so pass it through a raw request), origin: "unsubscribe"
is a superset value Resend does not have (its SDK's type union lacks it) and
behaves like a one-click opt-out — only an explicit re-subscribe of the
contact clears it — and an address whose personal
data was erased (GDPR/LGPD) keeps blocking sends but is hidden from the list
and from lookups by email — it is reachable by id only, reading
"[erased]" as its email, and re-suppressing the address returns that id
without restoring it.
Why this matters
SES tracks bounce and complaint rates and pauses senders that cross its thresholds. MillionSend's metrics page tracks your rates against those thresholds, and broadcast sending is blocked automatically when a rate crosses the pause line.
On Cloud, sending volume is governed by your plan's limits. Self-hosted, the limits are your own AWS SES account's quotas and reputation — crossing SES's thresholds can pause the whole account, which is exactly what suppression protects you from.