← Blog

How to bulk-validate a CSV of email addresses

Send the whole list in one request, get a job_id back, and pull a verdict per row — with the same checks, and the same honest gaps, as the single endpoint.

Contents

You have a CSV of email addresses — a signup export, a list you bought and now regret, a decade of newsletter sign-ups — and you want to know which rows are worth sending to before your bounce rate tells the story for you. That is the whole job of bulk email validation: a CSV in, a verdict per row out. Here is how to do it against the Boundstone API in two requests, and, just as importantly, exactly what each row does and does not get checked.

First, what you can do without an API

If your CSV problem is only syntax — malformed addresses, a missing @, a typo'd TLD — you do not need a service for that. A few lines of your standard library will read the file and run a regex. Our walkthrough in validate an email address in Python covers the local version, and the keyless email validator tool will spot-check a single address in the browser with no signup.

Syntax is where most languages stop, though. Knowing an address is shaped correctly tells you nothing about whether the domain can receive mail, whether it is a disposable burner, or whether it is info@ / sales@ — a role account no single human reads. That is the layer the bulk endpoint adds, once per row, across the whole file.

What each row actually gets checked

Every row gets the exact same checks as the single /v1/verify/email endpoint. No more, no less. For a valid address:

"checks": {
  "performed": ["syntax", "mx", "disposable_list", "role_list"],
  "not_performed": ["smtp_mailbox", "catch_all"]
}

Read not_performed as a feature, not a disclaimer. Boundstone does not open an SMTP conversation with the recipient's mail server, and it does not try to defeat catch-all domains. Those probes are slow, easy to get wrong, and the kind of thing that gets your sending IP blocked. What you get instead is fast and stated plainly: valid syntax, a live MX-record lookup, a disposable-domain check, and a role-account check. The results also carry a free_provider flag (gmail.com, outlook.com, and the like) — useful context, though note it is a returned field, not one of the checks.performed.

If you need mailbox-level liveness, that is a real thing we do not claim to do today. Handle that expectation in your own pipeline rather than pretending a green row means "inbox exists."

Post the CSV, get a job_id

Send the file as a raw CSV body to /v1/bulk/email. One column of addresses is all it needs.

curl -X POST https://api.boundstone.io/v1/bulk/email \
  -H "Authorization: Bearer bs_live_YOUR_KEY" \
  -H "Content-Type: text/csv" \
  --data-binary @contacts.csv

You get back HTTP 202 Accepted and a job id — the request returns immediately instead of holding the connection open while thousands of MX lookups run:

{ "job_id": "8f3c1a20-..." }

Fetch the results

Pull the finished results as a CSV from the job's results path. Give a large file a moment to drain before you fetch.

curl https://api.boundstone.io/v1/bulk/8f3c1a20-.../results.csv \
  -H "Authorization: Bearer bs_live_YOUR_KEY" \
  -o results.csv

You get one row per input address, carrying the same fields the single endpoint returns — valid_syntax, domain, mx_found, disposable, role_account, free_provider. Every row ran the same checks contract shown above, so mx_found and disposable are the columns you filter on to decide what actually ships.

Credits, caps, and refunds

A few numbers that decide how you batch:

  • The free tier caps a job at 250 rows. Paid plans go up to 10,000 rows per job. Split larger files into multiple jobs.
  • Credits are reserved per row at submit — a 400-row file reserves 400 credits up front.
  • Rows that error are refunded. If a row can't be processed, you get that credit back. You pay for work done, not for lines in a file.

The free tier is 250 credits a month, no card, and credits never expire — enough to validate a small list or prove the pipeline out before you commit to anything.

No code? Upload it in the dashboard

The same bulk engine is wired into the dashboard. Drop a CSV into the upload box, or paste addresses one per line, and you get the same job with the same per-row checks and the same downloadable results.csv. Same caps, same refunds, no curl required.

The short version

  • Two requests: POST /v1/bulk/email with the raw CSV → 202 + job_id; then GET /v1/bulk/:id/results.csv.
  • Each row gets syntax, MX, disposable, and role checks — and, honestly, not SMTP mailbox or catch-all.
  • 250 rows/job free, 10,000 paid; credits reserved per row, refunded on error.
  • A clean list is the point. Dropping dead domains and disposables before you send is the cheapest way to reduce your bounce rate — and to stop the addresses that stay from paying for the ones that don't.

Validate the list, filter on mx_found and disposable, and know exactly what a clean row did and did not promise.

Frequently asked questions

How do I bulk-validate a whole CSV of email addresses at once?

Send your CSV to a bulk endpoint such as Boundstone's /v1/bulk/email (upload it in the dashboard or POST the raw CSV), and it reads the email column, checks every row, and returns a results CSV you can download. Each email is checked for syntax, MX records on the domain, disposable domains, and role addresses like info@ or sales@. Boundstone reserves one credit per row when you submit and refunds any row that errors, so a partly broken file never overcharges you. The free tier handles up to 250 rows per job.

What does a "valid" result actually prove when you bulk-check an email list?

A "valid" result from Boundstone means the address passed syntax checks, its domain publishes MX records that can accept mail, and it is not on a disposable-domain or role-account list. It does not confirm that the specific mailbox exists or will accept your message, because SMTP mailbox verification and catch-all detection are explicitly reported as not_performed rather than guessed at. Every row spells out which checks were performed and which were not, so you always know exactly what a "valid" does and does not prove instead of trusting an inflated score.

How many rows can I validate for free, and what does bulk email validation cost?

On Boundstone's free tier you can bulk-validate up to 250 rows per job with no card required, and credits never expire. Paid plans raise the cap to 10,000 rows per job. Each row costs one credit, reserved when the job is submitted and refunded automatically for any row that errors, so you only pay for rows that were actually processed.

Thomas Tsui

Founder of Boundstone — building phone, email and IP validation you can actually verify.

One honest API for email, phone and IP — every response lists what it checked and what it didn't claim to. Free tier: 250 credits/month, no card, credits never expire.

More from Boundstone — API documentation · Benchmark methodology · The benchmark series · Buyer's checklist