Siftsmith
← All email error fixes

SPF PermError: what it means and how to fix it

spf=permerror means the receiver couldn’t interpret your published SPF records, so SPF can’t pass for that message. RFC 7208 defines seven ways to get there, from one typo to an include of a vendor that deleted its record. Check your domain below: it walks your whole SPF record tree in your browser and names the cause, the exact term, and the chain of includes that leads to it.

Last verified 2 October 2026.

What the error means

You see it as spf=permerror in the Authentication-Results header of a received message, as Received-SPF: permerror, as <spf><result>permerror</result> in a DMARC aggregate report, or in a bounce. RFC 7208, the SPF standard, defines it in §2.6.7:

“A ‘permerror’ result means the domain’s published records could not be correctly interpreted. This signals an error condition that definitely requires DNS operator intervention to be resolved.”

So it doesn’t fix itself: something in your DNS, or in a record you include, has to change. A receiver that rejects mail for it is told in §8.7: “If the message is rejected during the SMTP transaction for this reason, the software SHOULD use an SMTP reply code of 550 and, if supported, the 5.5.2 enhanced status code”. A bounce reading 550 5.5.2 with an SPF mention is most likely this error.

It is different from temperror, a transient DNS failure while checking. §4.4: “If the DNS lookup returns a server failure (RCODE 2) or some other error (RCODE other than 0 or 3), or if the lookup times out, then check_host() terminates immediately with the result ‘temperror’.” And it is different from none, which is what a domain with no SPF record gets (§4.5: “If the resultant record set includes no records, check_host() produces the ‘none’ result.”).

Find the cause of your SPF PermError

Enter the domain in your envelope sender (Return-Path), or your From: domain if you don’t know it. The check runs in your browser against public DNS: it reads your SPF record, checks the syntax of every record in the tree, follows every include and redirect, counts DNS lookups, and looks up every a, mx and exists name the way a receiver does.

This reads what your DNS publishes right now through Cloudflare’s public resolver, or Google’s when Cloudflare’s fails. A lookup that fails (server error or timeout) shows as “Couldn’t check”: it is never counted as a pass, a void lookup or a PermError. Names built from macros (%{i} and the like) and ptr depend on the sending server, so they are counted as lookups but not resolved. An a name counts as void only when it has neither A nor AAAA records. For the full A–F grade with DMARC, DKIM and MTA-STS, use the DMARC checker.

Every cause of SPF PermError

Two causes make every message permerror, whatever server sent it: a second SPF record on your domain and a syntax error in your own record. The rest, and those two inside an included record, are reached during evaluation, so they hit mail from any server not matched before the faulty term. Receivers evaluate left to right and stop at the first match (§4.6.2).

1. More than 10 DNS lookups

The include, a, mx, ptr and exists mechanisms and the redirect modifier each cost one lookup, counted across every record you include. §4.6.4: “SPF implementations MUST limit the total number of those terms to 10 during SPF evaluation, to avoid unreasonable load on the DNS. If this limit is exceeded, the implementation MUST return ‘permerror’.” This is the most common cause. How to get back under 10.

2. More than 2 void lookups

A void lookup is a term whose DNS query returns no records or NXDOMAIN. Same section: “SPF implementations SHOULD limit ‘void lookups’ to two.” and “Exceeding the limit produces a ‘permerror’ result.” It is a SHOULD, so not every receiver enforces it. Find and fix void lookups.

3. More than one SPF record

Two TXT records beginning with v=spf1 on the same name, usually one per provider. §4.5: “If the resultant record set includes more than one record, check_host() produces the ‘permerror’ result.” It applies at every name in the tree, so an included domain with two records is a permerror for you too. Merge them into one.

4. A syntax error anywhere in the record

§4.6: “The syntax of the record is validated first, and 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 terms before it. The usual ones:

