Learn

Learn

How the standards Domain Health AI measures actually work — the fine print, counted the way receivers count it. Skim or jump to a section.

Domain roles: what is this domain trying to be?

Every report names the domain's role, detected from two measured axes: does it run email (usable MX records — or an RFC 7505 null MX / v=spf1 -all declaring the opposite), and what does its web root do — serve a site, permanently redirect (301/308) to a different domain, or answer nothing. The two answers name one of six roles:

What's set up Role
Email + web full-service
Email + redirect redirect-service
Email only email-only
Web only web-only
Redirect only redirect-only
Neither no-service

The fine print, exactly: example.com landing on www.example.com (or the reverse) is canonicalization, not a redirect — www is a spelling of the same site. Landing anywhere else is a redirect role, even inside the same domain: example.com forwarding to blog.example.com forwards. A registrar sale-lander is a website, so it reads as web-only. When signals contradict or can't be measured, the report says unclassified rather than guessing.

The role changes how findings read: "no DKIM selectors found" is a caution on a mail domain and expected on a no-mail one, so statuses are adjudicated role-relatively — every adjustment is listed in the report, with the raw status preserved. Know what the domain is supposed to be? Set the intended use (the goal parameter) and the report is judged against that instead, with contradictions surfaced as gaps — "goal email-only, but no MX records exist."

Apex + subdomains: every report anchors at the apex

Registration, MX records, SPF, and DMARC governance belong to the registrable domain — the apex, example.com — so that's what every report is anchored to, whatever you type. www.example.com simply folds into example.com: www is a spelling, not a different place, and the report notes the fold.

Check any other subdomain — blog.example.com — and you get the apex report plus a lens on the name you asked about: what its web root does, and whether it runs any mail setup of its own. Most subdomains don't, and that's healthy — their mail posture is governed by the apex's DMARC (the sp= tag when present, otherwise p=), so the lens says so in one line instead of raising alarms about records that were never supposed to exist. A subdomain with its own MX, SPF, DKIM, or DMARC is usually deliberate — newsletters, ticketing, transactional mail — so the lens surfaces it and asks you to confirm it's yours.

One thing always carries down: a registry alarm on the apex — suspended, past expiry, expiring soon — takes every subdomain with it, so it shows at full volume on both reports.

Why the split? Each thing the report measures is owned by a different scope — the web's unit is the origin, mail records hang off individual DNS names, and registration belongs to the apex:

Concept Scope Why
security.txt (RFC 9116) Per-origin every https host answers for itself; a redirect can hand the question to the destination site
Website / redirect Per-name each DNS name serves, forwards, or stays silent on its own
Receiving mail (MX) Per-name any name can accept mail — subdomains included, which is why the lens looks
SPF Per-name authorizes senders for exactly the name in the return path; a sending subdomain needs its own
DKIM Per-signing-domain the signature's d= tag names the domain that vouches; keys live under that name
DMARC Apex, inherited the organizational domain's policy covers every subdomain (sp=, else p=) unless one publishes its own
Registration & expiry Apex only you register example.com — blog.example.com was never registered at all
Lifecycle alarms Apex only a suspended or expired apex takes every subdomain down with it

The 10-lookup limit, exactly

RFC 7208 §4.6.4 allows an SPF evaluation at most 10 DNS-querying terms. Cross it and the receiver returns permerror — the record is treated as unusable, as if you had published nothing. The rule is simple; how the ten are counted is where records quietly go wrong.

What costs a lookup

Six terms query DNS and count: include, a, mx, ptr, exists, and the redirect modifier. Three are free: ip4, ip6, and all — they compare against data already in hand. (mx has a further sub-limit of 10 address lookups per name, and ptr is deprecated outright.)

Duplicates are never flattened

The limit counts terms evaluated, not unique names. Every time evaluation reaches include:d.example, the receiver runs a fresh check_host() on d.example's record — and every DNS-querying term inside it counts again. The resolver's cache makes the repeat queries fast; the counter does not care. Nothing in RFC 7208 deduplicates, merges, or flattens repeated targets.

The classic shape is the diamond: two branches that both include the same target.

example.com  v=spf1 include:b.example include:c.example -all
b.example    v=spf1 include:d.example -all
c.example    v=spf1 include:d.example -all
d.example    v=spf1 a mx ptr exists:chk.example -all
A diamond: example.com → b, c; both → d. Legal SPF — and expensive.
lookup term evaluated via
1 include:b.example example.com
2 include:d.example b.example
3–6 a · mx · ptr · exists d.example, first evaluation
7 include:c.example example.com
8 include:d.example c.example
9–12 a · mx · ptr · exists d.example, evaluated again
12 of 10 — permerror. A counter that flattens duplicates sees 8 and passes a record real receivers reject.

One nuance keeps the number honest in both directions: receivers evaluate left to right and stop at the first mechanism that matches, so a given message may cost fewer than the worst case. But which path a message takes depends on the sender's IP — a checker has to count the path where nothing matches until the end, because some real message will take it.

A diamond is not a loop

Reaching the same target through two different branches is legal. What is not legal is the path revisiting itself — a includes b, which includes a again. In practice a loop never recurses forever: each round trip burns lookups, so the evaluation crosses the 10-lookup limit and dies as permerror within a couple of laps. A checker that confuses the two shapes will call your legal diamond a loop — or miss counting the diamond twice.

How the spf check counts

worst case, per edge
Every DNS-querying term counts each time evaluation would reach it. A diamond's shared target counts once per branch — never flattened.
fetched once
Records are cached for the walk, so the diamond's shared target is fetched from DNS once even though its terms count twice. Counting and traffic are separate questions.
loops = path only
A loop is flagged only when an include/redirect chain revisits a domain already on the current path. Diamonds pass.
dead includes
An include whose target publishes no SPF record is its own permerror (RFC 7208 §5.2) and is reported separately.
5-second budget
Slow or black-holed include targets can stall the walk, so counting carries a wall-clock budget. If it expires, the report says so and gives the count as "at least N" — a partial count is stated as partial, never rounded up to a verdict.

Getting back under ten

Flatten what you can: replace an include whose contents you control with its ip4/ip6 networks (they cost nothing), and keep a note to re-check them — flattened records go stale silently. Drop vendors you no longer send through, remove ptr, and consider moving bulk senders to a subdomain with its own record. Then run the check — the report counts your record's worst case the way receivers will, and tells you exactly what to change.