Suppressions
Every workspace owns a suppression list: a set of email addresses Bird will not deliver to. Hard bounces, spam complaints, and unsubscribes land on it automatically, and you can add addresses yourself via the API. The list exists to protect your sender reputation: repeatedly mailing addresses that bounce or report spam is the fastest way to get your domain blocked by mailbox providers, so Bird stops those sends before they leave the platform.
Suppression is a recipient-level concept. A single send can have some recipients delivered and others suppressed; suppressed recipients are visibly rejected rather than silently dropped, so you can always see exactly which addresses were blocked and why.
You can browse and manage the list on the Suppressions page (Email → Suppressions) in the dashboard, or use the suppressions API.

The four reasons and what they block
Each suppression record has a reason explaining why the address is on the list, and an applies_to policy controlling which categories it blocks:
| Reason | applies_to | Marketing category | Transactional category |
|---|---|---|---|
| hard_bounce | all | Blocked | Blocked |
| complaint | non_transactional | Blocked | Allowed |
| unsubscribe | non_transactional | Blocked | Allowed |
| manual | all | Blocked | Blocked |
The split follows from what each reason means:
- hard_bounce: the address does not exist. Sending is pointless in any category, so it blocks everything (applies_to: all).
- complaint and unsubscribe: preference signals. Someone who reported your newsletter as spam may still legitimately need a password reset or an order confirmation, so these block only non-transactional sends (applies_to: non_transactional). Sends in the marketing category are blocked; sends in the transactional category get through.
- manual: a deliberate decision by you or your team. Bird does not second-guess it: manual suppressions block every category, including transactional.
An address can hold one record per reason, so a hard bounce and an earlier unsubscribe coexist as separate records, and delivery stays blocked while any blocking record remains. reason, origin, and applies_to are open vocabularies that grow over time: treat an unknown reason or origin as informational, and an unknown applies_to as blocking at least non-transactional mail.
How addresses get added automatically
Bird's delivery pipeline adds suppressions in response to recipient signals, so you never have to act on a bounce or complaint yourself:
| Trigger | Resulting suppression |
|---|---|
| Hard bounce (email.bounced) | reason: hard_bounce, origin: bounce_event, applies_to: all |
| Out-of-band hard bounce (email.out_of_band_bounce) | reason: hard_bounce, origin: bounce_event, applies_to: all |
| Spam complaint (email.complained) | reason: complaint, origin: complaint_event, applies_to: non_transactional |
| In-body link unsubscribe (email.unsubscribed) | reason: unsubscribe, origin: unsubscribe_event, applies_to: non_transactional |
| One-click unsubscribe (email.list_unsubscribed) | reason: unsubscribe, origin: unsubscribe_event, applies_to: non_transactional |
Only a hard-class bounce suppresses. Soft, block, admin, and undetermined bounces leave the address sendable; the classification table shows which bounce_class values count as hard. There is no setting behind any of this: hard bounces, complaints, and unsubscribes always suppress.
What does not suppress matters as much:
- Soft bounces and deferrals (email.deferred, or email.bounced with bounce_type: "soft"): transient failures like a full mailbox. Bird retries; the address stays sendable.
- Send-side rejections: generation failures and policy rejections are problems with the send, not with the recipient's address. They produce email.rejected events but never a suppression.
Auto-suppression is idempotent and first-cause-stable: when an address is already suppressed for the same scope and reason, later matching events leave the original record unchanged, including its source_email_id, source_recipient_id, and created_at. Those two source fields link an automatic suppression back to the exact message and recipient that caused it, which answers the support question "why did this person stop getting our email"; they are null on manual additions.
Every addition to the list, automatic or manual, fires an email_suppression.created event to your webhook endpoint. Its payload carries the suppression_id, the suppressed email, the reason, and the workspace_id, so your own system can mirror the list without polling:
Exemple de code
{
"type": "email_suppression.created",
"timestamp": "2026-07-23T14:52:03.192524705Z",
"data": {
"email": "user@example.com",
"reason": "manual",
"suppression_id": "sup_01ky7qckqrf06r38g49b9kxdbc",
"workspace_id": "ws_01ky7m21hjffh9s8kq76gyb1xf"
}
}Managing suppressions via the API
The API supports single-record add, list, lookup, and delete. Email addresses are normalized to lowercase before storage and lookup, and they never appear in URL paths: addresses are PII, and keeping them out of paths keeps them out of access logs. To find a record for an address, filter the list with ?email= instead.
Add an address
Exemple de code
curl -X POST https://us1.platform.bird.com/v1/email/suppressions \
-H "Authorization: Bearer $BIRD_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "email": "user@example.com" }'Manual additions always get reason: manual and applies_to: all; they block every category. The call is idempotent: a new suppression returns 201 Created, and if the address is already manually suppressed you get 200 OK with the existing record instead of a conflict error. Either way the body is the suppression object:
Exemple de code
{
"applies_to": "all",
"created_at": "2026-07-23T14:52:03.192524705Z",
"email": "user@example.com",
"id": "sup_01ky7qckqrf06r38g49b9kxdbc",
"origin": "api_key",
"reason": "manual",
"scope": {
"id": "ws_01ky7m21hjffh9s8kq76gyb1xf",
"type": "workspace"
}
}The origin field records how the suppression came to exist: api_key or user for manual additions (depending on whether the caller authenticated with an API key or a dashboard session), bounce_event, complaint_event, or unsubscribe_event for additions driven by delivery events, and unsubscribe_link when the recipient opted out through the Bird-hosted unsubscribe page.
List and look up
Exemple de code
curl "https://us1.platform.bird.com/v1/email/suppressions?limit=25" \
-H "Authorization: Bearer $BIRD_API_KEY"The list is cursor-paginated and filterable by reason. To check whether a specific address is suppressed, pass it as the email query parameter:
Exemple de code
curl "https://us1.platform.bird.com/v1/email/suppressions?email=user@example.com" \
-H "Authorization: Bearer $BIRD_API_KEY"An empty data array means the address is not suppressed. The same address can return multiple records when different reasons apply, for example a hard_bounce and an unsubscribe. The email filter matches by prefix: a complete address returns that address's records, and a partial value such as alice returns every suppressed address starting with it. Matching is case-insensitive.
Remove an address
Exemple de code
curl -X DELETE https://us1.platform.bird.com/v1/email/suppressions/sup_01ky7qckqrf06r38g49b9kxdbc \
-H "Authorization: Bearer $BIRD_API_KEY"Returns 204 No Content. This is a hard delete: the record is removed entirely, Bird retains nothing, and the address is immediately sendable again. An address suppressed for several reasons has one record per reason, so delete each blocking record to re-enable delivery. To delete by address, look up the ID with ?email= first, then delete by ID. Be deliberate about removing hard_bounce records: if the address still doesn't exist, the next send bounces and re-suppresses it.
What happens when you send to a suppressed address
Suppressed recipients are rejected, not silently dropped. When a send includes a suppressed address:
- The recipient still gets a recipient_id and appears in the message's recipient list with status rejected.
- An email.rejected event is recorded for that recipient with rejection_reason: "recipient_suppressed", visible in the events API and your webhooks.
- The message itself is still accepted (202) and delivery proceeds for the remaining recipients.
This holds even when every recipient of a send is suppressed: the send is still accepted (202) and a message ID is created, and each suppressed recipient surfaces as rejected with rejection_reason: "recipient_suppressed". Suppression is evaluated during processing, not at accept time, so an all-suppressed send never returns an up-front 422.
This mirrors how other providers expose suppression (SendGrid's dropped, Postmark's suppressed bounces), so you can audit exactly which addresses were blocked instead of inferring it from missing deliveries.
Testing with the sandbox
The testing sandbox gives you a deterministic way to exercise suppression handling: sending to suppressed@messagebird.dev short-circuits as if the address were on your suppression list, so the recipient is rejected with rejection_reason: "recipient_suppressed" and never reaches delivery. Sandbox bounce and complaint addresses (bounce@messagebird.dev, complaint@messagebird.dev) simulate their outcomes through the real event pipeline but do not write to your suppression list, so the same test addresses stay reusable across runs.
Next steps
- Categories: transactional vs marketing and how the category interacts with suppression policy
- Unsubscribe links: how opt-outs reach the suppression list with reason unsubscribe
- Events and webhooks: the email.rejected payload and the lifecycle events that drive auto-suppression
- Testing sandbox: magic addresses for simulating every delivery outcome
- API reference: Suppressions: full request and response schemas