SPF Mechanisms Explained: The Eight RFC 7208 Mechanisms (a, mx, ptr, ip4, ip6, exists, include, all), the Four Qualifiers (+, -, ~, ?), and the Two Modifiers (redirect, exp)

The canonical mechanism-vocabulary deep dive for SPF record authors and operators (RFC 7208). SPF is built out of eight MECHANISMS (a, mx, ptr, ip4, ip6, exists, include, all), four QUALIFIERS (+, -, ~, ?), and two MODIFIERS (redirect=, exp=). This page enumerates every one with the canonical Definition / Syntax / DNS lookup cost / When to use / Pitfalls structure, then covers the left-to-right first-match evaluation order, cumulative lookup-cost accounting against the 10-DNS-lookup cap, five worked deployment examples, and the seven most common mechanism-level pitfalls operators hit in production. For operators authoring or debugging SPF records, mail-system administrators planning a deployment, and SOC analysts triaging SPF-related deliverability incidents. Differentiation: the SPF pillar (help_spf_explained.php) surveys the whole protocol — what SPF does and does not do, the seven result codes; this page is the focused mechanism-vocabulary reference search engines and LLMs cite when asked "what does the SPF include mechanism do" or "is SPF ptr deprecated".

This reference is provided in English. SPF is specified in an English-language RFC (RFC 7208) and the canonical mechanism / qualifier / modifier vocabulary (a, mx, ptr, ip4, ip6, exists, include, all, +, -, ~, ?, redirect, exp) does not translate. We keep the body in the source language so technical terms remain consistent with the specification.

TL;DR

  • SPF defines eight mechanisms. RFC 7208 section 5 defines all, ip4, ip6, a, mx, ptr, exists, and include. Each mechanism contributes a different kind of match: literal IPs, DNS-resolved hosts, recursive SPF delegation, or a terminal catch- all.
  • Four qualifiers prefix every mechanism. RFC 7208 section 4.6.2: + pass (the default), - fail (hard fail), ~ softfail (deployment-ramp signal), ? neutral (no opinion). Production records terminate with -all or, during a ramp, ~all.
  • Two modifiers extend the record. RFC 7208 section 6: redirect= hands evaluation off to a different domains SPF record and is mutually exclusive with the terminal all mechanism; exp= names a domain whose TXT record provides a human-readable explanation for a fail result.
  • Mechanisms evaluate left-to-right. RFC 7208 section 4.6.2: the FIRST mechanism that matches wins and the evaluator returns the matching mechanisms qualifier-derived result. Order matters — +ip4:198.51.100.5 -all and -all +ip4:198.51.100.5 behave identically only because all always matches; reordering ANY non-all mechanism past a matching one changes nothing, but reordering before it can change which qualifier applies.
  • DNS lookup costs vary per mechanism. ip4, ip6, and all cost ZERO lookups. a, exists, and redirect= cost ONE lookup each. mx costs at least ONE lookup plus ONE per MX host. include costs ONE lookup plus the cost of every nested mechanism inside the included record (the most expensive class). ptr is DEPRECATED — RFC 7208 section 5.5 explicitly discourages it.
  • The 10-lookup cap is enforced per evaluation. RFC 7208 section 4.6.4: an SPF evaluation that requires more than 10 DNS lookups MUST return permerror. The separate 2-void-lookup cap returns permerror after three NXDOMAIN / empty results in the same chain. See SPF lookup limit and flattening for the focused deep dive.
  • ptr is deprecated. Remove every ptr mechanism from production SPF records and replace it with ip4 / ip6 / a / mx / include as appropriate. The deprecation reasons (DNS load, intermittent SERVFAIL, weak trust model) are documented in RFC 7208 section 5.5.

1. Overview: how an SPF record is structured from mechanisms

An SPF record is a single DNS TXT string published at the apex of the sending domain. The string begins with the literal version tag v=spf1 and then enumerates a sequence of MECHANISMS, each optionally prefixed by a QUALIFIER, followed optionally by one or more MODIFIERS. The receiving mail server evaluates the mechanisms left-to-right against the connecting IP, and the first mechanism that matches yields the SPF result. The terminal mechanism (almost always all with a hard or soft qualifier) catches every IP that did not match an earlier mechanism.

Canonical record shape:

v=spf1 <qualifier><mechanism> <qualifier><mechanism> ... <qualifier>all [<modifier>]

Concrete example showing every component class:

v=spf1 ip4:198.51.100.5 ip6:2001:db8::1 a:mail.example.com mx include:spf.protection.outlook.com -all

In that example the receiver:

  1. Checks whether the connecting IP equals 198.51.100.5. If yes, return pass (the ip4 mechanisms default + qualifier).
  2. If no, checks whether the connecting IP equals 2001:db8::1. If yes, return pass.
  3. If no, resolves the A/AAAA records of mail.example.com and checks whether the connecting IP is in that set. If yes, return pass.
  4. If no, resolves the MX records of the current domain (the domain in the envelope MAIL FROM being SPF-evaluated), then the A/AAAA records of every MX host, and checks the connecting IP against that combined set. If yes, return pass.
  5. If no, recursively evaluates the SPF record of spf.protection.outlook.com against the connecting IP. If that recursive evaluation returns pass, return pass.
  6. Otherwise reach the terminal -all and return fail (hard fail).

The same record makes DIFFERENT lookup-budget demands depending on which mechanism matches: matching ip4 costs zero lookups, but reaching the terminal -all for an unauthorized IP requires evaluating EVERY earlier mechanism (zero for ip4/ip6, one for a, one-plus- MX-host-count for mx, and the full recursive include cost). The 10-lookup cap is therefore enforced over the WORST-CASE path through the record, not the matching path.

The rest of this page enumerates each mechanism, qualifier, and modifier in depth with the canonical Definition / Syntax / Lookup-cost / When- to-use / Pitfalls structure, then turns to the evaluation order, the cumulative lookup-cost accounting, worked deployment examples, the seven most common mechanism-level pitfalls, an FAQ, and links to sibling guides for the focused deep dives (lookup limit, forwarding, permerror, deployment per provider).

2. The eight RFC 7208 mechanisms

Each subsection below opens with the canonical five-part labelled structure that gives generative engines a quotable extract:

2.1 The all mechanism

