# SPF Record Too Long: Fix the 10-Lookup Limit with Microsoft 365

> Fix SPF permerror caused by more than 10 DNS lookups. Count lookups, remove unused includes, move senders to subdomains and flatten only where it is safe, without breaking Microsoft 365 mail.

Source: https://www.epifive.com/blog/spf-record-too-long-10-lookup-limit · Published 2026-10-25 · EpiFive

## Key takeaways

- SPF allows at most 10 DNS lookups per evaluation, and include, a, mx, ptr, exists and redirect count while ip4, ip6 and all do not.
- Nested include statements add lookups, so fewer than 10 includes does not guarantee fewer than 10 lookups.
- Moving third-party senders to subdomains gives each one its own SPF record and its own lookup budget.
- Do not flatten include:spf.protection.outlook.com because Microsoft's sending IP addresses change frequently.
- A domain can have only one SPF TXT record, and publishing two causes a permerror.

An SPF record fails with a permanent error when evaluating it needs more than 10 DNS lookups, and the message may be rejected with "The message exceeded the hop count" or "The message required too many lookups". To fix it, count the lookups your record really causes, delete includes for services you no longer use, move third-party senders to subdomains that have their own SPF record, and replace an include with `ip4:` ranges only for vendors whose addresses are stable. Do not flatten `include:spf.protection.outlook.com`.

## Who this is for and what you will have at the end

This guide is for administrators who use Microsoft 365 for mail from a custom domain and have added marketing tools, ticketing systems, CRMs or on-premises servers to the same SPF record, and who now see SPF permerrors or bounces.

At the end you will have:

- A clear picture of what counts toward the limit and what does not.
- A method to count lookups, including nested ones.
- A ranked list of fixes, from least to most risky.
- A corrected record, and a way to verify it.

## What the 10-lookup limit is

SPF is defined in RFC 7208. Section 4.6.4 requires that implementations cap the counted terms in an SPF evaluation at 10, and that exceeding the cap produces a `permerror`. Per section 2.6.7, a permerror means the published records could not be correctly interpreted and DNS operator intervention is needed.

The terms that count are:

| Counts toward 10 | Does not count |
| --- | --- |
| `include:` | `ip4:` |
| `a` | `ip6:` |
| `mx` | `all` |
| `ptr` | |
| `exists` | |
| `redirect` modifier | |

Microsoft's own page lists `include:`, `a`, `mx`, `exists` and `redirect` as counting, and `ip4:`, `ip6:` and `all` as not counting. The RFC also counts `ptr`, so use the RFC as the authority. The RFC adds two details:

- Each MX record queried counts toward the 10, and evaluating one MX record must not query more than 10 address records, or the `mx` mechanism returns permerror.
- Implementations should limit void lookups (answers that are empty or NXDOMAIN) to 2. Exceeding it produces a permerror, so a stale `include:` pointing to a domain that no longer has an SPF record can cause trouble on its own.

### Nested includes are the usual culprit

Each `include:` costs at least one lookup, and the record it points to is evaluated too. Microsoft's example: an `include:` that points to another SPF record containing three more `include:` statements adds four lookups in total. So having fewer than 10 `include:` statements does not guarantee fewer than 10 lookups.

### Why it fails only sometimes

Receivers evaluate sources from left to right and stop when the sending source matches. An SPF record that could cause more than 10 lookups can still pass for sources listed early, and fail for sources listed late. Microsoft notes that some destinations may not go deep enough in the record to hit the error. That is why the symptom looks random: one sender's mail works, another's bounces.

## Prerequisites

- Access to the DNS host for your domain. Microsoft 365 has no admin portal or PowerShell cmdlet for managing SPF records, so you edit the TXT record at your registrar or DNS hosting service.
- A list of every service that sends mail as your domain. If you do not have one, start with the bounce messages, message headers and, if you have it, DMARC reports. Microsoft recommends a DMARC reporting service for identifying email sources and SPF failures.
- A copy of the current SPF record, so you can roll back.

## Step 1: Read the current record

Query the TXT record for your domain. On Windows PowerShell:

```powershell
Resolve-DnsName -Name contoso.com -Type TXT | Where-Object { $_.Strings -match '^v=spf1' } | Select-Object -ExpandProperty Strings
```

On Linux or macOS:

```bash
dig +short TXT contoso.com
```

