Multiple DKIM Signatures: How to Scale Third-Party Senders Without Blowing the SPF Limit
A dozen SaaS platforms want to send mail as your domain, and stuffing them all into one SPF record dies at the 10-lookup ceiling. The real fix: delegate a per-sender DKIM selector to each provider and let DMARC pass on any one aligned signature.
EvilMail TeamJuly 19, 202611 min read
You wired HubSpot for marketing, Zendesk for support, and Amazon SES for transactional mail, all sending as example.com. To make them pass SPF you kept appending includes until the record read v=spf1 include:_spf.google.com include:sendgrid.net include:mailgun.org include:servers.mcsv.net include:spf.protection.outlook.com -all. Then password resets started landing in spam and MXToolbox flagged PermError: too many DNS lookups. You hit lookup 11.
Here is the one-line fix: SPF does not scale to many senders, DKIM does, and DMARC only needs one aligned signature to pass. Stop chaining includes. Give every sender its own DKIM selector instead.
Why SPF runs out of room and DKIM doesn't
SPF is evaluated by resolving your record and every mechanism it references. RFC 7208 §4.6.4 caps that at 10 DNS lookups, counting include, a, mx
Multiple DKIM Signatures for Third-Party Senders | evilmail.pro — EvilMail Blog
,
ptr
, and
exists
recursively. The catch is that each SaaS
include
is rarely one lookup —
include:_spf.google.com
expands to several nested includes, and
spf.protection.outlook.com
costs two or three. Four or five providers and you are over budget. Once you exceed 10, compliant receivers return PermError, treat the check as failed, and many quietly route you to spam.
The usual workaround is "flattening" — replacing includes with the raw IP ranges. That trades one problem for a worse one: providers rotate their sending IPs without telling you, so a flattened record is a silent time bomb that fails weeks later when SES adds a /24.
DKIM has no lookup budget at all. Each sender is an independent record at its own selector, and verifiers only fetch the one selector named in the signature they are checking. Ten senders means ten selector records that never interact, never sum, never PermError. That is why multi-signing scales where SPF cannot.
One From address, many DKIM-Signatures: how DMARC actually decides
A single message can legally carry multiple `DKIM-Signature:` headers. Each verifier evaluates every signature independently — fetch that selector's public key, check the hash, record a pass or fail per signature.
DMARC (RFC 7489) then applies simple OR logic: the message passes if SPF produces an aligned pass OR DKIM produces an aligned pass. Alignment is the part everyone gets wrong. A DKIM signature is *aligned* only when its d= domain matches the From-header domain. Relaxed mode (adkim=r, the default) accepts the same organizational domain, so d=mktg.example.com aligns with From: [email protected]. Strict mode (adkim=s) demands an exact match.
That third signature is the classic trap. A provider that signs with its own d=sendgrid.net gives you dkim=pass in the headers — which looks fine at a glance — but it does not align with your From domain, so it contributes nothing to DMARC. This is the number-one root cause behind "DKIM passes but DMARC fails" tickets. Never accept a third party that signs only with its own domain. Make it sign as you.
The OR logic is also why multi-signing is resilient. When a message is forwarded, the path changes and SPF breaks. DKIM signs the message body and selected headers, so a surviving aligned DKIM signature still carries DMARC to a pass — where SPF alone would have failed the forward outright.
Selectors and CNAME delegation: the mechanics
DKIM keys live under a namespaced host: <selector>._domainkey.example.com. The selector is just a label that lets each sender own a distinct key so they never collide. You have two ways to publish it.
Self-managed TXT — you generate the keypair and paste the public key into DNS yourself:
text
google._domainkey.example.com. IN TXT "v=DKIM1; k=rsa; p=MIIBIjANBgkq..."
Delegated CNAME — you point the selector at a hostname the provider controls, and they publish the actual key on their side:
text
s1._domainkey.example.com. IN CNAME s1.domainkey.u123456.wl.sendgrid.net.
Delegation is the one you want for third parties. It hands key management to the provider: when they rotate keys, they update the target of the CNAME and you never touch your zone again. For a dozen senders each rotating on their own schedule, that difference is the whole ballgame.
Wiring the real senders
Here is a representative zone excerpt covering the providers you are most likely to run. Substitute your real tokens — SES and SendGrid hand you provider-specific values in their consoles.
dns
; --- Amazon SES (Easy DKIM: three CNAMEs) ---
<token1>._domainkey IN CNAME <token1>.dkim.amazonses.com.
<token2>._domainkey IN CNAME <token2>.dkim.amazonses.com.
<token3>._domainkey IN CNAME <token3>.dkim.amazonses.com.
; --- SendGrid ---
s1._domainkey IN CNAME s1.domainkey.u123456.wl.sendgrid.net.
s2._domainkey IN CNAME s2.domainkey.u123456.wl.sendgrid.net.
; --- HubSpot ---
hs1-<id>._domainkey IN CNAME <id>.dkim.hubspot.com.
hs2-<id>._domainkey IN CNAME <id>.dkim.hubspot.com.
; --- Mailchimp ---
k1._domainkey IN CNAME dkim.mcsv.net.
; --- Zendesk ---
zendesk1._domainkey IN CNAME zendesk1.domainkey.zendesk.com.
zendesk2._domainkey IN CNAME zendesk2.domainkey.zendesk.com.
; --- Postmark ---
pm._domainkey IN CNAME pm.mtasv.net.
; --- Microsoft 365 ---
selector1._domainkey IN CNAME selector1-example-com._domainkey.<tenant>.onmicrosoft.com.
selector2._domainkey IN CNAME selector2-example-com._domainkey.<tenant>.onmicrosoft.com.
; --- Google Workspace (self-managed TXT, generated in Admin console) ---
google._domainkey IN TXT ( "v=DKIM1; k=rsa; "
"p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB..."
"...IDAQAB" )
Every one of these is independent. No lookup budget, no interaction, no PermError — you could add twenty more and nothing in the list would care.
Isolate reputation with per-sender subdomains
Do not send everything from the root domain. Send marketing from mktg.example.com, helpdesk from help.example.com, and keep transactional mail (password resets, receipts) on the root. Because relaxed DMARC alignment treats these as the same organizational domain, they all still pass against your DMARC record — but their reputations are separated. A marketing blast that triggers complaints and tanks mktg.example.com does not poison the deliverability of your password-reset mail on the root.
Give each subdomain its own selectors and, ideally, its own DMARC record. The sp= tag on your organizational DMARC controls the default policy for subdomains that lack one — so you can run the root at p=reject while easing a noisy new marketing subdomain in at sp=quarantine until its reports are clean.
Verify alignment before you trust it
Publishing records is not the same as passing. Confirm three things.
Then send a real message through each provider and read the headers. In Gmail, open the message, click Show original, and find the Authentication-Results: line:
The load-bearing field is `header.d=`, not dkim=pass. If it reads header.d=sendgrid.net while your From is @example.com, you have an authenticated-but-unaligned signature and DMARC will fail. The fix is on the provider side: enable their "domain authentication" / "branded sending" feature so they sign with d= set to your subdomain instead of theirs.
For a second opinion, mail-tester.com gives a /10 score with a per-check breakdown, dkimvalidator.com verifies your key, and learndmarc.com walks the alignment decision visually.
Keys, rotation, and the 255-char TXT trap
2048-bit RSA is the floor in 2026. 1024-bit is deprecated and flagged by a growing number of verifiers. A 2048-bit public key's base64 encoding exceeds the 255-character limit on a single DNS TXT string, so it must be split into multiple quoted strings inside one record — the resolver concatenates them:
text
big._domainkey.example.com. IN TXT ( "v=DKIM1; k=rsa; "
"p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA1x..."
"...9QIDAQAB" )
Miss the split and some DNS providers truncate the key silently, giving you an intermittent dkim=permerror. Ed25519 (k=ed25519; p=<32-byte base64>) sidesteps the length problem entirely and is far more compact, but verifier support is still only partial in 2026. The pragmatic move is to dual-sign: publish both an RSA and an Ed25519 selector so modern verifiers use Ed25519 and everyone else falls back to RSA.
Rotate keys periodically. With CNAME delegation this is a non-event — the provider swaps the key behind the CNAME and you do nothing. For self-managed selectors, publish the new selector, let it propagate, cut sending over to it, then retire the old record after a grace period. And if you care about forwarded mail surviving intact, make sure your mailflow supports ARC, which preserves authentication results across intermediaries.
Ship it: the checklist
Map every sender to a distinct selector; write the list down so nothing signs anonymously.
Delegate via CNAME wherever the provider offers it; only self-manage TXT when forced (Google Workspace, self-hosted).
Confirm each provider signs with an aligned `d=` — your domain or a subdomain of it, never the provider's.
Split marketing / helpdesk / transactional onto separate subdomains to isolate reputation.
Verify with dig and by reading header.d= in a live message, not just dkim=pass.
Use 2048-bit RSA minimum; dual-sign Ed25519 where you can; split long TXT keys into quoted strings.
Set DMARC to p=quarantine, watch rua aggregate reports for a week or two, then move to p=reject; tune subdomains with sp=.
Retire dead selectors after rotation and drop the SPF includes you no longer need.
Once each sender owns its own selector, adding the next SaaS platform is a two-record change that costs nothing and breaks nothing. That is the difference between fighting SPF and scaling with DKIM.