Deliverability guide
One-click unsubscribe (RFC 8058)
Why use this
RFC 8058 defines the unsubscribe button mail clients render themselves, at the top of the message, next to the sender name. Gmail and Yahoo both require it on bulk mail. It is also the cheapest deliverability win there is, because every use of it is a complaint that did not happen.
For example
A recipient cannot find your unsubscribe link, so they hit the spam button instead. That single complaint counts against your sending reputation in a way an unsubscribe never would.
The two headers
One-click needs both. The first has existed for years and can hold a mailto or an HTTPS URL; the second is what RFC 8058 adds, and it is the part that tells the mail client it may act without sending the user anywhere.
List-Unsubscribe: <https://<your-deployment>.convex.site/u/eyJ0Ijoi...>
List-Unsubscribe-Post: List-Unsubscribe=One-ClickWith only the first header, a client shows a link. With both, it shows its own button and POSTs List-Unsubscribe=One-Click to the URL when the user presses it. The recipient never leaves their inbox, which is exactly why they use it rather than the spam button.
Why a GET must never unsubscribe anyone
This is the rule that catches hand-rolled implementations, and it fails silently and expensively.
Mail scanners, link checkers and corporate security gateways fetch every URL in a message before the recipient ever opens it. If your unsubscribe endpoint acts on GET, a scanner unsubscribes your customer the moment the message arrives. Nobody complains, because nobody knows — your engaged users simply stop receiving mail and your list quietly decays.
The endpoint here follows the rule strictly: GET /u/:token renders a confirmation page and unsubscribes nobody. POST records it. The token is an HMAC scoped to the recipient and the account, so it cannot be edited into someone else's address.
Turning it on
One flag. Both headers are generated, signed and injected, the endpoint is hosted, and the result is enforced on future sends.
await yourmail.send({
from: "Acme <news@mail.acme.com>",
to: "customer@example.com",
subject: "Your monthly summary",
html: "<p>Here is what happened this month.</p>",
// The whole opt-in. Both headers are generated, signed and injected;
// the unsubscribe endpoint is hosted and the result enforced.
bulk: true,
});bulk: true means "this message is not transactional". It must be single-recipient — an unsubscribe applies to a person, and a header covering three of them cannot express which one pressed the button. If you set your own List-Unsubscribe header, yours is left alone.
The scoping rule that matters
An unsubscribe suppression blocks bulk sends only. Transactional mail to that address keeps sending.
This is deliberate and it is the distinction most implementations get wrong. Someone who unsubscribes from your monthly summary has not asked to stop receiving password resets, and honouring it as a blanket block means the next time they try to log in, they cannot — you have locked a customer out of their account because they opted out of a newsletter.
The other suppression reasons behave differently on purpose. A hard bounce, a complaint, or an address you suppressed by hand blocks everything, because in those cases the address is either undeliverable or actively hostile to your mail. If a hard reason later arrives for an address that had only unsubscribed, the row is upgraded and the block widens.
What you should still build
The headers are handled; your preferences are not. If you send more than one kind of bulk mail, a recipient who unsubscribes here is opted out of all of it, and a preference centre linked from the message body is how you offer "less, not none".
Keep the visible unsubscribe link in the body too. One-click is a requirement, not a replacement — plenty of clients render neither header, and a message with no visible way out is the one that gets reported.
Subscribe to the webhook events if your own database needs to know, and read the bulk sender requirements for the rest of what Gmail and Yahoo expect alongside this.