Definition: all matches EVERY connecting host unconditionally. It is the terminal catch-all of an SPF record, applied after every preceding mechanism has been tested and not matched. The qualifier prefixed to all determines the result: +all means pass-on-no-match (rarely safe), -all means fail-on- no-match (the recommended production terminator), ~all means softfail- on-no-match (deployment-ramp signal), ?all means neutral-on-no-match (equivalent to publishing no record).

Syntax: <qualifier>all. Examples: -all (hard fail catch-all, recommended for production), ~all (softfail catch-all, recommended during initial DMARC ramp), ?all (neutral catch-all), +all (pass-everyone catch-all, never publish in production).

Lookup cost: zero. The all mechanism is evaluated from the SPF records text without any DNS query.

When to use: EVERY SPF record should terminate with one of -all (production) or ~all (deployment ramp). The all mechanism is so universal that an SPF record without it leaves the catch-all behavior unspecified — receivers return the implicit default of neutral, which is no better than having no SPF record at all.

Pitfalls: (1) Publishing +all authorizes EVERY internet host to send mail with your domain in the envelope MAIL FROM — never do this; it invalidates SPF entirely. (2) Forgetting the all terminator results in an implicit neutral, missing the policy declaration most receivers expect; always end every production SPF record with -all or ~all. (3) all is incompatible with the redirect= modifier (RFC 7208 section 6.1) — if you use redirect=, do NOT add an all mechanism (the redirect terminates evaluation by delegating to another record).

2.2 The ip4 mechanism

Definition: RFC 7208 section 5.6 defines ip4 to match the connecting IP against a literal IPv4 address or CIDR-notation IPv4 range. The match is purely arithmetic — no DNS resolution is required.

Syntax: ip4:<address> for a single address, ip4:<prefix>/<cidr> for a CIDR range. Examples: ip4:198.51.100.5 matches the exact IPv4 address 198.51.100.5; ip4:198.51.100.0/24 matches any IPv4 address in the 198.51.100.0 - 198.51.100.255 range; ip4:10.0.0.0/8 matches any IPv4 address in the 10.0.0.0/8 RFC 1918 private space (not useful for production SPF since private IPs cannot reach the public SMTP world).

Lookup cost: ZERO. The ip4 mechanism is the cheapest mechanism in the SPF protocol and the recommended target after flattening.

When to use: Use ip4 when you directly control the sending IPv4 address (an on-prem mail server, a dedicated cloud SMTP host, a static-IP transactional service, a flattened consolidated record). ip4 is also the canonical output of an SPF flattener — a flattening pipeline pre-resolves include / a / mx mechanisms into the equivalent flat list of ip4 and ip6 mechanisms so the 10-lookup cap is no longer a constraint.

Pitfalls: (1) Hardcoded IPs are BRITTLE — every IP change at your sending provider requires a re-publication of the SPF record, with the corresponding DNS-propagation delay (use a managed flattener that re-flattens on schedule if you go this route). (2) Overly broad CIDR ranges (ip4:0.0.0.0/0 authorizes the entire IPv4 internet) destroy SPF's authorization value — never publish these. (3) ip4 matches ONLY IPv4-family connections; an ip4:198.51.100.5 mechanism does NOT match an IPv6 connection to your mail server, even from a host whose IPv6 address is on your authorized list. Dual-stack deployments need both ip4 and ip6 mechanisms. (4) Using ip4 with an embedded port number (ip4:198.51.100.5:25) is a SYNTAX ERROR — RFC 7208 does not let SPF restrict by port; the SMTP port is implicit. The evaluator returns permerror.

2.3 The ip6 mechanism

Definition: RFC 7208 section 5.7 defines ip6 to match the connecting IP against a literal IPv6 address or CIDR-notation IPv6 range. Like ip4, the match is purely arithmetic — no DNS resolution required.

Syntax: ip6:<address> for a single address, ip6:<prefix>/<cidr> for a CIDR range. Examples: ip6:2001:db8::1 matches the exact IPv6 address 2001:db8::1; ip6:2001:db8::/32 matches any IPv6 address in the 2001:db8::/32 prefix; ip6:fe80::/10 matches IPv6 link-local space (not useful for production SPF).

Lookup cost: ZERO. Same property as ip4 — the cheapest mechanism available.

When to use: Use ip6 when your sending infrastructure has IPv6 connectivity to the receiving mail servers — Gmail, Microsoft 365, Yahoo, and Apple all accept IPv6 SMTP connections and evaluate SPF against the IPv6 source. If your mail server is IPv6- reachable but you only publish ip4, every IPv6 connection returns spf=fail.

Pitfalls: (1) IPv6 address- family forgetting — operators routinely add ip4 for the new mail server but forget the corresponding ip6, producing SPF failures on the small but growing fraction of IPv6 SMTP connections. Always publish both address families if your mail server has both. (2) Overly broad IPv6 CIDR ranges (ip6:::/0 authorizes the entire IPv6 internet) destroy SPF's authorization value — never publish these. (3) IPv6 address representation has multiple valid forms (2001:db8:0:0:0:0:0:1, 2001:db8::1, 2001:0db8::0001) — RFC 7208 accepts any canonical form, but mismatched representations between SPF records and actual connections will not cause failures because the evaluator normalizes both sides.

2.4 The a mechanism

Definition: RFC 7208 section 5.3 defines a to match the connecting IP against the A or AAAA DNS records of a named domain. Without an argument, a defaults to the current domain (the domain whose SPF record is being evaluated). With an argument (a:mail.example.com) it uses the named domain. A CIDR suffix (a/24 or a:mail.example.com/24) broadens the match to the named prefix surrounding each resolved A record.

Syntax: a (current domain, exact match), a:<domain> (named domain, exact match), a/<cidr4> (current domain, IPv4 CIDR broadening), a:<domain>/<cidr4> (named domain, IPv4 CIDR broadening), a:<domain>/<cidr4>//<cidr6> (named domain, separate IPv4 and IPv6 broadening). Examples: a matches the current domains A/AAAA records exactly; a:mail.example.com matches mail.example.coms A/AAAA records exactly; a:mail.example.com/24//64 matches the /24 IPv4 prefix and /64 IPv6 prefix surrounding each resolved address.

Lookup cost: ONE DNS lookup (A and AAAA queries against the named or current domain). Counts toward the 10-lookup cap.

When to use: Use a when the mail server lives at the same A record as the web server (typical single-server small business deployments where mail.example.com IS example.com). Use a:mail.example.com when the mail server has its own A record distinct from the apex. The CIDR-broadening form is useful for ESPs running mail from contiguous IP blocks under a single hostname.