Unknown mechanisminclde:, ipv4:, ip:, a bare domain such as spf.protection.outlook.com without include:, or an en dash pasted in place of a hyphen in –all. The mechanisms are all, include, a, mx, ptr, ip4, ip6 and exists (§4.6.1).
Bad macro§7.3: “A ‘%’ character not followed by a ‘{’, ‘%’, ‘-’, or ‘_’ character is a syntax error.” Also an unknown macro letter (%{x}), a digit of 0 (“If a DIGIT is specified, the value MUST be nonzero.”), and %{c}, %{r} or %{t} outside the exp text, where §7.2 restricts them.
Invalid ip4An octet over 255, a partial address, or an IPv6 address. §5.6: “It is not permitted to omit parts of the IP address instead of using CIDR notations. That is, use 192.0.2.0/24 instead of 192.0.2.”
Invalid ip6Not an RFC 4291 address: two ::, three colons in a row, a group of more than four hex digits, or an IPv4 address (that belongs in ip4:).
Invalid CIDR lengthThe ABNF allows /0 to /32 for IPv4 and /0 to /128 for IPv6, with no leading zeros: ip4:192.0.2.0/33, ip4:192.0.2.0/024 and a/33 are errors. On a and mx, the IPv6 length goes after a double slash (a/24//64).
Duplicate redirect= or exp=§6: “These two modifiers MUST NOT appear in a record more than once each. If they do, then check_host() exits with a result of ‘permerror’.”
Missing or malformed domaininclude: with nothing after it (often a space after the colon, include: _spf.google.com), or a name that isn’t fully qualified. The ABNF requires the domain to end in a dot and a top label that isn’t all digits, so include:localhost and a:192.0.2.10 don’t parse. Strict SPF implementations such as pyspf return permerror for these (“Invalid domain found (use FQDN)”); others look the name up and get nothing.
Arguments on allall takes nothing: -all:example.com or all/24 is an error.
Invisible charactersA tab, a non-breaking space or a smart quote pasted from a document. Terms are separated by plain spaces and may hold only visible ASCII.

5. An include: whose target has no SPF record

An include runs a full SPF check on the target. If the target domain doesn’t exist, §4.3 says “if the DNS lookup returns ‘Name Error’ (RCODE 3, also known as ‘NXDOMAIN’ [RFC2308]), check_host() immediately returns the result ‘none’”, and so does a target with no v=spf1 record. §5.2’s table then turns that into an error:

A recursive check_host() result of:Causes the “include” mechanism to:
permerrorreturn permerror
nonereturn permerror

Microsoft puts it plainly: “If an include: domain doesn’t resolve or has no SPF record, SPF validation returns permerror and messages might be rejected.” The usual story is a vendor you stopped using that deleted its record, or a typo in the include.

6. A redirect= whose target has no SPF record

§6.1: “The result of this new evaluation of check_host() is then considered the result of the current evaluation with the exception that if no SPF record is found, or if the <target-name> is malformed, the result is a ‘permerror’ rather than ‘none’.” A redirect= is used only when no mechanism matched, so it is ignored in a record that has an all.

7. An mx name with more than 10 MX records

§4.6.4: “the evaluation of each ‘MX’ record MUST NOT result in querying more than 10 address records -- either ‘A’ or ‘AAAA’ resource records. If this limit is exceeded, the ‘mx’ mechanism MUST produce a ‘permerror’ result.” Rare, but it happens with mx on a domain that lists many inbound servers. Replace mx with the ip4/ip6 ranges or the include your mail provider documents.

Sources: RFC 7208 §2.6.7, §4.3, §4.4, §4.5, §4.6, §4.6.2, §4.6.4, §5.2, §5.6, §6, §6.1, §7.2, §7.3, §8.7 and Appendix G.3; pyspf spf.py; Microsoft Learn, Set up SPF to identify valid email sources for your Microsoft 365 domain (checked 2 October 2026).

What is not a PermError

What receivers and DMARC do with it

RFC 7208 leaves the handling to the receiver. Its Appendix G.3 notes that a permerror “gives no true indication about the authorized use of the data found in the envelope.” In practice:

Bulk senders to Gmail, Yahoo and Outlook.com need a working SPF record; see what the bulk sender rules require.

Sources: RFC 7208 Appendix G.3; RFC 9989 §4.4.2 and §5.3.5; Microsoft Learn, Set up SPF to identify valid email sources for your Microsoft 365 domain; Google Workspace Help, Troubleshoot SPF issues (checked 2 October 2026).

How to fix it

  1. Find the cause. Run the checker above. Each cause shows the term at fault and the path to it, for example “your record → include:vendor.example”.
  2. Cause in your own record (path “your record”): correct the typo, change a:192.0.2.10 to ip4:192.0.2.10, write partial addresses as full ones with a prefix length, delete the second redirect=, delete the include of a vendor you no longer use, and merge duplicate records into one.
  3. Cause inside an included record (path “your record → include:…”): check that you are using the include from the provider’s current setup guide, and report the error to the provider. If you no longer use the provider, delete the include.
  4. Too many lookups: remove senders you no longer use, replace a and mx with ip4/ip6 for your own servers, and move bulk senders to a subdomain with its own record. Details on the 10-lookup page.
  5. Change the existing record in place rather than adding a new one next to it, so you never publish two.
  6. Check again after the change has propagated (up to the record’s TTL). The checker should show “No PermError found” and 10 lookups or fewer.

For example, this record has two syntax errors, a misspelled mechanism and a second redirect=, so the whole record is a permerror. Fix those and a third cause appears: an include of a newsletter vendor whose domain is gone.

v=spf1 ipv4:192.0.2.10 include:spf.old-newsletter.example include:_spf.google.com redirect=_spf.example.com redirect=_spf.example.com

The fixed record:

v=spf1 ip4:192.0.2.10 include:_spf.google.com ~all

From a terminal, dig +short TXT example.com shows every TXT record on the name (count the ones starting with v=spf1), and dig +short TXT vendor.example shows whether an include target still publishes one.

FAQ

What is the difference between SPF permerror and temperror?

A permerror means the published records can’t be interpreted and stays until you change DNS: too many lookups, a syntax error, two SPF records, an include of a domain with no SPF record. A temperror is a transient DNS failure (a server error or a timeout) while the receiver was checking; RFC 7208 §4.4 says check_host() then terminates with temperror, and a later retry may succeed without any change.

Does SPF permerror make DMARC fail?

It makes SPF unable to pass, so SPF can’t give DMARC an aligned pass. Under RFC 9989 a domain is the SPF-Authenticated Identifier only if its use in MAIL FROM is validated by SPF, and a permerror validates nothing. DMARC can still pass on an aligned DKIM signature; if there is none, the message fails DMARC and your DMARC policy applies. See DMARC alignment failed.

Will mail with an SPF permerror be rejected?

It depends on the receiver. RFC 7208 leaves the handling to local policy and says a receiver that rejects for this reason SHOULD use SMTP code 550 with enhanced status 5.5.2. Microsoft’s documentation says that for more than 10 lookups the receiving system rejects the message with an NDR. Others deliver it and mark SPF as permerror, which still counts as not passing for DMARC.

Why does SPF permerror happen only for some of my mail?

Receivers evaluate the record left to right and stop at the first match. A lookup-limit, void-lookup or include error deep in the record is reached only for mail from servers not matched earlier, so mail from a sender listed first can pass while the rest gets permerror. A syntax error in your own record or two SPF records on your domain are different: they make every message permerror.

Is include=example.com a permerror?

No, and that makes it harder to spot. A term with an equals sign is a modifier, and RFC 7208 §6 says unrecognized modifiers MUST be ignored. So include=example.com authorizes nothing, and SPF doesn’t pass for that provider’s mail, with no error to point at the cause. Write include:example.com.

Checking many domains?

If you manage domains for clients or brands, Email Security Signals checks a whole list and returns one row per domain with SPF, DKIM, DMARC and an A–F grade. SPF problems show up as high-severity issues: spf_too_many_lookups, spf_multiple_records, and spf_permerror for more than 2 void lookups, an mx name with more than 10 MX hosts, an unknown mechanism, or an include or redirect target with no SPF record. $0.02 per graded domain ($20 per 1,000); invalid inputs, domains that don’t exist, DNS lookup failures and duplicates are free.

Check a list on the Apify Store →

Related: fixing SPF “too many DNS lookups”, fixing “too many void lookups”, fixing “multiple SPF records”, fixing an SPF record over 255 characters and the free DMARC, SPF and DKIM checker.