EmailGuard
  • Pricing
Log inRegister
Earth from orbit at night showing connected city lights

MX Record Lookup vs Email Validation

by EmailGuard Engineering

Unsplash
September 14, 2026engineering10 min read

Share this article

On this page

  • What you'll leave with
  • What an MX record is
  • RFC 5321: empty MX is not the whole story
  • Null MX is a published "do not send"
  • MX vs SMTP vs classification
  • What EmailGuard returns (first-hand)
  • Do not copy `dns.resolveMx` into the browser
  • Signup policy we recommend
  • Worked domains
  • Where MailCop-style architecture posts differ
  • Common mistakes
  • FAQ
    • Does an MX record prove the email address is real?
    • Should I block signups with no MX record?
    • Is MX lookup the same as email verification?
    • Why does EmailGuard omit mx_present sometimes?
    • Can I use the free MX tools instead of an API?
  • Next steps

Enjoyed this article?

More notes on building products, infrastructure, and teams.

EmailGuard

Email validation API for signup and lead intake—syntax, disposable, role, relay, and public-domain signals in one request.

Product

  • What's included

Docs

  • Documentation
  • API Reference
  • Knowledge Base

Resources

  • Blog
  • Changelog
  • Free tools
  • Alternatives
  • Legal & security
  • Contact
  • Data correction

Pricing

  • Pricing

© 2026 EmailGuard

A Baker Assets company

An MX record lookup asks DNS which hosts accept mail for a domain. It does not ask whether jane@ exists on that domain. Email validation at signup still needs syntax and classification. Mailbox existence is a third job, and it is SMTP. EmailGuard does the first two and can return mx_present from a cached DNS snapshot. Omitted mx_present means we did not have a snapshot, not that the live internet has no MX.

This page sits next to email validation vs verification. Buy the job, not the vendor word.

What you'll leave with

  1. What an MX record actually proves.
  2. Why "no MX means undeliverable" is incomplete under RFC 5321 §5.1.
  3. How Null MX (RFC 7505) differs from an empty MX set.
  4. How EmailGuard's mx_present, wildcard_dns, and infra_cluster_match flags differ from dns.resolveMx in your process.
  5. A signup policy that does not treat a DNS timeout as a fake user.

What an MX record is

An MX (mail exchanger) record is a DNS resource record. It names the host that should receive mail for a domain and a preference number. Lower preference is tried first. That is routing data for senders. It is not a directory of local-parts.

A lookup answers: "If I send to @example.com, which hosts did the domain publish?" It does not answer: "Is jane a mailbox on that host today?"

RFC 5322 defines the address format (local-part @ domain). Format plus MX still leaves the local-part unproven.

RFC 5321: empty MX is not the whole story

RFC 5321 §5.1 tells senders how to locate the target host. If there are no MX records, the client falls back to A or AAAA for the domain name itself and treats that as implicit MX. A Node one-liner that only calls dns.resolveMx and rejects on empty results will refuse some domains that still accept mail.

Two other DNS outcomes get collapsed in blog code samples:

DNS resultWhat it meansSignup instinct
NXDOMAINThe name does not existSafe to reject after syntax
NODATA / empty MX, domain existsNo explicit MXCheck A/AAAA before you call it dead
Timeout / SERVFAILYou learned nothingUnknown. Do not treat as disposable
MX presentDomain published mail routingDomain can receive mail in principle

Vendor posts sometimes quote a share of "no MX" domains that never accept mail. We have not reproduced that measurement. Treat it as marketing unless you ran the same resolver study.

Null MX is a published "do not send"

RFC 7505 defines Null MX: preference 0 and a target of . (the root). That is an explicit "this domain does not accept mail." It is cleaner than publishing nothing, because nothing triggers the A/AAAA fallback. If you write your own resolver, special-case Null MX. Do not treat it as "empty MX, try A."

EmailGuard classification does not open port 25 to test Null MX live on every signup. If you need that edge in your own resolver, implement RFC 7505 there.

MX vs SMTP vs classification

Signup blogs often stack three layers and call the stack "validation."

CheckNetworkProvesLies about
SyntaxNoneStoreable addr-specTypos that still parse (gnail.com)
MX / implicit MXDNSPublished (or implicit) mail routingLocal-part, catch-all, throwaway farms with real MX
ClassificationLists + DNS intelKind of address (disposable, role, relay, freemail)Inbox existence
SMTP RCPT / VRFYPort 25What that MX said to your IPs todayGmail/Microsoft anti-probe, greylist 4yz (RFC 6647)

A disposable domain can publish perfect MX. 10minutemail style hosts exist to receive the confirmation message. MX-pass then SMTP-pass still stores a user you cannot email next week.

A corporate catch-all publishes MX and accepts any local-part. MX-pass tells you the company runs mail. It does not tell you asdkjfh@bigco.com is a person.

We do not sell the SMTP row. If you need mailbox ping, that is Kickbox, ZeroBounce, NeverBounce, and peers. See alternatives. Prove a mailbox you care about with a confirmation link.

What EmailGuard returns (first-hand)

GET /api/v1/emails/detect classifies the address. When we have a cached DNS snapshot for the domain, the 2xx body may include:

FieldMeaning
mx_presentSnapshot saw mail-routing data we store as present
wildcard_dnsSnapshot looked like wildcard DNS typical of temp-mail farms
infra_cluster_matchDomain shares MX/A infrastructure with a known disposable cluster

These flags are documented on the detect API and in confidence and fail-open.

Omitted is not false. If the snapshot is missing, we omit the flags. Do not write if (!data.mx_present) reject(). That treats "we did not cache this domain" as "no mail route."

They are not SMTP. We do not VRFY or RCPT. mx_present: true is not deliverable. infra_cluster_match is a classification hint, not a mailbox probe.

