SPF errors

Diagnose and fix SPF permerror, the 10 DNS lookup limit, softfail vs fail, void lookups, and common syntax mistakes. Practical commands and real-world examples included.

SPF permerror: too many DNS lookups

What your aggregate report shows

When an SPF evaluation exceeds the DNS lookup limit, the receiving mail server records a permanent error. In your DMARC aggregate report XML, you will see this:

<result>permerror</result>

A permerror means the receiver gave up evaluating your SPF record entirely. It did not pass, it did not fail. It broke. The receiver treats this the same as having no SPF record at all, which means your DMARC SPF check fails. If DKIM also fails or is missing, your message fails DMARC authentication.

The RFC 7208 limit

RFC 7208 section 4.6.4 imposes a hard limit of 10 DNS mechanism lookups during SPF evaluation. The mechanisms that count toward this limit are:

  • include
  • a
  • mx
  • redirect
  • exists

The mechanisms that do not count are:

  • ip4
  • ip6
  • all

This is not 10 mechanisms in your record. It is 10 total DNS queries, including recursive lookups triggered by nested include statements. That distinction matters because a single include can consume several lookups on its own.

Diagnosis: counting your SPF lookups

Check your current SPF record

Start by retrieving your domain's SPF record:

dig TXT example.com +short

Look for the record that starts with v=spf1. A typical record might look like this:

v=spf1 include:_spf.google.com include:spf.protection.outlook.com include:sendgrid.net include:spf.mandrillapp.com include:mail.zendesk.com ~all

That is 5 include mechanisms at the top level. Each one costs at least 1 lookup, plus any nested lookups inside it.

How include triggers recursive lookups

Each include mechanism resolves to another SPF record, which may contain its own include, a, or mx mechanisms. These all count toward your total of 10.

Take Google Workspace as an example. When you include _spf.google.com, the receiver resolves it:

dig TXT _spf.google.com +short
"v=spf1 include:_netblocks.google.com include:_netblocks2.google.com include:_netblocks3.google.com ~all"

That is 3 more includes. Each of those resolves to a record containing only ip4 and ip6 mechanisms (which do not count), so the recursion stops there. Total cost for Google Workspace: 4 lookups (1 for _spf.google.com itself, plus 3 for its nested includes).

Common providers and their lookup costs

Here are the typical lookup costs for popular email services. These can change when providers update their SPF records, so verify them periodically.

  • Google Workspace: 4 lookups (include:_spf.google.com)
  • Microsoft 365: 2 lookups (include:spf.protection.outlook.com)
  • Salesforce: 2 lookups (include:_spf.salesforce.com)
  • SendGrid: 1 lookup (include:sendgrid.net)
  • Mailchimp / Mandrill: 2 lookups (include:spf.mandrillapp.com)
  • HubSpot: 1 lookup (include:spf.hubspot.com)
  • Zendesk: 1-2 lookups (include:mail.zendesk.com)
  • Amazon SES: 1 lookup (include:amazonses.com)

A real-world example that breaks

Here is a record that looks reasonable at first glance:

v=spf1 include:_spf.google.com include:spf.protection.outlook.com include:sendgrid.net include:spf.mandrillapp.com include:mail.zendesk.com include:_spf.salesforce.com ~all

Count the lookups: Google (4) + Microsoft (2) + SendGrid (1) + Mandrill (2) + Zendesk (1) + Salesforce (2) = 12 lookups. That exceeds the limit by 2, and every SPF evaluation will result in permerror.

To count manually, resolve each include recursively with dig and tally every DNS query that involves an include, a, mx, redirect, or exists mechanism.

Solution: reducing SPF lookups

Remove unused include mechanisms

The fastest fix is to audit which services actually send email for your domain. Check your DMARC aggregate reports to see which source IPs are sending mail. If you decommissioned a marketing tool six months ago but left its include in your SPF record, remove it.

Common culprits: old CRM systems, decommissioned marketing platforms, trial accounts for email services you never activated, and includes added by former employees who no longer manage the domain.

Use subdomain delegation

Instead of cramming every service into the SPF record for your root domain, delegate specific services to subdomains. For example:

  • marketing.example.com for your marketing ESP (Mailchimp, HubSpot)
  • support.example.com for your helpdesk (Zendesk, Freshdesk)
  • notify.example.com for transactional email (SendGrid, Amazon SES)

Each subdomain gets its own SPF record with only the includes it needs. The root domain's SPF record stays lean. Configure each service to use its designated subdomain as the envelope sender (Return-Path), and set up DMARC records on each subdomain as needed.

SPF flattening

SPF flattening replaces include mechanisms with the actual ip4 and ip6 addresses they resolve to. Since IP mechanisms do not count toward the 10-lookup limit, this can dramatically reduce your lookup count.

For example, instead of include:_spf.google.com (4 lookups), you would list the actual IP ranges that Google uses to send email.

Risks of flattening: IP addresses change. If Google, Microsoft, or any other provider adds, removes, or rotates IP ranges and you do not update your flattened record, legitimate mail will fail SPF. You must re-flatten regularly, ideally on an automated schedule.

For a detailed explanation of how flattening works and when to use it, see our SPF record flattening guide.

SPF softfail vs fail

Understanding ~all and -all

The all mechanism at the end of your SPF record defines what happens when a sending IP does not match any of the authorized mechanisms.

  • ~all (tilde) = softfail. The IP is not authorized, but the domain owner is not confident enough to say "reject it." The message is accepted but marked.
  • -all (hyphen) = fail. The IP is definitively not authorized. The receiving server may reject the message outright.

How softfail interacts with DMARC

