GLOSSARY

What is DKIM?

DKIM (DomainKeys Identified Mail, RFC 6376) is an email authentication standard in which the sending server signs chosen headers and the message body with a private key, and the receiving server verifies that signature against a public key published in DNS at selector._domainkey.yourdomain.com.

Free forever plan · No credit card required · Cancel anytime

Quick definition

DKIM (DomainKeys Identified Mail, RFC 6376) is an email authentication standard in which the sending server signs chosen headers and the message body with a private key, and the receiving server verifies that signature against a public key published in DNS at selector._domainkey.yourdomain.com.

In one line: the sender writes a signature into the headers, and DNS hands the receiver the key to check it.

What it means

SMTP was designed in an era when every server on the network was trusted, so it has no notion of who a message really came from. Any host can open a connection and claim to be sending on behalf of any domain. DKIM, standardised in RFC 6376, is the patch. Before the message leaves, the sending system computes a cryptographic hash of the body and of a chosen set of headers, signs that hash with a private key, and writes the result into a new header called DKIM-Signature. The matching public key lives in a DNS TXT record. Any receiver in the world can therefore fetch the key and verify the signature without ever contacting the sender.

The important consequence is that DKIM survives forwarding. SPF checks the connecting IP address, so the moment a message is relayed through a mailing list or a university forwarding address the IP changes and SPF fails. A DKIM signature travels inside the message, so it keeps verifying no matter how many hops the message takes, provided nothing along the way edits what was signed. That is why DKIM, not SPF, is the authentication result you should treat as load bearing.

Anatomy of a DKIM-Signature header

Every tag in the header does something specific, and reading them in order is how you debug a failure without guessing. The tags you will actually see:

  • v=1: the DKIM version. Always 1; there has never been a second one.
  • a=rsa-sha256: signing algorithm. RSA with SHA-256 is the default. ed25519-sha256 is defined in RFC 8463 and produces a far shorter key, but is not universally verified yet.
  • d=pinlyx.com: the signing domain identifier, or SDID. This is the domain that takes responsibility, and the one DMARC compares against your From header.
  • s=resend: the selector. It picks which key under the domain to use, which is what lets one domain run several signing systems at once.
  • c=relaxed/relaxed: canonicalization for headers and body. See the section below; this one tag causes most real-world failures.
  • h=from:to:subject:date:message-id: the ordered list of headers covered by the signature. From is mandatory. Anything not listed here can be changed or added in transit without breaking the signature.
  • bh=: the base64 hash of the canonicalized body. When a verifier reports that the body hash did not verify, this is the value that no longer matches.
  • b=: the signature itself, computed over the canonicalized headers listed in h= plus the DKIM-Signature header with an empty b=.
  • t= and x=: signature timestamp and expiry, as Unix seconds. An expired signature is treated as no signature at all.
  • i=: the agent or user identifier, normally the signing domain or a subdomain of it. Rarely needed.
  • l=: body length count. Do not use it, for the reason in the FAQ below.

The DNS side: what a selector record contains

The public key is published at selector._domainkey.yourdomain.com as a TXT record. Its own tag set is short:

  • v=DKIM1: version. If present it must come first.
  • k=rsa: key type. rsa is the default, ed25519 the other legal value.
  • h=sha256: which hash algorithms this key may be used with. Omitting it means any of them.
  • p=: the base64 public key. An empty p= is not a broken record, it is a revocation, and verifiers treat it as one.
  • t=y: testing mode. Receivers are asked not to act on failures. Useful for a week, dangerous forever, because some receivers also refuse to let a testing key satisfy DMARC.
  • t=s: the i= domain must exactly equal d=, with no subdomains allowed.
  • s=email: service type. Almost always left at its default of *.

Canonicalization, and why relaxed/relaxed is the answer

Mail servers along the path legitimately reshuffle whitespace. They re-fold long headers, they add or strip a trailing blank line, they change the case of a header name. Under simple canonicalization any of those edits changes the hash and the signature fails. Under relaxed, signer and verifier both normalise first: header names are lower-cased, runs of whitespace collapse to a single space, leading and trailing whitespace is trimmed, and trailing empty lines in the body are removed.

