Skip to content

Why two SPF records are worse than none

Publishing a second SPF TXT record makes every check a permerror. How it happens, how to detect it with dig, and how to merge records safely.

Koltrix Team4 min read
A magnifying glass on a white table
Photo by Markus Winkler on Unsplash
On this page(7 sections)
  1. Why two records break everything
  2. How it happens
  3. Detecting it
  4. Merging records correctly
  5. Publishing the change safely
  6. Preventing it from happening again
  7. Key takeaways

A domain with no SPF record gets a neutral result. A domain with two SPF records gets a permanent error on every single check.

Of all the ways to break email authentication, this one is the easiest to cause and the most expensive to ignore.

Why two records break everything

RFC 7208 is explicit: when a receiver queries TXT records for a domain and finds more than one that begins with v=spf1, the SPF result is permerror. The receiver does not try to merge them, pick the first, or pick the one that matches. It stops.

A permerror means the domain's SPF policy cannot be evaluated. For DMARC purposes it counts as SPF not passing. If your DKIM signatures are aligned, DMARC may still pass. If any of your mail streams depend on SPF alone, they now fail authentication everywhere, and some receivers will reject them outright.

Compare that with publishing nothing. No SPF record gives a none result, which receivers handle as an absence of information. Two records is strictly worse than zero.

How it happens

Nobody sets out to publish two SPF records. It usually happens in one of these ways:

  • A vendor's setup guide says "add this TXT record." Someone pastes v=spf1 include:spf.vendor.example ~all as a new record instead of adding the include to the existing one.
  • A DNS migration duplicates records. Moving zones between providers, an import tool copies the old record while someone also creates a new one by hand.
  • Two teams own DNS. Marketing adds their platform; engineering adds the transactional provider; neither looks at what is already there.
  • A registrar adds a default. Some registrars and hosting panels create an SPF record automatically when you enable their mail product.
  • Leftover SPF RR type records. RFC 4408 defined a dedicated SPF record type (type 99). RFC 7208 deprecated it, and receivers should query TXT. A stale type-99 record does not cause a permerror on its own, but it is a sign that the zone has not been reviewed in a long time and is worth cleaning up.

Detecting it

Query all TXT records on the domain and look for more than one that starts with v=spf1:

dig +short TXT example.com

A broken result looks like this:

"google-site-verification=abc123..."
"v=spf1 include:_spf.workspace.example ~all"
"v=spf1 include:spf.sender.example ~all"

Other TXT records (domain verification tokens, for example) are fine. Only records beginning with v=spf1 count, and the match is case-insensitive.

You can also see the effect in message headers. A receiver's Authentication-Results header will show something like spf=permerror with a reason mentioning multiple records. DMARC aggregate reports will show SPF results of permerror across every source.

Check each subdomain you send from as well. bounce.example.com and example.com are separate names with separate records, and each can only have one.

Merging records correctly

The fix is a single record that contains every mechanism from both, with one all at the end.

Two broken records:

v=spf1 include:_spf.workspace.example ip4:192.0.2.10 ~all
v=spf1 include:spf.sender.example -all

One merged record:

v=spf1 include:_spf.workspace.example include:spf.sender.example ip4:192.0.2.10 ~all

Work through these steps when merging:

  1. Collect every mechanism from each record, removing exact duplicates.
  2. Pick one all qualifier. If the two records disagree (~all versus -all), choose deliberately. While you are unsure that every sender is listed, ~all is the safer choice.
  3. Put all last. Anything after it is ignored.
  4. Recount lookups. Merging two records that were each within limits can push the combined record over ten DNS-querying terms. Count includes recursively before publishing.
  5. Check for redirect=. A record with redirect= hands evaluation to another domain when nothing matches. It cannot meaningfully coexist with an all mechanism, since all always matches first. If one of the records used redirect=, decide which model you want.
  6. Delete the old records in the same change window you publish the new one.

Publishing the change safely

The order of operations matters a little, because resolvers cache records for the TTL.

  • If possible, lower the TTL on the existing records first and wait for the old TTL to expire.
  • Publish the merged record and delete the duplicates in one edit, if your DNS provider supports atomic changes.
  • If it does not, delete one duplicate first. A brief window with one incomplete record is better than any window with two.
  • Verify from an outside resolver, not just your provider's dashboard:
dig +short TXT example.com @1.1.1.1
dig +short TXT example.com @8.8.8.8

Then send a test message to a mailbox you control at a major provider and read the Authentication-Results header to confirm spf=pass.

Preventing it from happening again

The root cause is almost always process, not knowledge. A few habits help:

  • Keep DNS in version control. Whether you use infrastructure-as-code or simply a reviewed zone file, a pull request makes a second v=spf1 line visible to a reviewer.
  • Add a CI check. A tiny script that fails if any name has more than one TXT value starting with v=spf1 catches the mistake before it ships.
  • Name an owner for email DNS. One person or team approves changes to SPF, DKIM and DMARC records.
  • Rewrite vendor instructions. When a vendor says "add this TXT record," translate that internally to "add this include to our existing SPF record."
  • Watch DMARC reports. A sudden jump in SPF permerrors across all sources is the signature of a duplicate record.

Here is a minimal check you can run in CI or a cron job:

for name in example.com bounce.example.com news.example.com; do
  n=$(dig +short TXT "$name" | grep -ci '"v=spf1')
  if [ "$n" -gt 1 ]; then
    echo "ERROR: $name has $n SPF records"; exit 1
  fi
done

Key takeaways

  • More than one v=spf1 TXT record on the same name is a permerror for every message, worse than having no record.
  • It usually comes from vendor instructions taken literally, DNS migrations or uncoordinated teams.
  • Merge into one record with every mechanism and a single all at the end, then recount DNS lookups.
  • Verify from public resolvers and a real test message.
  • Version control and a simple automated check stop it from recurring.

Start with Koltrix

Your domain, one inbox, and an API that sends.

A team inbox where AI sorts and drafts (nothing is sent without your click), plus the transactional API and SMTP relay your product sends with. 7 days free, no card.

SharePost on XLinkedIn
  • An old padlock hanging on a wooden fence
    Deliverability

    Locking down domains that never send email

    Unused domains are easy spoofing targets. Publish a null MX, a deny-all SPF record and a reject DMARC policy so nobody can send mail as them.

    4 min read

  • A row of six numbered mailboxes on a wooden rail in front of dense green plants
    Deliverability

    SPF, DKIM and DMARC in plain English

    Three DNS records decide whether your email arrives. What each one actually does, what to publish, and the four mistakes that cause most of the support tickets.

    4 min read