Bulk Email Verifier — CSV, Excel & List Cleaning avatar

Bulk Email Verifier — CSV, Excel & List Cleaning

Pricing

from $1.00 / 1,000 email verifieds

Go to Apify Store
Bulk Email Verifier — CSV, Excel & List Cleaning

Bulk Email Verifier — CSV, Excel & List Cleaning

Upload a CSV or Excel file, paste a list, or chain another Actor's dataset — every address is sorted into valid, invalid or risky. Checks syntax, mail servers, disposable providers, role accounts and domain typos, and reports exactly which checks it could run.

Pricing

from $1.00 / 1,000 email verifieds

Rating

0.0

(0)

Developer

Lianchang Zhou

Lianchang Zhou

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

a day ago

Last modified

Share

Clean an email list from a CSV file, an Excel spreadsheet, a pasted list or another Actor's dataset — before you send to it. This Actor checks syntax, mail-server configuration, throwaway providers, shared team mailboxes and likely typos, and tells you plainly which of those checks it was able to run for every single address.

What does this bulk email verifier do?

It takes a list of email addresses and sorts them into valid, invalid, risky and unknown, with a reason and a confidence score attached to each verdict. It catches malformed addresses, domains that publish no mail server at all, throwaway providers like Mailinator, shared mailboxes like info@ and support@, and near-miss typos such as gmial.com where it suggests the correction.

Feed it whatever you already have. Upload a CSV or Excel file, paste a list, or point it at another Actor's dataset — chain it straight onto a lead scraper and skip the export-and-re-import step entirely. The address column is detected automatically, whether it is called email, work_email or Contact E-Mail. Most verifiers on this Store accept a JSON array and nothing else; if your list already lives in a spreadsheet, that is a manual conversion step every single time.

It runs on the Apify platform, so you can call it from the API, schedule it, or pipe another Actor's output straight into it. Results download as JSON, CSV, Excel or HTML.

It does not send email. Nothing in this Actor delivers a message to anyone on your list.

Why clean an email list before you send?

  • Protect your sender reputation. Bounces and spam-trap hits are what get a sending domain throttled or blocked. Removing definitively bad addresses before a campaign is the cheapest reputation insurance there is.
  • Stop paying to store dead leads. Most CRMs and sending platforms bill per contact.
  • Filter role accounts out of cold outreach. sales@, info@ and admin@ reach a shared inbox, convert poorly, and count against you on several sending platforms.
  • Catch typos while the lead is still fresh. A signup that typed gmial.com is a real person you can still recover, not a dead record.

Honest limits — please read before you buy

This Actor does not confirm that an individual mailbox exists. Doing that requires an SMTP conversation with the recipient's mail server on port 25, and the Apify platform blocks outbound port 25 — as do most cloud providers and many consumer ISPs. Large mailbox providers also refuse these probes from datacenter IP ranges regardless.

Rather than pretend otherwise, every result carries a smtpChecked flag and a confidence score, so you always know how much was actually established:

VerdictWhat it really meansConfidence
invalidsyntax_errorNot a well-formed address. Certain.100
invalidno_mail_serverThe domain publishes no mail server, so it cannot receive mail. Certain.95
riskydisposable_domainA throwaway mailbox provider. Certain.95
riskyrole_accountA shared team mailbox, not a person. Certain.85
validdomain_verified_mailbox_uncheckedWell-formed, and the domain accepts mail. The specific mailbox was not probed.70
unknowndns_errorA lookup failed. Says nothing about the address either way.0

So the strong guarantee is on the negative side: what this Actor marks invalid or risky is reliably bad and safe to drop. A valid result means "passed every check that could be run", not "a human is reading this inbox".

If you need mailbox-level confirmation, you need a service that operates its own port-25-enabled relay infrastructure. If you are running this Actor somewhere with port 25 open, switch on Attempt SMTP mailbox probe and it will use it, including catch-all detection.

How to verify an email list from CSV, Excel or a dataset

  1. Open the Actor and go to the Input tab.
  2. Give it your addresses in whichever form you have them:
    • upload a spreadsheet under CSV or Excel file;
    • paste them into Paste a list instead, or add them one per row under Email addresses;
    • or pick a previous run's dataset under Verify another Actor's dataset.
  3. Click Start.
  4. When the run finishes, open the Output tab and download the results in your preferred format.

You can combine sources in one run — everything is merged and de-duplicated.

To run it on a schedule or from your own code, use the API tab — Apify generates ready-made snippets.

Verifying a CSV file or Excel spreadsheet

Drop the file into CSV or Excel file and start the run. There is nothing to configure:

  • Delimiters are detected — comma, semicolon, tab or pipe.
  • The address column is found automatically, whether the header says email, Work Email or Contact E-Mail. Set Address field name only if the sheet has several address columns and you want a specific one.
  • A header row is optional. A single bare column of addresses works.
  • Files are identified by their contents, not their extension, so a download link with no .csv on the end is fine.

