SPF permerror: multiple SPF records, syntax errors, and the other causes

Run the free SPF checker →This article is a how-to. The matching tool checks a live domain.

An SPF permerror means the receiver couldn't interpret the domain's SPF record. RFC 7208 §2.6.7 says it "definitely requires DNS operator intervention to be resolved." In other words, it won't fix itself, and every sender on the domain is affected at once: Microsoft 365, Google, the CRM, the invoicing tool. The best-known cause is exceeding the 10-lookup limit, which we cover in a separate guide. This page covers the other causes. In MSP environments they're at least as common, and usually quicker to fix.

Cause 1: two SPF records on the same name

RFC 7208 §4.5 is unambiguous. The receiver gathers every TXT record at the name that starts with v=spf1, and "if the resultant record set includes more than one record, check_host() produces the 'permerror' result."

It nearly always happens the same way. A vendor's setup page says "add this TXT record," and someone adds a second record instead of editing the first:

```

client.com TXT "v=spf1 include:spf.protection.outlook.com -all"

client.com TXT "v=spf1 include:sendgrid.net ~all"

```

Fix: merge them into one record, with one v=spf1 at the start and one all at the end:

```

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

```

Pick one ending deliberately. Don't keep both. Then confirm the new record doesn't exceed 10 lookups.

Two things don't cause this error. TXT records that don't start with v=spf1, such as verification tokens or old spf2.0/pra Sender ID records, are discarded before the count, so they're harmless. And a record that starts with a typo such as v=spf 1 or v=spf1.0 isn't selected at all. RFC 7208 gives v=spf10 as an example of a version that doesn't match. The domain then has no SPF record (result "none") rather than a permerror. That's arguably worse, because nothing flags it.

Cause 2: a syntax error anywhere in the record

RFC 7208 §4.6: "if there are any syntax errors anywhere in the record, check_host() returns immediately with the result 'permerror', without further interpretation or evaluation." One bad term breaks the whole record, including the parts that were fine.

These are the syntax errors we see most:

What's publishedWhat's wrong
include: spf.protection.outlook.comSpace after the colon, which leaves an include: with no domain
ip4:203.0.113.0/33An IPv4 prefix has to be 0–32 (§5.6)
ip4:203.0.113Not a complete IPv4 address
ip6:2001:db8::/129An IPv6 prefix has to be 0–128
inlcude:_spf.google.comMisspelled mechanism. It's not one of the defined mechanisms, so it's a syntax error
-exists:%(ir).list.exampleA malformed macro. §7.3 calls %( a syntax error
Smart quotes copied from a document, as in “v=spf1 … -all”A curly quote before v=spf1 means the record isn't recognized at all ("none"). Elsewhere in the record, it's a syntax error

Unknown modifiers behave differently. A term in the form name=value that SPF doesn't define "MUST be ignored" (§6), so a stray foo=bar won't cause a permerror. A misspelled mechanism will.

Cause 3: redirect or exp more than once

The two defined modifiers, redirect= and exp=, "MUST NOT appear in a record more than once each." If they do, the result is a permerror (§6). This usually comes from pasting two templates together.

A related rule isn't an error but surprises people: if a record contains all, any redirect= is ignored (§5.1). Also, mechanisms after all are never tested, so anything placed after -all is dead weight.

Cause 4: an include or redirect that points at nothing

Under §5.2, if an include: target has no SPF record (result "none"), the include returns a permerror. That isn't a non-match. For redirect=, §6.1 says the same: no record at the target is a permerror.

That happens when a vendor retires a host name, when someone mistypes a domain, or when a client cancels a service and the vendor deletes its SPF record. The client's record looks fine, but one dead include breaks it. Zendesk's documentation makes the same point: every domain included in the record "must be valid and resolve through DNS successfully."

Fix: remove the dead include, or replace it with the vendor's current value from their documentation.

Cause 5: too many void lookups

RFC 7208 §4.6.4 says implementations SHOULD limit "void lookups" (DNS queries that return no answer or NXDOMAIN) to two, and that exceeding the limit produces a permerror. Two dead includes or a: names on hosts that no longer exist can trigger it. This one is covered in more depth in the lookup-limit guide.

Long records: split strings, not records

A single TXT string can hold at most 255 characters. RFC 7208 §3.3 lets a record be published as several strings, which receivers concatenate "without adding spaces." So you have to split carefully:

```

"v=spf1 include:spf.protection.outlook.com include:_spf.google.com "

"include:sendgrid.net -all"

```

Note the trailing space inside the first string. Without it, the joined record reads _spf.google.cominclude:sendgrid.net, which is a syntax error. Splitting into multiple strings is fine. Splitting into multiple records is Cause 1.

Also, SPF must be published as a TXT record. RFC 7208 §3.1 discontinued the old SPF (type 99) record type, so a type-99 record on its own does nothing.

What the receiver does with a permerror

RFC 7208 §8.7 suggests that a receiver rejecting for this reason use SMTP code 550 with enhanced status 5.5.2, but local policy decides. Some receivers treat a permerror like a fail, and others just stop trusting SPF for the message. For DMARC, a permerror means SPF can't provide an aligned pass, so the message relies entirely on DKIM. If DKIM isn't aligned either, the message fails DMARC.

Find the permerror now

Run the free SPF checker →

It walks the include tree, flags duplicate SPF records and other RFC 7208 errors, counts lookups against the limit of 10, and shows which include in the chain failed.

Founding Partner: $1,999, one time. Find every client domain with a duplicate or broken SPF record before their mail starts failing. Founding Partner covers 50 client domains with full branding (logo and colors), weekly rescans and change alerts, unlimited Security Briefs (150 prospect scans a month), the Grow toolkit, the Policy library, a branded service menu, webhooks, the API, and MCP access (except quote drafts). If you later subscribe to Pro, you pay $299/mo instead of $399 for as long as that Pro subscription stays active. The offer is capped at 50 seats and closes on Dec 15, 2026, or when all 50 seats are claimed, whichever comes first. Proposals and the Service Library require Pro. Founding Partner is non-refundable, except that you're refunded if the seats run out before your payment can be fulfilled.

See Founding Partner details →

FAQ

What does SPF permerror mean?
The domain's SPF record couldn't be interpreted. RFC 7208 says it requires DNS operator intervention, so it won't clear up by itself.
Can a domain have two SPF records?
No. RFC 7208 §4.5 says more than one v=spf1 record at the same name produces a permerror. Merge them into one.
Does a typo in v=spf1 cause a permerror?
Usually not. A record that doesn't start with exactly v=spf1 is discarded, so the domain is treated as having no SPF record ("none"), which nothing flags.
Will an unknown tag break my SPF record?
An unknown modifier (name=value) is ignored. A misspelled or unknown mechanism, such as inlcude:, is a syntax error and causes a permerror.
Why did SPF break when we cancelled a vendor?
If the vendor removed its SPF record, the include: pointing at it now returns "none," which RFC 7208 turns into a permerror. Remove the dead include.
How do I split a long SPF record?
Publish it as multiple strings within one TXT record. Receivers join them without spaces, so leave a space at the end of each string except the last.
SPF PermError: Multiple Records, Syntax Errors and Fixes | Client Posture