SPF · PermError species 2 · DNS

“Multiple SPF records found” — the other PermError, and the merge that fixes it

First published 20 October 2026 · Grounded in RFC 7208, read directly for this guide (§§3.2, 4.5, 3.3, 8.7). The sibling error — too many lookups inside one record — has its own guide here; this page is only about count.

The fast answer

  1. Diagnose precisely (next section): two records beginning v=spf1 at the same name — not two quoted pieces of one record, not records at different subdomains.
  2. Worksheet: list every mechanism from both records; attribute each to a tool; drop the dead; keep one ending (all) — chosen deliberately.
  3. Publish in safe order: screenshot everything → edit one record into the merged record → delete the others in the same session. Never delete-first.
  4. Verify: one v=spf1 at the name, checker clean, and a test message from each real sender passing SPF.

Diagnose: records vs strings (the 60-second distinction)

Run a TXT lookup for your root domain (any DNS checker). Read the answer like this:

What the lookup showsVerdict
Two separate answers, each starting v=spf1The error. Two records. Merge them.
One answer whose value appears as two quoted chunks: "v=spf1 …" "… -all"One legal record. Long values are stored as multiple strings and joined (with no spaces added) by design. Do not “fix” this.
One record at the root, another at mail. or another subdomainAlso legal. SPF is per-name; a sending subdomain carries its own record. The merge in this guide concerns one name only.
A value starting v=spf10 or similar near-missNot an SPF record at all — the spec's own example of what gets discarded during selection. It doesn't cause this error, and it protects nothing.

A warning about vocabulary: some vendor help pages (Cloudflare's included) describe string-splitting as “splitting into multiple records.” When the panel language and the lookup disagree, believe the lookup.

The merge worksheet

  1. Copy both records into a document. Every include:, ip4:, a, mx — line them up.
  2. Attribute each mechanism to its owner. The include names usually confess (_spf.google.com → Workspace; spf.protection.outlook.com → Microsoft 365; a vendor domain → that tool). Unknown mechanism + no tool you still pay for = candidate dead sender.
  3. Drop the provably dead. If you run DMARC reports, they are the evidence: a sender that hasn't appeared in a full reporting period isn't sending. No reports? Keep the mechanism this round — deletion is next quarter's job, with data.
  4. Choose the ending once. Both records' all qualifiers collapse to one. The vendors themselves disagree — Google recommends ~all and calls -all potentially “overly restrictive”; Microsoft publishes -all. Our rule: provably complete inventory → -all; any doubt → ~all now, tighten after a clean reporting period. (+all or ?all in a legacy record never survives a merge — they authorise the world.)
  5. Budget-check the union: count the merged record's lookup cost with the method in our lookup-limit guide. Merging fixes the count error; it can create the other PermError if the union is fat.
Shape of a correct merge (your mechanisms will differ)
v=spf1 include:_spf.google.com include:spf.protection.outlook.com include:<your-tool> ~all
One record. One beginning. One ending. Every living sender inside.

Bad merges — exhibited, so you don't ship one

Botched mergeWhy it fails
Pasting record 2 after record 1 inside one valueEverything after the first all mechanism is dead text by the spec's grammar — the second tool is silently unauthorised, and the stray second v=spf1 mid-value is a syntax error that can keep the PermError alive.
“Merging” a subdomain's record into the rootDifferent name, different country. Subdomain records stay where they live.
Deleting all records, then adding the merged one “fresh”Between delete and add, lookups return none — a window where SPF simply doesn't exist. (One vendor's onboarding doc advises delete-and-regenerate inside its own walled garden; generalised, it's the most destructive version of this fix.) Edit-then-delete has no window.
Deleting the other TXT records in the tidy-upVerification tokens and service proofs share the name. The sweep in this guide touches records beginning v=spf1 only.

“But my mail has been working for years…”

Probably — and the RFC itself explains why, in its own appendix: the result of multiple records is deterministically PermError, but the consequence is each receiver's local policy. Some reject, some deliver with the error annotated, and DMARC can still pass on the DKIM leg — carrying mail that SPF has formally disowned. “Mostly working” and “invalid” have coexisted in your DNS for years; enforcement trends are why it surfaces now, typically as one major receiver starts honouring the letter of the spec.

FAQ

Which record should survive as the one I edit?

The most complete one — fewest changes between it and the merged union. Sentiment and age are not criteria; mechanism coverage is.

How long until the error clears?

As caches expire — typically within your TTL, with full global consistency inside 24–48 hours in the worst case. Verify by fresh lookup, not by clock.

I merged, and now one tool fails SPF.

Its mechanism didn't survive the merge (typo, a dropped include, or the dead-text merge above). Diff the live record against your worksheet — the worksheet existing is what makes this a two-minute repair instead of an archaeological dig.

Sources

RFC 7208 — §3.2 (the MUST NOT), §4.5 (selection and the permerror), §3.3 (multi-string records), §4.4 (per-name lookup), §8.7 & Appendix G.3 (result vs consequence) · Google Workspace Admin Help — SPF guidance (~all recommendation) · Microsoft Learn — SPF records (-all posture) · Cloudflare documentation (string-splitting wording, flagged as found).

Verification note: the RFC texts, the vendor qualifier disagreement, and the strings-vs-records rule were confirmed on 20 October 2026. The exact on-screen wording of Google's Check MX tool for this error could not be captured as text during research (the tool renders client-side), so this guide quotes the specification and lookup behaviour rather than a screenshot's caption.