Email Security

SPF Record Design That Survives the 10-Lookup Limit

SPF rarely fails because its grammar is difficult. It fails because a harmless-looking chain of vendors, redirects and nested includes spends a fixed DNS budget that nobody owns until receivers start returning permerror.

Count evaluation, not visible mechanisms

The limit is ten DNS-querying terms during one SPF evaluation, not ten words in the TXT record and not ten DNS packets. The mechanisms include, a, mx, ptr and exists, plus the redirect modifier, consume the budget when evaluated. Nested terms count too. An apex record with three includes can therefore exceed ten because each provider delegates again. Void lookups have a separate practical limit, and MX or PTR expansion has additional bounds; neither is free simply because it sits behind one token.

Start with a dependency graph, not a character counter. Resolve every include recursively, record which branches can be reached in mechanism order, and count the worst path. The SPF/DMARC/DKIM Checker is useful for inspecting the published record and catching syntax or duplicate-record faults, but it cannot replace ownership of the vendor graph. Providers change their SPF trees without changing your zone, so “we only have four includes” is not a capacity statement.

Use addresses where you own the addresses

ip4 and ip6 mechanisms require no DNS lookup during evaluation. They are the cleanest representation for stable outbound infrastructure that you operate, such as a fixed relay subnet. They are a poor copy of somebody else’s cloud estate: the moment a vendor adds or retires an egress range, your static transcription becomes wrong. Use an include when the provider, not you, controls the sending pool and publishes an explicit SPF domain for customers.

Do not substitute a or mx merely to make the record shorter. They cost queries, bind authorization to records maintained for different purposes, and often grant more hosts than intended. Inbound MX servers are not automatically outbound senders. Likewise, authorizing the web server through a because it currently relays a form submission turns a temporary architecture accident into policy. Name the actual egress addresses or move that traffic through the sanctioned mail platform.

Flattening trades lookup risk for drift risk

Flattening replaces include trees with their current terminal IP ranges. It can recover lookup headroom, but it also transfers change management from the provider to you. A flattened record is a snapshot with no expiry semantics: DNS keeps serving yesterday’s authorization perfectly. Blind flattening also loses qualifiers, ordering, macro behavior and conditional branches. If a service publishes wide, volatile address space, automation may create a TXT record that is both operationally brittle and close to DNS size limits.

Flatten only the stable branch that causes the pressure, and keep evidence of its source. Refresh it automatically, alert on upstream differences, validate the rendered record before publication, and retain a rollback value. A safe job fails closed on malformed or empty provider answers; it does not publish an empty authorization set at 03:00. Leave some lookup margin after flattening. Landing exactly on ten means the next invisible provider include becomes your outage.

Avoid permanent errors by construction

There must be exactly one SPF record for a name. Splitting a long policy into two v=spf1 TXT values does not concatenate it; it produces a permanent error. Long TXT data may be represented as multiple quoted strings inside one DNS record, which resolvers concatenate. Keep terms intentional, end with one -all once the inventory is proven, and treat provider documentation that says “add this SPF record” as a merge request against the existing policy, never as permission to paste a second record.

Watch for include targets that return no SPF record, multiple SPF records, malformed syntax or a DNS error. An include is a demand that the target evaluate to pass for the client; it is not textual insertion. Cycles and excessive recursion end badly, while a broad +all ends worse by authorizing everyone. Validate drafts before publication, then validate the live DNS answer after propagation. The distinction catches quoting, owner-name and control-panel mistakes that source review cannot.

Macros and redirect are scalpels

redirect= delegates the policy only when no preceding mechanism has matched. It is appropriate when several domains should inherit one complete policy; it is not equivalent to an include, and a missing target is an error rather than a neutral result. Centralization reduces duplicated edits, but it also creates a shared failure domain. The redirected owner must be operated like production infrastructure, with change review and monitoring for every domain that depends on it.

Macros can derive lookup names from the sender, domain or client IP. They solve real multi-tenant routing problems, especially behind exists, but make the authorization path data-dependent and harder to audit. Avoid macros when a plain address or provider include expresses the policy. Never put confidential data in a macro-expanded DNS name; queries escape your boundary. If you cannot explain the expanded name for a concrete message during an incident, the cleverness is already too expensive.

Change one branch, then watch real mail

Lower TTL before a planned migration, capture the old TXT answer, and stage the replacement with at least one spare lookup. Query through multiple resolvers after publication because your authoritative view is not what every receiver sees. Check that the domain still has one record, recursively recount the graph, and use the MX Record Check only for the adjacent routing layer: MX health matters to mail, but it does not prove outbound SPF authorization.

DNS correctness is the opening test. Send through every legitimate stream and inspect receiver evidence with the Email Message Header Analyzer: Authentication-Results, the envelope domain, client IP and DMARC alignment. Monitor aggregate DMARC reports for new permerror, temperror or unexpected sources for at least a full sending cycle. A record is finished when payroll, support, marketing and the forgotten appliance still authenticate—not when the zone editor accepts the text.