Bulk Email Verifier: MX, Disposable, Role & Typo Check
Pricing
Pay per event
Bulk Email Verifier: MX, Disposable, Role & Typo Check
Verify email lists without sending them to any third-party API: RFC syntax, DNS MX records, disposable, role and free-provider flags, typo suggestions and a deliverable / risky / undeliverable status with reasons. No SMTP mailbox check. No login, no API key.
Pricing
Pay per event
Rating
0.0
(0)
Developer
Cliqto Media
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
8 days ago
Last modified
Categories
Share
Bulk Email Verifier checks a list of email addresses and gives every address a status: deliverable, risky, undeliverable or unknown, with the reasons. It checks syntax, DNS MX records, disposable and role addresses, free providers and domain typos. It does not open an SMTP connection, so it cannot confirm that a mailbox exists. No login and no API key. Your list is not sent to any third-party verification service.
Use it to clean a list before a campaign or a CRM import. The smallest run is one address; 1,000 addresses cost about $0.40.

Table of contents
- What it does
- Quick start
- Use cases
- Input
- Output
- Pricing and limits
- API, export and integrations
- Limitations
- Troubleshooting
- FAQ
- Support
What it does
For each address the Actor:
- Checks the syntax by the email standards (RFC 5321 / 5322). For example,
john..doe@example.comis invalid because of the two dots. - Looks up the domain in DNS: MX records, a "null MX" record (RFC 7505) that says "this domain takes no mail", and the A/AAAA fallback when there is no MX.
- Flags disposable domains (temporary inboxes like mailinator.com), role addresses (info@, support@, sales@…) and free providers (gmail.com, outlook.com…).
- Suggests a fix for a likely typo:
hello@gmial.com→hello@gmail.com. - Names the mail provider from the MX hosts, for example Google or Microsoft.
- Gives one status and a list of reason codes.
All checks run inside the Actor. The list is not sent to an outside verification API. Only domain names go to DNS, like any mail server does.
Good fit
- Remove bad and risky addresses before an email campaign, so fewer emails bounce.
- Check emails found by a lead scraper before you add them to a CRM.
- Find typos and disposable addresses in sign-up lists.
- Check lists that you do not want to share with another verification company.
Not a fit / limits
- It does not connect to the mail server (SMTP). It cannot say if one mailbox exists, and it cannot detect catch-all domains. See Limitations.
- It does not send emails.
- It does not promise 100% accuracy. DNS and mail servers change all the time.
Quick start
- Open the Input tab.
- Paste your addresses into Email addresses (
emails), one per line. The form already has 5 example addresses. - Keep the other settings at their defaults.
- Click Start.
- Open the Output tab and check the Status and Reasons columns.
Expected result: one row per address. The 5 example addresses finish in a few seconds: 3 risky (role account, disposable domain) and 2 undeliverable (domain does not exist, invalid syntax).
Use cases
| Buyer job | Input path | Output entity | Key fields | Next step |
|---|---|---|---|---|
| Clean a list before a campaign | emails | One row per address | status, reasons | Keep deliverable, review risky, remove undeliverable |
| Check scraped leads | inputDatasetId + emailField | One row per address | status, isRoleAccount, mxProvider | Import good rows into a CRM |
| Fix sign-up typos | emails | One row per address | didYouMean, reasons | Ask the user to correct the address |
| Block temporary inboxes | emails | One row per address | isDisposable | Reject or flag the sign-up |
Input
Input fields
| Field | Plain name | Type | Required | Default / prefill | Allowed values | Effect | Recommendation |
|---|---|---|---|---|---|---|---|
emails | Email addresses | array of strings | no* | prefill: 5 example addresses | up to 10,000 in total | Addresses to check. A line with several addresses separated by commas, semicolons or spaces is split. | Paste one address per line. |
inputDatasetId | Input dataset | string | no* | empty | dataset picker in Console, dataset ID in the API | Adds addresses from an Apify dataset, e.g. the output of a lead scraper. Up to 20,000 dataset items are read. | Use it to chain this Actor after a scraper. |
emailField | Email field in dataset | string | no | email | up to 100 characters | Which dataset field holds the address. Items without it are counted in the run summary. | Change it if your dataset uses another name, e.g. contactEmail. |
concurrency | Parallel checks | integer | no | 20 | 5–50 | How many addresses are checked at the same time. | Use 50 for lists over 5,000 addresses. |
deduplicate | Remove duplicates | boolean | no | true | true / false | Checks each address once (case-insensitive). | Turn it off only if you need one output row per input line. |
* You need at least one address in emails or inputDatasetId. Both can be used together; the total limit is 10,000 addresses per run.
If there are no addresses, or more than 10,000, the run fails at once with a clear message and checks nothing.
Input examples
Check a pasted list
{"emails": ["support@apify.com","someone@nonexistentdomain-7c1e5b42.com","test@mailinator.com","sales@stripe.com","john..doe@example.com"]}
Expected output: 5 rows. support@apify.com and sales@stripe.com are risky (role account), test@mailinator.com is risky (disposable), and the other two are undeliverable.
Check emails from a lead scraper dataset
{"inputDatasetId": "YOUR_DATASET_ID","emailField": "email","concurrency": 50}
Expected output: one row per address found in the dataset. The run summary shows how many dataset items had no address.
Output
Each address gives one row in the dataset. The Overview view shows the main columns.
Output example
{"email": "sales@stripe.com","normalized": "sales@stripe.com","status": "risky","reasons": ["role_account", "mailbox_not_verified"],"syntaxValid": true,"domain": "stripe.com","hasMx": true,"mxRecords": [{ "exchange": "aspmx.l.google.com", "priority": 10 }],"mxProvider": "Google","isDisposable": false,"isRoleAccount": true,"isFreeProvider": false,"isCatchAll": null,"smtpCode": null,"smtpChecked": false,"didYouMean": null,"checkedAt": "2026-10-13T01:57:42.504Z"}
This is one real row from the example input. mxRecords is shortened (the real row has 5 records).
Output fields
| Field | Meaning | Type | Can be empty? | Example |
|---|---|---|---|---|
email | The address as you gave it (trimmed) | string | no | sales@stripe.com |
normalized | Lower-case address with an ASCII (punycode) domain | string | null if syntax is invalid | sales@stripe.com |
status | Final result | string | no | deliverable, risky, undeliverable, unknown |
reasons | Reason codes (see below) | array of strings | can be [] | ["role_account","mailbox_not_verified"] |
syntaxValid | Syntax is valid | boolean | no | true |
domain | Domain in ASCII | string | null if there is no domain part | stripe.com |
hasMx | Domain has MX records with a reachable mail host | boolean | null if DNS failed or syntax is invalid | true |
mxRecords | MX records sorted by priority | array of {exchange, priority} | can be [] | [{"exchange":"aspmx.l.google.com","priority":10}] |
mxProvider | Mail provider found from MX hosts | string | null if not recognised | Google, Microsoft, Zoho |
isDisposable | Domain is a temporary inbox service | boolean | null if the domain cannot be read | false |
isRoleAccount | Address is a role mailbox (info, support, sales…) | boolean | null if the local part cannot be read | true |
isFreeProvider | Domain is free webmail | boolean | null if the domain cannot be read | false |
didYouMean | Suggested fix for a domain typo | string | null if no typo is found | hello@gmail.com |
isCatchAll | Always null: needs SMTP, which is not used | null | always null | null |
smtpCode | Always null: no SMTP connection is made | null | always null | null |
smtpChecked | Always false: the mailbox is not probed | boolean | no | false |
checkedAt | Time of the check (ISO 8601) | string | no | 2026-10-13T01:57:42.504Z |
Statuses
| Status | When | Charged? |
|---|---|---|
deliverable | Valid syntax, the domain accepts mail, no risk flags. The mailbox itself is not checked. | yes |
risky | The domain accepts mail, but there is a flag: disposable, role account, possible typo, no MX (only A record), quoted or non-ASCII local part. | yes |
undeliverable | Invalid syntax, the domain does not exist, null MX, no mail server, or MX hosts that do not resolve. | yes |
unknown | DNS did not answer (timeout or server error). Try again later. | no |
Reason codes
- Syntax:
invalid_syntaxplus one detail, e.g.invalid_dots_in_local_part,missing_at_or_part,multiple_at,contains_whitespace,invalid_domain,invalid_tld,domain_without_tld,local_part_too_long,address_too_long;quoted_local_partandnon_ascii_local_partmake an address risky. - Domain:
domain_not_found,null_mx_domain_accepts_no_mail,no_mail_server,mx_host_unresolvable,no_mx_a_record_fallback,dns_lookup_failed. - Flags:
disposable_domain,role_account,possible_domain_typo. mailbox_not_verified: added to every deliverable and risky row, because the mailbox is not probed.
Run summary
The key-value store record RUN_SUMMARY has the run status (COMPLETE, PARTIAL, INVALID_INPUT or ERROR), a message, counts (input, unique, duplicates, processed, not processed, by status), charged events and the number of domains checked. It never contains email addresses.
Pricing and limits
Pricing unit
This Actor uses pay-per-event pricing. You pay for:
| Event | When | Price |
|---|---|---|
| Actor start | Once per run | $0.005 |
| Email checked | Once per address with status deliverable, risky or undeliverable | $0.40 per 1,000 (Free plan), lower on paid Apify plans: $0.38 Bronze, $0.36 Silver, $0.34 Gold per 1,000 |
unknownrows are free.- Duplicates removed by
deduplicateare free. - The start event is charged for every run, also when the input is wrong and the run fails.
- If the run stops early (time limit, your maximum charge, or an abort), you pay only for the rows saved in the dataset.
Cost examples (Free plan prices)
| Scenario | Addresses | Approximate charge |
|---|---|---|
| Example input | 5 | $0.007 |
| Small list | 100 | $0.045 |
| Campaign list | 1,000 | $0.405 |
| Maximum per run | 10,000 | $4.005 |
Limits
- Up to 10,000 addresses per run. Split larger lists into several runs.
- Up to 20,000 items are read from an input dataset.
- In our test, 10,000 addresses on 5,660 domains took about 4.5 minutes with
concurrency50 and 256 MB memory. Many addresses on one domain are faster, because each domain is looked up once. - Set Maximum cost per run in the run options to limit spending. The Actor stops before it goes over the limit and saves what it already checked.
API, export and integrations
- API: start the Actor with the Apify API and read the default dataset.
- Export: download results from the Output tab as JSON, CSV, Excel or other formats.
- Chaining: pass the dataset ID of a lead scraper run as
inputDatasetId. - Scheduling and webhooks: use Apify schedules and integrations to run it on new lists.
Use with AI agents (MCP)
- MCP server with only this Actor:
https://mcp.apify.com?tools=cliqtomedia/email-verifier - Smallest useful input:
{"emails": ["support@apify.com", "test@mailinator.com"]} - Key output fields:
status(deliverable / risky / undeliverable / unknown),reasons(why),didYouMean(typo fix),isDisposable,isRoleAccount. - A run with a few addresses takes a few seconds and costs about $0.006. Limit: 10,000 addresses per run.
Limitations
- No SMTP check. Apify does not allow outgoing connections on port 25, so the Actor does not talk to mail servers. A wrong mailbox on a real domain (for example a made-up name at gmail.com) is shown as
deliverablewithmailbox_not_verified. Use this Actor as a fast, private first filter. If you need mailbox-level checks, use an SMTP verification service after it. - No catch-all detection.
isCatchAllis always null. - Lists change. The disposable and free-provider lists come from open-source projects (disposable-email-domains, MIT; freemail, ISC). New temporary services may be missing.
- DNS can fail. If DNS does not answer for a domain, the row is
unknownand free. If DNS fails for more than half of the domains checked (after 10 domains, or after 30 seconds) or for more than half of the first 100 addresses, the run stops with an error so you do not wait for a list of dead domains.
Troubleshooting
Many rows are unknown
Symptom: rows with dns_lookup_failed. Likely reason: the domains' DNS servers do not answer, or there was a short network problem. Action: run again with only the unknown addresses. They were not charged.
The run failed with "DNS lookups failed…"
Symptom: the run ends with an error after about 100 addresses. Likely reason: most domains in the list do not answer DNS. Action: check that the list has real domains. Rows already checked are in the dataset.
The run failed with "No email addresses found"
Symptom: the run fails at once. Likely reason: emails is empty, or the dataset field in emailField has another name. Action: open the dataset, find the field with the address, and put its name in emailField.
The run stopped with status PARTIAL
Symptom: RUN_SUMMARY shows PARTIAL and notProcessed > 0. Likely reason: the run reached your maximum cost or its time limit. Action: raise the limit, or run the remaining addresses in a new run.
FAQ
Do I need an account or API key from an email verification service?
No. Everything runs inside the Actor. You only need an Apify account.
Why is a fake gmail.com address "deliverable"?
Because the Actor does not ask the mail server if the mailbox exists (no SMTP). It can only say that gmail.com accepts mail. The mailbox_not_verified reason shows this.
Is my email list stored or shared?
The addresses are only saved in your run's dataset. They are not logged, not sent to a verification API, and not kept after the run by the Actor. Domain names are looked up in public DNS.
Should I delete every risky address?
Not always. Role addresses like sales@ are often real. Disposable addresses and typos are usually worth removing or fixing.
Support
Open an issue in the Issues tab of this Actor. Include the run ID and the error message. Do not post your full email list or any tokens.