DMARC Engine
Home/Blog/Fix: multiple SPF records
Blog

Fix: multiple SPF records

Two SPF records on one domain force a permanent PermError that silently strips a DMARC route. Here is why it happens, how receivers treat it, and how to merge two records into one compliant record without losing a sender or blowing the 10-lookup limit.

14 April 2026 · 11 min read

Fix: multiple SPF records

Fix: multiple SPF records (the PermError that quietly breaks your mail)

If a receiver tells you your domain has "multiple SPF records", or your monitoring flags an SPF result of permerror, you have hit one of the most common and most misunderstood faults in email authentication. The rule is blunt and there is no grey area in the specification: a domain is permitted to publish exactly one SPF record. Publish two, and a strict receiver will not pick the "best" one or merge them for you. It will throw a permanent error and treat your SPF as unusable.

This article is specifically about the two-records case: why it happens, what a receiver actually does when it sees it, how it silently sabotages DMARC, and the exact mechanics of merging two records into one compliant record without losing any sender. If you want to know first whether your domain has this problem at all, run it through the SPF checker, which flags duplicate v=spf1 TXT records on the spot.

What "multiple SPF records" actually means

SPF (Sender Policy Framework, defined in RFC 7208) is published as a DNS TXT record on the exact domain that appears in the SMTP envelope sender, also called the Return-Path or MAIL FROM. An SPF record is just a TXT string that begins with the version token v=spf1.

The "one record" rule is about how many TXT records on that domain start with v=spf1. You can have plenty of other TXT records on the same name (a Google site verification token, a Microsoft MS= token, a DKIM key on a subdomain, a DMARC record on _dmarc) and none of those count. SPF only counts the TXT records whose value starts with v=spf1.

So this is one record and it is fine:

example.com.   TXT   "v=spf1 include:_spf.google.com include:sendgrid.net ~all"
example.com.   TXT   "google-site-verification=abc123..."
example.com.   TXT   "MS=ms12345678"

And this is the fault, because two separate TXT strings each begin with v=spf1:

example.com.   TXT   "v=spf1 include:_spf.google.com ~all"
example.com.   TXT   "v=spf1 include:sendgrid.net ~all"

RFC 7208, section 4.5, is explicit: if a check finds more than one record starting with v=spf1, the result is permerror. Not a warning, not a fallback to the first one. A permanent error.

Why it is a PermError and not just "ignore the extra one"

It is tempting to assume a receiver would simply use whichever record is more permissive, or combine them. It does not, and the reason is a security one.

SPF is an allow-list of who may send for your domain. If a receiver were free to pick one record and ignore the other, the outcome would be non-deterministic: two receivers could disagree about whether a message is authorised. Worse, an attacker who managed to inject an extra permissive record could weaken your policy if receivers merged records. To keep the decision unambiguous, the specification forces the configuration to be unambiguous. One record, or it is broken.

A permerror is defined as a configuration problem on the sender side, meaning your domain, not the receiver's. It means "this domain's published policy cannot be evaluated", which is a different and worse signal than fail (an explicit "not authorised") or softfail (the ~all "probably not authorised"). Receivers and filters treat permerror as a broken sender, which feeds straight into spam scoring and reputation.

How DMARC turns a PermError into a real failure

Here is the part that catches people out. SPF on its own rarely bounces mail outright. DMARC is where the permerror bites.

DMARC passes when at least one of SPF or DKIM both passes its own check and aligns with the domain in the visible From: header. If your SPF returns permerror, that is not a pass. It contributes nothing to DMARC. Your domain is now relying entirely on DKIM to carry every single message through alignment.

That is survivable while DKIM is signing cleanly on every stream. It becomes an outage the moment any sending service is misconfigured for DKIM, rotates a key you did not republish, or sends an unsigned message. With a healthy single SPF record you would have had two independent ways to pass DMARC. With a permerror, you have one, and you may not even know your safety net is gone.

If your policy is at p=reject or p=quarantine, the consequence of a DKIM hiccup is no longer "lands in spam". It is "rejected at the gateway" or "quarantined". The duplicate SPF record is the hidden reason a single DKIM glitch escalates into lost mail. To see exactly which mechanism is failing on your domain right now, check the DMARC checker alongside the SPF checker, and read how DMARC, SPF and DKIM work together for the alignment mechanics.

How you end up with two SPF records in the first place

