← Blog

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

By Thomas · virtual CISO · 2026-07-15

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 you trade in return — because nothing is free.

First, a reminder: what the limit actually counts

To grasp the trick, you need to remember what RFC 7208 caps. Not the authorized IP addresses, but the DNS lookups triggered while your SPF record is being evaluated. Each include:, a, mx, ptr, or exists: costs at least one query; ip4: and ip6:, being literals, cost nothing. Go past ten lookups and 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.

Hold on to this point, because everything rests on it: 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 you put in 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 let you insert, 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 you read the address from most specific to most general. As a result, each octet of the IP becomes a separate DNS label, which lets you build fine-grained zone trees. You can start to see where this is going.

The core: one lookup, an unlimited number of IPs

Put the two pieces together and you get this:

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

A single lookup-costing mechanism, so one lookup in the count of ten. But watch what happens at 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, your DNS server decides. The _spf.example.com zone, which you 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 → you fall through to -all, SPF fails.

The switch lives entirely in how your zone answers these synthesized names. You can wire it 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 you now have a single lookup-costing mechanism, 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

Picture a company that 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 your 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, which you leave 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.

You need a programmable DNS zone. The macro-based exists is only useful if your DNS server can respond dynamically to synthesized names it has never seen. A classic static DNS host, where you type records one by one into a UI, can't do that. You need either a DNS you program yourself, or a specialized service that exposes this mechanism. That is the real cost of entry.

Your SPF becomes opaque. An auditor reading exists:%{ir}._spf.example.com can no longer enumerate your 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 — you no longer prove your configuration by reading a record.

Debugging is harder. When a legitimate source fails, you can no longer read the record to understand why: you have to query the synthesized name for its IP yourself and see what your zone returns. It's doable, but less immediate than a plain-text record.

You move the risk, you don't remove it. Authorization no longer depends on a static record but on the behavior of a service. A bug in your zone's logic, or its compromise, can authorize an attacker as silently as it authorizes your servers. The rigor you used to put into your record must now go into your 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 your record: simple to set up, but fragile, because those IPs change and your 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. If you're not in that category, you'll be better off first running our free DMARC analyzer to see whether a simple cleanup brings you 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 you must not do is stack 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). Your 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 your DNS backend. As long as its authorization logic is correct and protected, your posture stays sound. A mistake there, however, has the same effect as a misplaced +all: it opens the door. Handle it with the same rigor as the rest of your configuration, PermError included.

Let Thomas judge whether you really need it

Macros with exists are a specialist tool, not a reflex. Before you program a DNS zone, you need to be sure the problem warrants it. Thomas, your virtual CISO, expands your SPF, measures your actual lookup count, spots the heaviest include entries, and tells you honestly which path suits you: a simple cleanup, a subdomain split, a targeted flattening — or, if your fleet truly demands it, macros. The right tool for your situation, not the most impressive one.

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

Ready to enforce DMARC?

Thomas, your virtual CISO, identifies every legitimate sending source, writes the exact DNS records, and takes your domain from p=none to p=reject — without breaking your mail.

Get to p=reject — free

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.