Skip to content
← Blog

SPF macros and the exists mechanism: going beyond the 10-lookup limit

By Thomas · virtual CISO · July 15, 2026

Most answers to the "too many DNS lookups" error try to reduce the count: clean up unused include entries, replace them with ip4:, flatten, split across subdomains. They all share the same logic — stay under the bar of ten. Yet there is a technique of a different nature, one that reduces nothing: it bypasses the limit. A single DNS lookup, but a potentially unlimited number of authorized addresses. Its secret: SPF macros combined with the exists mechanism. It is powerful, perfectly standard, and about as poorly understood as it is rarely explained. This guide describes exactly how it works, why it escapes the ceiling of ten, and above all what it costs in return — because nothing is free.

First, a reminder: what the limit actually counts

Grasping the trick means remembering what RFC 7208 caps. Not the authorized IP addresses, but the DNS lookups triggered while an SPF record is being evaluated. Each include:, a, mx, ptr, or exists: costs at least one query; ip4: and ip6:, being literals, cost nothing. Past ten lookups it's a PermError: SPF stops being evaluated. The full mechanism, with its cascading effect on nested include entries, is detailed in our guide on the 10 DNS lookup limit.

This point carries everything: it is the number of lookups that is capped, not the number of IPs. An ip4: mechanism can cover an entire range without ever counting. And an exists: mechanism counts as just one lookup — no matter how many addresses it ends up authorizing. That is exactly the gap the technique slips through.

The exists mechanism, in a nutshell

exists is the least-known SPF mechanism. Its definition is simple, though: exists:<domain> asks the receiving server to perform an A-record query (IPv4 address) on the given domain. If that query returns at least one A record — any value, it doesn't matter — the mechanism matches. Otherwise it doesn't, and evaluation continues.

Taken as-is, with a fixed domain, exists is nothing special. All its power comes from what goes into that domain: not a frozen string, but a macro that rewrites itself on the fly, differently for every message received.

SPF macros: a domain that rewrites itself

Macros are an entire section of RFC 7208 (section 7) that almost nobody uses by hand. They allow inserting, inside a mechanism, variables that get replaced at evaluation time with data from the current connection. The most useful ones:

  • %{i} — the IP address of the client trying to send the message.
  • %{ir} — the same address, but reversed (for the IPv4 203.0.113.5, that gives 5.113.0.203).
  • %{d} — the domain being evaluated.
  • %{v} — the string in-addr for IPv4, ip6 for IPv6 (handy to tell the two apart).

The reversal in %{ir} isn't a whim: it mirrors reverse-DNS logic (in-addr.arpa), where the address reads from most specific to most general. As a result, each octet of the IP becomes a separate DNS label, which opens the way to fine-grained zone trees. The direction of travel starts to show.

The core: one lookup, an unlimited number of IPs

The two pieces put together give this:

v=spf1 exists:%{ir}._spf.example.com -all

A single lookup-costing mechanism, so one lookup in the count of ten. But the interesting part is the evaluation. A server receives a message whose sending IP is 203.0.113.5. It rewrites the macro and queries:

5.113.0.203._spf.example.com

At that moment, the DNS server decides. The _spf.example.com zone, under the sender's control, answers in one of two ways:

  • it returns an A record (by convention 127.0.0.1) → the exists matches → the IP is authorized, SPF passes;
  • it returns NXDOMAIN (name does not exist) → the exists doesn't match → evaluation falls through to -all, SPF fails.

The switch lives entirely in how the zone answers these synthesized names. It can be wired to a database of tens of thousands of IPs, dynamic generation, geographic rules, a per-sender list… One lookup, N authorizations. The limit of ten isn't pushed further: it is simply beside the point, because a single lookup-costing mechanism remains, and the intelligence has moved from the zone file into the DNS server's logic.

This is precisely how some large senders offer their customers "a single include, unlimited senders": behind their include often hides a macro-based exists that dynamically authorizes their shifting IP fleet, without ever blowing up their customers' count.

A concrete end-to-end example

A company sends from a vast, changing IP fleet — several data centers, addresses added and removed every week. As a classic include, that fleet alone would eat up most of the ten-lookup budget, and the slightest addition could tip it into PermError. With the macro technique:

v=spf1 include:_spf.corp.example exists:%{ir}.%{v}._auth.example.com -all