Exporting from Google Sheets, Mailchimp, HubSpot or a CRM usually gives you a CSV already — that file goes in as-is. Legacy .xls is the one format not supported; re-save it as .xlsx or CSV.

Chaining it onto a lead scraper

Run any scraper that produces contacts, then select its dataset under Verify another Actor's dataset. Every item is scanned and its addresses verified — no export, no CSV round-trip. Leave Address field name blank and common keys are detected automatically; set it if your dataset has more than one address column and you want a specific one.

This Actor runs with limited permissions: it can read the one dataset you select and nothing else in your account.

Input

FieldTypeDefaultDescription
emailsarrayAddresses to verify. Accepts plain strings, or objects with an email field so another Actor's output pipes straight in.
emailsRawstringPaste addresses separated by newlines, commas or semicolons. Merged with emails and de-duplicated.
inputDatasetIdstringDataset from another Actor's run, chosen with the resource picker. Every item is scanned for addresses.
fileUrlstringUploaded .csv or .xlsx file, or a link to one.
emailFieldstringName of the field or column holding the address. Blank means auto-detect. Applies to both the dataset and the file.
maxConcurrencyinteger20How many addresses to check in parallel.
enableSmtpCheckbooleanfalseAttempt a mailbox-level SMTP probe. Normally unavailable — see the limits section above.
detectCatchAllbooleantrueWhen the SMTP probe works, also detect domains that accept every address.
smtpTimeoutSecondsinteger10Per-server timeout for the probe.
heloDomainstringexample.comDomain announced during the SMTP handshake.
mailFromstringverify@example.comEnvelope sender for the probe. No message is ever sent.
{
"emails": ["support@github.com", "someone@gmial.com", "test@mailinator.com"],
"maxConcurrency": 20
}

Verifying a scraper's output instead:

{
"inputDatasetId": "aBcDeFgHiJkLmNoPq",
"emailField": "work_email"
}

Output

One record per address, in the order you supplied them:

{
"email": "someone@gmial.com",
"normalizedEmail": "someone@gmial.com",
"status": "risky",
"deliverability": "risky",
"score": 10,
"subStatus": "disposable_domain",
"confidence": 95,
"reason": "address belongs to a throwaway/temporary mailbox provider",
"localPart": "someone",
"domain": "gmial.com",
"isDisposable": true,
"isRoleAccount": false,
"isFreeProvider": false,
"hasValidSyntax": true,
"hasMailServer": null,
"mxHost": null,
"mxRecords": [],
"didYouMean": "someone@gmail.com",
"smtpChecked": false,
"smtpOutcome": null,
"smtpDetail": null,
"checkedAt": "2026-08-01T11:06:24.918273+00:00"
}

Checks stop as soon as the verdict is settled — the example above is a throwaway domain, so its mail servers were never looked up. Fields belonging to a check that did not run are null, never false: "not checked" and "checked, found nothing" are different answers, and only the second one is safe to filter on.

A SUMMARY record in the key-value store holds the run totals per status.

Data fields

FieldTypeDescription
emailstringThe address exactly as you supplied it.
normalizedEmailstringTrimmed and lowercased form.
statusstringvalid, invalid, risky or unknown.
deliverabilitystringThe same verdict in the vocabulary other verifiers use: good, risky, bad or unknown.
scoreinteger0–100 deliverability score — how likely the address is to accept mail.
subStatusstringThe specific reason behind the status.
confidenceinteger0–100 certainty in the assigned status — a confident invalid scores as high as a confident valid. Not the same axis as score: a malformed address is a certain verdict (confidence 100) about an address that will never deliver (score 0).
reasonstringHuman-readable explanation.
localPart / domainstringThe two halves of the normalized address.
isDisposablebooleanThrowaway mailbox provider.
isRoleAccountbooleanShared team mailbox (info@, support@, …).
isFreeProviderbooleanConsumer webmail. A B2B signal, not a deliverability problem.
hasValidSyntaxbooleanPassed RFC-compliant syntax validation.
hasMailServerbooleanDomain publishes an MX record, or an A/AAAA record usable as an implicit MX. null when the lookup did not run or could not answer — see the note above.
mxHost / mxRecordsstring / arrayHighest-priority mail server, and the full list. Empty when hasMailServer is null.
didYouMeanstringSuggested correction for a near-miss domain typo, else null. Only the part before the suffix is corrected, so real regional domains (live.cn, gmx.net, yahoo.ca) are left alone; a suffix typo (gmail.con) is suggested only once the domain is confirmed to accept no mail.
smtpCheckedbooleanWhether the individual mailbox was actually probed. Normally false.
smtpOutcome / smtpDetailstringProbe result and explanation, when one ran.
checkedAtstringISO 8601 timestamp.

