DomainKeys Identified Mail — an email authentication mechanism by which the sending domain attaches a cryptographic signature to outbound messages. The signature is computed over selected headers and the message body using a private key held by the sender. The receiving server retrieves the corresponding public key from DNS and verifies the signature. A valid signature confirms two things simultaneously: the message was authorized by the signing domain, and the signed portions of the message were not modified between signing and verification. Unlike SPF, which authenticates the sending IP, DKIM authenticates the message content itself.
How DKIM works — the signing and verification flow
DKIM operates as a three-actor protocol: the sending mail server, your DNS, and the receiving mail server. The signing happens once at send time; verification happens at every hop that chooses to verify. The signature rides in a single header (DKIM-Signature) attached to the message and survives every routing decision until the message arrives at its destination.
The cryptographic guarantee is meaningful: a valid DKIM signature proves the message came from the signing domain (only the holder of the private key could have generated the signature) and that the signed bytes haven't changed. What DKIM doesn't prove is anything about the visible From: header — that's why DMARC exists, to tie DKIM authentication results back to the visible sender domain through alignment.
The DKIM-Signature header — tag by tag
The DKIM signature is a single header with a structured tag list. Every tag has a purpose; understanding what each one does is the difference between debugging a DKIM failure in 10 minutes vs 10 hours. Below is a real signature, followed by what each tag means and what to check when something fails.
| Tag | Purpose | What to check when DKIM fails |
|---|---|---|
v= | DKIM version (always 1) | Should always be 1. Anything else is broken. |
a= | Signature algorithm | rsa-sha256 is standard. rsa-sha1 is banned (RFC 8301). ed25519-sha256 is RFC 8463 — receiver support varies. |
c= | Canonicalization mode (header/body) | relaxed/relaxed is the production default. simple/simple breaks on any whitespace change. |
d= | Signing domain | Must align with the From: domain for DMARC pass-through. Check this first when DMARC fails despite DKIM passing. |
s= | Selector — points to the public key in DNS | DNS lookup is s._domainkey.d. If the selector doesn't match a published key, dkim=permerror. |
t= | Signature timestamp (Unix epoch) | Used by receivers to detect very old messages. Optional but recommended. |
x= | Signature expiration | Optional. After this timestamp, signature is invalid. Rarely used in practice. |
h= | List of signed headers | From: must be included. Avoid signing headers modified by intermediaries (e.g., Return-Path). |
bh= | Base64 hash of canonicalized body | "Body hash did not verify" = this hash doesn't match what the receiver computed. Body modified in transit. |
b= | Base64 signature value | The actual cryptographic output. ~344 chars for RSA-2048, ~88 for Ed25519. |
l= | Body length limit (deprecated) | Signs only first N bytes of body. Avoid — security risk. RFC 6376 deprecated guidance. |
The DKIM DNS record — what receivers actually look up
The public key is published as a DNS TXT record at selector._domainkey.example.com. Receivers extract the selector and signing domain from the DKIM-Signature header, perform the DNS lookup, then verify the signature with the published key. The record format:
p= revocation pattern. When you need to retire a compromised key without removing the DNS record, set p= to empty (v=DKIM1; k=rsa; p=). Receivers treat this as an explicit revocation: any message signed with that selector fails verification immediately. Removing the DNS record entirely also works but creates a window where existing in-flight messages return temperror because the selector lookup fails. The empty-p= approach is the cleaner revocation pattern.
Algorithm choice — RSA-2048 vs Ed25519
The current cryptographic landscape for DKIM is straightforward: RSA-2048 with SHA-256 is the universal default, with Ed25519 as the forward-looking alternative for receivers that support it. RSA-1024 is deprecated as of 2024 (Google, Yahoo, Microsoft reject it). RSA-4096 introduces DNS UDP truncation problems and provides no meaningful security gain over 2048 for email authentication. The decision matrix:
RSA-2048 Default
Universal compatibility. Every receiver verifies RSA-2048 reliably. Public key is large enough to require careful TXT record handling but fits within standard DNS limits. The right baseline for any new deployment in 2026.
RSA-2048 + Ed25519 dual Recommended
Sign every message twice — once with RSA-2048 (universal compatibility), once with Ed25519 (forward compatibility). Receivers that support Ed25519 use it for verification; receivers that don't fall back to the RSA signature. One dkim=pass from either signature is sufficient for DMARC pass-through — both signatures don't need to verify.
Ed25519-only Future
Eventually the right answer. Smaller, faster, side-channel resistant. Don't deploy alone in 2026 — Microsoft 365 and Yahoo verification support is still inconsistent. Going Ed25519-only causes silent DKIM failures at the largest receivers.
RSA-1024 Deprecated
Cryptographically marginal and explicitly rejected by major receivers since the 2024 Gmail/Yahoo bulk sender requirements. Migrate any remaining 1024-bit deployments to 2048-bit before further reputation damage accumulates. Some legacy ESPs (e.g., Postmark in some account states) still default to 1024 — verify and rotate.
Receiver verification support matrix (2026)
The biggest gap in most DKIM documentation is concrete data on which receivers verify which algorithms. Below is the empirical support matrix as of May 2026, based on test sends to each receiver and inspection of Authentication-Results headers. Treat as moving — these change with little notice.
| Receiver | RSA-1024 | RSA-2048 | RSA-4096 | Ed25519 |
|---|---|---|---|---|
| Gmail / Google Workspace | ✗ Rejected (2024) | ✓ Verified | ✓ Verified | ✓ Verified |
| Microsoft 365 / Outlook | ✗ Rejected (2024) | ✓ Verified | ⚠ Inconsistent | ✗ Not verified |
| Yahoo / AOL | ✗ Rejected (2024) | ✓ Verified | ✓ Verified | ✗ Not verified |
| Apple iCloud Mail | ✗ Rejected | ✓ Verified | ✓ Verified | ⚠ Inconsistent |
| Fastmail | ✗ Rejected | ✓ Verified | ✓ Verified | ✓ Verified |
| Proton Mail | ✗ Rejected | ✓ Verified | ✓ Verified | ✓ Verified |
| Cisco IronPort gateway | ⚠ Legacy only | ✓ Verified | ✗ Not supported | ✗ Not supported |
| Proofpoint | ✗ Rejected | ✓ Verified | ✓ Verified | ⚠ Inconsistent |
Canonicalization — relaxed vs simple
Canonicalization is how DKIM normalizes the message before hashing. Mail servers routinely make minor adjustments to messages in transit — header folding, whitespace normalization, line ending conversion — that don't change content meaning but change byte content. Canonicalization tells the receiver how to handle these changes when computing the verification hash.
The c= tag specifies two canonicalization modes: one for headers, one for the body, separated by /. Production deployments should use relaxed/relaxed. Use simple/simple only if you have a specific compliance reason — it's a documented source of unnecessary failures.
| Canonicalization | What it does | Tolerates | Recommendation |
|---|---|---|---|
simple/simple |
No modifications. Bytes must match exactly. | Nothing. Trailing CRLF removal in body only. | Avoid — breaks on any whitespace adjustment. |
relaxed/simple |
Headers normalized; body strict. | Header whitespace/folding changes only. | Rare — unusual choice; consider relaxed/relaxed instead. |
simple/relaxed |
Headers strict; body normalized. | Body whitespace and trailing whitespace. | Rare — strict header is fragile. |
relaxed/relaxed |
Both headers and body normalized. | Whitespace, header folding, line endings, trailing whitespace, body line wraps. | Default — production-grade tolerance for normal mail handling. |
Safe DKIM key rotation
The single biggest operational risk in DKIM isn't choosing the wrong algorithm — it's botching key rotation. Done wrong, rotation creates a window where messages signed with the new key fail verification because resolvers still cache the old public key, or messages signed with the old key fail because the DNS record was replaced too early. Done right, rotation is invisible to receivers and adds zero authentication failures.
The pattern below is the safe rotation sequence. Notice that the new key is signed with for the entire DNS propagation window before retiring the old key — the old key stays valid until propagation is confirmed.
Generate new key pair
Create a new RSA-2048 key pair (and Ed25519 if dual signing). Choose a date-based selector name: s202603 for Q3 2026 rotation. Store the private key on the signing server but do NOT enable signing with it yet.
Publish new public key in DNS
Add the new TXT record: s202603._domainkey.example.com with the new public key. Old selector (s202602) remains active. Use a short TTL (300-900 seconds) on both records during the rotation window for fast rollback if needed.
Switch signing to new selector
Reconfigure the signing server to use the new selector. From this point, outbound messages are signed with s202603. Old selector is still published but not actively signing.
Verify dkim=pass on new selector
Send test messages to seed mailboxes (Gmail, Outlook, Yahoo, Fastmail). Inspect Authentication-Results headers. Confirm dkim=pass with header.s=s202603. If failures appear, you can roll back instantly by switching the signing server back to the old selector — DNS still has it.
Wait for in-flight messages to drain
Mail forwarded, mailing-list re-distributed, or sitting in queues somewhere may still try to verify with the old selector for up to 14 days. Keep the old selector published throughout this window. Do NOT remove the DNS record yet.
Revoke old key with empty p=
Replace the old selector's TXT record with v=DKIM1; k=rsa; p= (empty p=). This explicitly revokes the old key — any message claiming to be signed with it now fails verification. Don't delete the DNS record entirely; the empty p= revocation is cleaner than NXDOMAIN.
Remove the revoked selector
Once 90+ days have passed since rotation, remove the old selector's DNS record entirely. Repeat from Step 1 for the next rotation cycle. Date-based selectors make this trivial — no name conflicts, audit history is the DNS history.
ESP DKIM signing patterns — who aligns with your domain and who doesn't
A critical operational distinction often missed: not all ESPs sign DKIM with your domain. Some sign with their own domain (d=sendgrid.net), which produces dkim=pass but fails DMARC alignment because sendgrid.net ≠ yourdomain.com. The setup pattern matters more than whether DKIM is "configured" at all.
| ESP | Default DKIM signing | Custom domain signing | DMARC aligns with your domain? |
|---|---|---|---|
| SendGrid | d=sendgrid.net (default) |
CNAME setup required for custom d= |
Only with custom CNAME — not by default |
| Mailchimp | d=mail.mcsv.net (default) |
CNAME for "Authenticate Domain" feature | Only with Authentication Domain — not by default |
| Mailgun | Custom domain by default | TXT record required at setup | Yes — domain auth is part of setup |
| Postmark | Custom domain by default | TXT records during signature setup | Yes — domain auth is mandatory |
| Amazon SES | Custom domain | CNAME Easy DKIM (3 CNAMEs) or BYO DKIM | Yes with Easy DKIM |
| Microsoft 365 | Tenant default d=onmicrosoft.com |
CNAME required to enable custom-domain DKIM | Only after enabling — not by default |
| Google Workspace | None (must be enabled) | TXT record at google._domainkey |
Yes after setup |
| HubSpot | None until configured | CNAME setup in Marketing Hub settings | Yes after setup |
| Klaviyo | Klaviyo subdomain by default | CNAME for dedicated sending domain | Only with dedicated domain — not by default |
| Salesforce Marketing Cloud | SAP/MC subdomain default | Sender Authentication Package (SAP) configuration | Only with SAP — not by default |
dkim=pass in receivers' Authentication-Results, but DMARC reports show DKIM as failing alignment. The signature is technically valid — but d=sendgrid.net ≠ yourdomain.com, so DMARC's alignment check fails. The fix isn't a DKIM configuration change; it's enabling the ESP's domain authentication feature (CNAME setup, custom signing domain). Audit your DMARC aggregate reports for ESP-source rows showing dkim_aligned=false — those are the ESPs where this fix is missing.
CNAME vs TXT — Microsoft 365's distinct pattern
Microsoft 365's DKIM setup pattern differs from most other ESPs and is responsible for a large share of "DKIM looks configured but isn't working" tickets. Microsoft publishes the actual public key at a Microsoft-controlled DNS location and asks you to publish CNAMEs that point to it. The advantage is that Microsoft can rotate keys without your DNS update; the disadvantage is that admins frequently configure TXT records directly when CNAMEs were expected.
Common DKIM failure modes in production
The error message is the diagnosis. Below are the seven most common DKIM failure patterns, with the symptom you'll see in Authentication-Results headers and the operational root cause to investigate.
Body hash did not verify
The message body changed between signing and verification. Investigate: gateways inserting footers/disclaimers, anti-virus modifying content, TLS termination re-encoding characters, Microsoft 365 normalizing non-ASCII characters (smart quotes, accented chars). Compare raw body byte-by-byte sent vs received.
Microsoft non-ASCII encoding bug
Documented Microsoft 365 issue: their servers normalize certain non-ASCII characters (curly quotes → straight quotes, em-dash variants, smart apostrophes) before DKIM verification. Body hash mismatch only on Microsoft routes. Fix: force UTF-8 encoding throughout, replace smart quotes with ASCII quotes in templates, or accept partial Microsoft-only failures.
Selector not in DNS
The s= tag in the signature points to a selector that doesn't have a published DNS record. Check: was the new selector published before signing started? Did DNS propagation complete? Is there a typo in the selector name in either the signing config or the DNS record?
Multiple TXT strings concatenation
RSA-2048 public keys exceed 255 chars and must be published as multiple quoted strings within one TXT record. DNS providers that don't auto-split or auto-concatenate produce broken records. Use dig +short TXT and verify the returned key is exactly the published key, no spaces or quotes injected.
Key length still 1024-bit
1024-bit keys have been rejected by major receivers since 2024. Some legacy ESP defaults still set up 1024-bit. Audit every selector with dig +short TXT and inspect the p= value length: ~390 chars = 2048-bit, ~196 chars = 1024-bit. Migrate immediately.
Forwarder breakage
The recipient set up forwarding that adds a header/footer or modifies the message. Original DKIM signature breaks. Fix isn't on your end — the forwarder is corrupting the message. ARC (RFC 8617) is designed to preserve original auth across forwarding hops; configure your sending infrastructure to ARC-sign for forwarders that propagate ARC.
Header in h= modified post-signing
A header listed in h= was modified after signing. Common culprits: signing Return-Path (which intermediate hops rewrite), signing List-Unsubscribe with link tracking that mutates the URL. Audit the h= tag and exclude any header known to mutate.
DNS lookup timeout
Receiver couldn't fetch the public key — DNS resolver timeout, authoritative DNS unavailable, NXDOMAIN. Transient; the receiver typically retries. Persistent temperror means your DNS provider is slow or your authoritative DNS has reachability issues. Check resolver-side diagnostics and authoritative DNS health.
DKIM in Cloud Server for Email's managed infrastructure
For clients on managed PowerMTA + MailWizz infrastructure, DKIM is configured during the day 1-3 onboarding sequence as part of the broader authentication setup (SPF + DKIM + DMARC + MTA-STS + TLS-RPT). The patterns we use across most accounts:
- RSA-2048 default with date-based selectors. A new client typically gets selectors named
s2026q2,s2026q3, etc., aligned to the rotation calendar. The selectors are tied to PowerMTA's domain configuration so signing happens automatically per VirtualMTA without per-message setup. - Per-stream selectors for multi-stream architectures. Clients with separated transactional, marketing, and prospecting streams get separate selectors per stream (
tx2026q2,mk2026q2,pr2026q2). This lets each stream rotate independently and shows up as separate rows in DMARC aggregate reports — invaluable for diagnosing per-stream reputation issues. - Quarterly rotation calendared, executed as a coordinated change with the client's DNS administrator. The 7-step rotation sequence above is the documented playbook.
- relaxed/relaxed canonicalization. The default in PowerMTA's DKIM signing module. Clients have requested simple/simple in only one historical case (compliance requirement); we documented the operational implications and reverted within a quarter when the resulting failure rate proved unacceptable.
- No
l=tag. The body length limit is a documented security risk — attackers can append content beyond the signed range. Our PowerMTA configuration explicitly omitsl=so the entire body is always signed. - DMARC aggregate report monitoring for DKIM alignment failures specifically. ESP integrations on customer accounts (when a client's MailWizz also sends through SendGrid for some segments) are common sources of DKIM-pass-but-not-aligned signals — we alert on these and coordinate the ESP's domain-auth setup.
For Microsoft 365 customers using our infrastructure for the marketing layer alongside their corporate Microsoft 365 mail, we maintain documentation specifically on the CNAME-vs-TXT distinction since it's a recurring source of confusion. The CSE-managed sending domain uses TXT records; the Microsoft 365 corporate domain uses CNAMEs. Both can coexist, just on different subdomains.
Auditing your DKIM — practical workflow
Related concepts
- SPF — IP-based authentication; complements DKIM. Most production DMARC pass-through relies on DKIM because SPF breaks during forwarding.
- DMARC — the policy layer that uses DKIM (and SPF) authentication results plus alignment to decide what receivers should do with failing mail.
- ARC — Authenticated Received Chain (RFC 8617); preserves original auth results across forwarding hops where DKIM/SPF would otherwise break. (Coming soon)
- BIMI — brand logo display, requires DMARC enforcement, which requires functional DKIM and SPF. (Coming soon)
- Sender Reputation — DKIM authentication is one input to overall sender reputation at major receivers, especially for receivers that weight "stable, aligned authentication" as a positive signal.
Frequently asked questions
What's the difference between DKIM and SPF?
SPF authenticates the sending IP — it answers "is this connecting server authorized to send for this domain?" DKIM authenticates the message content itself — it answers "was this message authorized by the signing domain, and has the content been tampered with?" Critically, DKIM signatures survive forwarding because they sign the message body, not the envelope. SPF almost always breaks during forwarding because the forwarder's IP isn't in your authorized list. This is why DMARC pass-through in the real world relies more on DKIM than on SPF — and why DKIM is the more important of the two for forwarding-heavy environments.
Should I use RSA-2048 or Ed25519 for DKIM keys?
RSA-2048 in 2026, with optional Ed25519 dual signing for forward compatibility. Ed25519 (defined in RFC 8463) produces shorter keys, faster signatures, and equivalent or better security, but Microsoft 365 and Yahoo do not reliably verify Ed25519 signatures as of May 2026 — going Ed25519-only causes DKIM failures at major receivers. The recommended pattern is dual signing: publish both an RSA-2048 selector and an Ed25519 selector, sign every message with both. Receivers that support Ed25519 use it; receivers that don't fall back to the RSA signature. RSA-1024 has been rejected by Google, Microsoft, and Yahoo since 2024 — never deploy new infrastructure with 1024-bit keys.
What does "body hash did not verify" mean?
It means the receiving server's hash of the message body does not match the hash recorded in the DKIM-Signature header. The message body changed between signing and verification. Common causes: email gateways inserting disclaimers/footers post-signing, forwarders modifying content, TLS termination at intermediate hops re-encoding characters, Microsoft 365 normalizing non-ASCII characters (smart quotes → straight quotes) before verification, or anti-virus systems modifying the body. The error is almost always due to content modification in transit, not a key configuration problem. Diagnostic step: compare the raw body as sent vs as received byte-for-byte.
How often should DKIM keys be rotated?
Industry standard is quarterly rotation (every 90-180 days), recommended by Google. Studies across large sending operations show 6-9% reduction in transient DKIM failures with rotation cadences in the 120-180 day range vs semiannual or annual rotations. Use date-based selector naming (s202601, s202602) to make audits trivial and rotation a routine operation. The actual security risk from a leaked private key grows linearly with time — quarterly rotation caps the exposure window. The bigger risk is doing rotation incorrectly: DNS propagation must complete before retiring the old key, otherwise messages signed with the new key fail verification because resolvers still cache the old public key.
What is a DKIM selector and how do I choose one?
The selector is the identifier that points receivers to a specific public key in DNS, allowing multiple keys per domain. The DNS lookup is selector._domainkey.example.com. Choose date-based names tied to rotation cycle: s202601 for Q1 2026, s202602 for Q2, etc. Avoid generic names like 'default', 'key1', or 'mail' — they make rotation tracking impossible and conflict with ESP-default selectors. For multi-stream architectures, use stream-prefixed selectors: tx202601 for transactional, mk202601 for marketing, sup202601 for support. This lets you rotate one stream's key without disrupting others, and per-stream selectors can be observed independently in DMARC aggregate reports.
Can a forwarded message preserve DKIM?
Yes, if the forwarding server doesn't modify the signed portions. Simple forwarding (the message is reused unaltered) preserves DKIM and is one of the main reasons DKIM is preferred over SPF for DMARC pass-through. Forwarding that adds footers, modifies headers in the signature's h= list, or re-encodes characters breaks DKIM. The signed headers (From, Subject, Date, Message-ID) and the signed body must arrive byte-identical to verify. ARC (Authenticated Received Chain, RFC 8617) is the protocol designed to let forwarders preserve original authentication results across hops where SPF and DKIM would otherwise break. Major providers (Gmail, Microsoft) increasingly check ARC chains as a signal.
What does relaxed vs simple canonicalization mean?
Canonicalization is how DKIM normalizes headers and body before hashing. Two modes per dimension. Simple/simple: no modifications allowed — any whitespace change between signing and verification breaks the signature. Relaxed/relaxed: tolerates whitespace normalization, line ending changes, header folding adjustments. Production deployments should use relaxed/relaxed (the default in most signers) because mail systems routinely make minor whitespace adjustments that don't affect content meaning but would break simple canonicalization. The c= tag in the DKIM-Signature header takes the form c=header_canon/body_canon, e.g., c=relaxed/relaxed. Use simple/simple only if you have specific compliance reasons — it's a documented source of unnecessary DKIM failures.
Why do my Microsoft 365 DKIM records use CNAME instead of TXT?
Microsoft 365 publishes DKIM keys via CNAME records that point to Microsoft-managed DNS records. When you enable DKIM in Microsoft 365 admin center, you add CNAME records like selector1._domainkey.example.com pointing to selector1-example-com._domainkey.your-tenant.onmicrosoft.com. The actual public key lives at the Microsoft-controlled target. This pattern lets Microsoft rotate keys without requiring your DNS update on every cycle — you publish CNAMEs once, Microsoft handles the rotation upstream. The most common Microsoft 365 DKIM failure is configuring TXT records directly when CNAMEs were expected, or vice versa. The setup is correct only when CNAMEs resolve to populated TXT records at the Microsoft target.
Need DKIM set up correctly across your sending stack?
Cloud Server for Email's onboarding includes complete DKIM setup: per-stream selectors, RSA-2048 with optional Ed25519 dual signing, calendared quarterly rotation, and DMARC aggregate report monitoring for alignment failures. We've untangled DKIM for clients running 8+ concurrent ESPs with mixed Microsoft 365 / custom infrastructure deployments.
Talk to engineering →