The SPF Include Mechanism, Explained Properly
What include: actually does during SPF evaluation, why it's not the same as simply copy-pasting another domain's IP ranges, and where it commonly goes wrong.
What include: Actually Evaluates
The include mechanism is the piece of SPF syntax that lets one domain delegate a portion of its sender authorization to another domain's record, and it's by far the most common mechanism in real-world SPF records because almost nobody sends all of their mail directly from infrastructure they own. When a receiving server hits include:example-vendor.com during evaluation, it doesn't just note the domain name and move on β it performs a full DNS lookup for example-vendor.com's own SPF TXT record, then evaluates that entire record's mechanisms exactly as if they'd been written inline, purely to determine whether the current sending IP produces a pass under that vendor's rules.
What comes back from that nested evaluation isn't the vendor record's own qualifier result in a literal sense β it's translated into a simple match or no-match signal for the include mechanism itself. If the included record's evaluation results in a pass for the IP in question, the include mechanism matches. Anything else β fail, softfail, neutral, or even the included record running out without matching β means the include mechanism itself simply doesn't match, and the parent record's evaluation moves on to whatever mechanism comes next.
Syntax and a Worked Example
The syntax is simple: include: followed immediately by a domain name, no spaces, case-insensitive. A typical multi-vendor record looks like this: v=spf1 include:_spf.google.com include:sendgrid.net include:mailgun.org ~all. Reading it left to right, a receiving server checking a message that claims to be from Google Workspace infrastructure will match on the first include and stop there β it never needs to evaluate the SendGrid or Mailgun includes at all for that particular message, since SPF evaluation halts at the first match.
This left-to-right, stop-at-first-match behavior matters more than it looks like it should. Ordering includes roughly by sending volume, with your highest-volume vendor first, has no effect on correctness but can meaningfully reduce the average number of lookups a receiving server performs across your actual mail traffic, since most checks will resolve on an early match rather than working through every include.
What Breaks When an Include Domain Goes Away
An include is a live pointer, not a snapshot, and that cuts both ways. It's convenient because your record automatically reflects a vendor's current sending infrastructure without you touching DNS. But it also means your SPF evaluation now depends on that vendor's DNS zone continuing to exist and continuing to publish a valid record indefinitely. If a vendor is acquired, rebrands, or simply lets an old SPF include domain lapse, every domain still referencing it in an include mechanism will get a permerror on that mechanism the next time mail is evaluated β not a graceful "no longer applicable," but a hard error condition, because include specifically requires the target to resolve to a valid record.
This is a real, if uncommon, failure mode worth checking for periodically: audit your includes not just for relevance but for whether the target domains still resolve to valid SPF records at all, particularly for less actively maintained third-party tools.
Why Vendors Ask for an Include Instead of Publishing IPs Directly to You
It's worth understanding the vendor's side of this relationship, because it explains why include exists as a mechanism at all rather than every setup guide just listing IP ranges to paste in. A mail-sending vendor operating at any real scale is constantly adjusting its outbound infrastructure β adding capacity in a new region, rotating out address space that's picked up a bad reputation, migrating to new cloud provider ranges, or splitting traffic across additional pools for deliverability reasons. If every one of their customers had pasted static IPs into their own SPF records, none of those changes could happen without asking thousands of customers to manually update their DNS in lockstep, which is operationally unworkable at scale. Publishing a single SPF record at a stable include hostname and asking customers to reference it with include: turns that coordination problem into something the vendor can solve unilaterally, on their own timeline, without anyone else needing to do anything.
This is also why the include mechanism's "live pointer" behavior, which can feel like a downside when you're trying to reason about exactly what's authorized at any given moment, is actually the entire point of the design. The alternative β static, vendor-provided IP lists β is precisely what flattening produces, and precisely why flattening carries the ongoing maintenance cost it does.
Reading a Real Vendor's Include Chain
Take Google Workspace as a concrete example, since it's one of the most widely referenced includes in real-world records. A typical setup adds include:_spf.google.com to a domain's SPF record. Resolving that hostname's own TXT record doesn't return a flat list of IPs directly; historically it has returned a small number of further include mechanisms pointing at region- or purpose-specific sub-records, each of which is what actually contains the ip4 and ip6 ranges. The practical effect is that one line in your record, include:_spf.google.com, can expand through two layers of nested includes before reaching literal IP ranges β meaning that single include can cost three or four lookups against your budget, not one, once every layer is counted. This is exactly the kind of detail a naive top-level read of your own record will never surface, and exactly why recursive tooling matters more than manual inspection for anything beyond the simplest setups.
What "No Match" From an Include Actually Tells You
It's worth sitting with the earlier point about fail results inside an include a bit longer, because it trips people up in a specific way. Imagine your record is v=spf1 include:vendor-a.com include:vendor-b.com -all, and a message arrives from an IP that vendor-a.com's own record would explicitly mark with a hard fail for. That fail does not propagate upward as your record's overall result. What actually happens is: the include:vendor-a.com mechanism evaluates vendor-a.com's record, finds a hard fail for this IP, and translates that into "this mechanism does not match" β evaluation simply proceeds to include:vendor-b.com next, exactly as if vendor-a.com's record had said nothing about the IP at all. Only if none of your mechanisms match, all the way through, does your record's own trailing -all take over and produce the final fail. In other words, an included record's own qualifiers (pass, fail, softfail, neutral) only ever matter for producing a match or no-match signal for that one include mechanism β they never directly determine your record's ultimate outcome.
What Happens Mechanically Inside a Single Include Evaluation
It's worth breaking the include evaluation process into its literal steps, because the sequence explains several behaviors that otherwise seem arbitrary. First, the evaluator takes the domain named after include: and issues a DNS TXT query for it, exactly one query, which is the lookup that counts against your budget. Second, it inspects the returned TXT records and selects the one beginning with v=spf1 β if there isn't exactly one, that's a permerror right there, independent of anything else. Third, it evaluates that selected record's own mechanisms in order, recursively applying the same left-to-right, stop-at-first-match logic your top-level record uses, including recursively counting any further lookups those mechanisms trigger. Fourth, and this is the step people most often get wrong intuitively, it translates whatever qualifier that nested evaluation lands on β pass, fail, softfail, or neutral β into a simple binary signal for the include mechanism itself: pass becomes "this include matches," and everything else becomes "this include does not match, keep going." There is no fifth step where a fail inside the included record propagates outward as anything other than "no match here."
A Common Real-World Failure: The Renamed Vendor Include
One specific include-related failure worth knowing about by name, because it doesn't look like a typical SPF problem when it happens: a mail vendor rebrands, migrates to new infrastructure, or gets acquired, and in the process retires their old SPF include hostname in favor of a new one, sometimes with a transition period, sometimes without. If your record still references the old hostname after that domain stops resolving to a valid SPF record, you don't get a warning β you get a permerror the next time your record is evaluated, because include: specifically requires its target to resolve. What makes this failure mode particularly sneaky is that it can happen entirely without you doing anything, and the resulting bounce messages from receiving mail servers usually just say something generic about SPF failing, rarely pointing specifically at "your include target no longer has a valid record." Diagnosing it requires actually resolving each of your record's include targets directly and confirming each one still returns a valid v=spf1 TXT record, not just assuming that because it worked at setup time it still works now.
Include and the exp= Explanation Modifier, Together
A detail that rarely comes up until it actually matters: when an included record's own evaluation contributes to an eventual overall fail (via the parent record's trailing all mechanism, not the include itself), any exp= modifier that fires to produce a human-readable explanation is the one defined on whichever record's evaluation actually produced the final all match β typically the top-level record's own exp=, not one buried inside an included vendor's record. This matters for domain owners who've configured a custom rejection explanation: don't expect an included vendor's own exp= text to surface in your own domain's failure messages, since include's translation of the nested result into a simple match/no-match signal, as covered earlier, means the included record's own modifiers largely stay local to that nested evaluation rather than propagating outward.
A Deeper Look at What "Selecting the Correct Record" Means at Each Domain
When include: queries a target domain for its SPF record, that domain's DNS zone may return multiple TXT records at the queried hostname for entirely unrelated reasons β domain verification strings for various third-party services, DKIM records if the query happens to overlap with an unusual zone structure, or other TXT-based configuration entirely unrelated to SPF. RFC 7208 is explicit that exactly one of the returned TXT records must begin with the literal string v=spf1 for evaluation to proceed; if zero records match that prefix, or if more than one does, the result is a permerror, precisely mirroring the same "must have exactly one SPF record" rule that applies to a domain's own top-level record. This means a vendor's include target domain is just as susceptible to the multiple-SPF-records failure mode as any other domain would be, and it's specifically the vendor's own responsibility to keep their include-target zone clean of duplicate v=spf1 entries β a responsibility outside your direct control, but worth knowing about as one more category of failure that traces back to the vendor's side rather than your own configuration.
Include Chains and the Practical Limits of Manual Auditing
Beyond a certain chain depth, manually tracing an include's full nested structure by hand becomes genuinely impractical, not just tedious. A chain three or four levels deep, where each level might branch into two or three further includes, can represent dozens of individual DNS records that would all need to be manually queried, read, and cross-referenced to build a complete picture by hand. This is precisely the point at which automated recursive tooling stops being a convenience and becomes a practical necessity β not because the underlying logic is complex to understand conceptually, but because correctly executing that logic across a genuinely deep, branching chain, entirely by hand, introduces enough opportunity for simple human error (missing one nested include, miscounting one branch) that the manual result can't be trusted with the same confidence as a tool that mechanically walks the entire tree without skipping anything.
How Include Behaves When the Same Domain Appears More Than Once in a Chain
An edge case worth understanding: if the same domain ends up being referenced more than once within a single record's full recursive expansion β for instance, because two different top-level includes both happen to nest down into a shared third domain somewhere in their own chains β each occurrence is still evaluated as its own separate lookup by a strictly compliant implementation, since SPF evaluation doesn't inherently deduplicate or cache results across different branches of the same overall evaluation tree. This means a shared dependency reached via two different paths genuinely does cost two lookups, not one, even though the underlying DNS record being queried is identical both times. Being aware of this is useful specifically when trying to understand why a record's actual lookup count came out higher than expected based on a mental model that assumed shared dependencies would somehow be counted only once.
ToolsNovaHub guides are researched against primary sources (RFCs, vendor docs) and kept up to date as standards change. Spotted an error? Let us know.
π Related Tools & Guides Comparison
| Resource | Type | Link |
|---|---|---|
| SPF Lookup | Tool | Open Tool β |
| SPF 10 Lookup Limit | Guide | Read Guide β |
| SPF Redirect Explained | Guide | Read Guide β |
| SPF Record Optimization | Guide | Read Guide β |
| DKIM Lookup | Tool | Open Tool β |