Skip to content
notspoofed.comSPF · DKIM · DMARC

List-Unsubscribe isn't DKIM-signed

By · Published

Both headers are present. The value is exact. The URL works when you visit it. DKIM passes, SPF passes, DMARC passes. And Gmail still shows no unsubscribe button. This is the failure that wastes the most time, because every tool you can find tells you that you are compliant.

What the error means

A DKIM signature does not cover the whole message. It covers the body, plus a list of headers that the signer chose — and that list is published in the signature itself, as the h= tag:

DKIM-Signature: v=1; a=rsa-sha256; d=acmenews.com; s=mail202608;
        h=from:to:subject:date:message-id;
        bh=...; b=...

Any header not named in h= is unsigned. It travelled with the message, but nothing proves it left your server in that form. Anyone who could modify the message in transit could have added it, or rewritten the URL inside it to point at their own endpoint.

RFC 8058 §3 therefore requires both List-Unsubscribe and List-Unsubscribe-Post to be covered by the signature. A receiver that finds them unsigned does not warn you and does not reject the message. It delivers it normally and declines to offer the one-click button — because acting on an unsigned unsubscribe URL would mean sending a POST to whatever an attacker put there.

Why nothing tells you

There is no bounce, no error, and no entry in any postmaster dashboard. The message is delivered, DKIM genuinely passes — the signature is valid, it just does not cover these headers — and your own tests pass too, because you are looking at the headers rather than at what signs them. The only visible symptom is the absence of a button you cannot see from the sending side, and a spam-complaint rate that is higher than it should be.

It is also easy to introduce by accident. Signing configurations are usually written once, and a list that was complete in 2019 has no reason to contain a header that only started mattering in 2024.

The fix

Add both header names to the signed list. You do not edit the message — the h= tag is produced by whatever signs your mail.

OpenDKIM

Set SignHeaders in opendkim.conf, appending both names to the existing list, then restart. If you have never set it, OpenDKIM uses a default list that does not include them.

SignHeaders from,to,subject,date,message-id,list-unsubscribe,list-unsubscribe-post

rspamd

Set sign_headers in dkim_signing.conf. Note the different separator — rspamd uses colons.

sign_headers = "from:to:subject:date:message-id:list-unsubscribe:list-unsubscribe-post";

On an ESP

The signing list is not yours to change, and it is almost never exposed in the interface. Ask support to add List-Unsubscribe and List-Unsubscribe-Post to the signed header list for your sending domain. Say it in those words — a general “one-click is not working” ticket will usually come back telling you the headers are present.

If your mail carries two signatures

Messages sent through an ESP on your own domain are often signed twice: once by the ESP and once by you. A receiver may verify either. If only one signature covers the unsubscribe headers, whether one-click works depends on which signature that receiver happened to check — which looks exactly like intermittent breakage and is impossible to reproduce. Both need fixing. The checker reports each signature separately, named by its d= and s=, so you can tell which one is short.

Checking it worked

Send yourself a message and read the h= tag in its DKIM-Signature header. Both names should be there. Or paste the headers into the checker, which reads every signature on the message and tells you which of them, if any, is missing which header.

One thing worth knowing while you are in there: a header name listed twice in h= is not a mistake. That is oversigning, and it means “this header must not be added” — a defence against someone appending a second List-Unsubscribe in transit. It is a reasonable thing to do once the basics work.

Related guides