CertaDNS
Skip to lesson

How an Evaluation Runs · lesson 1 of 3

The algorithm, term by term

After this lesson you can

Walk any evaluation in the order a receiver walks it, and say where it stopped.

Fundamentals gave you the parts. This course is about operating them, and that starts with being able to run the algorithm in your head exactly as a receiver runs it — because every diagnosis in module 7 is a matter of saying which term the evaluation reached and what it did there.

The three inputs

An evaluation takes three values, and RFC 7208 §4.1 names them:

InputWhere it comes fromNotes
ipThe connecting client address.The only one that cannot be forged.
senderThe MAIL FROM address.If MAIL FROM is empty — a bounce — the HELO name is used instead.
heloThe HELO/EHLO name.Used for the check above, and to expand the h macro.

The empty-sender case matters more than it looks. A bounce message has no envelope sender, so SPF is evaluated against the HELO name instead — which means a server whoseHELO is wrong can fail SPF on bounces while passing on everything else.

The loop

1. Fetch the TXT records at the sender's domain.
   none found        -> none
   more than one v=spf1 -> permerror
   DNS error         -> temperror

2. For each term, left to right:
     a. If it is a modifier (redirect=, exp=), set it aside.
     b. Otherwise evaluate the mechanism against ip.
        - Does it need DNS? Spend one from the budget.
        - Budget exhausted, or >2 void lookups  -> permerror
        - DNS error                             -> temperror
     c. Match? Return the result its qualifier names. STOP.

3. No mechanism matched.
     redirect= present -> evaluate that domain and return its result
     otherwise         -> neutral
RFC 7208 §4.6 and §4.7, as a procedure.

Two things that trip people at step 2c

Evaluation stops at the first match. Not the best match, not the most specific — the first. A record is read like a firewall ruleset, and everything after a matching term is unreachable.

A term that does not match is not a failure. An include whose target evaluates to fail simply does not match, and evaluation continues to the next term. That is the single most misread behaviour in the whole specification.

A worked evaluation

Take isc.org, checked 2026-09-15, and a connection from 149.20.1.5:

$ dig +short TXT isc.org | grep spf1
v=spf1 a mx ip4:149.20.0.0/20 ip6:2001:04F8::0/32 ... include:shops.shopify.com ... -all
  • a — resolve isc.org’s address records. One lookup. Is 149.20.1.5 among them? No. Continue.
  • mx — resolve the MX set, then each host’s addresses. Several lookups. No match. Continue.
  • ip4:149.20.0.0/20 — range 149.20.0.0 to 149.20.15.255. 149.20.1.5 is inside it. Match. Qualifier is the implicit +, so the result is pass.

Everything after that ip4 term — the remaining ranges, three includes, the -all — was never evaluated, and cost nothing. That is why term order is a performance decision as well as a correctness one.

Where this bites in practice

Because the budget is spent left to right, a record that puts a and mx before its ip4 ranges spends DNS lookups on every evaluation, including the ones that were always going to match a literal range two terms later. Moving cheap literal terms ahead of costly ones changes no result and removes work from every receiver that evaluates you.

Try it on a real domain

Free, no account, public DNS only.

Last reviewed