Every MX check nobounce.dev runs goes over DNS-over-HTTPS (DoH), not through a stub resolver on UDP port 53. That is not a style choice. The engine runs at the edge, where a function gets an HTTP fetch and no datagram socket, so classic DNS as defined in RFC 1035 is simply unreachable — RFC 8484 carries the same query over an HTTPS request instead. The side effect is the useful part: a DoH JSON response hands the validator the RCODE, the record TTLs and the SOA of the negative answer as plain fields, which is exactly the evidence a verdict has to branch on.
The request
curl -H 'accept: application/dns-json' \
'https://cloudflare-dns.com/dns-query?name=gmail.com&type=MX'
Three fields do the work. Status is the RCODE from RFC 1035. Answer holds the records. Authority holds the SOA on a negative answer, and its TTL is the resolver's negative cache lifetime under RFC 2308 — the minimum of the SOA TTL and the SOA MINIMUM field.
Five answers, measured on 2026-10-07
Every row below was resolved live today against Cloudflare's public DoH resolver, then run through the fixture corpus at /demo/check. No row is hypothetical.
| query | DoH returned | nobounce verdict |
|---|---|---|
gmail.com MX |
Status 0, five MX records, TTL 979 | deliverable / ok |
example.com MX |
Status 0, single answer "0 ." |
undeliverable / reserved_domain |
gmail.co MX |
Status 0, answer "0 ." |
undeliverable / null_mx |
outlook.con MX |
Status 3, no answer, SOA TTL 86400 | undeliverable / domain_not_found |
gmaill.com MX |
Status 0, no answer, SOA TTL 300; no A, no AAAA | undeliverable / no_mx_no_a |
gmial.com MX |
Status 2 SERVFAIL, EDE(22): No Reachable Authority |
unknown / dns_error |
Four of those rows deserve comment beyond the table.
"0 ." is a decision, not an absence. A null MX per RFC 7505 is the domain operator publishing "this domain accepts no mail, do not fall back". Two different domains can publish the identical record and deserve different reasons: example.com is an RFC 2606 reserved name that must surface as reserved_domain or headless QA breaks, while gmail.co is a real registration refusing mail and returns null_mx. The distinction and its trap are covered in Null MX and Reserved Domains.
An empty answer is not a rejection. gmaill.com returns NOERROR with no MX. Under RFC 5321 the implicit-MX rule says a sender then tries the domain's own A/AAAA records — so the validator has to ask again for both before it may conclude anything. Today gmaill.com has neither, which is what no_mx_no_a means: nothing to deliver to, nothing to fall back to.
NXDOMAIN arrives with a clock attached. The outlook.con authority section carries an SOA whose negative TTL is 86400 — a full day. A resolver that cached this answer serves it for up to a day even if the domain is registered five minutes later. Your validator inherits that cache; "the domain does not exist" always means "does not exist as far as this resolver's cache says right now".
SERVFAIL carries a reason. The gmial.com failure comes back with an Extended DNS Error, EDE(22): No Reachable Authority — RFC 8914's way of saying the resolver could not reach the delegation, not that the delegation answered no. Status 2 and Status 3 produce opposite caller behaviour: NXDOMAIN is a reject, SERVFAIL is a fail-open. SERVFAIL Is Not NXDOMAIN is the full write-up of why collapsing them is the classic bug.
What the resolver's cache does to your latency
That gmail.com TTL of 979 is not a constant — it is a countdown from 3600 on a cached answer. You are always reading the resolver's cache, never the authoritative zone. A validator on top of DoH therefore has two cache layers of its own to get right: nobounce keeps a shared domain cache with 6h positive and 1h negative TTLs, and a failed lookup never writes a row, so a resolver hiccup cannot pin a wrong verdict for the TTL. The cached field in the response tells you which layer your answer came from.
The resolver is a dependency — plan for its outage
Because every check crosses the resolver, the resolver is on your signup path. Treat it the way you treat any third-party on that path: with a deadline and a fail-open branch. unknown with checked.mx: false is the contractual statement that DNS did not complete, and it is never reported as deliverable. Note that even that gmial.com response still carries suggestion: user@gmail.com — typo correction runs before DNS and does not depend on it, so a resolver outage costs you the MX verdict, not the correction. The deadline mechanics are in How to Handle an Email Validation Timeout at Signup.
What leaves your system
Only the domain crosses to the resolver — the local part is never part of a DNS query. On the nobounce side, no email address is persisted in any form: anything stored is SHA-256 hashed, and domains are kept in clear because a domain is not personal data. A DNS-based validator that never opens an SMTP session cannot leak the local part to a receiving mail server either, because no SMTP session exists.
The caller's branch table
curl -X POST https://nobounce.dev/v1/check \
-H "Authorization: Bearer $NOBOUNCE_KEY" \
-H 'Content-Type: application/json' \
-d '{"email":"user@gmaill.com"}'
{
"verdict": "undeliverable",
"reason": "no_mx_no_a",
"suggestion": "user@gmail.com",
"confidence": 0.94,
"checked": { "syntax": true, "mx": true, "typo": true, "disposable": true },
"cached": false
}
Branch on reason, not on prose: domain_not_found, null_mx and no_mx_no_a are three different DNS stories with the same caller action (show the suggestion, end the attempt); dns_error with checked.mx: false is the one that means "accept and continue". Do not cache a failed check on your side either — the resolver may recover before your TTL does.
For the full verdict-to-action mapping, key handling and the fail-open contract, give your coding agent the integration guide at nobounce.dev/integrate.md.