One-click unsubscribe is described in RFC 8058 as two header fields. Engineers read that, ship two headers, and the feature does not work. The reason is in the same section: the message must carry a valid DKIM signature that covers both headers.
The exact requirements
From the specification, a sender enabling one-click unsubscription places:
- One
List-Unsubscribeheader containing one HTTPS URI. It may also contain non-HTTP URIs such asMAILTO:. - One
List-Unsubscribe-Postheader containing exactly one key/value pair:List-Unsubscribe=One-Click. - A valid DKIM signature that covers at least the
List-UnsubscribeandList-Unsubscribe-Postheaders.
All three, or the one-click path is not honoured.
Why the DKIM clause exists
The motivation is stated plainly in the document's abstract: mail software sometimes fetches URLs found in message headers, which means a URL in a header can be triggered accidentally. An auto-fetching client hitting a GET on an unsubscribe endpoint would silently remove a recipient who never asked.
One-click solves this by requiring an explicit POST. The RFC's reasoning, spelled out in the header section, is that no mail client performs an accidental HTTP POST to a URL discovered in a header. So the POST itself is the human intent signal. That is also why the URI must carry enough information to identify both the recipient and the list — there are no extra POST arguments, so it all has to be encoded in the URL.
The DKIM requirement then closes the loop: without it, anyone who can modify a message in transit could substitute their own unsubscribe URL or strip the headers, and the receiver would act on an unauthenticated instruction. DKIM coverage binds the unsubscription instruction to the domain that actually sent the mail.
The consequence people miss
Because DKIM coverage is mandatory, unsubscribe is not a marketing-feature toggle. It is coupled to the sending domain's signing configuration. Practical fallout:
- If your signing setup only covers
From,Subject,Date, andMessage-ID, adding the two headers to the message is not enough. They must be in theh=list of the signature. - If a relay appends
List-Unsubscribeafter signing, the header is present but not signed, and one-click quietly does not work. The message still delivers; the feature just is not honoured. - If DKIM verification fails at the receiver for any reason — DNS timeout, body modified in transit, key rotated without overlap — the unsubscription instruction is no longer trusted, even though the headers are syntactically perfect.
That second bullet is the most common real-world failure: an email service provider signs, then a downstream system injects the unsubscribe headers for compliance reasons. Everything looks right in the raw message and the feature is dead.
What a working setup looks like
In order:
- The endpoint is an HTTPS URL that accepts
POST, and it identifies recipient and list from the URL itself, no body parameters. - Both headers are added before signing.
- The DKIM
h=tag lists both headers explicitly. - The signing domain's selector resolves and the key matches what signed.
- Any relay in the path is configured to preserve the headers, or signing happens after it.
Testing it without a real subscriber list
The verification is mechanical, and it does not require a live campaign. Send a message through the real pipeline, capture the raw message, then check three things: are both headers present, does the DKIM signature's h= include them, and does verification pass against the published key. Then POST the URL and confirm the endpoint responds — a POST, not a GET.
This is exactly the class of test a disposable inbox makes cheap: mint an address, drive the real send path, read the raw message, assert on headers and signature, delete. No subscriber list, no production data, and it exercises the same signing code that will run in production.
The takeaway
Two headers plus one signature, or the feature does not exist. The signature requirement is what makes one-click trustworthy, and it is also what makes the feature fragile in pipelines that inject headers after signing. Add the headers before signing, list them in h=, and verify against the published key before believing the feature works.