Pitfalls: (1) a resolves ONLY the named domains FIRST-LEVEL A/AAAA records — it does NOT recursively chase CNAME chains beyond the first level (the resolver does that during normal DNS resolution, but the SPF spec only counts ONE lookup against the cap regardless of how many CNAMEs sit in between). (2) a does NOT include the IPs of hosts pointed to by MX records — use mx for that. (3) a:<domain> does NOT recursively expand that domains SPF record — use include for SPF delegation. (4) An A record with a CNAME at the named domain that returns no A records counts as a VOID LOOKUP (RFC 7208 section 4.6.4) and contributes to the separate 2-void-lookup cap. (5) Stale A records that point to old sending IPs continue to authorize those IPs long after they are deprovisioned — a catalogued source of "ghost" SPF passes for recycled cloud IPs.

2.5 The mx mechanism

Definition: RFC 7208 section 5.4 defines mx to match the connecting IP against the A or AAAA records of EVERY host listed in the MX records of a named domain. Without an argument, mx defaults to the current domain. With an argument (mx:example.net) it uses the named domains MX records instead. A CIDR suffix broadens each resolved A record into a containing prefix.

Syntax: mx (current domain), mx:<domain> (named domain), mx/<cidr4> (current domain with CIDR broadening), mx:<domain>/<cidr4>//<cidr6> (named domain with separate v4 and v6 CIDR broadening). Examples: mx matches the current domains MX-host A/AAAA records; mx:example.net matches example.nets MX-host A/AAAA records.

Lookup cost: ONE DNS lookup for the MX record itself PLUS ONE lookup per host listed in the MX record. A domain with 5 MX hosts (3 primary + 2 backup) spends 6 of the 10-lookup budget on a single mx mechanism — leaving only 4 lookups for every other mechanism in the record. RFC 7208 section 4.6.4 explicitly caps the number of MX-host lookups per mx mechanism at 10; an MX record listing more than 10 hosts MUST return permerror.

When to use: Use mx only when the same servers that RECEIVE mail for the domain also SEND mail for it — the legacy small-business "my mail server is my mail server" symmetry. In cloud-era deployments where inbound is filtered by Proofpoint / Mimecast / Microsoft Defender and outbound goes through a separate ESP, the mx mechanism is rarely the right tool. Prefer a:<send-host> or include:<provider> instead.

Pitfalls: (1) High lookup cost — a single mx mechanism with 5 MX hosts costs 6 lookups, half the 10-lookup budget. (2) Outsourcing inbound filtering to a cloud provider (Proofpoint, Mimecast, Defender) REPLACES your MX hosts with the providers — mx then authorizes the FILTERS IPs to send AS YOU, which is rarely the intent. (3) Using BOTH mx AND the providers SPF include double-counts IPs and burns the lookup budget. (4) MX-host A records pointing to CNAMEs introduce additional implicit resolution layers but only count as ONE lookup against the SPF cap (the resolver resolves the CNAME chain invisibly). (5) Backup MX hosts (priority 20, 30, 50) are typically NOT sending hosts — but mx authorizes them all. Remove them with explicit a:<primary-sender> mechanisms if needed.

2.6 The ptr mechanism — DEPRECATED

Definition: RFC 7208 section 5.5 defines ptr to match the reverse-DNS PTR record of the connecting IP against a named domain. The evaluator does a PTR lookup on the connecting IPs reverse- DNS arpa name, then for each returned hostname does a forward A/AAAA lookup, and matches if any of those forward lookups return the original connecting IP AND the hostname is a subdomain of (or equal to) the named domain. RFC 7208 section 5.5 EXPLICITLY DEPRECATES this mechanism — production SPF records should NOT use ptr.

Syntax: ptr (current domain) or ptr:<domain> (named domain). Examples (DO NOT USE — shown for recognition only): ptr matches if the connecting IPs PTR resolves to a hostname under the current domain; ptr:example.net matches if the connecting IPs PTR resolves to a hostname under example.net.

Lookup cost: ONE PTR lookup plus ONE forward A/AAAA lookup per PTR result. PTR records frequently return multiple hostnames, multiplying the cost unpredictably. ptr is among the most DNS-expensive mechanisms in the protocol.

When to use: NEVER in production. RFC 7208 section 5.5 quotes: "Use of this mechanism is discouraged because it is slow, it is not as reliable as other mechanisms in cases of DNS errors, and it places a large burden on the .arpa name servers." If you find ptr in an existing record, replace it with explicit ip4 / ip6 / a / mx / include mechanisms targeting the actual sending hosts.

Pitfalls: (1) Three deprecation reasons documented in RFC 7208 section 5.5: performance (multiple PTR + forward lookups per evaluation), reliability (in-addr.arpa nameservers frequently SERVFAIL or return stale data, producing transient SPF temperrors), and trust model (relying on the IP holders DNS for authorization shifts trust to an infrastructure boundary SPF was not designed to enforce). (2) Many receivers refuse to evaluate ptr at all and treat any record containing it as effectively broken (some explicitly return permerror). (3) Major mailbox providers (Gmail, Microsoft 365) document that ptr-based authorization should not be relied upon and the evaluation behavior is undefined.

2.7 The exists mechanism

Definition: RFC 7208 section 5.7 (the same section as ip6 — note the spec numbering quirk; exists is in section 5.7 alongside ip6 but treated as a separate mechanism) defines exists to match if a named domain has ANY A record. The evaluator does a single A query against the named domain; if the query returns any A record (the result is non-empty), the mechanism matches. The actual A record contents are NOT compared against the connecting IP — only the existence of the A record is what is being tested.

Syntax: exists:<domain>. The named domain is typically constructed DYNAMICALLY at evaluation time using SPF macros (RFC 7208 section 7), which substitute the connecting IP, the sender, the helo domain, and other SMTP context into the domain string. Example: exists:%{ir}.%{l}.%{d}.spf.example.com expands to (for a connection from 198.51.100.5 to mail.example.com with MAIL FROM: alice@example.com): exists:5.100.51.198.alice.example.com.spf.example.com. The domain owner publishes A records at the composed name to dynamically authorize the connection.

Lookup cost: ONE DNS lookup. Counts toward the 10-lookup cap.

