Inbox
articleJune 15, 20266 min read

List-Unsubscribe Header: One-Click Per RFC 8058

The List-Unsubscribe header lets recipients opt out in one click. Implement RFC 2369 and RFC 8058 correctly to stay in the inbox under Gmail and Yahoo's 2024 bulk-sender rules.

By SESMetric Editorial

Since February 2024, Gmail and Yahoo reject or throttle bulk mail that does not offer a one-click way to unsubscribe. The list-unsubscribe header is the protocol-level mechanism that makes that one click possible, and getting it wrong is the fastest path from the inbox to the spam folder. This guide walks through both legal header formats, the POST endpoint you must operate, and the exact requirements your sending pipeline has to meet to stay compliant.

What the list-unsubscribe header does

The list-unsubscribe header is a hidden field your sending system writes into the outgoing message. Recipients never read it directly. Instead, Gmail, Yahoo Mail, Apple Mail, and Outlook.com parse it and render a native "Unsubscribe" link near the sender's name in the message view. When the user clicks that link, the mail client either opens a mailto compose window or, under RFC 8058, posts a small body to your server and shows a confirmation toast inside the inbox.

RFC 2369, published in 1998, introduced the original header for mailing lists. RFC 8058, published in 2017, layered on the one-click HTTPS variant by adding a companion header named List-Unsubscribe-Post. Modern bulk senders ship both forms in every commercial message.

The two header formats

You can express the unsubscribe target as a mailto link, an HTTPS URL, or both. Most receivers prefer the HTTPS form because it produces a clean in-app experience, but the mailto fallback still matters for older clients and for users on locked-down corporate mail.

mailto form (RFC 2369)

The original RFC 2369 syntax wraps one or more URIs in angle brackets, separated by commas:

List-Unsubscribe: <mailto:unsub+abc123@bounces.example.com?subject=unsubscribe>

When the recipient clicks, the mail client opens a compose window pre-addressed to that mailbox. Your inbound mail handler reads the address-plus token (here, abc123), looks up the subscription, and records the opt-out. The mailto form is useful as a fallback but is not sufficient by itself for Gmail or Yahoo's one-click requirement.

https form (RFC 8058)

The HTTPS form pairs a URL inside List-Unsubscribe with a second header that signals one-click capability:

List-Unsubscribe: <https://api.example.com/u/abc123>, <mailto:unsub+abc123@bounces.example.com>
List-Unsubscribe-Post: List-Unsubscribe=One-Click

List-Unsubscribe-Post: List-Unsubscribe=One-Click is the exact, case-sensitive string from RFC 8058 section 3.1. It tells the receiving mail provider that they may POST the body List-Unsubscribe=One-Click to your URL without waiting for explicit user confirmation. If you omit this second header, the receiver assumes a GET and shows the user a browser confirmation page, which is no longer enough to satisfy the 2024 bulk-sender rules.

Best practice is to include both URIs in the same List-Unsubscribe header. The receiver picks whichever it supports. Order matters less than you might think — receivers parse the entire field and prefer the HTTPS variant when List-Unsubscribe-Post is present, but listing the HTTPS URI first is still a small readability win for anyone debugging a raw message in their MTA logs.

Note that the angle brackets are required, not decorative. A bare URL without <> is technically malformed per RFC 2369 and some receivers will silently ignore it. Likewise, the comma between entries must sit outside the brackets. These syntactic details are unforgiving: a single broken header demotes the whole message to the same tier as a sender with no header at all.

Anatomy of a compliant header set

A bulk message that satisfies the current rules looks roughly like this on the wire:

From: Acme Updates <updates@news.example.com>
To: user@gmail.com
Subject: Your weekly digest
List-Unsubscribe: <https://api.example.com/u/abc123>, <mailto:unsub+abc123@bounces.example.com>
List-Unsubscribe-Post: List-Unsubscribe=One-Click
Feedback-ID: digest:news:example:abc
DKIM-Signature: v=1; a=rsa-sha256; d=news.example.com; s=s1; ...

Three details matter here. The URL is HTTPS, never HTTP. The DKIM signature must cover the From and List-Unsubscribe headers and the signing domain must align with the visible From domain, as required by RFC 8058 section 3 and reinforced by DMARC alignment. And the token in the URL must be unguessable per recipient, so an attacker cannot enumerate other addresses on your list.

Building the POST endpoint

