One-click unsubscribe (RFC 8058) for developers
How to implement List-Unsubscribe and List-Unsubscribe-Post per RFC 8058: the HTTPS POST endpoint, DKIM coverage, the mailto fallback and testing.
By Exmoor Software6 min read
When a marketing message carries the right headers, mailbox providers such as Gmail can show an Unsubscribe button next to the sender’s name. When the recipient uses it, the provider’s servers — not the recipient’s browser — send an HTTPS POST request to your server, and you remove the address from the list. No landing page, no login, no “are you sure?”.
The mechanism is defined in RFC 8058, on top of the older List-Unsubscribe header from RFC 2369. Gmail and Yahoo require it from bulk senders for marketing and subscribed messages (see the sender requirements checklist). This guide covers what you need to build.
Why a POST and not just a link
List-Unsubscribe could carry HTTPS links long before RFC 8058. The problem, as the RFC explains, is that anti-spam software often fetches URLs in headers automatically, and your server can’t tell an automated request from a person clicking. Senders protected themselves with confirmation pages, which an automated system won’t click and which make unsubscribing more than one click.
RFC 8058 adds a separate signal: a POST request with a fixed body. A scanner that fetches a URL sends a GET; a mailbox provider acting on the user’s request sends the POST. Your server can treat the POST as a definite unsubscribe.
The two headers
List-Unsubscribe: <https://example.com/unsubscribe/7f3c9a1e2b>, <mailto:unsubscribe@example.com?subject=unsubscribe-7f3c9a1e2b>
List-Unsubscribe-Post: List-Unsubscribe=One-Click
The rules from RFC 8058, section 3.1:
- The message contains one
List-Unsubscribeand oneList-Unsubscribe-Postheader field. List-Unsubscribemust contain one HTTPS URI. It may also contain non-HTTP URIs such asmailto:.List-Unsubscribe-Postmust contain exactly the pairList-Unsubscribe=One-Click.- The URI must carry enough information to identify the recipient and the list. The POST has no other arguments, and there is nobody to ask.
- The URI should contain an opaque, hard-to-forge component, and your server should verify it. Otherwise anyone could unsubscribe your subscribers by posting to guessed URLs, or put your URLs into spam to provoke unsubscriptions.
RFC 2369 syntax applies: each URI in angle brackets, several URIs separated by commas, in order of preference from left to right.
For the token, use a random identifier stored with the subscription, or an HMAC over the subscriber and list IDs. Don’t put a plain email address or a sequential ID in the URL.
What the mailbox provider sends
The request goes to the HTTPS URI from the header, with the key/value pair as the body. This is the example from RFC 8058, section 8.1, with our token:
POST /unsubscribe/7f3c9a1e2b HTTP/1.1
Host: example.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 26
List-Unsubscribe=One-Click
- The body should be sent as
multipart/form-dataand may be sent asapplication/x-www-form-urlencoded. Accept both. - The request must not include cookies, HTTP authentication or any other context. Your endpoint can’t rely on a session.
- The receiver must not send the POST without the user’s consent; how it obtains that consent is up to the provider.
Building the endpoint
// POST https://example.com/unsubscribe/<token>
export async function handleOneClick(token: string): Promise<Response> {
const sub = await findSubscriptionByToken(token); // only tokens you issued
if (!sub) return new Response(null, { status: 404 });
await suppress(sub.subscriberId, sub.listId); // idempotent: already removed = success
return new Response(null, { status: 200 }); // no redirect, no confirmation page
}
The rules behind those few lines:
- The POST itself unsubscribes. No confirmation step. RFC 8058 was written for cases where nobody visits your website, for example when a provider combines “report spam” with an unsubscribe.
- No redirects. The sender must not return an HTTPS redirect, because redirected POSTs have historically been unreliable. The URL in the header must be the final one: not a click-tracking link, not an
http://URL that redirects tohttps://, not a path your framework redirects to add a trailing slash. - No CSRF token, no login. The request comes from the provider’s servers, without cookies. If your framework enforces CSRF protection on POST requests, exempt this route; the hard-to-forge token in the URL does that job.
- Make it idempotent. The same request can arrive more than once: a user clicks twice, a request is retried. Treat an address that is already unsubscribed as success. A repeated request must never produce an error or a resubscription.
- Unsubscribe from the list the URI identifies. Google notes that one-click removes the recipient only from the mailing list associated with the message, so separate lists can stay separate.
- Act quickly. Yahoo requires unsubscribes to be honored within 2 days; Google recommends 48 hours. Remove the address from queued and scheduled sends too, not only from the next import.
- Return a success status. RFC 8058 doesn’t define the response, and nobody reads it;
200or204is enough.
The same URL can also serve people who open it in a browser: RFC 8058 notes that the POST target is the same as for a manual unsubscribe, so one handler can serve both. For a GET, show a page with an unsubscribe button (a form that POSTs to the same URL). Don’t unsubscribe on a GET alone — that is exactly what link scanners trigger.
DKIM must cover both headers
RFC 8058, section 4: the message needs at least one valid DKIM signature, and both List-Unsubscribe and List-Unsubscribe-Post must be listed in the h= tag of that signature. Without it, the receiver should not offer one-click unsubscribe for the message.
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=example.com; s=s2026;
h=from:to:subject:date:message-id:list-unsubscribe:list-unsubscribe-post;
bh=...; b=...
The usual failure is ordering: the headers are added after signing — by a relay, a filter on your mail server or a second service — so they are present but not covered. Add them in your application or in the email service’s API call, before the message is signed. For the Gmail and Yahoo requirements, the signature should also be aligned with your From domain. More about signing in how DKIM works.
The mailto fallback
RFC 8058 allows extra non-HTTP URIs in List-Unsubscribe, and mailto: is the original form from RFC 2369. Some mail clients still use it.
- Gmail supports
mailto:but doesn’t count it as one-click; you need the HTTPS URI. - Yahoo highly recommends the POST method and accepts
mailto:.
If you include a mailto:, process those messages automatically and encode the same token, either in the subject as above or in the address (for example unsubscribe+7f3c9a1e2b@example.com, if your mail system supports plus addressing). An unsubscribe address that delivers into an unread inbox is worse than none.
Which messages need it
Marketing and subscribed messages. Google and Yahoo both exclude transactional messages such as password resets and order confirmations. Both also want a visible unsubscribe link in the body; that link may lead to a preferences page and doesn’t have to be one-click. For mail sent from your own code, see transactional email from your app.
Testing
-
Check the headers. Send a campaign message to a mailbox you control, open the original (in Gmail: ⋮ → “Show original”) and paste the headers into the email header analyzer. The Unsubscribe panel lists the URIs and shows “one-click (RFC 8058)” when
List-Unsubscribe-Postis correct. Under DKIM signatures, the Signed headers list must includelist-unsubscribeandlist-unsubscribe-post, and the authentication results must showdkim=pass. -
Simulate the provider’s request in both encodings:
curl -i -X POST -H 'Content-Type: application/x-www-form-urlencoded' \ --data 'List-Unsubscribe=One-Click' 'https://example.com/unsubscribe/7f3c9a1e2b' curl -i -F 'List-Unsubscribe=One-Click' 'https://example.com/unsubscribe/7f3c9a1e2b'Expect a
2xxstatus and noLocationheader. Send it again: still2xx. A modified token should be rejected. -
Check the result. The address is unsubscribed from that list and excluded from the next send.
-
Open the URL in a browser. You should see a page with a button, and nothing should change until you press it.
Don’t use the Gmail button as your test. Google shows it only for messages that pass its automated eligibility checks and meet its sender requirements, so a missing button doesn’t necessarily mean your headers are wrong.
Common mistakes
- The header URL goes through a click-tracking redirect.
- The endpoint answers POST with
405because only GET was implemented. - CSRF middleware rejects the request, often with
403. - The headers are added after DKIM signing.
- The URL contains the plain email address, so anyone can unsubscribe anyone.
- Only a
mailto:URI: some clients use it, but it doesn’t meet Gmail’s requirement. - Unsubscribes are applied only at the next weekly import.