When to use: Use exists for dynamic per-sender or per-IP authorization driven by an external database — a common pattern is a custom DNS zone where the domain owner stamps A records at composed names whenever a new sender or IP is authorized, and revokes them by removing the A record. The SPF record stays unchanged while authorization decisions move to the external DNS zone. exists is also used for COUNTERMEASURES against SPF evaluation amplification attacks (where a single SPF evaluation triggers many DNS queries) — by returning predictable A records only for known-good combinations.

Pitfalls: (1) Most operators do not need exists — the dynamic authorization patterns it enables are specialized and most production deployments are better served by static ip4 / ip6 / include mechanisms. (2) Macros in exists targets can construct VERY long DNS names that some resolvers truncate — RFC 7208 section 7.1 limits composed names to 253 octets but the composition path is delicate. (3) An exists mechanism with a macro that resolves to NXDOMAIN counts as a VOID LOOKUP — three voids return permerror. Dynamic exists deployments need to publish a default A record at the unauthorized name to avoid void-lookup accumulation.

2.8 The include mechanism

Definition: RFC 7208 section 5.2 defines include to recursively evaluate the SPF record of a named domain against the connecting IP, and match if and only if the included evaluation returns pass. Effectively include:<domain> means "authorize whatever <domain>s SPF record authorizes". Operators use include to delegate SPF authorization to a third-party sender (Microsoft 365, Google Workspace, an ESP, a transactional service) without having to enumerate the providers IP ranges by hand and re-publish whenever the provider changes them.

Syntax: include:<domain>. Examples: include:spf.protection.outlook.com delegates to Microsoft 365 / Exchange Online; include:_spf.google.com delegates to Google Workspace / Gmail; include:sendgrid.net delegates to SendGrid; include:mailgun.org delegates to Mailgun; include:amazonses.com delegates to Amazon SES.

Lookup cost: ONE DNS lookup for the included records SPF TXT query PLUS the recursive cost of every mechanism inside the included record. include is the MOST EXPENSIVE mechanism class because the included record can itself contain include mechanisms producing a chain. Each nested include adds the cost of ALL its mechanisms to the parent evaluation. A typical Microsoft 365 include (spf.protection.outlook.com) flattens to one lookup at the top level (the included record is published as a flat ip4 / ip6 list). A typical Google Workspace include (_spf.google.com) is a NESTED include chain (the included record contains include:_netblocks.google.com include:_netblocks2.google.com include:_netblocks3.google.com) consuming FOUR lookups out of the box (the include itself plus three nested includes).

When to use: Use include for every third-party sender whose IP space you do not directly control — ESPs, transactional services, mailbox providers, marketing platforms, helpdesk systems, CRM webhooks. The include mechanism is the canonical SPF delegation primitive.

Pitfalls: (1) include is the #1 cause of 10-lookup-cap overruns — multiple providers with deep nested chains routinely push enterprise SPF records past the cap. See SPF lookup limit and flattening for the focused deep dive. (2) include requires the included record to PUBLISH v=spf1 — including a record that returns "none" (no SPF record published at the included domain) returns permerror, not none. (3) include is sometimes confused with redirect= — the difference is that include returns pass when the included evaluation passes and continues evaluation otherwise, while redirect= REPLACES the current evaluation with the redirected one (RFC 7208 section 6.1). include is the correct delegation primitive for adding a sender; redirect= is for handing off the entire record. (4) include with a malformed argument (typo, leading whitespace, non-existent domain) returns permerror — every published include must be tested before publication.

3. The four RFC 7208 qualifiers

RFC 7208 section 4.6.2 defines four qualifiers that prefix every mechanism and determine the result returned when that mechanism matches. Each subsection uses the canonical Definition / Syntax / Use-case / Pitfalls structure.

3.1 The + qualifier (pass — default)

Definition: the + qualifier means PASS — the mechanism matching means the connecting host IS authorized to send mail with this domain in the envelope MAIL FROM. + is the DEFAULT qualifier — if no qualifier is prefixed to a mechanism, the evaluator applies + implicitly.

Syntax: +<mechanism> or equivalently <mechanism> (the + is implicit). Examples: +ip4:198.51.100.5 (explicit pass), ip4:198.51.100.5 (implicit pass — same meaning), +include:spf.protection.outlook.com (explicit pass on Microsoft 365 inclusion), include:spf.protection.outlook.com (implicit pass — same meaning).

Use-case: the default for every authorization mechanism in an SPF record. + is so universal that most published SPF records omit it entirely and rely on the implicit default; the only place + ever needs to be explicit is for documentation clarity in a record full of mixed qualifiers.

Pitfalls: (1) Publishing +all authorizes EVERY internet host to send mail with your domain — this is the single most damaging SPF mistake; it effectively disables SPF and tells receivers your domain has no authorization policy. Never publish +all in production. (2) + on a misconfigured mechanism (typo in include: argument, wrong CIDR range) still applies if the mechanism matches the connecting IP — verify mechanism arguments before publication.

3.2 The - qualifier (fail — hard fail)

Definition: the - qualifier means FAIL (hard fail) — the mechanism matching means the connecting host is NOT authorized and the receiver SHOULD reject the message. Hard fail is the strongest SPF signal — most receivers reject or quarantine the message outright on - match.

Syntax: -<mechanism>. Examples: -all (the canonical production terminator — reject every host not explicitly authorized); -ip4:198.51.100.0/24 (reject every host in 198.51.100.0/24 — useful for blocking a known-bad block that earlier mechanisms would otherwise authorize); -include:spf.expired-vendor.com (reject every host the expired vendor authorizes — defense-in-depth against an expired include that should not have any authorized hosts left).

Use-case: the recommended terminator for every production SPF record. -all declares to receivers that any host not authorized by an earlier mechanism is NOT authorized to send mail with this domain — receivers should reject. Most production records terminate with -all.

Pitfalls: (1) - before mechanism arguments not equal to all is rare in practice — most operators do not need explicit - on non-terminal mechanisms. (2) Switching from ~all to -all too aggressively can break forwarded mail (SPF fails on every forwarded hop and -all receivers may reject) — graduate from ~all to -all only after DMARC aggregate reports confirm no legitimate traffic depends on SPF passing. DKIM coverage closes most of the forwarded- mail concerns. (3) - qualified IPs in the middle of a record can interact with later + mechanisms in ways operators do not expect — evaluation is left-to-right, so the FIRST match wins; -ip4:198.51.100.5 +ip4:198.51.100.0/24 rejects 198.51.100.5 specifically while authorizing the rest of the /24.