Nobody sets out to publish two SPF records. It happens through entirely reasonable, separate actions taken at different times, often by different people. The common routes:

  • Adding a sender without checking for an existing record. You turn on Microsoft 365, the setup wizard says "add v=spf1 include:spf.protection.outlook.com -all", and you paste it as a new TXT record. There was already a Google Workspace record from two years ago. Now there are two.
  • A marketing or transactional tool's onboarding. SendGrid, Mailchimp, Brevo, Klaviyo, HubSpot and similar each show you a full v=spf1 ... include:theirdomain ~all snippet in their docs. Pasting that verbatim, when a record already exists, creates a duplicate instead of adding the one include: you actually needed.
  • Two teams, two changes. IT publishes one for the corporate mail platform; marketing publishes one for the campaign tool. Neither knew the other had touched DNS.
  • A migration leftover. You moved from one host or provider to another and added the new record without removing the old one.
  • The "I'll add a second one to be safe" mistake. Believing more coverage is better, someone adds a second record rather than editing the first.

In every case the fix is the same: there must be one record, and it must contain every legitimate sender.

Merging two records into one compliant record

Merging is mechanical once you understand the anatomy of a record. An SPF record is the version token, followed by a list of mechanisms (the things that authorise senders, like include:, ip4:, a, mx), finished with an all mechanism that sets the default for everyone not matched.

Take these two real-world records:

example.com.   TXT   "v=spf1 include:_spf.google.com ~all"
example.com.   TXT   "v=spf1 include:spf.protection.outlook.com include:sendgrid.net -all"

Step 1: keep one v=spf1 at the very start

A merged record begins with v=spf1 once and only once. You never repeat the version token mid-record.

Step 2: collect every mechanism from both records

From the two records above, the unique authorising mechanisms are:

  • include:_spf.google.com
  • include:spf.protection.outlook.com
  • include:sendgrid.net

Step 3: remove duplicates

If both records contained, say, include:sendgrid.net, you keep it once. Identical ip4: or ip6: entries collapse to a single entry too. Order between include: mechanisms does not affect the result, so you can list them in whatever order is readable.

Step 4: choose a single all mechanism

This is the one judgement call. Your two records may end differently: one with ~all (softfail) and one with -all (hardfail). You get exactly one all in the merged record, and it goes at the very end.

  • -all (hardfail) tells receivers to treat anything not listed as unauthorised. This is the strong, correct end state.
  • ~all (softfail) says "probably not authorised", a safer choice while you are still confirming you have captured every sender.

If you are merging and not yet fully confident the new combined list is complete, use ~all first, watch your DMARC aggregate reports for a couple of weeks to confirm no legitimate source is being missed, then tighten to -all. Never use +all: it authorises the entire internet to send as your domain.

Step 5: publish the single merged record

example.com.   TXT   "v=spf1 include:_spf.google.com include:spf.protection.outlook.com include:sendgrid.net -all"

Step 6: delete the other record

This is the step people forget, and it is the one that actually fixes the fault. Editing one record to be correct while the second still exists leaves you with two records and the same permerror. You must remove the duplicate so that exactly one TXT string starting with v=spf1 remains on the name. After publishing, re-run the SPF checker to confirm it now sees a single, valid record.

The trap waiting on the other side: the 10-lookup limit

Merging duplicate records solves the "multiple records" permerror, but it can move you straight into a different permerror if you are not careful, so it is worth understanding now rather than discovering it next week.

SPF imposes a hard cap of 10 DNS lookups per evaluation. Every include:, a, mx, ptr, exists and redirect mechanism costs at least one lookup, and an include: that itself contains more include: mechanisms costs all of those too. Exceed 10 and you get a permerror, exactly the same error class you were trying to escape.

When you merge two records, you are summing their lookup costs into one. Two records that each used six lookups will, combined, blow straight through the limit. A single include:_spf.google.com already expands to several nested lookups internally; stack three or four big providers and you are close to or over the cap.

