← All posts

How to check whether an email domain can receive mail: MX and SPF

Run this:

dig MX example.com +short

Hostnames come back, the domain publishes somewhere for mail to go. Nothing comes back, it doesn't, and an address at that domain is almost always a typo. SPF is a second lookup, dig TXT example.com +short, and it answers a different question — which servers may send as this domain — which is useful colour and no proof at all about receiving. That's the check. The rest of this page is the edge cases and what to do with the answer at a signup form.

An MX record is a list of servers that will take your mail

MX stands for mail exchanger. It's a DNS record naming a host that accepts SMTP for the domain, with a preference number in front of it. Lower numbers are tried first; equal numbers share the load. Most real domains publish several, so a single dead server doesn't lose their mail:

$ dig MX gmail.com +short | sort -n
5 gmail-smtp-in.l.google.com.
10 alt1.gmail-smtp-in.l.google.com.
20 alt2.gmail-smtp-in.l.google.com.
30 alt3.gmail-smtp-in.l.google.com.
40 alt4.gmail-smtp-in.l.google.com.

One is enough, though — plenty of big domains publish a single record, because the host behind it is already a cluster:

$ dig MX github.com +short
0 github-com.mail.protection.outlook.com.

The hostname on the right is often the most useful part. mail.protection.outlook.com means Microsoft 365, l.google.com means Google Workspace, and either way somebody administers this domain on purpose.

What an empty answer proves, and what it doesn't

An empty result is strong evidence and not quite proof. RFC 5321 lets a sender fall back to the domain's A or AAAA record when no MX exists — the implicit MX rule — so a domain with an address record but no MX is technically still deliverable. In practice that shape is nearly always a parked typo. Here's the classic one:

$ dig MX gmial.com +short
$ dig A gmial.com +short
51.79.68.169

No mail server, one parked web host. A signup form should treat that as a bad address and say so.

Two answers are unambiguous. If the domain doesn't exist at all, the response status is NXDOMAIN and +short shows you nothing, so check the header when you care about the difference:

$ dig MX thisdomaindoesnotexist-zzq.com | grep status
;; ->>HEADER<<- opcode: QUERY, status: NXDOMAIN, id: 43093

And a domain can refuse mail explicitly. A single MX record pointing at . is the null MX of RFC 7505: this domain accepts no mail, don't try. example.com publishes exactly that:

$ dig MX example.com +short
0 .

The same query without dig

host is terser and installed nearly everywhere:

$ host -t MX github.com
github.com mail is handled by 0 github-com.mail.protection.outlook.com.

On Windows, or anywhere the BIND tools aren't around, nslookup -type=mx github.com gets you the same records. In application code you want the resolver your language already has — dns.resolveMx() in Node, dns.resolver.resolve(domain, "MX") with dnspython — not a shell out to dig.

SPF describes sending, not receiving

Sender Policy Framework is a TXT record starting v=spf1 that lists which servers are allowed to send mail claiming to come from the domain. Receiving servers compare it against the IP that connected to them. It has nothing to do with whether the domain will accept anything.

The clearest demonstration of that is a disposable mailbox provider:

$ dig TXT mailinator.com +short | grep spf1
"v=spf1 -all"

$ dig MX mailinator.com +short
1 mail.mailinator.com.
1 mail2.mailinator.com.

That SPF record says no server on earth is authorised to send mail as mailinator.com. The MX records say it will happily receive. Both are true at once, because they describe opposite directions.

Three details worth knowing when you read one of these:

At signup time, an SPF record on the domain tells you somebody configured outbound mail for it — a weak signal that this is an administered organisation rather than a registration from last Tuesday. Its absence tells you nothing at all about receiving. Score it, don't gate on it.

What to do with the answer at signup

What comes backWhat it meansWhat to do
One or more MX hostnames Mail has somewhere to go Accept, carry on to confirmation
A single . Null MX — the domain refuses mail by policy Reject
No MX, but an A record Usually a parked typo domain Reject, and show the domain back to the person
NXDOMAIN The domain doesn't exist Reject
SERVFAIL or a timeout You don't know anything Accept, and let the send find out

Check the domain, not the address. There are far fewer domains than submissions, DNS answers carry a TTL, and a small cache in your app turns a busy form into roughly one lookup per domain per TTL. A cache hit costs nothing, which is why this belongs in front of the send rather than in a nightly cleanup job.

Then say something useful. "We can't find a mail server for gmial.com — did you mean gmail.com?" recovers the address; "Invalid email" makes the person retype the same typo.

Don't take the next step and probe the mailbox. An SMTP callout — connecting to their MX, issuing RCPT TO and reading the response — looks like the obvious upgrade and isn't. Catch-all domains answer 250 to everything, plenty of servers answer 250 deliberately to defeat exactly this, and repeated probing gets your IP rate-limited or blocklisted. Fail open on DNS errors, fail closed on definite answers, and stop there.

Where the check stops

A valid MX proves a server will accept a connection for the domain. It says nothing about the local part in front of it: nobody@ a real company resolves exactly as well as the CEO's address does.

Everything below survives an MX check intact:

So DNS is the cheap filter, not the answer. The only thing that proves a person holds an inbox is a message that arrives and gets acted on, which is the argument for making people confirm an address before it joins your list even though confirmation costs you signups.

How this runs in Replilo

A Replilo share link asks a visitor for an email address before it hands over the file. Three things happen before a message is sent: the domain is matched against a mirrored public blocklist of disposable providers, its DNS is checked for MX and SPF records, and only then does a six-digit code go out. Entering the code starts the download and stores the visitor as a verified subscriber; access is remembered per link, so coming back is just a download.

Each layer catches what the one before it can't — the blocklist stops throwaways with perfect DNS, the DNS check stops typos and dead domains, the code stops everything else. There's more on the whole flow in our guide to putting a PDF behind a signup wall.

The short version

dig MX domain +short tells you whether mail has anywhere to land: hostnames mean yes, a lone . means a flat no, NXDOMAIN means the domain isn't real, and nothing at all usually means a typo. dig TXT domain +short shows the SPF record, which describes sending and is a hint about the domain rather than a verdict on it. Run both on the domain, cache the result, fail open when the resolver does, and make somebody prove they hold the mailbox before you call the address good.

Skip building the check

Free account, 100 MB of storage, no card. Blocklist, DNS check and a six-digit code, in front of any file you share.

Start sharing free