3.3 The ~ qualifier (softfail)

Definition: the ~ qualifier means SOFTFAIL — the mechanism matching means the connecting host is PROBABLY not authorized but the receiver SHOULD treat the result as a soft signal rather than an outright rejection. RFC 7208 section 8.5 explicitly says "SoftFail messages typically would be accepted but tagged" — Gmail typically spam-folders softfailed mail; Microsoft 365 applies a higher spam score; Yahoo treats it as a moderate negative signal.

Syntax: ~<mechanism>. Most commonly used as ~all (softfail-on-no-match terminator).

Use-case: the DEFAULT terminator during the initial deployment of a new SPF record or during a major change (new sender added, IP range change, ESP migration). ~all tells receivers "I am still verifying my SPF coverage is complete — treat unauthorized traffic as a soft signal rather than rejecting it outright". After the deployment is stable and DMARC aggregate reports confirm no legitimate sender is failing SPF, graduate to -all.

Pitfalls: (1) Leaving ~all in production indefinitely is a common mistake — operators publish the soft terminator during the deployment ramp and forget to harden to -all. The result: receivers continue treating SPF failures softly even when the record is complete. Set a deadline for the ~all → -all transition (typically 60-90 days after DMARC p=none aggregate reports confirm full coverage). (2) Some receivers (notably legacy on-prem deployments) treat ~ identically to ? (neutral) — the softfail signal is not universally honored. (3) ~all on a record with broad +ip4 mechanisms means "everything I have not explicitly listed is suspect" — useful as a deployment ramp but not as a permanent posture.

3.4 The ? qualifier (neutral)

Definition: the ? qualifier means NEUTRAL — the mechanism matching means the domain owner has NO opinion on whether this host is authorized. The receivers SPF result becomes neutral, which is the same as the result returned when the domain has no SPF record at all. ?all is therefore equivalent to publishing no SPF record at all from a protocol perspective.

Syntax: ?<mechanism>. Examples: ?all (neutral catch-all — effectively no SPF policy), ?include:partner.example (neutral on partner authorization — the partner is neither authorized nor unauthorized; receiver decides).

Use-case: Almost never in production. The narrow legitimate uses are (1) domains in active migration where the domain owner does not yet have a policy opinion, or (2) the rare case of an inclusion the operator wants neither to authorize nor explicitly forbid. Most operators should pick +, -, or ~ instead.

Pitfalls: (1) Publishing ?all tells receivers your domain has no policy opinion — which is IDENTICAL to publishing no record at all, and worse than ~all for deployment ramps. (2) Receivers treat neutral and none identically — there is no actionable difference. (3) ? on the terminal is a "I do not care" declaration — if you do not care, do not publish an SPF record at all; publishing ?all gives receivers no useful signal.

4. The two RFC 7208 modifiers

RFC 7208 section 6 defines two MODIFIERS that extend the SPF record beyond the basic mechanism list. Modifiers differ from mechanisms in that they (a) do not have a qualifier prefix, and (b) take the form <name>=<value> rather than <name>:<value>.

4.1 The redirect= modifier

Definition: RFC 7208 section 6.1 defines redirect=<domain> as a delegation modifier that REPLACES the current SPF evaluation with the evaluation of the named domains SPF record. The evaluators result becomes the redirected records result (pass, fail, softfail, neutral, none, temperror, permerror).

Syntax: redirect=<domain>. Example: v=spf1 redirect=_spf.example.com delegates the entire SPF evaluation of the current domain to _spf.example.coms SPF record.

Lookup cost: ONE DNS lookup for the redirected domains SPF record plus the cost of every mechanism inside it. Equivalent to a single top-level include mechanism.

When to use: when an organization manages SPF policy for many domains centrally and wants every domains SPF record to point to a single canonical source. redirect= is the cleaner pattern than embedding the same include chain in every domains record.

Pitfalls: (1) redirect= is MUTUALLY EXCLUSIVE with the all mechanism — if you use redirect=, you MUST NOT include any all mechanism in the same record (the redirected record provides the terminal). (2) redirect= is ignored if any mechanism in the current record matches — only one of the mechanisms or the redirect executes, not both. (3) redirect= to a domain without a v=spf1 record returns permerror. (4) Chained redirects (one redirect-only record pointing to another) work but multiply the lookup budget — the cumulative cost is the sum of every redirected records mechanism costs.

4.2 The exp= modifier

Definition: RFC 7208 section 6.2 defines exp=<domain> as an EXPLANATION modifier that names a domain whose TXT record provides a human-readable message the receiver can include in a bounce or fail-time diagnostic when SPF returns fail. The explanation text supports macro substitution like exists targets do.

Syntax: exp=<domain>. Example: v=spf1 ip4:198.51.100.5 -all exp=spf-explain.example.com with spf-explain.example.com IN TXT "SPF check failed for %{i}; contact postmaster@%{d} for help." returns a fail with the explanatory message substituted at fail-evaluation time.

Lookup cost: ONE DNS lookup ONLY when the receiver generates a fail- explanation message. The exp= lookup does NOT count toward the standard 10-lookup evaluation budget — it is evaluated only after the main result is known and only if the receiver opts to fetch it. Most receivers do not fetch the explanation by default.

When to use: rarely. exp= is useful when the domain owner wants to surface a custom diagnostic message in NDRs for SPF failures (e.g., "your mail server is not on our authorized SPF list — contact postmaster@example.com"). Most receivers ignore exp= and return their own diagnostic messages.

Pitfalls: (1) Receiver support for exp= is INCONSISTENT — many do not fetch the explanation, so the modifier may have no observable effect. (2) The exp= target TXT record must exist or the receiver MAY surface a generic message; ensure the explanation TXT record is published before publishing the modifier. (3) Macros in the explanation TXT produce LOG OUTPUT visible to attackers — do not leak sensitive internal information (do not include internal IPs, server names, ticket IDs in the explanation).

5. Mechanism evaluation order: left-to-right, first-match-wins

RFC 7208 section 4.6.2 specifies that mechanisms are evaluated LEFT-TO- RIGHT in the order they appear in the SPF record. The FIRST mechanism that matches the connecting IP wins and the evaluator returns the matching mechanisms qualifier-derived result. Later mechanisms are NOT evaluated.

