Bulk Email Verifier & Validator: MX, Disposable, $0.40/1k
Pricing
from $0.28 / 1,000 email validateds
Bulk Email Verifier & Validator: MX, Disposable, $0.40/1k
Bulk email verifier for lead lists: syntax, MX, disposable, role and typo flags, $0.40 per 1,000. No SMTP probing, no catch-all detection. Click Start: 5 prefilled emails run as is.
Pricing
from $0.28 / 1,000 email validateds
Rating
0.0
(0)
Developer
Penny Data
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
11 days ago
Last modified
Categories
Security
Limited permissionsShare
Email Validator: Syntax, MX, Disposable & Typo Check (Bulk, $0.40/1k)
Sample output
Real output from a run on 5 emails (syntax, MX, disposable and role checks, no SMTP):
| input | valid | status | score | riskType | MX records |
|---|---|---|---|---|---|
| bad@@example | false | invalid_syntax | 0 | invalid | 0 |
| info@apify.com | true | deliverable_domain | 70 | role | 5 |
| sales@stripe.com | true | deliverable_domain | 70 | role | 5 |
| jane.doe@gmail.com | true | deliverable_domain | 85 | free | 5 |
| test@mailinator.com | false | disposable | 20 | disposable | 2 |
Try it: paste this into the Input tab and click Start.
{"emails": ["jane.doe@gmail.com", "info@apify.com", "test@mailinator.com", "bad@@example", "sales@stripe.com"]}
Try it in Apify Console: the form opens pre-filled with 5 sample addresses, so one click on Start gives you a result table. A new Apify account includes free monthly credit.
What you get from one click (default input)
Input: john.smith@gmail.com, founder@stripe.com, temp123@mailinator.com, jane.doe@gmial.com, not-an-email. Output rows (from a local code run of the validator, live DNS):
| input | valid | status | score | riskType | hasMx | didYouMean |
|---|---|---|---|---|---|---|
| john.smith@gmail.com | true | deliverable_domain | 85 | free | true | |
| founder@stripe.com | true | deliverable_domain | 100 | none | true | |
| temp123@mailinator.com | false | disposable | 20 | disposable | true | |
| jane.doe@gmial.com | false | disposable | 20 | disposable | false | jane.doe@gmail.com |
| not-an-email | false | invalid_syntax | 0 | invalid | false |
Email verifier and bulk email checker to validate an email list in one run: paste addresses, give a CSV or Google Sheets URL, or pass another Actor's dataset, and get one row per unique email with syntax, MX/DNS, disposable-domain, role-account and free-provider flags plus typo suggestions. It never opens an SMTP connection, so there are no blocked mail ports or per-mailbox timeouts, and it does not claim a mailbox exists. You pay only for rows that were checked; DNS failures are free.
Track record, stated plainly: a young Actor (12 runs so far, 2 users). The only 2 failed runs were on early builds on 2026-10-16; later builds, including the default-input run, succeed. Inputs saved with the field emailList, emails, email or csvFile run unchanged.
Free to try: a new Apify account includes free monthly platform credit, enough to validate a few dozen emails before you pay anything. No hidden minimums.
Features
- Per-row status and run summary: every output row has
rowStatus(ok,invalidorerror) androwReason, and each run saves aRUN_SUMMARYrecord in the key-value store with counts per status and rows billed.errorrows (DNS failures) are never billed.
How to use Bulk Email Validator
- Open the Actor and paste addresses into emails, or set csvUrl to a public CSV or a Google Sheets link (shared as "Anyone with the link can view";
/editlinks are converted to CSV export automatically, and the email column is auto-detected). - Click Start. Duplicates are removed (case-insensitive) and validated once.
- Open the Output tab: the Human summary link (
OUTPUT.mdin the key-value store) shows valid / risky / invalid counts and the first 10 rows as a table. Or download JSON/CSV/Excel. TheSUMMARYrecord shows counts by status. - Re-run rows with status
unknown(DNS failure) later; they were not charged.
Lead-list dedupe mode
Set dedupeMode to clean a lead list in the same run, before anything is billed:
exact(default): same address ignoring case.normalized: also collapsesname+tag@x.comtoname@x.comand ignores dots in Gmail/Googlemail addresses.perDomain: keep only the first address per domain (one contact per company).
Dropped duplicates are never validated or charged; RUN_SUMMARY reports duplicatesDropped.
{ "emails": ["a@x.com", "A@x.com", "j.doe+news@gmail.com", "jdoe@gmail.com"], "dedupeMode": "normalized" }
Pricing
Pay per event, no subscription and no compute fees on top. Price: $0.40 per 1,000 emails (FREE tier; volume tiers are cheaper).
| Event | When it is charged | Price |
|---|---|---|
email-validated | One email address checked (syntax, DNS/MX, disposable, role, free-provider). | $0.0004 |
Rows with status unknown (DNS lookup error) are not billed. Every other checked row is billed, including invalid_syntax and invalid_domain. Set the run's maximum cost in the run options to cap spend: the Actor stops when that limit is reached.
Example input
{"emails": ["jane@gmail.com","info@example.com","bob@mailinator.com","x@gmial.com","not-an-email"]}
Example output
Illustrative sample of the output shape (field names from the Actor; values shown for a disposable address).
{"input": "bob@mailinator.com","email": "bob@mailinator.com","valid": false,"score": 20,"riskType": "disposable","status": "disposable","reason": "disposable_domain","domain": "mailinator.com","hasMx": true,"isDisposable": true,"isRole": false,"isFreeProvider": false,"didYouMean": null}
Rows from a local code run of the Actor's validateEmail on the default Console input (live DNS, no SMTP; wiki record dated 2026-10-24; not an Apify platform run):
| input | valid | status | score | riskType | hasMx | didYouMean |
|---|---|---|---|---|---|---|
| john.smith@gmail.com | true | deliverable_domain | 85 | free | true | |
| founder@stripe.com | true | deliverable_domain | 100 | none | true | |
| temp123@mailinator.com | false | disposable | 20 | disposable | true | |
| jane.doe@gmial.com | false | disposable | 20 | disposable | false | jane.doe@gmail.com |
| not-an-email | false | invalid_syntax | 0 | invalid | false |
Run it from a terminal and get the rows back (replace YOUR_APIFY_TOKEN):
curl -s -X POST "https://api.apify.com/v2/acts/pennydata~bulk-email-validator-no-smtp/run-sync-get-dataset-items?token=YOUR_APIFY_TOKEN" \-H "Content-Type: application/json" \-d '{"emails":["john.smith@gmail.com","founder@stripe.com","temp123@mailinator.com","jane.doe@gmial.com","not-an-email"]}'
Details and limits
Cleans an email list without contacting any mail server. For every address it checks:
| Check | Output field |
|---|---|
| Syntax (RFC 5321/5322-style, length limits, IDN domains converted to punycode) | status = invalid_syntax, reason |
| Domain DNS: MX records, A/AAAA fallback, RFC 7505 null-MX | hasMx, hasARecord, mxRecords |
| Disposable / throwaway domain (incl. subdomains of listed domains) | isDisposable |
Role account (info@, admin@, support@, noreply@ ...) | isRole |
| Free mailbox provider (Gmail, Outlook, Yahoo, iCloud, Proton ...) | isFreeProvider |
Typo suggestion for common providers (gmial.com, hotmial.com, outlok.com) | didYouMean |
Score and risk type
Every row has score (0-100) and riskType (none, free, role, disposable, invalid, unknown), computed from the offline checks above:
invalid(bad syntax, no mail records, null MX, likely typo with no MX) scores 0;disposablescores 20.- Otherwise start at 100 and subtract 30 for a role account, 15 for a free provider, 10 for an A-record-only fallback (no MX), 15 when a typo fix is suggested.
riskTypeis the first match of role, free, elsenone. unknown(DNS lookup failed, not billed) hasscore: null. The score is a heuristic ranking, not a mailbox-exists guarantee.
Use it from Google Sheets
- In Google Sheets click Share, set "Anyone with the link" to Viewer, and copy the link (a File > Share > Publish to web link also works).
- Paste that link into csvUrl (add
#gid=Nfor a tab other than the first). The email column is found automatically; set csvColumn to force one. - Run. You get one validated row per unique address, merged with anything in emails; copy the results back into your sheet.
Read emails from another run's dataset
Set inputDatasetId to a dataset ID or name (for example a scraper's output) and emailField to the item field holding the email (default email). Results are merged with emails and csvUrl; if no row has that field the run fails with a clear message.
valid is true when the syntax is correct, the domain can receive mail (MX, or A/AAAA fallback) and it is not a disposable domain.
Validate leads from a Google Maps Scraper run
- Run a Google Maps scraper that outputs contact emails (for example
compass/crawler-google-placeswith company contacts enabled) and copy the dataset ID of the run (Storage tab). - Paste it into
inputDatasetIdhere and leaveemailFieldasemail. The actor reads both theemailsarray and theemailfield of each place and validates every address. - Each result row keeps the place's
title,placeId,websiteandaddresswhen present, so you can join results back to the scrape. Keep rows wherevalidistrue.
For phones from the same run use phone-number-validator-offline with the same dataset ID. This is checking syntax, domain and disposable/role status only, not whether a mailbox exists.
What this does NOT do (read before buying)
- No SMTP probing. It cannot tell whether a specific mailbox (
jane@company.com) exists.valid: truemeans "the address is well-formed and its domain accepts mail", not "the mailbox exists". Catch-all domains, full mailboxes and non-existent users on real domains pass. - Disposable detection is only as good as the open list (about 9 900 domains). New throwaway services are missed until the list is updated; the list in this version was fetched 2026-10-15.
- The role and free-provider lists are short hand-curated lists, not exhaustive.
- Typo suggestions cover about ten big providers only and are suggestions, not corrections.
- DNS answers can change between runs (domains get new records); flags for a given domain are deterministic only while its DNS is.
Privacy
Only the emails you supply are processed. Nothing is stored beyond the run's dataset and summary (subject to your Apify retention settings). No third-party API is called; only DNS queries for the email domains are sent.
Data sources and licenses
Disposable domains: disposable-email-domains, CC0 1.0. See data/SOURCES.md.
More from PennyData
- Website Screenshot & URL to PDF: Full-page screenshots and URL/HTML to PDF with headless Chromium.
- App Store Reviews Scraper: Apple app reviews by app ID and country from the public RSS feed.
- Bulk Domain WHOIS & DNS Lookup: Availability, registrar, expiry, nameservers, MX and TXT via RDAP.
- Website Tech Stack Detector: CMS, ecommerce, analytics, CDN and JS frameworks per URL.
Works with: Shopify leads -> emails -> phones
Chain three PennyData actors to turn a list of store domains into checked contacts.
- Shopify Store Leads & Contacts takes
{"domains": [{"url": "https://www.gymshark.com"}]}and returns rows withemails(array) andphones(array). - This actor: copy the
emailsvalues intoemailshere, e.g.{"emails": ["press@gymshark.com", "support@gymshark.com"]}. - Phone Number Validator (Offline) takes the
phonesvalues from step 1, e.g.{"phones": ["+44 20 7946 0958"], "defaultRegion": "US"}.
Each step is a separate run billed at its own pay-per-event price, so you only pay for the steps you use.
Switching from api-ninja/email-address-validator?
Both actors take a list of emails. This one adds CSV input and a row cap. Input mapping:
| api-ninja/email-address-validator | bulk-email-validator-no-smtp |
|---|---|
emails (array of strings) | emails (array of strings), same shape |
| (none) | csvUrl + csvColumn to read a public CSV, maxEmails to cap rows, concurrency |
This actor checks syntax, MX/DNS, disposable domains, role accounts and typos without an SMTP mailbox probe, so it cannot confirm that a specific mailbox exists. If you need mailbox-level SMTP confirmation, it will not replace that. Output field names differ between the two actors, so map them from a small sample run before you switch a production job. Price here: $0.0004 per email checked ($0.40 per 1,000), see Pricing above and compare with the other actor's Pricing tab. Not affiliated with the other actor's author.
Drop-in alternative: accepts the same input fields
If you already have a saved input for another email validator, it runs here unchanged. Accepted input fields (aliases of the canonical emails): emailList, emailAddresses, emailAddress, email; CSV aliases: csvFile, csv, fileUrl. Each can be an array of strings, a newline/comma-separated string, or an array of { "email": "..." } objects. Extra keys such as timeout are ignored.
The input of fatihtahta/email-verifier-validator-pro is only emailList (read from its public schema on 2026-10-22). That actor's public Store stats on 2026-10-22: 1,199 users, and 38,010 of 50,777 runs in the last 30 days listed as failed. Your saved {"emailList": [...]} input works here with no edits. Output field names and checks differ: we do NOT do SMTP mailbox checks (syntax, MX/DNS, disposable, role, typo only), so run a small sample before switching a production job. Not affiliated with that author.
Migrating from easy-bulk-email-validator (xmiso_scrapers)?
The input is the same: { "emails": ["a@example.com", "b@example.org"] } runs here with no edits. The other actor's second input, timeout, is accepted and ignored here (there are no per-mailbox SMTP waits). Mapping below is from its public dataset view (build 0.0.14, read 2026-10-23; no runs were made on it). Their email_result and subresult values are not documented in that view, so those rows map by meaning only; run a small sample on both before switching a production job.
| easy-bulk-email-validator output | Here | Note |
|---|---|---|
email | email (normalised; original in input) | duplicates removed before checking |
email_result | valid (true/false) and status | status is one of deliverable_domain, fallback_a_record, invalid_syntax, invalid_domain, disposable, unknown; rowStatus is ok, invalid or error |
subresult | reason (for example disposable_domain, dns_lookup_error) | |
email_quality | score (0-100) and riskType | heuristic from offline checks, not a mailbox verdict |
free (Is_From_Free_Email_Provider) | isFreeProvider | also isRole, isDisposable, hasMx, didYouMean |
Pricing is pay per event: $0.0004 per email checked, no per-run start fee, DNS failures free (their listed price on 2026-10-21: $0.0025 per result plus $0.01 per run; check its Pricing tab, prices can change). This actor never opens an SMTP connection, so it cannot confirm that a specific mailbox exists; if you need mailbox-level confirmation it will not replace that. Not affiliated with the other actor's author.
Migrating from michael.g/email-verifier-validator?
Its published input schema (read 2026-10-21) takes emails, an array of strings, so a saved input runs here with no edits.
| Field in your saved input | What this actor does |
|---|---|
emails (array of strings; also accepts a newline/comma string) | validated as-is |
Listed price on 2026-10-21 (check each Pricing tab, prices can change): michael.g $0.001 per email, no start fee listed; this actor $0.0004 per email, no start fee.
We do NOT do SMTP mailbox checks: this actor checks syntax, MX/DNS, disposable domains, role accounts and typos, so it cannot confirm that a specific mailbox exists. Output field names differ, so check a small sample run before switching a production job. Not affiliated with the author.
Migrating from snipercoder/email-validator?
An input saved for snipercoder/email-validator runs here without edits. Accepted field names:
| Field in your saved input | What this actor does |
|---|---|
emails (array or newline/comma string) | validated as-is |
email (single string) | validated as one address |
csvFile (public CSV URL) | read like csvUrl; uses the column whose header contains "email" |
This actor checks syntax, MX/DNS, disposable domains, role accounts and typos without an SMTP mailbox probe, so it cannot confirm that a specific mailbox exists. Output field names differ, so check a small sample run before switching a production job. Rows whose DNS lookup fails are not billed. Not affiliated with the other actor's author.
FAQ
Can I verify a list of emails in bulk for free?
New Apify accounts include free monthly platform credit, so a small list costs nothing out of pocket; after that it is pay per event (see Pricing; it can change). Paste addresses into emails, or set csvUrl (and csvColumn) to a public CSV, and download one row per unique address.
{ "emails": ["jane@gmail.com", "info@example.com", "x@gmial.com"], "concurrency": 10 }
How do I check for disposable (temporary) email addresses?
Every row has isDisposable, matched against the open disposable-email-domains list (subdomains included), and valid is false with status: "disposable" for them. The list is only as current as its fetch date (2026-10-15), so brand-new throwaway services can be missed.
{ "emails": ["bob@mailinator.com", "temp123@mailinator.com"] }
Am I billed for rows that failed?
No for DNS failures: a row with status unknown (reason: dns_lookup_error) is written to the dataset but not charged, so re-run those later for free. Rows that were actually checked are charged, including addresses found invalid_syntax or invalid_domain, because the check ran. To cap spend, set the maximum cost per run in the run options; the Actor stops once that limit is reached. The SUMMARY record shows counts per status so you can compare it with your bill.
What does status unknown mean? Am I billed for it? Can I re-run it?
status: "unknown" (reason: dns_lookup_error) means the DNS lookup for the domain failed or timed out, so the Actor could not tell whether the domain accepts mail. It does not mean the address is bad. unknown rows are never billed. They are written to the dataset with no charge, so filter status = unknown and paste those addresses into a new run later. A re-run that again returns unknown is free again; if the lookup now works, the row is checked and charged as a normal row ($0.0004). The SUMMARY record shows how many rows ended up unknown.
What does it cost? Worked example.
$0.0004 per email checked (FREE tier price; volume tiers are cheaper), nothing else on top. 1,000 emails = $0.40. 25,000 emails = $10.00. Duplicates are removed before checking, so a repeated address is billed once.
How many emails per run?
maxEmails defaults to 10,000 and can be raised to 100,000 per run (the input field limit). It is a safety cap on unique addresses, not a hard product limit: extra addresses beyond it are ignored with a log warning, so raise it for bigger lists. For a CSV, set csvUrl (and csvColumn) instead of pasting.
Why no SMTP check?
SMTP probing (connecting to the mail server and asking whether the mailbox exists) gets sender IPs blocklisted, is blocked or faked by many providers (catch-all domains answer yes to everything) and is unreliable from cloud IPs. This Actor only does DNS lookups and list checks, which are fast, cheap and safe for your IP.
What does it NOT verify?
Whether a specific mailbox exists, is full, or will accept your mail. valid: true means the syntax is correct, the domain has MX (or an A/AAAA fallback) and it is not a known disposable domain. Catch-all domains, full mailboxes and non-existent users on real domains pass. Use it to clean obvious bad rows before sending, not as a guarantee of delivery.
Does it detect catch-all addresses or confirm a mailbox exists?
No. It never opens an SMTP connection, so it cannot detect catch-all domains, full mailboxes or non-existent users on real domains. valid: true means well-formed with a domain that accepts mail. That is also why it cannot get your IP blocklisted.
How do I flag role accounts, free providers and typos?
Use isRole (info@, admin@, support@ ...), isFreeProvider (Gmail, Outlook, Yahoo ...) and didYouMean (about ten big providers, e.g. gmial.com to gmail.com). Rows with unknown DNS status are not charged; re-run them later.
How do I clean a whole lead list, not just emails?
Find public business emails for store domains with Shopify Store Leads & Contacts, validate phone numbers with Phone Number Validator (Offline), and check the email domains with Bulk Domain WHOIS & DNS Lookup. You are responsible for a lawful basis (e.g. GDPR) to process the addresses you submit.
Need a custom field or a different source? Open an issue on the Actor page.
Built with AI assistance (Claude); maintained by the publisher.
Use from Claude Desktop / Cursor (MCP)
Apify's MCP server can expose this actor as a tool, so an AI agent can validate a list of emails from a plain-English prompt. Use this URL (?tools= takes a comma-separated list of actors, see the Apify MCP docs):
https://mcp.apify.com/?tools=pennydata/bulk-email-validator-no-smtp
Client config for Claude Desktop or Cursor (mcpServers format; sign in with OAuth when the client asks):
{"mcpServers": {"apify": {"url": "https://mcp.apify.com/?tools=pennydata/bulk-email-validator-no-smtp"}}}
Runs started by an agent are billed like any other run, at the prices above.
Related pennydata actors
Other small pay-per-event Actors from the same publisher:
- Phone Number Validator (Offline): validate and normalize phone numbers in the same lead list.
- Bulk Domain WHOIS & DNS Lookup: check whether the email domains are registered and have MX records.
- Shopify Store Leads & Contacts: find public business emails for store domains, then validate them here.
Feedback
If this actor saved you time, a short review on its Apify Store page helps others find it: https://apify.com/pennydata/bulk-email-validator-no-smtp