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:
- It lives in TXT. There used to be a dedicated SPF record type; RFC 7208 retired it.
dig SPFreturns nothing useful, and a domain should publish exactly onev=spf1TXT record — two of them is a permanent error that makes every check fail. - The tail is the policy.
-allmeans reject anything from a server not listed,~allmeans treat it as suspicious,?allmeans the domain isn't asserting anything. - Ten lookups, maximum. Mechanisms like
include:anda:each cost a DNS query, and the limit for one evaluation is ten. Chain enough vendors together and your own SPF stops evaluating, which is worth checking on your own sending domain right now.
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 back | What it means | What 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:
- Mailboxes that don't exist. The domain accepts the connection and rejects the recipient afterwards, which is a bounce you only see after you send.
- Disposable providers. Their DNS is immaculate, as the mailinator records above show. Catching those needs a blocklist of known providers, not a resolver.
- Mailboxes nobody reads. Full, forwarded, auto-archived, or belonging to someone who left in 2021.
- Domains that reject you specifically. Greylisting, strict DMARC alignment, or a corporate filter that drops mail from senders it doesn't recognise.
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