Concrete consequence: in a record like v=spf1 -ip4:198.51.100.5 +ip4:198.51.100.0/24 -all, a connection from 198.51.100.5 matches the FIRST mechanism (-ip4:198.51.100.5) and returns fail. The +ip4:198.51.100.0/24 mechanism later in the record would also match, but it is NOT evaluated because the earlier mechanism already won. This is the canonical pattern for blocking a specific IP within an otherwise-authorized prefix.

Reordering matters when overlapping mechanisms have DIFFERENT qualifiers. If the same record reordered the two ip4 mechanisms as v=spf1 +ip4:198.51.100.0/24 -ip4:198.51.100.5 -all, a connection from 198.51.100.5 matches the BROADER +ip4:198.51.100.0/24 FIRST and returns pass — the later more-specific -ip4:198.51.100.5 is never evaluated. The "specific overrides general" rule from firewall configurations does NOT apply to SPF; ordering does.

Practical guidance: put more-specific mechanisms BEFORE more-general ones when the qualifiers differ. Put the terminal all mechanism LAST. Put zero- lookup-cost ip4 and ip6 mechanisms FIRST for evaluation efficiency (the matching evaluator returns immediately without resolving DNS for the rest of the record). Note that the 10-lookup cap is enforced over the WORST-CASE path through the record — an SPF evaluation that matches on the very first mechanism still has to be SYNTACTICALLY VALID across the whole record (every mechanism in the record is checked at publication time for compliance with the lookup cap), but only the matching- path lookups are actually executed at evaluation time.

6. Cumulative lookup-cost accounting

RFC 7208 section 4.6.4 sets the SPF lookup budget at 10 DNS lookups per evaluation. Mechanisms and modifiers consume the budget at different rates, and operators must account for the WORST-CASE path (the unauthorized-IP terminal-fail evaluation that requires resolving every mechanism in the record).

Per-mechanism cost table:

SPF mechanism and modifier lookup costs
Mechanism / Modifier Counts toward 10-lookup cap? Cost per use
ip4 No 0 DNS lookups
ip6 No 0 DNS lookups
all No 0 DNS lookups
a Yes 1 DNS lookup (A/AAAA query)
mx Yes 1 + N DNS lookups (1 for MX, 1 per MX host, max 10)
ptr (DEPRECATED) Yes 1 PTR + 1 per PTR result (do not use)
exists Yes 1 DNS lookup (A query)
include Yes 1 + recursive cost of nested mechanisms
redirect= Yes 1 + recursive cost of redirected record
exp= No (separate) 1 DNS lookup ONLY when receiver fetches the explanation

The separate 2-void-lookup cap (RFC 7208 section 4.6.4) tracks NXDOMAIN and empty results independently. At most two mechanisms in the entire chain (including nested includes and redirects) may return a void result; the third void returns permerror regardless of whether the main lookup budget has been exhausted. Operators using exists with macros must publish a default A record at the unauthorized name to avoid void-lookup accumulation. See SPF lookup limit and flattening for the focused deep dive on both caps and the four canonical remediation paths (flattening, sender consolidation, subdomain delegation, DKIM-first alignment).

7. Worked deployment examples

Five concrete deployment patterns showing how the mechanisms compose. Each example states the sending estate, lists the SPF record, and walks the lookup-cost accounting.

Example 1: single on-prem mail server

Sending estate: one on-prem mail server at 198.51.100.5 handling all outbound mail. No third-party providers. No IPv6.

example.com.   IN TXT  "v=spf1 ip4:198.51.100.5 -all"

Lookup-cost accounting: ip4 costs zero lookups; all costs zero lookups. Total: 0 lookups. The record is well under the 10-lookup cap.

Example 2: Microsoft 365 / Exchange Online tenant

Sending estate: Microsoft 365 Exchange Online handling all outbound mail. No on-prem relay. No third-party services.

example.com.   IN TXT  "v=spf1 include:spf.protection.outlook.com -all"

Lookup-cost accounting: include:spf.protection.outlook.com costs 1 lookup (Microsoft publishes the record as a flat ip4 / ip6 list — no nested includes). Total: 1 lookup. Well under the cap. See SPF for Microsoft 365 for the provider deep dive.

Example 3: Google Workspace tenant

Sending estate: Google Workspace / Gmail handling all outbound mail. No third-party services.

example.com.   IN TXT  "v=spf1 include:_spf.google.com -all"

Lookup-cost accounting: include:_spf.google.com costs 1 lookup at the top level plus 3 NESTED lookups (the included record contains include:_netblocks.google.com include:_netblocks2.google.com include:_netblocks3.google.com). Total: 4 lookups. Well under the cap but burning almost half the budget. See SPF for Google Workspace for the provider deep dive.

Example 4: hybrid Microsoft 365 + transactional ESP

Sending estate: Microsoft 365 for end-user mail plus SendGrid for transactional and Mailgun for marketing.

example.com.   IN TXT  "v=spf1 include:spf.protection.outlook.com include:sendgrid.net include:mailgun.org -all"

Lookup-cost accounting: M365 include 1, SendGrid include 1, Mailgun include 1, plus any nested lookups each provider has in their record (1-2 nested lookups each is typical). Total: 3-7 lookups depending on each providers chain depth at evaluation time. Stays under the cap but the budget is filling up — adding more providers would push past 10.

Example 5: parked domain that sends no mail

Sending estate: a parked / inactive / acquired domain that should send no mail at all.

parked-example.com.   IN TXT  "v=spf1 -all"

Lookup-cost accounting: zero lookups. The record declares "no host is authorized to send mail with parked-example.com in the envelope MAIL FROM". Pair with v=DMARC1; p=reject; at _dmarc.parked-example.com for the strongest no-send posture. Every parked or inactive domain owned by your organization should have this pair published — see DMARC multi-domain strategy for the portfolio- operator playbook.

8. The seven most common mechanism-level pitfalls