RFC 8058 section 3.1 spells out exactly what your endpoint has to accept. It must respond to a POST request with Content-Type: application/x-www-form-urlencoded and a body of List-Unsubscribe=One-Click. It must not require a login, captcha, JavaScript, or a confirmation page. It must return a 2xx response after the opt-out is recorded, and it must be idempotent — receivers sometimes retry, and the user is allowed to click twice.

The simplest pattern is a stateless token: HMAC the tuple (recipient_id, list_id, secret) and put the result in the URL path. The endpoint verifies the HMAC, flips the suppression bit, and returns. No database lookup is needed to validate the token itself.

A minimal Python handler with FastAPI:

import hmac, hashlib, base64
from fastapi import FastAPI, HTTPException, Request

app = FastAPI()
SECRET = b"replace-with-a-real-secret"

def verify(token: str, recipient: str, list_id: str) -> bool:
    raw = f"{recipient}:{list_id}".encode()
    expected = base64.urlsafe_b64encode(
        hmac.new(SECRET, raw, hashlib.sha256).digest()
    ).rstrip(b"=").decode()
    return hmac.compare_digest(expected, token)

@app.post("/u/{recipient}/{list_id}/{token}")
async def one_click_unsubscribe(
    recipient: str, list_id: str, token: str, request: Request
):
    body = (await request.body()).decode()
    if body.strip() != "List-Unsubscribe=One-Click":
        raise HTTPException(status_code=400, detail="bad body")
    if not verify(token, recipient, list_id):
        raise HTTPException(status_code=403, detail="bad token")
    # Idempotent suppression write — safe to call repeatedly.
    suppress(recipient, list_id)
    return {"ok": True}

You can exercise the endpoint locally with the same request the receiver will send:

curl -i -X POST \
  -H 'Content-Type: application/x-www-form-urlencoded' \
  -d 'List-Unsubscribe=One-Click' \
  https://api.example.com/u/abc123/digest/sig

If the response is anything other than a 2xx within a few seconds, the receiver will fall back to the mailto URI or, worse, count the failure against your sender reputation.

Transactional versus commercial mail

The one-click rule applies to commercial and bulk mail, not to strictly transactional messages. Password resets, two-factor codes, receipts, shipping notifications, and other one-to-one messages triggered by an explicit user action are exempt from the unsubscribe header requirement under Gmail's bulk sender policy. Adding the header to a password reset is harmless but unnecessary.

The trap is mixing the two on one sending identity. If the same domain or IP pool sends both your receipts and your weekly newsletter, a single bad campaign drags down deliverability for everything else. Separate marketing and transactional traffic onto distinct subdomains, and only attach the list-unsubscribe header to the commercial stream. Pure transactional mail should never carry it.

When you classify a message, ask whether the recipient explicitly initiated it within the last few minutes. A password reset triggered by clicking "forgot password" qualifies. A monthly account summary sent to every user does not, even if you consider it informational. Receivers grade by behavior, not by your internal taxonomy, so anything that resembles bulk in the receiver's eyes should carry the header.

What changed in 2024 for bulk senders

On February 1, 2024, Gmail and Yahoo enforced a joint policy aimed at senders that push more than 5,000 messages per day to Gmail or Yahoo addresses. The threshold is measured per sending domain, not per IP, and it is sticky — once you cross it on any single day, you are treated as a bulk sender going forward.

Bulk senders now must meet all of the following:

  • SPF and DKIM both pass, with DKIM aligned to the visible From domain.
  • A DMARC record published at minimum with p=none.
  • A user-reported spam complaint rate kept under 0.30%, with 0.10% as the target.
  • A one-click unsubscribe via RFC 8058, plus a visible unsubscribe link in the message body.
  • Opt-outs honored within two days of the click.

Failure modes are graded. A few percent of messages start landing in spam first, then bulk delivery is throttled, and finally Gmail begins returning 421 temporary failures on SMTP that escalate to 550 5.7.26 permanent rejections. The official references are Google's sender guidelines at https://support.google.com/a/answer/81126 and Yahoo's at https://senders.yahooinc.com/best-practices/.

Before your next bulk send, verify four things: the list-unsubscribe header is present on every commercial message, the URL it points to returns 2xx within a second under POST load, your DMARC record resolves on the From domain, and your complaint rate from the last 30 days sits well under 0.30%. If all four hold, RFC 8058 has done its job and the inbox stays open.

Tagsdeliverabilitycomplianceheadersgmail-bulk-sender