A "super" email verifier is a pipeline, not a product: cheap local checks first, a mailbox probe second, a stored verdict third, and a send rule at the end. Each layer removes a different kind of bad address.
Most guides on this keyword explain why verification matters. This one gives you the code and the decision table. For tool comparisons, read best email verification tools. For the concepts, read email verification explained.
TL;DR
- Run four layers in order: syntax, DNS, SMTP, then a stored status. Layers 1 to 3 cost nothing and take about 20 minutes to wire up.
- The hard part is the answer "maybe". Catch-all domains return it for every address, so the pipeline needs a rule for it, not a better probe.
- Sizing a list by
emailStatuson LeadOcean spends 0 records. Pulling or enriching a person spends 1.
Prerequisites
- Python 3.10 or newer and
digon your path. - Outbound port 25 for layer 3. Many cloud hosts block it.
- A LeadOcean key in
$LEADOCEAN_API_KEYfor layer 4. The free plan has 1,000 records for life and no card. The free MCP toolleadocean_count_leadsalso works for sizing.
Step 1: Reject what cannot be an address
Syntax is the cheapest layer, so it runs first and stays permissive. Reject only what no mail server would accept.
import re
SYNTAX = re.compile(r"^[^@\s]+@[^@\s]+\.[^@\s]{2,}$")
def layer1(addr: str) -> str:
if len(addr) > 254 or not SYNTAX.match(addr):
return "invalid"
return "pass"Do not write a clever regex. A strict one rejects real addresses, and a pass proves nothing about the mailbox.
Step 2: Check that the domain can receive mail
If the domain has no mail server, nothing you send can arrive. The check is a DNS query.
import subprocess
def dig(kind: str, domain: str) -> list[str]:
out = subprocess.run(["dig", "+short", kind, domain],
capture_output=True, text=True).stdout
return [l.split() for l in out.strip().splitlines() if l.strip()]
def layer2(addr: str):
domain = addr.split("@", 1)[1]
mx = sorted(dig("MX", domain), key=lambda r: int(r[0]))
if mx:
return "pass", mx[0][1].rstrip(".")
if dig("A", domain): # RFC 5321: no MX falls back to the A record
return "pass", domain
return "invalid", NoneRFC 5321 (checked September 2026) is why the fallback to the address record exists. Returning the host saves a second lookup in step 3.
Step 3: Ask the mail server, and accept "maybe"
The SMTP probe asks whether the server would take mail for the address, then quits before any message is sent. It is the only direct test of one mailbox, and the least reliable layer.
import smtplib
def layer3(addr: str, host: str) -> str:
try:
with smtplib.SMTP(host, 25, timeout=10) as s:
s.helo("probe.example.org")
s.mail("check@example.org")
code, _ = s.rcpt(addr)
junk, _ = s.rcpt("zz-no-such-user-91@" + addr.split("@")[1])
except (OSError, smtplib.SMTPException):
return "unknown"
if code == 250 and junk == 250:
return "catch_all" # server accepts anything: 250 proves nothing
if code == 250:
return "pass"
if code in (550, 551, 553):
return "invalid"
return "unknown" # 4xx, greylisting, blocked portThe second RCPT TO is the useful trick. A server that accepts a made-up mailbox is a catch-all, so its yes for your address means nothing. Large providers also rate-limit probes, and probing from a shared IP can get it blocklisted. Run this layer from a dedicated host at a low rate, or skip it and use step 4.
Step 4: Read a stored verdict
A stored status is a verdict someone collected in bulk, so you skip steps 2 and 3 for every address in the database. LeadOcean publishes one emailStatus per person. The values are verified, catch_all_valid, catch_all, risky, unknown, untested, invalid, role, disposable, spam_trap, abuse, derived and none.
Size first. The count is free. This is the body for leadocean_count_leads, the MCP tool, with emailStatus set to one value at a time:
{
"country": ["DE"],
"jobLevel": ["C-Team"],
"emailStatus": ["verified"],
"facets": false
}Then pull rows over REST. The body is flat JSON, filters are arrays and limit runs from 1 to 100.
curl -s https://api.leadocean.io/v1/people/search \
-H "x-api-key: $LEADOCEAN_API_KEY" \
-H "Content-Type: application/json" \
-d '{"country":["DE"],"jobLevel":["C-Team"],
"emailStatus":["verified"],"limit":25}'To check one person you already hold, enrich by linkedin_url, email, phone or person_id, with reveal_email on.
curl -s https://api.leadocean.io/v1/people/enrich \
-H "x-api-key: $LEADOCEAN_API_KEY" \
-H "Content-Type: application/json" \
-d '{"email":"jane.doe@acme.com","reveal_email":true}'Two rules from the API reference (checked October 2026) change how you read the answer. On a business address, verified means LeadOcean's own verifier tested it. On a personal address it only means a supplier asserted it. And a verified count at search time is an upper bound, because enrichment applies the stricter per-address rule.
LeadOcean does not verify an arbitrary address you paste in. It returns the status of addresses it holds. For one-off lists from elsewhere, keep layers 1 to 3 in front of your own data.
Step 5: Turn each status into a send rule
The pipeline only helps if every answer maps to an action. This table is the part to bookmark.
| Status | Meaning | Action |
|---|---|---|
verified | Tested and accepted | Send |
catch_all_valid | Domain accepts anything, mailbox confirmed through a Microsoft 365 or Google Workspace check | Send |
catch_all | Domain accepts anything, mailbox unconfirmed | Send in a small, separate batch. Watch bounces. |
risky, role, derived | Weak signal, shared inbox or pattern guess | Hold. Do not send cold. |
untested, unknown | Nobody checked it, or the check was inconclusive | Run layers 2 and 3 yourself first |
invalid, disposable, spam_trap, abuse | Known bad | Drop |
none | No email held | Skip |
SEND = {"verified", "catch_all_valid"}
TRIAL = {"catch_all"}
DROP = {"invalid", "disposable", "spam_trap", "abuse", "none"}
def route(status: str) -> str:
if status in SEND: return "send"
if status in TRIAL: return "trial_batch"
if status in DROP: return "drop"
return "recheck"LeadOcean gives no refund or credit-back for bounces, so this routing is your protection. Read the status before you send.
What you get
These counts come from leadocean_count_leads on 2026-10-21. The filter is country DE, job level C-Team, plus the single emailStatus shown. All four calls spent 0 records.
| emailStatus | People | Distinct companies |
|---|---|---|
verified | 69,149 | 42,748 (estimate) |
catch_all | 56,021 | 28,407 |
risky | 6,740 | 5,850 |
untested | 150,025 | 83,353 (estimate) |
As a filter, catch_all also matches catch_all_valid, so that row includes both. The largest group is untested, more than double verified. That is the real shape of any email database: most addresses have simply never been checked. The verified figure is an upper bound at search time.
Enrichment returns a small email object. This is the response shape from the API reference, with placeholder values:
{
"email": "jane.doe@acme.com",
"type": "work",
"status": "verified",
"verified_batch_date": "2026-09-20"
}A non-null verified_batch_date means LeadOcean's own verifier tested the address in that run. A null means the status came from a source held to the same standard, not a LeadOcean run. A whole batch shares one date, so it is not a freshness timestamp.
Troubleshooting
| Error | Cause | Fix |
|---|---|---|
| 401 | Missing or wrong x-api-key header | Check $LEADOCEAN_API_KEY is exported and the key is active in the app. |
| 429 | Above the plan's rate limit (5 requests a second on Free, 200 on Agency) | Wait for the Retry-After value, then retry. |
| 402 | The 1,000 free records are spent | Check GET /v1/account, or move to a paid plan. |
| Empty results | A bad enum value, such as "VP Sales" for jobLevel | Use exact strings: C-Team, VP, Director, Manager, Staff, Other. List values with leadocean_list_enum_values. |
| Export refused | Over 50,000 rows in one request or 500,000 in a day | Split into several POST /v1/exports calls with narrower filters. Bulk CSV export is not on Free. |
| SMTP timeout (step 3) | Port 25 blocked by your network | Run the probe from a host that allows outbound 25, or rely on step 4. |
FAQ
What makes a verifier "super"?
Layers and rules, not a bigger probe. Local checks remove typos and dead domains, SMTP tests the mailbox, a stored status covers what SMTP cannot, and a routing table decides what to send.
Can any verifier confirm a catch-all address?
Not by probing, because the server says yes to everything. LeadOcean marks a mailbox catch_all_valid when a Microsoft 365 or Google Workspace identity check confirmed it. Everything else on those domains stays catch_all.
Should I verify before or after I buy a list?
Before. Size by emailStatus first, which is free, and pull only the statuses your send rule accepts. See the email verifiers benchmark for how verifiers compare.
How many records does this pipeline use?
Layers 1 to 3 use none, and neither does a count. Each person you pull or enrich costs 1 record. An enrichment that finds nobody is a 404 and costs nothing.
Filter by email status before you send
Free to start. No credit card. 1,000 records to spend whenever you like.
Get your free API key →Back to the blog hub.