Sign with c=relaxed/relaxed unless you have a specific reason not to. It is what every major provider uses, and it removes an entire category of failure that is impossible to reproduce locally because it only happens on the hop you cannot see.

Reading a verification result

Receivers record what they found in an Authentication-Results header, defined in RFC 8601. It is the single most useful thing in a raw message when you are debugging, and because it is added by the receiving server it can be trusted in a way the rest of the headers cannot. It names the checking host first, then gives one result per method. If you see dkim=pass next to dmarc=fail, the signature was valid but the domain that signed it was not yours.

What DKIM does not do

  • It is not encryption. The message is as readable in transit as it was before. TLS is what solves that.
  • It is not a spam verdict. Spammers sign their mail correctly, and are often better at it than legitimate senders. A valid signature only makes reputation attributable.
  • It does not protect the visible From line on its own. A signature with d=some-vendor.net on a message reading From: you@yourdomain.com verifies perfectly and proves nothing about you. Alignment is a DMARC concept, not a DKIM one.
  • It does not cover headers you left out of h=. If List-Unsubscribe is not signed, a relay can replace it. Sign the headers that matter, including List-Unsubscribe and Content-Type.

Related concepts

  • SPF: the IP-based half of the same problem, and the one that breaks on forwarding.
  • DMARC: the policy layer that turns a DKIM pass into a decision about the From header.
  • Domain warm-up: none of this matters until the receiving side has a reputation to attach your signature to.
  • Bounce rate: where authentication failures show up first, as 5.7.x rejections.
  • Cold outreach: the workflow where an unsigned sending path costs you the most.

How Pinlyx handles it

Outbound notifications, lifecycle mail and campaign sends all leave through a signing relay, so every message carries a signature whose d= is your own domain rather than ours. When you connect a sending domain we generate the selector, show the exact TXT record to paste, and then re-query it from outside your network before we let you send, because a record that resolves on your laptop and not on a public resolver is the most common setup failure we see. Inbound mail synced into the CRM keeps its original Authentication-Results header, so when a customer claims they never received a reply you can read what their provider actually decided.

Wire format

A real signature, and the record that verifies it.

Both sides of DKIM in one view: the header the sender adds, and the TXT record the receiver looks up.

The DKIM-Signature header

DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;
  d=pinlyx.com; s=resend; t=1757289600;
  h=from:to:subject:date:message-id:mime-version:content-type:list-unsubscribe;
  bh=2jmj7l5rSw0yVb0vlWAYkK3YBwk=;
  b=Xk1pQ0m8lZ5rNvC3sK9dTb7WqY2eR4hU6oI0aP8fL1nJ...

The signature in b= covers the headers named in h= plus this header itself with b= emptied. That is why a verifier can read the signature without invalidating it.

The DNS TXT record

resend._domainkey.pinlyx.com. 3600 IN TXT (
  "v=DKIM1; k=rsa; h=sha256; t=s; "
  "p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxq2Vd0Fz7Lm4"
  "9pWnS1kYbQe8Rr0TjCu6HdA3vNxK5oPfZgIyBmLqEw2sVtXrJ4hM0dUcNa"
  "7Yb1QIDAQAB" )

$ dig TXT resend._domainkey.pinlyx.com +short

A 2048-bit key does not fit in one TXT string. Split it into quoted chunks of 255 characters or fewer; they are concatenated when the record is read. Never insert spaces or line breaks inside the base64 itself.

What the receiver writes back

Authentication-Results: mx.google.com;
  dkim=fail reason="body hash did not verify"
    header.i=@pinlyx.com header.s=resend header.b=Xk1pQ0m8;
  spf=pass (google.com: domain of bounce@pinlyx.com designates
    46.225.21.115 as permitted sender) smtp.mailfrom=bounce@pinlyx.com;
  dmarc=fail (p=NONE sp=NONE dis=NONE) header.from=pinlyx.com

SPF passed and DKIM failed, so DMARC failed too. With a vendor return-path, SPF frequently does not align, which means the DKIM signature was the only thing actually keeping the domain authenticated.

Debug table

Five DKIM failures and what each one really means.

