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:
- Who the message was for. A valid signature carries no proof you were an intended recipient — so a correctly signed message can be captured and replayed to millions of other addresses, still passing authentication, riding on the original signer's reputation.
- Where the bounces go. A message's return path can name a domain that never handled it, so bounces, abuse reports, and delivery notifications can't be trusted or traced back.
- What changed in transit. When a forwarder or mailing list edits a message, the signature simply breaks — with no way to tell an innocuous subject or footer tweak from wholesale replacement by a bad actor.
How DKIM2 fixes it
DKIM2 makes every hop on the delivery path accountable. Each hop:
- Binds the message to where it's going — the next hop and recipient are inside the hop's own signature, so a signed message can't be replayed somewhere else. (who it was for)
- Records what it changed, as a reversible recipe, so the next hop can undo the change and re-check every earlier signature. (what changed)
- Promises to carry control messages back to the previous hop for a reasonable time, giving bounces and abuse reports a trustworthy return channel. (where the bounces go)
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.
| Address | What it does | What 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:
- your message already had a verified DKIM2 chain — the reply continues the chain;
- your message had no DKIM2 chain but a valid DKIM1
signature aligned with your
From:domain — the reply is signed as a DKIM1 → DKIM2 bridge; - neither — the reply comes back unsigned (the mode's transformation is still applied).
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
| Header | What 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:
- Two DKIM2-Signatures on one Message-Instance —
i=1as your domain (the delegated key) andi=2asdkim2.com, forming a chain of custody. - Two classic DKIM1 signatures —
d=<your-domain>(the same delegated key) andd=dkim2.com— exactly how a real ESP signs. - The visible
From:isdkim2demo@<your-domain>andTo:is you;dkim2.comnever appears in the headers a reader sees — it is only in the signatures and the envelope.
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
- Validate a DKIM2 message — paste an email for a per-level breakdown
- Verify a DKIM2 message in your browser — a standalone client-side verifier (no server round-trip)
- IETF DKIM working group
- DKIM2 specification (Internet-Draft)
- Source & reference implementations on GitHub