How much does it cost to verify emails?

Price
Per address verified$0.001 (i.e. $1.00 per 1,000)
Per address returned as unknownfree
Per run started$0.00005

An unknown result means a check failed in a way that established nothing about the address — almost always a nameserver that did not answer. You are not billed for those: you did not get a verification, so you do not pay for one. Setting a maxTotalChargeUsd cap on the run is also honoured properly — the run stops there rather than quietly finishing the rest for free on your compute.

This Actor becomes free on 16 August 2026. Pricing has been switched off and, per Apify's policy, the change takes effect after a two-week notice period. From that date there is no per-address charge at all — you pay only the Apify platform compute a run consumes.

It is cheap to run either way, because it does no web scraping: no browser is launched, no page is rendered, and DNS lookups are cached per domain. A list of 5,000 addresses spread across 200 domains costs 200 lookups, not 5,000, so large single-domain lists are especially efficient.

Measured on the Apify platform, 945 addresses at a 256 MB memory setting:

Run time44.5 s
Peak memory82 MB
Compute consumed0.0031 compute units

How reliable is it?

On that same 945-address platform run, 0.53% of addresses came back unknown — five addresses across four domains whose own nameservers were failing at the time. Everything else reached a definite verdict.

Two design choices keep that number low:

  • Fallback resolver. If the primary resolver cannot answer, a second is asked before giving up. Only "the resolver could not tell us" errors are retried; an authoritative "this domain does not exist" is trusted immediately.
  • A failed lookup never becomes invalid. A DNS timeout says nothing about an address, so it is reported as unknown rather than silently deleting a good contact from your list.

Tips

  • De-duplicate first if you are cost-sensitive. The Actor de-duplicates within a run, but not across separate runs.
  • Decide what to do with risky deliberately. Dropping every role account is right for cold outreach and wrong for support or billing contacts. isRoleAccount and isDisposable are separate fields so you can filter on exactly what you mean.
  • Treat unknown as "retry later", not as "bad". It usually means a DNS timeout, and it is not charged for.
  • Act on didYouMean. A suggested correction is often a recoverable customer.
  • Sort on score and cut where you like. deliverability gives you the three-bucket view; score lets you pick your own threshold.

Code examples

Runnable Python examples live in a separate repository: github.com/zhoulianchang/email-verifier-examples

  • verify_list.py — read a local CSV, verify, split into keep / drop / review
  • chain_from_actor.py — pass an upstream Actor's dataset ID straight in, with no download or re-upload
  • samples/output-sample.json — 14 records of real output covering all four statuses, so you can see the exact shape before writing any code

The examples treat unknown as its own bucket rather than folding it into "bad", which is the filtering mistake most likely to cost you good contacts.

FAQ

Does this send any email? No. Even with the SMTP probe enabled, the conversation stops at RCPT TO, before any message body is transmitted.

Why is a well-known good address marked risky? Most likely it is a role account such as info@ or support@. Check the isRoleAccount field. These are usually deliverable — they are flagged because they are a poor outreach target, not because they bounce.

Why does valid only score 70 confidence? Because the mailbox itself could not be probed. See the limits section — that number is the honest one.

Can it detect catch-all domains? Only when the SMTP probe is available, which on the Apify platform it is not.

Which file formats can I upload? .csv (comma, semicolon, tab or pipe delimited — the delimiter is detected) and .xlsx. A single column of addresses with no header row works too. Legacy .xls is not supported; re-save it as .xlsx or CSV. Files are identified by their contents, not their extension, so a link with no extension is fine.

Can I verify an email list exported from Google Sheets or Excel? Yes. Export the sheet as CSV or .xlsx and upload it under CSV or Excel file. No column mapping to set up — the address column is detected by name.

How many addresses can it handle in one run? Tested at 5,000 in a single run at 39 MB peak memory, and 945 addresses on the platform in 44.5 seconds. There is no built-in ceiling; DNS lookups are cached per domain, so a large list concentrated in few domains is disproportionately fast.

Can it run automatically on new leads? Yes — schedule it from the Schedules tab, or chain it onto a scraper so each new run's dataset is verified as it lands.

My dataset has several address columns. Which one is used? Auto-detection prefers an exact match on common names (email, work_email, contactEmail, …). If you want a specific one, set Address field name — it is then honoured strictly, so you get an obvious empty result rather than a silent fallback if the name is wrong.

Is the disposable-domain list kept current? It ships with the Actor image from the public-domain disposable-email-domains project and is refreshed when the Actor is rebuilt. Bundling it means a run never depends on a third-party host being reachable.

Support

Found an address the Actor gets wrong? Open an issue on the Issues tab with the address and what you expected — misclassifications are the most useful bug reports for this Actor.