Macros exist to solve one problem: a platform with tens of thousands of sending addresses cannot list them in a record that fits inside ten lookups. So instead of listing hosts, the record asks a question about the host in front of it.
exists, the mechanism that makes it work
exists: constructs a name and matches if that name resolves to anything at all. The content is irrelevant; existence is the answer. Combine it with %{i} and a record becomes a query rather than a list.
v=spf1 exists:%{i}._spf.mta.salesforce.com -allEvaluating that for two addresses:
%{i} = 13.110.208.1 -> dig 13.110.208.1._spf.mta.salesforce.com NXDOMAIN -> no match
%{i} = 192.0.2.1 -> dig 192.0.2.1._spf.mta.salesforce.com NXDOMAIN -> no match
An authorised address resolves; everything else does not. The zone is the allowlist.Why %{ir} shows up so often
Reversing the address puts the most significant octet last, which mirrors how reverse-DNS zones are delegated. A provider can then hand a customer authority over 3.2.1.in-addr.example.net and let them manage their own addresses beneath it, without the provider regenerating anything.
Booking.com’s record from the previous lesson is exactly this, one level up: the expansion embeds the customer domain as well as the address, so a single Proofpoint name serves every customer with per-customer answers.
What it costs
| Static list | exists + macros | |
|---|---|---|
| Lookups | One per include, growing with senders. | One, forever. |
| Freshness | Stale the moment a platform renumbers. | Always current — the answer is computed per connection. |
| Requires | Nothing special. | A DNS backend that can answer a very large or synthesised namespace. |
| Debuggability | Read the record and you know the answer. | You must expand the macro and query before you know anything. |
| Caching | Normal. | Many distinct names, so cache hit rates are lower for receivers. |
The debuggability cost is real
A static record tells you who can send by reading it. A macro record tells you nothing until you expand it for a specific address and query the result. When someone reports that mail is failing, you cannot answer from the record alone — you need the sending IP first. Budget for that in your runbook.
Who should build one
- Platforms, yes. If you send on behalf of many customers from a changing pool, this is the correct architecture and the reason it exists.
- Ordinary domains, almost never. If your record has six includes and fits, macros add operational complexity and buy nothing.
- Over-budget domains, maybe — and read the next module first, because delegation usually solves it with less machinery.