Bulk Email Verifier - Validate & Clean Email Lists
Pricing
from $2.00 / 1,000 results
Bulk Email Verifier - Validate & Clean Email Lists
Check a list of email addresses before you send. Catches dead domains, bad syntax, typos, disposable addresses and catch-all servers, and tells you which mail platform each domain runs. Built to run straight after a contact or lead scrape.
Pricing
from $2.00 / 1,000 results
Rating
0.0
(0)
Developer
Project Desk
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
3 days ago
Last modified
Categories
Share
Bulk Email Verifier — validate and clean an email list before you send
Give it a list of email addresses. Get back a deliverability verdict for every one — as a clean table you can export to CSV, Excel or JSON, or push straight into your sending tool.
It exists because a scraped list is never a send list. Somewhere between 10% and 30% of the addresses on one are dead domains, typos, shared inboxes or throwaway mailboxes, and every one of them is a bounce. Enough bounces and your domain reputation goes with them.
What it checks
Each address goes through five layers, cheapest first, and stops as soon as there is a definite answer.
| Layer | Catches |
|---|---|
| Syntax | Malformed addresses, illegal characters, over-length local parts, missing or bogus TLDs |
| Typo detection | sid@gmial.com → did you mean sid@gmail.com — transposition-aware, so the most common real typo is caught |
| Domain | Domains that do not exist, and domains that publish a null MX (RFC 7505 — "this domain never receives mail") |
| Mail server | MX records, with the RFC 5321 fallback to an A record, so small domains that really do receive mail are not thrown away |
| Mailbox (optional) | An SMTP conversation as far as RCPT TO — no message is ever sent — plus catch-all detection so a meaningless 250 is not read as a real mailbox |
Things most verifiers get wrong, and this one handles
- Catch-all servers. A domain that accepts every address will happily accept a
mailbox that does not exist. We probe a random address first, and if it is accepted the
whole domain is marked
isCatchAlland scored as risky rather than sold to you as deliverable. - Gmail normalization.
J.Smith+news@gmail.comandjsmith@gmail.comare one mailbox. ThenormalizedEmailcolumn collapses them, so you can deduplicate a list properly instead of paying to mail the same person twice. - Role vs personal.
info@andsales@reach a shared inbox. They are still valid, so they are stilldeliverable— but they get their ownisRoleAccountcolumn, because cold outreach usually excludes them and a silent drop would be the wrong call to make on your behalf. - Public suffixes are not disposable. Auto-generated blocklists tend to swallow
co.ukandedu.plbecause throwaway services hand out subdomains on them. Flagging every UK business as disposable is a far more expensive mistake than missing a throwaway, so the parent-domain list here is curated by hand. - Honest
unknown. Where the mailbox could not be proven, the status isunknown— not "probably fine". See the note on port 25 below. - The mail platform. Free by-product of the MX lookup: Google Workspace, Microsoft 365, Zoho, Proofpoint, Mimecast and 25 others, identified per domain.
Input
| Field | Type | Default | What it does |
|---|---|---|---|
emails | array | — | The addresses. Paste one per line, upload a CSV, or link another Actor's dataset. Display names and mailto: prefixes are handled. |
smtpCheck | boolean | true | Probe the mailbox over SMTP. See the note below. |
onlyKeepSendable | boolean | false | Drop undeliverable rows so the export is already a clean send list. |
maxEmails | integer | 0 | Safety cap while testing. 0 = no limit. |
maxConcurrency | integer | 20 | Addresses handled at once. |
dnsTimeout | integer | 5 | Seconds to wait for an MX record. |
smtpTimeout | integer | 8 | Seconds to wait on the mail server. |
heloDomain | string | example.com | The domain the probe introduces itself with. Set it to one you own for large lists — servers trust a real domain more. |
mailFrom | string | verify@example.com | Return address used during the probe. Nothing is ever sent to it. |
Minimal input:
{"emails": ["info@apify.com","sid@gmial.com","test@mailinator.com","nobody@nosuchdomain-zzq7.com"]}
Output
One row per address.
{"email": "sid@gmial.com","normalizedEmail": "sid@gmial.com","status": "risky","score": 30,"reason": "likely_typo","localPart": "sid","domain": "gmial.com","isRoleAccount": false,"isFreeProvider": false,"isDisposable": true,"isCatchAll": false,"hasMxRecord": true,"mxProvider": "Other","mxHost": "mx.gmial.com","didYouMean": "sid@gmail.com","checkLevel": "dns","smtpDetail": ""}
The status column
Four values, and they are the one thing in this Actor that is never fudged.
| Status | Means | What to do |
|---|---|---|
deliverable | The mailbox exists and accepted the address | Send |
risky | Real domain, but something is wrong with it — catch-all, disposable, or a likely typo | Send at your own risk, or fix the typo first |
undeliverable | Guaranteed bounce — bad syntax, dead domain, null MX, or the server rejected the mailbox | Remove |
unknown | The domain is fine but the mailbox could not be proven | Your call — score 65 means everything short of the mailbox check passed |
score is 0–100 and is there for sorting; reason is a stable machine-readable code you
can filter on.
A straight note about port 25 and unknown
The mailbox probe needs outbound port 25, and many networks block it. When that happens the Actor:
- notices after a handful of domains,
- stops probing rather than burning your compute on timeouts that all say the same thing, and
- returns
unknownwithreason: mailbox_unverified_mx_validfor the rest.
A DNS-level run is still worth running. It removes bad syntax, dead domains, null-MX domains, typos and disposable addresses — which on a scraped list is the large majority of what would have bounced. What it cannot tell you is whether one specific mailbox at a live domain exists. The Actor says so instead of guessing, because a verifier that quietly upgrades "I could not check" to "deliverable" is worse than no verifier at all.
Where this sits in a pipeline
Google Maps / directory scrape → company names + websites↓Website Contact Extractor → emails, phones, social profiles↓Bulk Email Verifier (you are here) → a list that is safe to send to
Link the dataset from
Website Contact Extractor
directly into the emails field — its emails and emailsRole columns are read
automatically, so no reshaping is needed in between.
Pricing
$1.00 per 1,000 emails ($0.001 each). No monthly fee, no minimum, nothing to cancel — you pay for the addresses you actually put through it, and a failed check costs the same as a successful one because the work happens either way.
For comparison, the mainstream web services charge $3–$16 per 1,000 for the same job.
Run it locally first
No Apify account needed:
$python tools/try_local.py info@apify.com sid@gmial.com test@mailinator.com
Add --no-smtp for DNS-level checks only, or --file list.txt to read a file.
Unit tests, no network required:
$python tests/test_verify.py
Notes
- The disposable-domain list is bundled from
martenson/disposable-email-domains
(CC0). Refresh it with
python tools/refresh_disposable.py. - No message is ever sent to any address you submit. The SMTP conversation stops at
RCPT TOand issuesQUIT. - Addresses you submit are processed for the run and written only to your own dataset.