So after merging, count the lookups. Mechanisms that count toward the limit:

  • include: (one, plus everything inside it)
  • a and mx (one each, plus a query per resolved record in some cases)
  • ptr (one, and you should avoid ptr entirely as it is deprecated and slow)
  • exists (one)
  • redirect (one, plus the target's own lookups)

Mechanisms that do not cost a lookup: ip4: and ip6:. They are matched directly against the connecting IP with no DNS query, which is why flattening (replacing an include: with the actual ip4: and ip6: ranges it resolves to) is the standard way to stay under the limit. The cost of flattening is maintenance: if a provider changes its IP ranges, a hardcoded ip4: list goes stale and you must update it. The SPF checker reports your live lookup count so you know how much headroom you have, and the SPF section of the SPF product page explains automated flattening that keeps the IP list current for you.

A second, lesser-known cap also applies: the record itself must fit DNS limits. A single TXT string is capped at 255 characters; longer records are split into multiple quoted strings inside one TXT record, which is fine, but the total SPF record should stay well under roughly 512 bytes to be safe. If a merged record is getting that long, that is another signal you should be flattening or consolidating senders rather than appending forever.

A worked example, start to finish

Suppose a domain has accumulated this mess over two years:

acme.co.   TXT   "v=spf1 include:_spf.google.com ~all"
acme.co.   TXT   "v=spf1 include:spf.protection.outlook.com ip4:198.51.100.20 ~all"
acme.co.   TXT   "v=spf1 include:mail.zendesk.com include:_spf.google.com -all"

Three records starting with v=spf1. Every receiver returns permerror. DMARC has been leaning on DKIM alone, and nobody noticed because DKIM happened to be healthy.

To fix it:

  1. List every unique sender mechanism across all three: include:_spf.google.com (appears twice, keep once), include:spf.protection.outlook.com, ip4:198.51.100.20, include:mail.zendesk.com.
  2. Pick one all. The records disagree (~all and -all). Because we are consolidating and want to verify completeness, start with ~all.
  3. Compose the single record:
acme.co.   TXT   "v=spf1 include:_spf.google.com include:spf.protection.outlook.com include:mail.zendesk.com ip4:198.51.100.20 ~all"
  1. Count lookups. Google, Outlook and Zendesk each cost one include: plus their nested lookups; the ip4: costs nothing. Verify the total stays at or under 10 with the SPF checker. If it is over, flatten the heaviest include: to its IP ranges.
  2. Delete the other two records. Only the merged one survives.
  3. Verify. Re-run the SPF checker and confirm a single valid record with a pass-capable evaluation, then watch your DMARC aggregate data via monitoring for a fortnight before tightening ~all to -all.

How to verify the fix properly

Publishing the merged record is not the end. DNS caches, and you need to confirm the old records are genuinely gone everywhere, not just in your registrar's panel.

  • Confirm a single v=spf1 record exists. Use the SPF checker or query the TXT records directly. You want exactly one string starting with v=spf1.
  • Confirm the evaluation is no longer permerror. The checker should report a clean result and a lookup count of 10 or fewer.
  • Confirm every legitimate sender is covered. Send a test from each platform you use and check the message passes SPF, or read your DMARC aggregate reports, which list every source sending as your domain. The DMARC report analyzer turns those XML reports into a readable list of who is sending and whether they align.
  • Watch DMARC for a week or two before tightening all. If a long-forgotten sender appears in reports failing SPF, add its include: or IP before you move to -all.

For a fuller treatment of SPF as a whole, the SPF product page and the glossary cover mechanisms, qualifiers and the lookup limit in more depth, and the requirements page lays out what major mailbox providers now expect.

Common mistakes when fixing this

  • Editing one record and leaving the second. The single most frequent non-fix. Two records remain, the permerror persists.
  • Merging into a record that now exceeds 10 lookups. You trade one permerror for another. Always recount after merging.
  • Putting two all mechanisms in the merged record. Only the first all is evaluated; mechanisms after it are ignored, which can silently drop senders you listed at the end. Exactly one all, at the very end.
  • Wrapping unrelated TXT data into the SPF record. Your Google or Microsoft verification tokens are separate TXT records. Do not fold them into v=spf1.
  • Using +all to "make it work". It makes everything pass, including spoofers. Never publish +all.
  • Forgetting subdomains. SPF is checked on the exact envelope domain. If you send from mail.example.com as well as example.com, each name needs its own correct single record, or an inherited one via the right design.

The practical takeaway

Multiple SPF records are not a style preference a receiver tolerates. They are a permerror, a permanent, specification-mandated failure that silently strips one of your two routes to passing DMARC and leaves you one DKIM glitch away from rejected mail. The fix is conceptually simple: combine every legitimate sender into one record that starts with a single v=spf1, ends with a single all, stays under the 10-lookup limit, and replaces every duplicate so that exactly one v=spf1 TXT record remains.

The fiddly parts are recounting lookups after you merge and making sure you have captured every sender before you tighten ~all to -all. Start by confirming whether you have duplicates at all with the SPF checker, build your clean record, and verify it against your DMARC reports with the DMARC report analyzer.

If you would rather not hand-manage SPF merges, lookup limits and flattening as senders come and go, that is exactly what our hosted SPF and done-for-you monitoring handle: one correct record kept under the limit, with alerts the moment a change risks breaking authentication, so a duplicate never quietly takes your domain back to permerror.

Share

See where your domain stands today

Run a free DMARC scan, then let us take you to enforced p=reject with no email outage.