This is one of the most misunderstood aspects of email authentication. SPF softfail (~all) passes the DMARC SPF check. DMARC only cares whether the SPF result is "pass" or "not pass." An SPF softfail result counts as "not pass," but DMARC evaluates the SPF authentication result separately from the SPF policy result.

More precisely: for DMARC purposes, SPF needs to return a "pass" result. Both ~all and -all will cause a "fail" or "softfail" result for unauthorized senders. Neither ~all nor -all will cause SPF to "pass" for an unauthorized sender. The DMARC SPF check fails in both cases for unauthorized senders, and passes in both cases for authorized senders. The difference between ~all and -all only matters for receivers that enforce SPF independently of DMARC.

When to use -all vs ~all

Use ~all when:

  • You are still deploying DMARC and not sure you have identified all legitimate senders
  • You rely on email forwarding (forwarded messages fail SPF because the forwarding server's IP is not in your SPF record)
  • You want to be cautious during initial rollout

Use -all when:

  • You have a complete inventory of all services that send email on your behalf
  • Your DMARC policy is at p=reject and you have confirmed no legitimate mail is being blocked
  • You want maximum protection against spoofing for receivers that enforce SPF independently

SPF record syntax errors

Missing v=spf1 prefix

Every SPF record must begin with v=spf1 exactly. No space before it, no variation. If the prefix is missing or misspelled, the entire record is ignored.

# Wrong - missing prefix
"include:_spf.google.com ~all"

# Wrong - misspelled
"v=spf2 include:_spf.google.com ~all"

# Correct
"v=spf1 include:_spf.google.com ~all"

Multiple SPF records

RFC 7208 section 3.2 is explicit: a domain must not have more than one SPF record. If a DNS lookup returns two TXT records that both start with v=spf1, the result is a permerror.

This commonly happens when a new service provider tells you to "add this SPF record" and you create a new TXT record instead of modifying the existing one. Always merge new mechanisms into your single existing SPF record.

Check for duplicates:

dig TXT example.com +short | grep "v=spf1"

If you see more than one line of output, you have a problem.

The +all mistake

Using +all at the end of your SPF record means "every IP address on the internet is authorized to send email for my domain." This is almost never what you want. It effectively disables SPF entirely.

# Dangerous - authorizes everyone
v=spf1 include:_spf.google.com +all

# Correct
v=spf1 include:_spf.google.com ~all

If you see +all in a production SPF record, treat it as a security incident and fix it immediately.

The deprecated ptr mechanism

RFC 7208 section 5.5 explicitly discourages the use of the ptr mechanism. It requires the receiver to do a reverse DNS lookup for every sending IP, then a forward lookup on the result, which is slow and unreliable. Many receivers skip ptr evaluation entirely or treat it as a lookup failure.

If you find ptr in your SPF record, replace it with explicit ip4 or ip6 mechanisms for the same servers.

Other common typos

  • include _spf.google.com (missing colon after include)
  • include:spf.google.com (wrong hostname, should be _spf.google.com)
  • ip4: 192.168.1.0/24 (space after the colon)
  • v=spf1 ... all (bare all without a qualifier defaults to +all)

Use the DMARCTrust SPF generator to build a syntactically correct record from scratch, or validate an existing one with our domain checker.

Void lookups

What void lookups are

A void lookup occurs when a DNS query made during SPF evaluation returns either NXDOMAIN (the domain does not exist) or an empty answer (the domain exists but has no relevant records). RFC 7208 section 4.6.4 limits void lookups to 2 per SPF evaluation. If a third void lookup occurs, the result is permerror.

Why they happen

The most common cause is an include pointing to a domain that no longer publishes an SPF record. This can happen when:

  • A service provider changes their SPF hostname and you still reference the old one
  • A domain in your SPF record expires or is deleted
  • An a or mx mechanism references a domain with no A/MX records

How to find void lookups

Resolve each domain referenced in your SPF record and check for NXDOMAIN or empty responses:

# Check if an included domain has an SPF record
dig TXT _spf.oldprovider.com +short

# If the response is empty or you get NXDOMAIN:
# ;; ->>HEADER<<- opcode: QUERY, status: NXDOMAIN
# That is a void lookup.

Walk through every include, a, mx, and exists mechanism in your SPF record (including nested ones) and verify each referenced domain resolves correctly.

Prevention

Monitor SPF with DMARC reports

DMARC aggregate reports tell you exactly which messages are failing SPF and why. If you are not already receiving reports, create a DMARCTrust account and publish a DMARC record with a rua tag pointing to your reporting address. Within 24 to 48 hours, you will start seeing data about every SPF evaluation for your domain.

Audit SPF records quarterly

Set a calendar reminder to review your SPF record every quarter. Check for:

  • Includes for services you no longer use
  • Total lookup count (stay at or below 10)
  • Void lookups (domains that no longer resolve)
  • Syntax correctness (one record, correct prefix, valid mechanisms)

Use the SPF generator

The DMARCTrust SPF generator lets you build an SPF record by selecting your email providers from a list. It calculates the lookup count automatically and warns you if you are approaching the limit.

Test changes safely

When making changes to your SPF record, follow this sequence:

  1. Use ~all (softfail) during testing so legitimate mail is not rejected
  2. Publish the new record and wait at least 48 hours for DNS propagation and report collection
  3. Review your DMARC aggregate reports for unexpected SPF failures
  4. Once you are confident that all legitimate senders are covered, switch to -all (fail)

This approach lets you catch mistakes before they impact mail delivery. If something goes wrong, switching back to ~all is a one-line DNS change.

Was this page helpful? Send us feedback

Last updated: March 2026