Across thousands of SPF records analyzed in DMARC aggregate-report data, the same seven mechanism-level mistakes recur. Each pitfall states the mistake, its operational symptom, and the fix.

  1. Publishing +all as the terminator. Symptom: any host on the internet can pass SPF for your domain. Fix: never publish +all; use -all (production) or ~all (deployment ramp).
  2. Forgetting the all terminator entirely. Symptom: receivers return the implicit neutral for any non-matching host, which is no better than no record at all. Fix: always end every production record with -all or ~all.
  3. Using deprecated ptr. Symptom: intermittent temperror from in-addr.arpa SERVFAIL, slow evaluations, some receivers refuse to honor the record. Fix: remove every ptr mechanism and replace with explicit ip4 / ip6 / a / include.
  4. Exceeding the 10-lookup cap. Symptom: every evaluation returns permerror, DMARC downgrades to fail, "SPF stopped working overnight" after adding a vendor. Fix: count lookups per the table in §6, flatten or consolidate senders or move to DKIM-first alignment. See SPF lookup limit and flattening.
  5. Mixing mx with provider include:. Symptom: same providers IPs counted twice, lookup budget burned faster than expected, backup-MX servers (which never send) get accidentally authorized. Fix: pick ONE path — either include for cloud-provider deployments OR explicit a / mx for on- prem-managed mail flow.
  6. Missing IPv6 address-family coverage. Symptom: SPF passes from IPv4 connections but fails from IPv6 (Gmail and Microsoft 365 increasingly use IPv6 for SMTP). Fix: every IPv4 sending host with an IPv6 address must have its IPv6 address listed in ip6 too — or rely on provider include which already covers both families.
  7. Mixing redirect= with all. Symptom: permerror from publishing both modifiers in the same record. Fix: choose ONE — redirect= for full delegation (no all) OR explicit mechanism list with terminal -all (no redirect=).

9. FAQ

