Unsubscribe links
Marketing mail needs a way for recipients to opt out, and bulk senders are now required by Gmail and Yahoo to offer one-click unsubscribe. There are two paths a recipient can take — the unsubscribe button the mailbox provider renders from your List-Unsubscribe header, and a link you put in the message body — and Bird reports both as webhook events and acts on them by suppressing the address. This guide covers wiring up both, the events Bird fires, and how suppression closes the loop.
Set the category first
Unsubscribes only mean something for mail a recipient can opt out of, so the first step isn't a header — it's the category. Send marketing, newsletter, and announcement mail with category: "marketing". That is what makes an unsubscribe stick: a recipient who opts out is suppressed with reason unsubscribe, which blocks marketing sends but still lets transactional mail (password resets, receipts) through. Leave the category at its transactional default on a newsletter and the unsubscribe is recorded but never stops the next send.
One-click List-Unsubscribe (RFC 8058)
The one-click mechanism is the unsubscribe button mailbox providers render in their own UI, driven by two headers on your message:
Code example
List-Unsubscribe: <https://yourapp.com/unsubscribe?token=abc123>, <mailto:unsubscribe@yourdomain.com?subject=unsubscribe>
List-Unsubscribe-Post: List-Unsubscribe=One-ClickList-Unsubscribe lists where to send the opt-out — an HTTPS URL, a mailto:, or both. List-Unsubscribe-Post: List-Unsubscribe=One-Click is what makes it one-click (RFC 8058): the provider sends a plain POST to your HTTPS URL when the recipient taps unsubscribe, with no confirmation page. Your endpoint must accept that unauthenticated POST and record the opt-out without requiring the recipient to log in or confirm — that is the contract Gmail and Yahoo check for.
How the headers get set depends on how you send:
- Broadcasts sent from the Bird dashboard include the List-Unsubscribe and List-Unsubscribe-Post headers automatically — you don't add them, and they can't be overridden.
- API sends (POST /v1/email/messages) don't get them added for you. Set them yourself with the headers field:
Code example
{
"from": "news@yourdomain.com",
"to": ["subscriber@example.com"],
"subject": "June product update",
"html": "<p>What's new this month...</p>",
"category": "marketing",
"headers": {
"List-Unsubscribe": "<https://yourapp.com/unsubscribe?token=abc123>, <mailto:unsubscribe@yourdomain.com>",
"List-Unsubscribe-Post": "List-Unsubscribe=One-Click"
}
}When a recipient uses the provider's button, Bird fires an email.list_unsubscribed event.
An in-body unsubscribe link
The other path is a plain unsubscribe link in your email — the footer link that satisfies the visible-opt-out expectation for marketing mail. Bird doesn't generate this link or host the opt-out page for you: point it at your own unsubscribe handler, the same endpoint your one-click List-Unsubscribe URL targets.
Code example
<p>
You're receiving this because you subscribed at yourdomain.com.
<a href="https://yourapp.com/unsubscribe?token=abc123">Unsubscribe</a>.
</p>When a recipient unsubscribes through this link, Bird fires an email.unsubscribed event — distinct from email.list_unsubscribed so you can tell a footer-link opt-out apart from a provider-button one.
The webhook events
Bird delivers both unsubscribe events to your webhook endpoint in the standard event envelope. Subscribe to them the same way you subscribe to any other email event — there is no separate unsubscribe webhook to configure:
| Event | Fires when |
|---|---|
| email.unsubscribed | The recipient clicked the unsubscribe link in your message body. |
| email.list_unsubscribed | The recipient used the mailbox provider's one-click button (List-Unsubscribe). |
Both carry the identity base every email event carries — email_id, recipient_id, workspace_id, the recipient address and its recipient_role, plus the tags and metadata from the original send — so you can reconcile the opt-out against your own records without an extra lookup:
Code example
{
"type": "email.list_unsubscribed",
"timestamp": "2026-06-10T14:30:00Z",
"data": {
"email_id": "em_01krdgeqcxet5s7t44vh8rt9mg",
"recipient_id": "er_01krdgeqcxet5s7t44vh8rt9mg",
"workspace_id": "ws_01krdgeqcxet5s7t44vh8rt9mg",
"recipient": "subscriber@example.com",
"recipient_role": "to",
"tags": [{ "name": "category", "value": "newsletter" }],
"metadata": { "list_id": "weekly-digest" }
}
}Use these events to update preferences in your own system — flip the subscription off, log the opt-out, stop including the address in your sends. The field set and the rest of the email event catalog are in the events reference.
Suppression closes the loop
You don't have to act on the webhook to stop Bird from mailing an unsubscribed recipient again — Bird does it automatically. Both email.unsubscribed and email.list_unsubscribed add the address to your workspace suppression list with reason unsubscribe and applies_to: non_transactional:
- Future marketing sends to the address are rejected up front with email.rejected and rejection_reason: recipient_suppressed.
- Future transactional sends still go through — an unsubscribe is a preference about marketing mail, not a dead address.
That makes the webhook events a mirror for your own database rather than the thing that protects your reputation; Bird already blocks the send. If you ever need to inspect or undo an unsubscribe, the suppression record carries source_email_id and source_recipient_id linking back to the message that caused it, and you can remove it through the suppressions API.
Next steps
- Categories — why marketing is what makes an unsubscribe take effect
- Suppressions — the list unsubscribes land on, and how to manage it
- Email events — the full email.unsubscribed / email.list_unsubscribed payloads and the rest of the catalog
- Gmail & Yahoo requirements — where one-click unsubscribe is required for bulk senders
- Webhooks — subscribing an endpoint, signature verification, and retries