Flattening replaces an include with the addresses behind it. It works, it is the most commonly reached-for fix, and it converts a loud failure into a silent one. This lesson is the mechanics and the obligation it creates; the trade-off in full is in the reference article at the end.
What it does
before: v=spf1 include:_spf.vendor.example include:_spf.other.example -all
2 lookups, plus whatever those records spend
after: v=spf1 ip4:198.51.100.0/24 ip4:203.0.113.0/25 ip4:192.0.2.64/26 ... -all
0 lookupsYou resolve the include tree once, collect every literal range it authorises, and publish those directly. The result is functionally identical at the moment you generate it.
Doing it correctly
- Expand recursively. Includes contain includes. A partial expansion silently drops senders.
- Keep both address families. Dropping
ip6breaks any sender that reaches the receiver over IPv6, which is an increasing share and fails in a way that looks random. - Preserve the terminal qualifier. The flattened record ends the same way the original did.
- Mind the 255-character string limit. A long flattened record is split into multiple strings inside one TXT record, joined with nothing between them. A space introduced at a join corrupts an address.
- Do not flatten what you do not need to. Flatten the most expensive include, re-count, and stop when you are under with headroom.
What it costs you
| include | flattened | |
|---|---|---|
| Lookups | Spends budget. | Free. |
| When the vendor renumbers | You are updated automatically. | Your record is now wrong and nothing says so. |
| Failure mode | Loud — permerror is visible in reports. | Silent — legitimate mail starts failing SPF one host at a time. |
| Ongoing work | None. | Regeneration and verification, forever. |
A flattened record is a cache with no expiry
The moment you flatten, you have taken a copy of someone else’s operational data and published it as fact. They will change it — platforms renumber, add regions, migrate providers — and they have no idea you hold a copy. There is no notification and no error; mail from the new addresses simply stops passing.
So flattening by hand, once, is not a fix. It is a fix plus a standing obligation to re-generate and verify. If nothing is going to do that on a schedule, do not flatten — delegate instead, which is the next lesson but one.
When it is the right answer
- The vendor’s ranges are genuinely stable and small.
- You have automation that regenerates and diffs on a schedule, and alerts on change.
- Delegation and removal have already been tried and were not sufficient.
If all three are not true, you are choosing a silent failure mode over a loud one to buy headroom you could have found another way.