Every email verifier promises the same thing: tell me whether this address will bounce before I send to it. Under the hood, though, "verification" is a stack of separate checks, and each one can only answer part of the question. When I built MailRambo I had to decide what each layer is allowed to conclude, and that decision is the difference between a verifier that protects your bounce rate and one that hands you a pile of "unknown" results.
This post walks through the layers in the order a verifier runs them, what each one can prove, and where the honest answer is "I can't tell".
The layers at a glance
Here is the whole pipeline in one table. Each step is cheaper than the next, so a verifier stops as soon as it has a definite "no".
| Layer | What it checks | Can prove "bad"? | Can prove "good"? | Typical speed |
|---|---|---|---|---|
| 1. Syntax | Is this a well-formed address? | Yes | No | Microseconds |
| 2. Domain and MX | Does the domain exist and accept mail? | Yes | No | Milliseconds |
| 3. Disposable / role lists | Is it a throwaway provider or a role inbox? | Yes (disposable) | No | Milliseconds |
| 4. SMTP RCPT probe | Does the server accept this mailbox? | Yes | Sometimes | 1-5 seconds |
| 5. Catch-all test | Does the server accept everything? | No | Downgrades a "yes" | Same connection |
Or, as a flow:
address
|
v
[syntax] --bad--> invalid_syntax
|
v
[DNS: MX?] --none--> no mail server
|
v
[disposable list] --hit--> disposable
|
v
[SMTP: RCPT TO real address] --550--> mailbox_not_found
| |
| +--timeout / 4xx--> unverifiable
v
[SMTP: RCPT TO random address] --250--> catch_all
|
v
mailbox_exists
Notice that only the last box ever produces a confident "yes". Everything before it can only eliminate.
Layer 1: syntax
The first check is whether the string is an email address at all. That sounds trivial until you read RFC 5322, which allows quoted local parts, comments and other things no human types. In practice a verifier checks a sane subset: one @, a local part with allowed characters, a domain with valid labels and a real top-level domain, and length limits (64 characters for the local part, 254 overall).
Syntax catches jane@, jane@@acme.com and jane@acme (no TLD). It does not catch jane@acme.con or jane@gmial.com, which are perfectly valid syntax. I wrote more about where regex stops being useful in email validation: regex vs API, and you can test addresses with the email syntax checker.
A good verifier also treats obvious typos of big providers as their own signal. gmial.com is a real registered domain, but a signup with it is almost always a mistake, so it's worth suggesting gmail.com instead.
Layer 2: domain and MX records
Next, DNS. The verifier looks up the domain's MX records, which list the servers that accept mail for it. If there are no MX records (and no usable fallback A record), nobody can receive mail at that domain, and the address is dead no matter what the local part says.
This layer catches expired company domains, made-up domains and typos that don't resolve. It's fast and cheap, and it's the reason a mistyped @acme.con fails immediately. You can see what a domain publishes with the MX lookup tool.
What it can't tell you: whether jane exists at a domain that does have mail servers.
Layer 3: disposable and role lists
Before touching SMTP, most verifiers check the domain against a list of disposable email providers (the ten-minute-inbox kind). These mailboxes often exist and accept mail, so an SMTP probe would say "yes", but the person behind them is gone within the hour. For signup protection and list quality, a disposable address is a "no" even though it's technically deliverable.
The same stage flags role addresses like info@ or support@. Those are real, shared inboxes, and whether you want them depends on what you're sending. I cover that in role-based email addresses.
Lists go stale quickly, since new throwaway domains appear all the time. That's why static lists can't keep up on their own and why the list needs regular updates.
Layer 4: the SMTP RCPT probe
This is the part people usually mean by "email verification". The verifier connects to the domain's mail server and starts a normal SMTP conversation, but stops before sending any message:
> EHLO verifier.example
< 250 mx.acme.com
> MAIL FROM:<check@verifier.example>
< 250 OK
> RCPT TO:<jane@acme.com>
< 250 OK <- server says it will accept mail for jane
> QUIT
If the server answers RCPT TO with a 5xx code like 550 5.1.1 User unknown, the mailbox doesn't exist and a real send would hard-bounce. If it answers 250, the server is at least claiming the mailbox exists.
A few details matter here:
- The probe must come from a clean IP with proper reverse DNS. Mail servers are suspicious of connections that look like address harvesting, and some block them outright.
- Some 5xx codes aren't about the mailbox. A
550that says your IP is blocked is a statement about the verifier, not about Jane. Good verifiers parse the enhanced status code and text instead of trusting the first digit. - Full mailboxes and disabled accounts often have their own responses (
452/552for over quota, specific texts for disabled users), which is how a verifier can returninbox_fullormailbox_disabled.
Layer 5: the catch-all test
A 250 on RCPT TO isn't proof, because some servers say 250 to everyone. To detect that, the verifier asks the same server about an address that can't possibly exist, like zq8x71-not-real@acme.com. If that's accepted too, the domain is a catch-all and the earlier "yes" for Jane tells you nothing.
Catch-alls are common at companies behind security gateways. The messages are accepted first and filtered later, and non-existent recipients then bounce late or vanish. I wrote a whole post on this: what is a catch-all email address.
Greylisting, timeouts and why "unknown" exists
Even with a clean probe, many servers simply don't give a straight answer:
- Greylisting. The server answers the first attempt from an unknown sender with a temporary
451or450and expects a retry minutes later. Real mail servers retry; a verifier that has to answer in seconds can't wait that long. - Timeouts and tarpits. Some servers deliberately slow down unknown connections.
- Rate limits. Large providers throttle connections that look like probing.
- Providers that always accept at RCPT. Some hosted mail services accept every recipient during the SMTP conversation and decide later, which from the outside looks exactly like a catch-all.
In all these cases the verifier has no evidence either way. That's where the "unknown", "risky" or "accept-all" labels come from. They're not a bug in the verifier; they're an honest description of a server that won't talk.
Why MailRambo is fail-closed
The interesting question is what you do with "unknown". Most verifiers return it as a third state and let your code decide. In my experience, nobody's code decides. The address ends up in the "not invalid" bucket and gets sent to, and those are exactly the addresses that bounce later.
So I made MailRambo fail-closed: deliverable is true only when the mailbox was confirmed. Catch-all and unverifiable addresses come back as false, each with a specific reason, so you can still override deliberately:
{ "email": "jane@acme.com", "deliverable": false, "reason": "unverifiable" }
The full set of reasons is mailbox_exists, role_account, disposable, catch_all, mailbox_not_found, spamtrap, inbox_full, mailbox_disabled, unverifiable and invalid_syntax. Only mailbox_exists and role_account are true. The promise is bounce avoidance, and "uncertain" is not a confirmation.
Fast checks vs full checks
Not every situation needs all five layers. A signup form can't wait 1-5 seconds for an SMTP conversation, so MailRambo also has a free mode=fast check that runs only layers 1-3 (syntax, typo suggestion, MX, disposable, role and free-provider flags). It can prove an address is bad, but never that the mailbox exists, so its deliverable is either false or null, never true:
curl -X POST "https://www.mailrambo.com/v1/verify?mode=fast" \
-H "Authorization: Bearer $MAILRAMBO_KEY" \
-H "Content-Type: application/json" \
-d '{"email": "jane@gmial.com"}'
The full check, POST /v1/verify without mode, runs the whole stack. A typical setup is fast mode at signup and a full check before the first marketing send. More on that in real-time email validation on signup forms.
Summary
- Syntax, DNS and list checks can only prove an address is bad.
- The SMTP probe is the only layer that can say "this mailbox exists", and catch-alls and uncooperative servers limit even that.
- "Unknown" is a real state, but treating it as "probably fine" is how bounces sneak through.
If you want to see these layers in action, paste an address into the free email verifier. The API gives you 100 free checks a month plus unlimited fast checks, and the developer docs have the full reason list and code samples.