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.

πŸ› οΈ Related tool: Open SPF Lookup β†’

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.

⭐
ToolsNovaHub Pro Tip
Before adding a new vendor's include, resolve it yourself first and see how many lookups it actually costs. A vendor that looks like "just one include" in their setup docs can secretly nest through two or three more, quietly eating your lookup budget.
⚠️
Common Beginner Mistake
Treating a fail result inside an included record as if it fails your whole SPF check. It doesn't β€” a fail inside an include only means that particular mechanism doesn't match; your own record's later mechanisms still get evaluated normally.

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.

πŸ“… Last updated: September 2026πŸ“œ Sourced from: RFC 7208

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

ResourceTypeLink
SPF LookupToolOpen Tool β†’
SPF 10 Lookup LimitGuideRead Guide β†’
SPF Redirect ExplainedGuideRead Guide β†’
SPF Record OptimizationGuideRead Guide β†’
DKIM LookupToolOpen Tool β†’
Try it yourself β€” 100% free
πŸš€ Open SPF Lookup

FAQ

It tells the evaluator to fetch a separate domain's SPF record and evaluate that record's mechanisms as part of determining whether the current sending IP is authorized, then return a specific match result back to the parent evaluation.
No. include performs a live, recursive lookup of the target domain's current SPF record every time your record is evaluated, so it always reflects whatever that vendor currently publishes, unlike a static copy-paste of IPs.
Because their sending IP ranges change over time as they scale infrastructure, rotate providers, or add regions, and an include lets every customer's SPF stay accurate automatically without needing to be manually updated whenever that happens.
No, this is a specific technical nuance: an include only ever contributes a match/no-match signal upward based on whether the target record's mechanisms produced a pass. A fail or softfail inside the included record does not propagate as an overall fail for your record β€” it's simply treated as no match, and evaluation continues to the next mechanism.
Yes, in the specific sense that SPF evaluation is left-to-right and stops at the first mechanism that matches. Placing a broad include before a narrower one can mean the narrower one, and its more accurate 'all' qualifier, never actually gets evaluated for a given sending IP.
The result is a permerror for that mechanism, since include: specifically requires the target to publish a valid SPF record; a missing record is treated as an error condition, not simply a non-match.