Bulk Email Validator: MX, Disposable, Role & Typo Check
Pricing
from $0.70 / 1,000 email verifieds
Bulk Email Validator: MX, Disposable, Role & Typo Check
Clean email lists with syntax + IDN, real DNS mail-routing (MX / Null MX), disposable, role and free-provider flags, typo hints and reason codes. No proxy, no SMTP probing, no false 'mailbox exists' claims. Pay per email checked; unknown results are free.
Pricing
from $0.70 / 1,000 email verifieds
Rating
0.0
(0)
Developer
MST MORIUM AKTHER MAYA
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
3 days ago
Last modified
Categories
Share
Clean an email list before you send to it. This Actor checks every address for
bad syntax, dead domains, disposable domains, role mailboxes (info@, sales@),
and likely typos (gmail.con), and tells you why for each row.
It uses only public DNS. No proxy, no API keys, no third-party service, no email is ever sent.
Important: this Actor checks the address format and the domain, not the mailbox.
validmeans "well-formed, and the domain can receive email". It does not mean "this person's inbox exists". See "What valid means here" below.
Quick start: open the Input tab, paste your addresses into Email addresses
(or Paste emails), press Start, then open the Output tab. Each row has a
status and a reason that tell you what to do with that address.
What "valid" means here (please read)
valid= the address is well-formed AND its domain publishes a working way to receive mail. The mailbox itself is NOT checked. Every row carries"mailboxChecked": false.
Nobody can tell from DNS alone whether john@company.com exists. Tools that claim
to do it use SMTP probing, which is unreliable (cloud hosts block port 25, big
providers accept everything, many servers greylist or lie) and is not part of
this Actor. What you get instead is a set of honest, deterministic signals
that remove the addresses that are certainly bad and flag the ones that are probably risky.
This Actor does not: test mailboxes via SMTP, detect catch-all servers, detect spam traps, or guarantee inbox delivery.
What it checks
| Check | Result |
|---|---|
| Syntax (RFC 5321/5322/6531) | invalid with a precise reason (missing_at_sign, consecutive_dots_in_local_part, ...) |
| Internationalised domains (IDN) | bücher.de is converted to punycode and checked |
| DNS mail routing | MX lookup, RFC 7505 Null MX, A/AAAA fallback, dangling MX hosts |
| Disposable domains | Bundled community list (CC0), including subdomains |
| Role accounts | info@, support@, noreply@ ... -> risky |
| Free providers | gmail.com, yahoo.com ... flagged in isFreeProvider (informational, never lowers the status) |
| Typos | Conservative "did you mean" for near-misses of major providers only |
| Duplicates | Detected after normalisation (case, spaces, Name <a@b.com> wrappers). Skipped by default, or kept and marked with duplicateOf. Never charged |
Statuses
| status | meaning | typical action |
|---|---|---|
valid | Syntax OK and the domain can receive mail. Mailbox not checked. | keep |
risky | Usable but has a risk signal (see reasons): role_account, possible_typo, no_mx_record_uses_a_record, internationalized_local_part | your call |
invalid | Syntax error | remove |
undeliverable_domain | Domain does not exist, has no mail records, or publishes Null MX (domain_not_found, no_mail_records, null_mx, mx_hosts_unresolvable) | remove |
disposable | Throw-away mail domain | remove |
unknown | We could not decide (DNS timeout / server failure). Not charged. | re-run later |
confidence (high / medium / low) says how sure we are about the status,
not about the mailbox. valid is at most medium because the mailbox is unverified.
Input
Use any combination of:
emails: list of addresses (best for API / MCP / AI-agent use)emailsText: paste a column (new lines, commas or semicolons)datasetId+emailField: read emails from another Actor's dataset (e.g. a lead scraper). The field may hold one email or a list, and can be nested (contact.email).
For big lists (thousands of rows) prefer emailsText or datasetId; the list editor is meant for short lists.
Other options: includeDuplicates, maxEmails (default 100,000, max 200,000; if your list is longer, the run summary and status message tell you how many rows were not processed), dnsConcurrency.
{"emails": ["support@apify.com", "someone@mailinator.com", "john.doe@gmail.con"]}
Output
One dataset row per unique address, in input order, with a stable index
(the position among your non-blank input rows, starting at 0; blank rows are dropped before
numbering, so if your list contains blanks, join back on the input field rather than on index):
{"index": 2,"input": "john.doe@gmail.con","email": "john.doe@gmail.con","status": "undeliverable_domain","reason": "domain_not_found","reasons": ["domain_not_found"],"confidence": "high","mailboxChecked": false,"isValidSyntax": true,"domain": "gmail.con","hasMx": false,"mxRecords": [],"mxProvider": null,"isDisposable": false,"isRoleAccount": false,"isFreeProvider": false,"suggestion": "john.doe@gmail.com","normalizations": [],"duplicateOf": null}
A run summary (counts per status, duplicates, rows charged, whether the run
stopped at your max charge) is saved in the key-value store under SUMMARY.
Pricing (pay per event)
You pay per unique address that received a conclusive result. That includes invalid,
undeliverable_domain and disposable rows, because telling you an address is bad is the result.
Free: duplicates, blank rows, and unknown rows (DNS problems on our side of the check).
If you set a maximum cost per run, the Actor stops cleanly when it is reached and
never charges beyond it. Rows already checked stay in the dataset.
See the Pricing tab for the current per-email price. At the time of writing it is
$0.70 per 1,000 checked addresses, plus a tiny fixed start fee of $0.00005 per run.
For example, 10,000 unique addresses cost about $7.00. A list of 1,000 addresses where 200 are
duplicates and 30 could not be decided (unknown) is charged for 770 addresses only.
Use it with other Actors
Lead scraper -> this Actor -> clean list. Pass the scraper's dataset ID in
datasetId and the field name in emailField. The output keeps your rows in order
and repeats the original value in input, so you can join it back to your source data.
The dataset must be one your own Apify account can read.
Use it from code, automation tools and AI agents
The input is one plain JSON object and every output row is flat JSON with fixed status values and reason codes, so scripts and AI agents can act on it without parsing prose. The Actor needs no secrets and runs with limited permissions.
Python
from apify_client import ApifyClientclient = ApifyClient("<APIFY_TOKEN>")run = client.actor("yourname_mahi/bulk-email-validator").call(run_input={"emails": ["support@apify.com", "john@gmail.con"]})for row in client.dataset(run["defaultDatasetId"]).iterate_items():print(row["status"], row["reason"], row["suggestion"])
JavaScript
import { ApifyClient } from 'apify-client';const client = new ApifyClient({ token: '<APIFY_TOKEN>' });const run = await client.actor('yourname_mahi/bulk-email-validator').call({emails: ['support@apify.com', 'john@gmail.con'],});const { items } = await client.dataset(run.defaultDatasetId).listItems();console.log(items.map((r) => [r.status, r.reason]));
cURL (small lists; this endpoint waits for the result, so keep it to a few thousand addresses)
curl -X POST "https://api.apify.com/v2/acts/yourname_mahi~bulk-email-validator/run-sync-get-dataset-items?token=<APIFY_TOKEN>" \-H "Content-Type: application/json" \-d '{"emails": ["support@apify.com", "john@gmail.con"]}'
n8n / Make / Zapier: use the Apify integration's Run Actor step with the JSON input above,
then Get dataset items, and branch on the status field (for example keep only valid and risky).
AI agents: Actors can be called through the Apify MCP server. Pass emails as a list and read
status, reason and confidence from each row; mailboxChecked is always false.
Large lists
- Use Paste emails or a dataset (
datasetId) instead of the list editor for thousands of rows. - Domains are looked up once and cached, so lists with many repeated domains finish very fast.
- Set a Maximum cost per run in the run options to cap spending. The Actor stops cleanly at the limit and tells you how many rows were checked. Rows already checked stay in the dataset.
- If many rows come back
unknown, re-run only those rows later, or lowerdnsConcurrency.unknownrows are free.
FAQ
Can I use valid to guarantee delivery?
No. It means "worth sending to", not "the mailbox exists".
Why not SMTP mailbox checks?
They are unreliable from cloud infrastructure and cannot be presented honestly as proof. We prefer fewer, correct claims.
Is my list stored or shared?
Addresses are processed in memory and written only to your run's dataset. They are never written to logs and never sent to any third-party service. Lookups go to public DNS: the domain part of your addresses is visible to the DNS resolver, as with any DNS query.
How fast is it?
Domains are checked once and cached, so lists with repeated domains are very fast (thousands per second); lists where every address has a different domain are limited by DNS speed.
How complete are the lists?
The disposable, free-provider and role lists are bundled and not exhaustive; new disposable domains appear constantly.
Why is a real-looking address marked risky?
Look at reasons. Typical causes: a role address (info@), a possible typo of a big provider (gmial.com),
or a domain with no MX record where mail would fall back to the domain's web server address.