DKIM2

DKIM2 makes every domain that handles a message accountable for it — binding each signature to the path the message actually took, so recipients can verify who it was for, where it came from, and what changed along the way.

Work in progress · IETF Internet-Draft

Status: a work in progress

DKIM2 is being developed in the IETF DKIM working group. It is an active Internet-Draft, not yet a finished standard — details may still change.

What DKIM can't tell you today

A DKIM signature proves a domain signed some version of a message. Even layered with DMARC and ARC, it still can't establish three things:

How DKIM2 fixes it

DKIM2 makes every hop on the delivery path accountable. Each hop:

Two mechanisms make this work: Message-Instance (MI) headers — a hash snapshot of the message at each hop — and chained DKIM2-Signature headers, where each hop signs the current snapshot and every prior signature, forming a tamper-evident chain from author to recipient.

The rationale is set out in the DKIM2 motivation draft.

Implementations & demo

This host runs a live DKIM2 demonstration server. Four independent implementations — in Python, Perl, Go, and C — interoperate in the project repository. Two mailing-list managers on this host sign with DKIM2 as they forward:

Try it: reflector addresses

Addresses on this host let you watch DKIM2 behaviour end-to-end, in three flavours: six that reflect the message you send back to you after a defined transformation (below), three that originate a brand-new message for you (Originate a new message), and two that demonstrate bounces and delivery notifications (Bounces & delivery notifications).

Send a DKIM2-signed message — for example from one of the reference implementations — to a reflector-*@dkim2.com address. You'll get a reply, transformed per the mode below. Whether the reply is signed depends on how it was authenticated (see When the reply is signed); either way it includes Authentication-Results and X-DKIM2-Reflector headers explaining what happened. Paste the reply into the validator to see the result.

AddressWhat it doesWhat to expect
reflector-raw@dkim2.com Re-signs the unchanged message Chain verifies cleanly
reflector-subject@dkim2.com Prefixes the subject with [DKIM2]  Verifies; the change reverses to restore the original subject
reflector-body@dkim2.com Appends a footer line to the body Verifies; the change reverses to restore the original body
reflector-both@dkim2.com Subject prefix and body footer Verifies; both changes reverse
reflector-redacted@dkim2.com Changes the body, recording it as non-reversible (r={"b":null}) Verifies, but the body cannot be reconstructed
reflector-damage@dkim2.com Mutates the body after signing Body-hash verification fails — tamper detection at work

Note: the body/both footer is appended to the raw message body. On a multipart/* message that lands in the MIME epilogue (after the closing boundary), so most mail clients won't render it — but it is present in the raw bytes, covered by the body hash, and recorded in the Message-Instance recipe (so the change still verifies and reverses). On a simple text/plain message the footer is visible.

When the reply is signed

The reflector adds its own dkim2.com chain link only when it can vouch for your message:

A change-recording Message-Instance is added for every mode that alters the message (subject, body, both, redacted), whether or not the reply is signed.

Headers to look at in the reflected message

HeaderWhat it tells you
X-DKIM2-Reflector The reflector's summary: mode, the verdicts it saw (auth = DKIM2, dkim1), what it signed on (basis = dkim2/dkim1/none), and signed.
Authentication-Results The dkim2= and (when a bridge applied) dkim=pass header.d= results the reflector relied on.
DKIM2-Signature Present only when the reply is signed — the reflector's link in the chain.
Message-Instance The m= snapshot recording the reflector's change (present for the changing modes).
DKIM-Signature (classic DKIM1) Every reply also carries an ordinary DKIM1 signature (d=dkim2.com; the brand address adds a second, d=<your-domain>), so the demo mail looks like real-world mail.

Originate a new message

Three addresses don't reflect your message at all — they originate a brand-new DKIM2 message and send it to you. Because they send from a dkim2.com-aligned identity, the replies pass DMARC and land in your inbox.

reflector-fresh@dkim2.com

Generates the simplest valid DKIM2 message: a single Message-Instance (m=1) and one dkim2.com DKIM2-Signature — no forwarding chain — plus a matching dkim2.com DKIM1 signature. From <fresh@dkim2.com> to you. Paste it into the validator to see one clean signature and one instance.

reflector-brand@dkim2.com — brand delegation

Models an email sending platform (ESP) that sends on a brand's behalf without showing its own identity. With one DNS record you delegate a signing key to dkim2.com, and it returns a brand-new message that is signed as your domain:

Because the delegated d=<your-domain> DKIM1 aligns with the From: domain, the message passes DMARC and lands in the inbox — the same way Mailchimp, SendGrid and similar platforms send for a brand today.

Setup — publish one CNAME in your domain's DNS:

dkim2test._domainkey.<your-domain>.  CNAME  dkim2test._domainkey.dkim2.com.

This single delegation serves both the DKIM2 chain signature and the DKIM1 signature — one key, held by dkim2.com, that your domain authorises it to use. Then email reflector-brand@dkim2.com from an address at that domain and paste the reply into the validator to watch both DKIM2 signatures verify.

Until the CNAME is in place (and has propagated), the address replies with a plain reflector-fresh-style message explaining how to set up the delegation.

reflector-brand-nd@dkim2.com — brand delegation via nd=

The same brand delegation as reflector-brand, but the i=1 brand hop uses the nd= "next domain" encoding (the imaginary-hop form, draft §9.3) instead of mf=/rt=: the first link names the domain that will sign next rather than an envelope path. The chain still verifies — the nd= of i=1 matches the d= of i=2 (dkim2.com). Same one-CNAME setup as reflector-brand; paste the reply into the validator to see the nd= link verify.

Bounces & delivery notifications

Two addresses show how DKIM2 handles Delivery Status Notifications (DSNs) — the bounces an MTA sends when a message can't be delivered. Per the spec a DKIM2 DSN is itself an ordinary DKIM2 message: envelope MAIL FROM <>, its own Message-Instance and DKIM2-Signature on the top message, addressed back to the return path the failed message recorded.

reflector-dsn@dkim2.com — a DKIM2-signed DSN

Accepts your message and returns a freshly-signed DSN for it: a multipart/report that embeds your original as a message/rfc822 part and carries one Message-Instance and one dkim2.com DKIM2-Signature of its own — a clean one-hop DKIM2 message (not a continuation of your chain). It bounces every message, whether or not it arrived DKIM2-signed. Paste the reply into the validator to watch the DSN verify as a new one-hop message.

reflector-delayedbounce@dkim2.com — an MTA-originated bounce

Unlike every address above, this is not a reflector transform. The address is accepted at the SMTP prompt (a 250, so the sending session completes normally), and only then fails at delivery — so the bounce is a genuine delayed bounce, generated asynchronously by Postfix's own bounce generator (bounce(8)) after the session is gone, exactly as a real deferred failure would be. The outbound DKIM2 milter signs that bounce on the way out as a fresh DKIM2 message (its own Message-Instance and DKIM2-Signature, MAIL FROM <>). The signed bounce is itself a valid DKIM2 message and verifies, demonstrating that a mail server's own bounces — generated by the MTA, not by our reflector code — can be DKIM2-signed. (One operational requirement: the MTA must not re-encode the message after the milter signs it. We set disable_mime_output_conversion to prevent the 8bit→7bit rewrite that would otherwise break the signature — see the spec's "Preventing Transport Conversions".)

Learn more