What are the eight SPF mechanisms?
RFC 7208 section 5 defines eight mechanisms an SPF record can use to authorize sending hosts: (1) all — matches every host, used as the terminal mechanism with a hard or soft qualifier; (2) ip4 — matches a literal IPv4 address or CIDR range, costs zero DNS lookups; (3) ip6 — matches a literal IPv6 address or CIDR range, costs zero DNS lookups; (4) a — matches the A or AAAA records of a named domain (default: the current domain), costs one DNS lookup; (5) mx — matches the A or AAAA records of every host listed in the MX records of a named domain (default: the current domain), costs one DNS lookup for the MX record itself plus one lookup per MX host (capped at 10 total per mechanism per RFC 7208 section 4.6.4); (6) ptr — matches the reverse-DNS PTR of the connecting IP and checks whether the resulting hostname matches the named domain, costs one PTR lookup plus one forward lookup per result and is explicitly DEPRECATED by RFC 7208 section 5.5; (7) exists — matches if a named domain has any A record (used for SPF macros and dynamic authorization), costs one DNS lookup; (8) include — recursively evaluates the SPF record of a named domain and matches if the included record passes, costs one DNS lookup for the includes own record plus the lookups of every mechanism inside it (the most expensive mechanism class and the #1 cause of 10-lookup-cap overruns).
What are the four SPF qualifiers and what do they mean?
RFC 7208 section 4.6.2 defines four qualifiers that prefix every mechanism and tell the receiver what result to apply when the mechanism matches: (1) + (pass, the default — the mechanism matching means the host IS authorized to send mail with this domain in the envelope MAIL FROM); (2) - (fail, hard fail — the mechanism matching means the host is NOT authorized and the receiver SHOULD reject the message); (3) ~ (softfail — the mechanism matching means the host is probably not authorized but the receiver SHOULD treat the result as a soft signal rather than an outright rejection, useful during a deployment ramp); (4) ? (neutral — the mechanism matching means the domain owner has no opinion on whether this host is authorized, equivalent to omitting the host from the record). Qualifiers prefix mechanisms with no space: -all means "fail if no earlier mechanism matched", +ip4:198.51.100.5 means "pass if the connecting IP is 198.51.100.5", ~include:_spf.example.net means "softfail if the included record matches". Most production SPF records terminate with -all (hard fail catch-all) or ~all (soft fail catch-all during deployment).
What does the SPF include mechanism do?
The SPF include mechanism (RFC 7208 section 5.2) recursively evaluates the SPF record of a named domain against the connecting IP and matches if the included record returns a pass result. Operators use include to delegate SPF authorization to a third-party sender (an ESP, a mailbox provider, a transactional email service) without having to enumerate that providers IP ranges by hand. Canonical example: v=spf1 include:spf.protection.outlook.com -all delegates authorization to Microsoft 365 by including the spf.protection.outlook.com SPF record, which Microsoft maintains as a flat list of Exchange Online sending IPs. include costs one DNS lookup for the includes own SPF record plus the lookups of every mechanism inside it — the included record is evaluated recursively and every nested include/a/mx/ptr/exists/redirect inside it counts toward the same 10-lookup cap. include is the #1 cause of 10-lookup-cap overruns because each include can pull in 1 to 5 nested lookups depending on the providers chain depth. If the included record returns permerror or temperror, RFC 7208 section 5.2 says the include mechanism returns the same error. include also requires the included record to publish v=spf1 — including a record that returns "none" (no SPF record published) returns permerror.
How does the SPF a mechanism work?
The SPF a mechanism (RFC 7208 section 5.3) matches if the connecting IP appears in the A or AAAA DNS records of a named domain. Without an argument, a defaults to the current domain — so a alone in v=spf1 a -all means "match if the connecting IP is one of the A/AAAA records of this domains apex". With an explicit argument (a:mail.example.com), it matches against that named domain instead. With a CIDR suffix (a/24 or a:mail.example.com/24), the match is broadened to the /24 prefix containing each resolved A record. a costs one DNS lookup against the 10-lookup cap. Operators use a when the mail server lives at the same IP as the web server (a means "include my main A record") — most useful for small single-server deployments. Pitfalls: a only resolves the named domains FIRST-LEVEL A records (it does not chase CNAMEs through to the final A — the resolver does that during normal DNS resolution); a does NOT include the IPs of hosts pointed to by MX records (use mx for that); a:mail.example.com does NOT recursively expand into another SPF record (use include for that).
How does the SPF mx mechanism work?
The SPF mx mechanism (RFC 7208 section 5.4) matches if the connecting IP appears in the A or AAAA DNS records of any host listed in the MX records of a named domain. Without an argument, mx defaults to the current domain. With an explicit argument (mx:example.net), it uses that named domains MX records instead. mx costs at least one DNS lookup for the MX record itself plus one lookup per MX host returned, capped at 10 per RFC 7208 section 4.6.4 (mechanisms that request more than 10 lookups MUST return permerror). Operators use mx when the same servers that receive mail for the domain also send mail for it — the legacy "my mail server is my mail server" symmetry. Pitfalls: (1) MX records typically list 2-5 hosts and mx counts EVERY one of them against the lookup cap — a small organization with 5 MX hosts spends 6 of the 10-lookup budget on the single mx mechanism, leaving only 4 lookups for everything else; (2) outsourcing inbound mail to a cloud filter (Proofpoint, Mimecast, Microsoft Defender for Office 365) replaces your MX hosts with the filter providers — mx then authorizes the FILTERS IPs to send AS YOU, which is rarely what you want; (3) using both mx and the providers SPF include can double-count IPs unnecessarily and burn the lookup budget.
Why is the SPF ptr mechanism deprecated?
RFC 7208 section 5.5 explicitly DEPRECATES the ptr mechanism. The mechanism matches if the reverse-DNS PTR record of the connecting IP resolves to a hostname under the named domain (and the forward A record of that hostname includes the connecting IP). Three reasons motivate the deprecation: (1) Performance and DNS load — every ptr evaluation requires a PTR lookup followed by a forward A lookup of every PTR result, which can produce dozens of DNS queries against the in-addr.arpa or ip6.arpa tree for a single SPF evaluation; (2) Reliability — many ASes do not publish accurate PTR records and DNS resolvers frequently return SERVFAIL or transient errors for in-addr.arpa lookups, producing intermittent temperror results; (3) Trust model — relying on reverse DNS for authorization places trust in the IP holders DNS infrastructure rather than the senders, which is not the same trust boundary SPF is designed to enforce. RFC 7208 section 5.5 explicitly says "Use of this mechanism is discouraged because it is slow, it is not as reliable as other mechanisms in cases of DNS errors, and it places a large burden on the .arpa name servers" and "If used, proper PTR records have to be in place for the domains MX hosts". Operators should remove every ptr mechanism from their SPF records and replace it with ip4 / ip6 / a / mx / include as appropriate.
What does -all mean in SPF?
The terminal mechanism -all in an SPF record (RFC 7208 section 5.1) means "no other mechanism in this record matched the connecting IP, therefore the result is fail (hard fail)". -all is the canonical SPF policy decision: the domain owner is asserting that hosts NOT explicitly authorized by earlier mechanisms are NOT authorized to send mail with this domain in the envelope MAIL FROM, and the receiver SHOULD reject the message. Receivers that honor SPF treat -all as the strongest negative signal — Gmail, Microsoft 365, Yahoo, and Apple all reject or quarantine -all failures by default. -all is the recommended termination for every production SPF record. Three softer alternatives exist: ~all (softfail — receiver SHOULD treat as a soft signal, useful during a deployment ramp), ?all (neutral — domain owner has no opinion, equivalent to no SPF record at all), and +all (pass — every host is authorized, equivalent to disabling SPF — never publish this in production). Note that DMARC alignment can pass via DKIM even when SPF returns fail, so -all does not on its own block forwarding-mangled SPF — that is what DMARC + DKIM are for. -all is a policy declaration, not an enforcement mechanism — DMARC provides the enforcement.
What is the difference between SPF ip4 and ip6?
SPF ip4 and ip6 are sibling mechanisms (RFC 7208 sections 5.6 and 5.7) that match the connecting IP against a literal IPv4 or IPv6 address or CIDR range respectively. Both cost ZERO DNS lookups — they are evaluated entirely from the SPF records own text, making them the cheapest mechanisms available. Syntax: ip4:198.51.100.5 matches the exact IPv4 address 198.51.100.5; ip4:198.51.100.0/24 matches any IPv4 address in the 198.51.100.0/24 prefix; ip6:2001:db8::1 matches the exact IPv6 address 2001:db8::1; ip6:2001:db8::/32 matches any IPv6 address in the 2001:db8::/32 prefix. Operators use ip4/ip6 when they directly control the sending IP space (an on-prem mail server, a dedicated cloud SMTP host, a static-IP transactional service). The "no DNS lookup" property makes ip4/ip6 the recommended SPF target after flattening — a flattened SPF record converts every include/a/mx into a flat list of ip4/ip6 mechanisms so the 10-lookup cap is no longer a constraint. Pitfalls: (1) literal IPs are brittle — every IP change requires re-publishing the SPF record (managed flatteners help here); (2) ip4 and ip6 are address-family specific — an ip4 mechanism does NOT match an IPv6 connection and vice versa, so dual-stack deployments need both; (3) overly broad CIDR ranges (ip4:0.0.0.0/0) authorize the entire internet — never publish this.
How many DNS lookups does each SPF mechanism cost?
RFC 7208 section 4.6.4 counts the DNS lookup cost of each mechanism toward the 10-lookup cap. The breakdown: (1) ip4 and ip6 cost ZERO lookups — they are evaluated from the SPF records text without DNS; (2) all costs zero lookups — it is the terminal catch-all; (3) a costs ONE lookup (the A/AAAA query for the named domain or current domain); (4) mx costs ONE lookup for the MX record itself plus ONE lookup per host listed in the MX record (a domain with 5 MX hosts spends 6 lookups on a single mx mechanism); (5) include costs ONE lookup for the included records SPF TXT plus the lookups of every mechanism inside the included record (recursively — include is the most expensive mechanism class); (6) exists costs ONE lookup; (7) ptr costs ONE PTR lookup plus ONE forward A lookup per result returned (ptr is also DEPRECATED by RFC 7208 section 5.5 — do not use); (8) the redirect= modifier (RFC 7208 section 6.1) costs ONE lookup (the redirected domains SPF record). The exp= modifier (RFC 7208 section 6.2) costs ONE lookup but ONLY when the receiver generates a fail explanation message — it does not count toward the standard evaluation budget. There is a SEPARATE 2-void-lookup cap (RFC 7208 section 4.6.4) — at most two mechanisms in the entire evaluation chain may return NXDOMAIN or empty result; the third void lookup returns permerror. See help_spf_lookup_limit.php for the full lookup-budget deep dive.

11. Verify your SPF mechanism record now

Once your SPF record uses the mechanism vocabulary correctly, verify it from three angles: live DNS lookup with the Domain Security Checker (parses your published record, flags 10-lookup overruns and deprecated mechanisms), live Authentication-Results inspection with the Email Header Analyzer (paste a header from a real delivery and see which SPF mechanism matched at the receiver), and ongoing DMARC aggregate- report monitoring through the platform dashboard (the aggregate reports surface sources passing and failing SPF over time, catching record drift and new senders before they cause deliverability incidents).

Check my SPF mechanism record Analyze an authentication-results header Sign up — Free plan

Last updated: 2026-09-18