Confirm there is exactly one record that starts with `v=spf1`. Multiple SPF TXT records for the same domain cause SPF to return `permerror`, because the receiver cannot decide which to evaluate. If you find two, merge them into one.

A typical over-grown record looks like this (the includes are placeholders):

```text
v=spf1 include:spf.protection.outlook.com include:mail.crm.example include:send.marketing.example include:tickets.support.example include:old.newsletter.example mx a ip4:203.0.113.10 -all
```

## Step 2: Count the lookups

Walk the record and count each counted term, then open every `include:` target and count the terms inside it, and so on. Microsoft's page says some free online tools show your SPF record and even calculate the lookups required. Use one to confirm your manual count, but check the result against the rules above because tools differ on `ptr` and void lookups.

A worksheet helps:

| Term | Lookups at this level | Nested lookups | Total |
| --- | --- | --- | --- |
| `include:spf.protection.outlook.com` | 1 | Count what its record contains | |
| `include:mail.crm.example` | 1 | Count | |
| `mx` | 1 (plus each MX record queried) | | |
| `a` | 1 | | |
| `ip4:203.0.113.10` | 0 | | 0 |

If the total is above 10, continue.

## Step 3: Apply the fixes in order

### Fix 1: Remove what you no longer use

Delete includes for services that no longer send mail as your domain. Old newsletter tools, trial products and decommissioned ticketing systems are the common ones. This costs nothing and cannot break current mail, provided your list of senders is accurate.

Also check for includes that no longer resolve. Microsoft warns that if an `include:` domain does not resolve or has no SPF record, SPF validation returns `permerror` and messages might be rejected. Verify each include by querying the TXT record of the referenced domain.

### Fix 2: Remove `mx` and `a` if they are not needed

Each costs a lookup, and `mx` costs one for each MX record it queries. If your mail is sent only by Microsoft 365 and listed services, you probably do not need them. Check first, because an on-premises server that sends mail may rely on them. If you do need an on-premises server, it is usually cheaper to list its public address with `ip4:`, which does not count.

### Fix 3: Move third-party services to subdomains

Microsoft recommends using a subdomain such as `marketing.contoso.com` for email services that you do not directly control, so problems with those senders do not affect the reputation of your main domain. It is also the best fix for the lookup limit, because each subdomain has its own SPF record and its own budget of 10 lookups.

The steps are:

1. Choose a subdomain for the service.
2. Configure the service to send from that subdomain, following its documentation.
3. Publish an SPF TXT record for the subdomain.
4. Remove the service's include from the main domain's record.

Following Microsoft's example for a bulk mailing service:

```text
contoso.com            TXT  "v=spf1 ip4:192.0.2.10 include:spf.protection.outlook.com -all"
marketing.contoso.com  TXT  "v=spf1 include:servers.adatum.com include:spf.protection.outlook.com -all"
```

Remember that the record for `contoso.com` does not cover `marketing.contoso.com`, so every subdomain that sends mail needs its own SPF TXT record. Only include `spf.protection.outlook.com` in the subdomain's record if that subdomain also sends from Microsoft 365.

### Fix 4: Consolidate services

If two tools do the same job, retire one. Fewer vendors means fewer includes and fewer things to monitor.

### Fix 5: Replace an include with IP ranges (flattening), carefully

SPF flattening replaces `include:` with the resolved IP addresses, which cost nothing because `ip4:` and `ip6:` do not count. Microsoft's guidance:

- Appropriate for vendors with stable, documented IP ranges, services that rarely change addresses, and when you are at or over the limit and the other fixes are not feasible.
- Do not flatten Microsoft 365 (`include:spf.protection.outlook.com`), because its sending infrastructure uses dynamic IP addresses that change frequently. Also do not flatten any cloud service with dynamic or frequently changing addresses.
- If you flatten, document what you replaced and when, monitor vendor documentation for address changes, review the flattened entries at least quarterly and test SPF after each change.

Non-Microsoft flattening services can automate tracking vendor changes if manual maintenance is impractical. Evaluate them against your own risk tolerance, because they place a third party in control of part of your DNS.

## Step 4: Check the syntax

A record can fail even with few lookups if it is malformed. Microsoft lists these common mistakes:

