RFC 8058 one-click unsubscribe is mandatory for bulk senders. The five implementation mistakes that fail compliance and how to test your endpoint properly.
What RFC 8058 Is, And Why It Exists
RFC 8058 was published in February 2018. It standardizes a mechanism that had existed informally for years: a one-click unsubscribe that does not force the recipient through a web confirmation flow.
The original List-Unsubscribe header (defined in RFC 2369, 1998) included a URL recipients could visit to unsubscribe. The problem was UX. Clicking the link opened a webpage. The webpage often required login, asked for a reason, presented a "are you sure?" confirmation, or simply broke. Recipients who wanted off a list took the path of least resistance and clicked the spam button instead.
This was bad for everyone. Senders accumulated complaint rates from people who actually just wanted to unsubscribe. Mailbox providers got noisy spam signal that did not reflect actual abuse. Recipients ended up with mail they did not want.
RFC 8058 solved it: a POST request to a specified URL, processed immediately, no UI in between. The mailbox provider intercepts the unsubscribe click in its own UI, sends the POST on behalf of the user, and the sender removes them from the list. The recipient never leaves the mail client.
For six years, it was a best practice. As of February 2024, Yahoo and Gmail require it for bulk senders. There is no longer an option to skip implementation.
The Headers, Exactly
Two headers must be present on every bulk message:
List-Unsubscribe can contain one or two values:
- An HTTPS URL (in angle brackets)
- A
mailto:address (also in angle brackets) - Both, comma-separated
If both are present, the order does not matter to the spec, but mailbox providers may prefer one. Gmail uses the HTTPS URL for one-click. Yahoo also prefers HTTPS. The mailto: is a fallback for older clients.
List-Unsubscribe-Post is the signal that announces RFC 8058 compliance. Its value is always exactly:
If this header is missing, mailbox providers fall back to legacy behavior they do not POST, the link is treated as a regular URL, and the user is sent through whatever flow your endpoint serves on a GET request. That is not RFC 8058 compliance, even if your List-Unsubscribe header is technically present.
What the Endpoint Has to Do
When a recipient clicks the unsubscribe button in Gmail's UI, Gmail sends:
1POST /unsubscribe?id=abc123 HTTP/1.1
2Host: example.com
3Content-Type: application/x-www-form-urlencoded
4Content-Length: 26
5
6List-Unsubscribe=One-ClickYour endpoint must:
- Process the unsubscribe immediately. No queuing for batch jobs that run nightly. The recipient is unsubscribed before the response is sent.
- Respond with HTTP 200. Any 2xx is acceptable. 3xx (redirects), 4xx, and 5xx all signal failure to the mailbox provider, even if the underlying unsubscribe technically succeeded.
- Not require authentication. The POST has no cookies, no session, no auth headers. Your endpoint must accept it anonymously based purely on the URL parameters.
- Not require a confirmation step. RFC 8058 explicitly forbids "are you sure?" pages. The POST itself is the confirmation.
That is the whole spec. Four requirements. Most senders fail at least one.
The Five Implementation Mistakes
These are the failures I see in audits when teams claim RFC 8058 compliance and Gmail's UI says otherwise.
Mistake 1: Endpoint Requires a Session Cookie
The most common failure mode. Your unsubscribe endpoint was originally built for users clicking a link in their email client, opening their browser, and following a flow. The flow was: arrive at unsubscribe page → check session → if logged in, unsubscribe; if not, prompt login.
Gmail's POST has no session. The endpoint sees an unauthenticated request, returns a redirect to the login page, and Gmail receives a 302. From Gmail's perspective, the unsubscribe failed. The user clicked. Nothing happened.
The fix:
Treat the URL parameters as the authorization. The id=abc123 (or whatever token format you use) is signed or otherwise unforgeable—it functions as a one-time bearer token. Process the POST based on that token alone. No session, no login.
If you are worried about replay attacks, sign the token with an expiration. Honor the unsubscribe regardless when the token is valid. The recipient you are trying to protect from accidental unsubscribes is also the recipient you are inconveniencing by requiring login.
Mistake 2: Endpoint Redirects to a Confirmation Page
This is the second most common failure and a particularly painful one because the team often thinks they did the right thing by adding a "you have been unsubscribed" page.
What the team built:
POST /unsubscribe → 302 redirect to /unsubscribe-confirmed
GET /unsubscribe-confirmed → "You have been unsubscribed. Click here to undo."What Gmail sees: a 302. Gmail does not follow redirects from this POST. The unsubscribe is recorded as failed.
The fix:
The POST handler returns 200 directly. No redirect. If you want to log the unsubscribe, do it server-side. If you want to provide a confirmation UX, do it via a separate flow on the GET request the user clicking the body link, not the header POST.
POST /unsubscribe → process unsubscribe → return 200 with empty body or short JSON
GET /unsubscribe → render the confirmation/undo UI for users who clicked the body linkThe same URL serves both. The HTTP method is the differentiator.
Mistake 3: Honoring Delay Exceeds 2 Days
The Yahoo/Gmail spec requires unsubscribes to be honored within 2 days. RFC 8058 itself does not specify a window, but the senders requirements do.
I have audited ESPs that queued unsubscribes for batch processing. The reasons varied database load, integration with downstream CRM, "real-time was too expensive." The common factor was a 24-72 hour delay between POST and actual list removal.
That breaks compliance. It also produces a worse problem: recipients who unsubscribe, receive another message the next day, and click the spam button. Their complaint contributes to your rate. Your batch job did not save you database load it cost you reputation.
The fix:
Process the unsubscribe in the request handler. If your downstream systems cannot handle real-time updates, write the unsubscribe to a fast suppression list (Redis, dedicated table, ESP suppression API) that your sending pipeline checks before every message. Sync with downstream systems asynchronously. The recipient is suppressed within milliseconds; the rest catches up later.
Mistake 4: The mailto: Address Bounces
Your List-Unsubscribe header has two values:
List-Unsubscribe: <https://example.com/unsubscribe?id=abc123>, <mailto:unsubscribe@example.com>Gmail uses the HTTPS endpoint. Older clients—including some mobile clients and many corporate email systems use the mailto:.
If unsubscribe@example.com is unmonitored, returns "no such mailbox," or routes to a black-hole inbox that nobody processes, the unsubscribe fails for that subset of recipients. Mailbox providers do not differentiate. To them, a fraction of your unsubscribes are silently dropping. Trust signals decline.
The fix:
Either monitor the mailto: address with a process that triggers unsubscribes, or remove it from the header and only publish the HTTPS URL. The latter is cleaner if you do not have a mail-processing pipeline already in place.
List-Unsubscribe: <https://example.com/unsubscribe?id=abc123>
List-Unsubscribe-Post: List-Unsubscribe=One-ClickA single HTTPS endpoint is fully compliant.
Mistake 5: Header URL Differs From Body Link
Senders often have two unsubscribe paths:
- The header URL:
/unsubscribe?id=abc123(used by mailbox provider POSTs) - The body link:
/email/unsubscribe?campaign=spring2024&user=12345(used when recipients click the visible link)
These are different endpoints with different code paths. The body link works because it has been tested and used for years. The header endpoint was added later, sometimes by a different team, and never fully validated against RFC 8058.
The result: the body link unsubscribe works perfectly. The header POST fails silently. Compliance breaks even though the user-visible flow looks fine.
The fix:
Test the header URL specifically. Do not assume that working body-link unsubscribes mean the header is also working. They are independent code paths.
How to Actually Test Your Endpoint
The single most useful diagnostic is curl. Send yourself a test campaign, view the message source, copy the List-Unsubscribe URL, and run:
curl -X POST -d "List-Unsubscribe=One-Click" -i "<https://example.com/unsubscribe?id=abc123>"Expected output:
HTTP/2 200
content-type: application/json
date: ...
{"status":"unsubscribed"}The response body content does not matter for compliance. The status code does. Anything other than 2xx fails.
After the curl, check your suppression list directly. The address should be marked unsubscribed within seconds. If it is queued, batched, or pending—your honoring delay is wrong, even if the HTTP response was 200.
A second test: send another campaign immediately to the same address. It should not arrive. If it does, your sending pipeline is not checking the suppression list before send, and you are out of compliance regardless of how fast your endpoint responded.
What "ESP Support" Actually Means
When an ESP says they support RFC 8058 one-click unsubscribe, they typically mean:
- They inject the headers automatically
- They host an unsubscribe endpoint at their domain
- The endpoint processes POSTs correctly
This handles compliance for senders using the ESP's domain in the unsubscribe URL. It does not handle compliance for senders who:
- Use custom unsubscribe URLs on their own domain
- Have multi-platform sends where some traffic goes through the ESP and other traffic goes through internal systems
- Need the unsubscribe to flow into a specific downstream system (CRM, marketing automation, internal preference center)
If you fall into any of those buckets, the ESP's RFC 8058 support is not enough. You need to validate your own implementation.
The exception worth flagging: if you are using a single ESP for all sending, and you are using their default unsubscribe domain (not a CNAME on your domain), and you have not customized the unsubscribe flow, you are probably fine. Test with curl anyway.
Frequently Asked Questions
Is one-click unsubscribe required for transactional email?
Does the POST need to validate the user is real?
What if the recipient unsubscribed by accident?
Can I use a single endpoint for all my domains?
Do I need a visible unsubscribe link in the email body?
What happens if my endpoint is down when a POST comes in?
Is there a way to know how many one-click unsubscribes I am receiving?
Key Takeaways
- RFC 8058 requires two headers (List-Unsubscribe and List-Unsubscribe-Post) and an endpoint that processes POST requests without authentication or redirects.
- Yahoo and Gmail's February 2024 requirements made it mandatory for senders pushing 5,000+ messages per day. There is no longer an opt-out.
- The five common failures: session-required endpoints, redirects to confirmation pages, batch-processing delays, broken mailto: addresses, and untested header URLs that differ from body links.
- Test with curl. The only reliable signal is what the endpoint returns to a real POST. Trust nothing else.
- ESP "support" for RFC 8058 covers the default case. Custom domains, custom flows, and downstream system integrations need to be tested independently.
- Process unsubscribes in real time. Batch processing breaks compliance and produces complaint-rate damage that costs more than database load.
If you implemented this correctly the first time, congratulations you are in a small minority. If your endpoint is failing one of the five mistakes above, the fix is straightforward and worth doing today. The next deadline (the 0.3% spam complaint threshold) is harder to hit if your unsubscribes are silently dropping.
Compliance with Yahoo and Gmail's February 2024 requirements requires this header to work correctly. So does every future bulk sender requirement, in every jurisdiction, going forward. Build it once. Build it right.
Related articles

Deliverability Incident Response: A Playbook for the First 4 Hours
When email reputation collapses, the first 4 hours determine the recovery timeline. Triage, containment, communication the operational playbook teams actually need.

List Hygiene Beyond Bounces: Engagement-Based Suppression Strategies
Bounce-based list cleaning is decades behind modern deliverability. The 90/180/365-day engagement model, sunset policies, and re-engagement that actually works.

Blocklist Remediation Playbook: Spamhaus, SORBS, and UCEPROTECT
How to remove your IP or domain from Spamhaus, SORBS, UCEPROTECT, and other blocklists. Which lists matter, which to ignore, and the delisting process for each.