ResultActual causeFix
dkim=fail (body hash did not verify)Something changed the body after signing: a mailing list appended a footer, a security appliance rewrote links, or an outbound gateway converted the character set.Sign with c=relaxed/relaxed, stop any middlebox that rewrites the body, and never use the l= body-length tag as a workaround.
dkim=permerror (key not found)The TXT record is missing, published at the wrong name, or the DNS panel appended the zone twice so the record lives at selector._domainkey.example.com.example.com.Query it directly with dig TXT resend._domainkey.example.com +short. If nothing comes back, the record is not where the verifier looks.
dkim=fail (signature did not verify)A 2048-bit key was pasted into a TXT record without splitting it into quoted 255-character chunks, so the DNS provider truncated it.Publish it as "p=MIIBIjAN..." "...QIDAQAB". TXT strings concatenate on the wire; the 255-character limit is per string, not per record.
dkim=noneNo DKIM-Signature header was added at all. Usually a second sending path (a helpdesk, a billing provider, an on-premise MTA) that was never given a selector.Inventory every system that sends as your domain and give each one its own selector. One unsigned path is enough to fail DMARC.
dkim=pass but dmarc=failThe signature is valid, but d= is the vendor domain rather than yours, so it does not align with the From header.Ask the vendor for a branded CNAME so the signature is made with d=yourdomain.com. A valid signature from the wrong domain buys nothing under DMARC.
Rotation order

Rotate the selector, not the record.

  • Generate a new key pair and publish it under a new selector name, for example s2026a, instead of overwriting the existing one.
  • Wait out the TTL of the old record plus a margin. One hour of TTL means at least two hours of waiting.
  • Switch the signer to the new selector. Nothing else changes: same domain, same alignment, same DMARC result.
  • Leave the old public key published for 30 days. Queued and deferred messages are verified whenever the receiver finally gets to them.
  • Only then replace the old record with an empty p= to revoke it explicitly, or delete it outright.

DKIM: FAQ

The questions that come up the first time a domain fails authentication.

Only two things: that the domain in the d= tag took responsibility for the message, and that the signed headers and body were not modified between signing and verification. It does not prove the message is wanted, that the sender is honest, or that the visible From address belongs to that domain. That last gap is exactly what DMARC alignment closes.
RFC 8301 forbids keys shorter than 1024 bits and recommends 2048. Use 2048-bit RSA unless a DNS provider genuinely cannot store the record, in which case split the TXT value into 255-character quoted chunks rather than dropping to 1024. Ed25519 keys are defined in RFC 8463 and are far shorter, but verification support is still uneven, so publish an Ed25519 signature alongside an RSA one rather than instead of it.
Yes, and it is common. A message can be signed by the author domain and again by a relay or a mailing list. Verifiers evaluate each DKIM-Signature header independently, and DMARC passes if at least one valid signature aligns with the From domain. Adding a second signature never invalidates the first, as long as the second signer does not modify what the first one signed.
Publish the new selector first, wait for DNS to propagate, then switch signing to the new selector, and keep the old public key published for at least 30 days. Messages already queued at a receiver are verified against the old selector when they are finally processed. Deleting the old record on the same day you rotate is the classic way to produce a burst of dkim=permerror.
The l= tag tells the verifier to hash only the first N bytes of the body, which was meant to survive footers added by mailing lists. It also means anyone can append arbitrary content to your message and keep the signature valid, because the appended part sits outside the hash. Never set it, and treat an inbound message that uses it with suspicion.
No. DKIM is a signature, not encryption. The message travels in exactly the same readable form as before; the signature only lets a receiver detect tampering. Transport encryption is TLS (STARTTLS, or implicit TLS on port 465), and end-to-end encryption is S/MIME or PGP. The three solve different problems and are used together.
Ready to ship

Authenticate every send. Land in the inbox.

Pinlyx signs outbound mail with your own domain, verifies the DNS record from outside your network before you send, and keeps the raw authentication headers on every thread.

Free forever plan · GDPR-ready · No credit card required

We value your privacy

We use cookies to improve our site, analyze traffic, and personalize ads. You can accept all, reject non-essential, or customize your choices. Read our Cookie Policy.