detection_source stays precomputed or live_dns for the disposable path. Do not mix that field up with "we just ran dig MX in your region."

Do not copy dns.resolveMx into the browser

A browser MX check via DNS-over-HTTPS still leaks the typed domain to a third resolver and still misses classification. Run server-side detection. Keep the API key off the client.

A reasonable Node sketch for your own pre-check, if you insist on a local MX gate before calling anyone:

import { promises as dns } from "node:dns";
 
export async function domainHasMailRoute(domain: string): Promise<
  "yes" | "no" | "unknown"
> {
  try {
    const mx = await dns.resolveMx(domain);
    if (mx.some((r) => r.exchange === "" || r.exchange === ".")) {
      return "no"; // Null MX style
    }
    if (mx.length > 0) return "yes";
  } catch (err: unknown) {
    const code = (err as { code?: string }).code;
    if (code === "ENOTFOUND") {
      try {
        await dns.resolve4(domain);
        return "yes"; // implicit MX, RFC 5321 §5.1
      } catch {
        try {
          await dns.resolve6(domain);
          return "yes";
        } catch (inner: unknown) {
          const innerCode = (inner as { code?: string }).code;
          if (innerCode === "ENOTFOUND") return "no";
          return "unknown";
        }
      }
    }
    return "unknown";
  }
  return "unknown";
}

Use unknown the way you use a fail-open timeout. Then call detect for disposable, role, relay, and public domain. That is the email validation API checklist.

Signup policy we recommend

SignalAction
Syntax fail422, ask them to fix. If suggested_email is present, offer it. See email typo suggestions.
Detect timeout / 5xx / 429Fail open on ordinary signup. Recheck later.
disposable: trueBlock. MX will often be fine.
wildcard_dns or infra_cluster_match with disposableBlock. You already have the disposable flag.
mx_present omittedIgnore. Do not invent a reject.
mx_present: false on a snapshotStrong hint the domain is not a mail destination. Still combine with syntax. Confirm with your own resolver if you hard-block.
Role / relay / public domainPolicy, not MX. See role signup policy and work email.

Checkout and KYC can fail closed on transport errors. Ordinary signup should not.

Worked domains

Use these as staging fixtures. Do not treat them as a forever-accurate catalog.

Address-shaped inputWhat MX-only code often doesWhat you want
user@gmail.comPass (MX present)Classify: public_domain, not disposable
user@gnail.comFail or pass depending on whether the typo domain publishes MXSyntax / typo path first
A current Guerrilla-style hostPass (they want the confirmation mail)disposable: true
@privaterelay.appleid.comPassrelay_domain, allow or confirm
Made-up TLD, NXDOMAINRejectReject
Domain with only A, no MXReject if you skipped RFC 5321Implicit MX, then classify

Microsoft 365 Directory-Based Edge Blocking can 550 invalid recipients at the edge when the accepted domain is Authoritative. That is an SMTP-time behavior after MX is already published. It does not show up in resolveMx. Internal relay tenants turn DBEB off. "M365 is catch-all" is tenant config, not an MX fact. Google documents catch-all routing for unknown local-parts. Again, MX is present either way.

If your threat model is bounce rate on a blast, buy a verifier and accept unknown. If your threat model is trial abuse, MX is a weak filter and classification is the filter.

Where MailCop-style architecture posts differ

MailCop's 2026 MX article (fetched for this piece) tells you to run a 3-second DNS timeout at signup, skip SMTP on the request path, and mark unresolvable names as pending. That timeout budget is for their resolver plus an optional later SMTP job. EmailGuard's detect call is a classification GET. Live DNS inside our pipeline has its own budget (EMAIL_DNS_TIMEOUT_MS on our side). Your client abort should be 400–800 ms if you want that work to finish, not 3 seconds copied from an SMTP guide. See fail-open timeouts.

They also publish a 94% figure for domains without MX that never accept mail. That is their study, not ours. We are not repeating it as a fact.

DEV posts in this SERP often stop at "MX then SMTP" and treat classification as a footnote. Signup abuse lives in the footnote.

Common mistakes

Rejecting empty MX with no A/AAAA fallback. You are not implementing RFC 5321.

Treating MX timeout as invalid. You are implementing packet loss as fraud.

Skipping disposable checks because MX passed. Throwaway providers publish MX on purpose.

Caching mx_present for weeks. Snapshots go stale. Recheck on a short TTL. See how to cache detect results.

Calling SPF a recipient check. RFC 7208 authorizes sending hosts. It does not list inboxes.

FAQ

Does an MX record prove the email address is real?

No. It proves the domain published mail routing, or that you are looking at implicit MX / a snapshot of that fact. The local-part is unproven.

Should I block signups with no MX record?

Block NXDOMAIN and Null MX after you understand the lookup. Do not block on timeout. Do not block on omitted mx_present from EmailGuard. For empty MX, follow RFC 5321 A/AAAA fallback before you decide.

Is MX lookup the same as email verification?

No. Verification blogs usually mean SMTP after MX. That is a mailbox ping. Classification (disposable, role, relay) is a different job. Vocabulary: validation vs verification.

Why does EmailGuard omit mx_present sometimes?

No cached DNS snapshot for that domain. Absence of the field is not a negative MX result.

Can I use the free MX tools instead of an API?

You can inspect a single domain in any dig-style tool. Signup needs a server-side policy and disposable/relay flags. Start from pricing or the disposable checker while you wire detect.

Next steps

  1. Stop treating resolveMx as your whole signup gate.
  2. Add detect and store disposable, relay_domain, and the DNS flags when they appear.
  3. Fail open on DNS and HTTP timeouts for ordinary registration.
  4. Read features and the detect reference before you copy an SMTP TTL table onto this stack.