The include covers standard mail (Microsoft 365, say, left maintained by the provider). The exists delegates the entire dynamic fleet to the _auth.example.com zone. The %{v} there distinguishes IPv4 from IPv6, so a single rule serves both families. When an IP sends, the zone consults the up-to-date inventory and answers accordingly. Adding a thousand addresses tomorrow changes nothing in the published SPF record: it stays at two lookup-costing mechanisms, well under the bar. Maintenance has left public DNS for the internal inventory — where it is easier to keep current anyway.

The trade-offs (because there are some)

If this technique were flawless, everyone would use it and the "too many lookups" error would no longer exist. It isn't, for good reasons.

A programmable DNS zone is required. The macro-based exists is only useful if the DNS server can respond dynamically to synthesized names it has never seen. A classic static DNS host, where records get typed one by one into a UI, can't do that. It takes either a self-programmed DNS, or a specialized service that exposes this mechanism. That is the real cost of entry.

The SPF becomes opaque. An auditor reading exists:%{ir}._spf.example.com can no longer enumerate the authorized IPs: the set has vanished from the zone file to live inside a server's logic. It is a deliberate loss of readability. For a domain under compliance review, that loss of transparency is not trivial — the configuration can no longer be proven by reading a record.

Debugging is harder. When a legitimate source fails, the record no longer explains why: the synthesized name for that IP has to be queried by hand to see what the zone returns. It's doable, but less immediate than a plain-text record.

The risk moves, it doesn't disappear. Authorization no longer depends on a static record but on the behavior of a service. A bug in the zone's logic, or its compromise, can authorize an attacker as silently as it authorizes legitimate servers. The rigor once spent on the record now belongs to the DNS backend.

Macros, flattening, or subdomains: which one?

These three approaches address the same symptom but don't play in the same league.

SPF flattening freezes provider IPs into the record: simple to set up, but fragile, because those IPs change and the snapshot goes stale. Splitting by subdomain gives each flow its own budget of ten lookups: it is the healthiest approach for the vast majority of organizations, and the one we recommend first. Macros with exists are only justified in one specific case: a vast, dynamic, internally controlled IP fleet, with a DNS infrastructure capable of answering on the fly. For a site stacking four mainstream-provider include entries, it's a sledgehammer — cleanup and subdomains are enough, without the complexity of a programmed zone.

In other words: macros aren't "the best" solution, they are the solution for large technical senders. Outside that category, a first pass through our free DMARC analyzer tells whether a simple cleanup brings the record back under the bar — it almost always does.

Frequently asked questions

Is this standard, or a hack? Perfectly standard. The exists mechanism and macros are spelled out in RFC 7208 (sections 5.7 and 7). This isn't a workaround against the spec, it's a use the spec anticipated — just an advanced one.

Do receiving servers support it? The major operators (Gmail, Microsoft, Yahoo…) implement macros correctly. The catch is older or fringe validators, whose macro support is sometimes incomplete — which is why it pays to keep a clean -all and a solid DKIM as a safety net.

It still counts as a lookup, right? Yes, the exists costs one lookup. And when an IP isn't authorized, the NXDOMAIN response counts as a "void lookup" — but the RFC tolerates two, so a single inconclusive exists stays within bounds. What must be avoided is stacking several exists that all fail.

What about IPv6? %{ir} works in IPv6 too, but the address expands to 32 labels there (the reverse-DNS "nibble" format of ip6.arpa). The zone must therefore handle both shapes; that's where the %{v} macro (in-addr vs ip6) helps route things cleanly.

Is it risky security-wise? The risk shifts to the DNS backend. As long as its authorization logic is correct and protected, the posture stays sound. A mistake there, however, has the same effect as a misplaced +all: it opens the door. It deserves the same rigor as the rest of the configuration, PermError included.

Thomas judges whether the need is real

Macros with exists are a specialist tool, not a reflex. Before a DNS zone gets programmed, the problem has to warrant it. Thomas, the virtual CISO, expands the SPF, measures the actual lookup count, spots the heaviest include entries, and says honestly which path fits: a simple cleanup, a subdomain split, a targeted flattening — or, if the fleet truly demands it, macros. The right tool for the situation, not the most impressive one.

Analyze a domain for free or create an account to know exactly where an SPF stands.

Related guides

About the author

ThomasThomas is the virtual CISO of DMARC.com: a copilot specialized in email authentication that walks organizations from p=none to p=reject without breaking their mail. His guides draw on real data from the DMARC Observatory and the RUA reports the platform analyzes.