| Incorrect | Problem | Correct |
| --- | --- | --- |
| `v=spf1 include:spf.protection.outlook.com. -all` | Trailing period after the domain | `v=spf1 include:spf.protection.outlook.com -all` |
| `v=spf1 include=spf.protection.outlook.com -all` | Equals sign instead of colon | `v=spf1 include:spf.protection.outlook.com -all` |
| `v=spf1 include: spf.protection.outlook.com -all` | Space after the colon | `v=spf1 include:spf.protection.outlook.com -all` |

The minimum Microsoft 365 record for a domain that sends only from Microsoft 365 is:

```text
v=spf1 include:spf.protection.outlook.com -all
```

Microsoft recommends `-all` (hard fail) for Microsoft 365 domains when you also use DKIM and DMARC. With `~all` (soft fail), the DMARC policy is effectively ignored for SPF failures if the message has no DKIM signature. Do not use `?all` in production. The GCC High and DoD clouds and Microsoft 365 operated by 21Vianet use different include values, which are listed in Microsoft's SPF page.

Microsoft also recommends a TTL of at least 3600 seconds on SPF TXT records to avoid DNS lookup timeouts.

## Step 5: Publish and verify

1. Lower your change risk by editing the record outside your busiest sending hours.
2. Replace the existing record rather than adding a second one.
3. Wait for the TTL of the old record to expire.
4. Query the record again with the command from Step 1 and recount the lookups.
5. Send a test message from each sender, including each third-party service, to a mailbox you can inspect, and check the `Authentication-Results` header for `spf=pass`.
6. Watch DMARC reports and bounces for a week for senders you missed.

If you have not yet set up DKIM and DMARC, read [SPF, DKIM and DMARC setup for Microsoft 365](https://www.epifive.com/blog/spf-dkim-dmarc-microsoft-365-setup) and [fixing a missing DKIM CNAME in Microsoft 365](https://www.epifive.com/blog/dkim-cname-missing-microsoft-365-fix). SPF alone is not enough for protection against spoofing.

## Troubleshooting

| Symptom | Likely cause | Fix |
| --- | --- | --- |
| Bounce says the message exceeded the hop count or required too many lookups | More than 10 lookups | Work through the fixes above |
| Permerror but fewer than 10 includes | Nested lookups or `mx` with many records | Recount including nested records |
| One sender fails and others pass | Failing source is deep in the record | Reorder or reduce, and recount |
| Permerror after adding a service | Second SPF TXT record published | Merge into one record |
| Permerror with a short record | Typo or include without an SPF record | Check the syntax table and query each include |
| Works at some receivers, not others | Receivers evaluate to different depths | Reduce lookups, not just reorder |
| Subdomain mail fails SPF | No SPF record for that subdomain | Publish a record for it |

## Checklist

- One SPF TXT record per domain and per sending subdomain.
- A documented list of every sender, with an owner for each.
- Ten lookups or fewer, counted including nested includes.
- Third-party senders on subdomains, not the main domain.
- No flattening of `include:spf.protection.outlook.com`.
- `-all` at the end, with DKIM and DMARC in place.
- A quarterly review of any flattened ranges.

## References

- [Set up SPF to identify valid email sources for your Microsoft 365 domain (Microsoft Learn)](https://learn.microsoft.com/en-us/defender-office-365/email-authentication-spf-configure)
- [RFC 7208: Sender Policy Framework, section 4.6.4 DNS Lookup Limits](https://datatracker.ietf.org/doc/html/rfc7208#section-4.6.4)

## Frequently asked questions

### What is the SPF 10 DNS lookup limit?

RFC 7208 caps the number of DNS-querying terms in an SPF evaluation at 10. The include, a, mx, ptr and exists mechanisms and the redirect modifier count, and ip4, ip6 and all do not. If the limit is exceeded, SPF returns a permanent error (permerror).

### Does a long SPF record always fail?

No. Receivers evaluate sources from left to right and stop when the sending source matches. A record that could exceed 10 lookups may still work for some senders, but fail for sources listed later, which makes the problem intermittent and hard to spot.

### Should I flatten include:spf.protection.outlook.com?

No. Microsoft advises against flattening the Microsoft 365 include, because its sending infrastructure uses dynamic IP addresses that change frequently. Flatten only vendors with stable, documented IP ranges, and review the result at least quarterly.

### Can I have two SPF records to get more lookups?

No. Only one SPF record is allowed per domain or subdomain, and multiple SPF TXT records cause a permerror. To get more lookup budget, move services to subdomains, because each subdomain has its own SPF record and its own limit of 10.

