Bulk email verification API
Email list cleaning service with a bulk verification API
Upload a CSV or TXT file, or send the addresses as JSON. emailvalidation.io checks every unique address in the background and gives you back your list with a result for every row, as CSV or JSON. A webhook tells you when the job has finished.
Example request and response
Upload a file and pass a webhook URL. The answer (201) is the queued job: how many rows the list has, how many unique, valid addresses will be checked, and the requests charged. Already have the list in your code? Send {"emails": [...]} as JSON instead of a file. Every parameter is in the bulk documentation.
curl https://api.emailvalidation.io/v1/bulk \
-H "apikey: YOUR-API-KEY" \
-F file=@contacts.csv \
-F webhook_url=https://example.com/hooks/emailvalidation
{
"job_id": "629954218123464704",
"status": "queued",
"source": "csv",
"catch_all": false,
"counts": {
"total": 5,
"unique": 3,
"duplicates": 1,
"invalid": 1,
"processed": 0,
"deliverable": 0,
"undeliverable": 0,
"risky": 0,
"unknown": 0
},
"progress": 0,
"quota": {
"charged": 3,
"refunded": 0
},
"webhook": {
"status": "pending",
"attempts": 0
},
"error": null,
"created_at": "2026-10-05T08:13:09+00:00",
"started_at": null,
"finished_at": null,
"expires_at": "2026-11-04T08:13:09+00:00",
"links": {
"self": "https://api.emailvalidation.io/v1/bulk/629954218123464704",
"results": null
},
"webhook_secret": "whsec_faf08666fa26ea2b20c188fa97822ecb4fcd9bc39598cc41"
}
What every row of your list gets back
- state, reason
deliverable,undeliverable,riskyorunknown, and why, for examplevalid_mailbox,invalid_mailbox,invalid_mxorinvalid_format.- score
- A quality score from 0 (poor) to 1 (good).
- format_valid, mx_found, smtp_check
- Whether the address is well-formed, whether its domain has MX records, and whether the mail server accepted the mailbox.
- disposable, role, free
- Throwaway addresses, role addresses such as info@ or support@, and addresses at free email providers.
- catch_all
- Whether the domain accepts mail for any address. Filled when you ask for the catch-all check (plans from Small), otherwise empty.
- did_you_mean
- A suggested address when the domain looks like a typo.
- email, user, tag, domain
- The address as it was checked, and its parts.
Use cases
Before a campaign
Remove undeliverable and disposable addresses from an export before you send, and send to risky (catch-all) addresses in smaller batches.
Regular CRM clean-ups
Export your contacts on a schedule, submit the file, and import the results CSV back when the webhook arrives. Your columns are kept and the result columns are added; check cells that start with =, +, - or @, such as phone numbers, because they get a leading ' so that spreadsheet programs do not run them as formulas.
Moving to a new email tool
Check a list before you import it into a new email service provider, so the new account does not start with a high bounce rate.
Old sign-up lists
Verify addresses that were collected without validation, such as old newsletter or event sign-ups, before you write to them again.
AI agents
Through the MCP server, an agent can submit a list, follow the job and delete it. It downloads the results over the REST API.
How a list cleaning job works
- Submit the list with
POST /v1/bulk: a CSV or TXT file, or a JSON array of addresses. The answer is the job, with statusqueued. - Wait for the webhook, or check
GET /v1/bulk/{job_id}about once a minute until its status iscompleted. Checking a job is free. On the free plan, which allows 10 requests a minute, a submitted list counts against that minute’s limit too, so wait until the next minute before you check the job (details). - Download the results with
GET /v1/bulk/{job_id}/results: as CSV, with your columns plus theemailvalidation_*columns, or as JSON. - Delete the job with
DELETE /v1/bulk/{job_id}once you have the results, or let it expire 30 days after it finished.
Every unique address gets the same checks as a single request to the email validation API: syntax, MX records, a live check of the mailbox over SMTP, and the disposable, role and free-provider flags. Duplicates are checked once and repeat that result in every row where they appear. Rows without a valid address are kept and marked undeliverable with the reason invalid_format.
What a job costs
- Charged when you submit: one request of your quota per unique, valid address. Addresses are compared without regard to case and surrounding spaces, so
Anna@Example.comandanna@example.comcount once. TheX-Costheader andquota.chargedshow the amount. - Free: duplicates, rows without a valid address, and checking, listing, downloading and deleting jobs.
- Refunded: addresses we could not verify because of a failure on our side, when the job completes. Deleting a job refunds the addresses not checked yet, and a failed job is refunded. Refunds go back to your current quota and cannot raise it above its monthly size, so a refund after your quota has reset gives back at most what you have used since the reset.
- Checked first: a list is only accepted if your quota has more requests left than the list costs. Otherwise the API answers
429with the number of requests the job needs, before anything is stored or checked.
Files and limits
| Limit | Value |
|---|---|
| Formats | CSV (comma, semicolon, pipe or tab), TXT (one address per line), JSON array |
| Rows per list | 100,000 (1,000 with a sandbox key) |
| File size, or all JSON items together | 25 MB |
| Columns per CSV row | 500 |
A header row is detected. The address column is the one you name with email_column (its title or its number); without it, the API uses the column titled like email, and otherwise the column with the most addresses in the first 100 rows. The CSV and TXT rules are in the documentation.
Your list and your data
- Lists and results are encrypted with AES-256 before they are stored, on top of our storage provider’s encryption at rest. While a job runs, its addresses are kept encrypted in our database, and they are deleted from it when the job finishes.
- The results are only available through the API, with an API key of your account. There are no public download links, and the webhook carries the job’s status and counts, never addresses or results.
- The stored list and the results of a job are deleted 30 days after the job finished, or right away when you delete the job.
- The webhook is signed with HMAC-SHA256 and a secret for each job, goes only to
httpsURLs on public hosts, and is sent in up to 6 attempts over about 2.5 hours until your endpoint answers with a 2xx status. See Webhooks.
Free tool, dashboard or API?
- A few addresses, no account: the free bulk email verifier on this site, which checks a few addresses an hour.
- A file now and then, no code: upload it in the emailvalidation.io dashboard.
- Recurring lists, automation, up to 100,000 rows: the bulk API on this page, with webhooks and results in your own column layout.
Test without using your quota
Sandbox keys work with bulk jobs. A sandbox job is free, accepts up to 1,000 rows and never contacts a mail server: the documented test addresses get their made-up results, and every other address is deliverable. Its webhook is signed like a live one, so you can test your receiver before you send a real list.
Start on the free plan
Bulk jobs are part of every plan, including the free plan with 100 requests a month. Each unique, valid address in a list costs one request. Paid plans start at $9.99 a month for 5,000 requests.
Frequently asked questions
What does an email list cleaning service do?
How long does cleaning a list take?
What does it cost?
Which files can I upload?
email_column.Do I get my own columns back?
emailvalidation_* columns. It has one row for every row of your list, in the same order, including rows with an empty or invalid address. Two changes to check before you import it: cells that start with =, +, - or @ get a leading ' so that spreadsheet programs do not run them as formulas, and a file without a header row gets one (column1, column2, …).