Jun 2, 202611 min read

Cleaning a 50,000-Row Email List with a Bulk Verification API

At some point somebody hands you a CSV. It came from a CRM export, or three years of form fills, or an acquisition, and the question is whether you can send to it.

The honest answer is usually “not as-is”. Lists that have not been maintained degrade at roughly 20–30% per year — people change jobs, domains lapse, addresses get abandoned. Send to a stale list and the bounce rate does the damage: mailbox providers read high bounces as a signal that you do not know who your recipients are, and your deliverability drops for the addresses that were good.

This is a walkthrough of doing that cleanup properly.

Before the API: fix the file

Roughly a fifth of the problems in a real list are solved in the CSV, before any verification runs.

Deduplicate case-insensitively. [email protected] and [email protected] are the same mailbox. Duplicates cost you verification quota and, worse, produce duplicate sends.

Trim whitespace and strip display names. Exports frequently contain "Ada Lovelace" <[email protected]> or a trailing space. Extract the address.

Drop obvious junk early. Rows with no @, addresses ending in . or ,, and the test entries somebody typed into a form. [email protected] is in every list.

Segment by age and source before you verify. A list is rarely homogeneous — last quarter’s signups and a 2019 tradeshow scrape have very different profiles, and averaging them hides that. Verify separately and you can make a source-by-source decision.

The CSV endpoint

The Bulk Email Verification API accepts a file directly:

curl --request POST \
  --url https://bulk-email-verification-api.p.rapidapi.com/v1/email/bulk/validate-csv \
  --header "X-RapidAPI-Key: $RAPIDAPI_KEY" \
  --header "X-RapidAPI-Host: bulk-email-verification-api.p.rapidapi.com" \
  --form "[email protected]"

The CSV should have an email column; if it does not, the first column is treated as the email column.

email,name
[email protected],Ada
[email protected],Support

Limits: 1,000 rows and 512 KB per upload. So a 50,000-row list is not one call — it is at least 50 chunks. Split the file, keep a stable row index so you can rejoin results to your original records, and process the chunks with bounded concurrency against your plan’s rate limit.

If you already have the addresses in memory, the JSON endpoint is simpler: POST /v1/email/bulk/validate takes up to 500 addresses per call.

curl --request POST \
  --url https://bulk-email-verification-api.p.rapidapi.com/v1/email/bulk/validate \
  --header "Content-Type: application/json" \
  --header "X-RapidAPI-Key: $RAPIDAPI_KEY" \
  --header "X-RapidAPI-Host: bulk-email-verification-api.p.rapidapi.com" \
  --data '{"emails":["[email protected]","[email protected]","[email protected]"]}'

Reading the result

{
  "success": true,
  "data": {
    "results": [
      {
        "input": "[email protected]",
        "is_valid": true,
        "email": "[email protected]",
        "local_part": "hello",
        "domain": "example.com",
        "is_disposable": false,
        "has_mx_records": true,
        "is_role_account": false,
        "reason": null
      }
    ],
    "summary": {
      "total": 3,
      "valid": 3,
      "invalid": 0,
      "disposable": 1,
      "role_accounts": 1,
      "domains_with_mx": 3,
      "duplicates": 0
    }
  },
  "error": null,
  "meta": { "request_id": "req_example" }
}

Note that is_valid, is_disposable, and is_role_account are independent axes. A role account can be perfectly valid. A disposable address is usually syntactically fine. Treating any single flag as “delete this row” is the most common mistake.

What each flag means, and what to do about it

is_valid: false — the address failed syntax or structural checks. reason says why. Delete these. There is no upside; they will hard-bounce.

has_mx_records: false — the domain publishes no mail exchanger, so it cannot receive mail at all. Delete these too. This catches typo domains (gmial.com), lapsed company domains, and addresses at domains that were never mail-capable. In an old list this is usually the single largest removal bucket.

is_disposable: true — a throwaway provider like Mailinator or 10MinuteMail. The address may work right now; it will not work in a week, and the person behind it explicitly signalled they did not want ongoing contact. Remove from marketing sends. Whether to block them at signup is a separate product decision — see validating emails before signup for that side of it.

is_role_account: truesupport@, info@, sales@, admin@. These go to a shared inbox rather than a person. Do not delete; segment. They are frequently your most valuable B2B contacts, and they are also disproportionately likely to hit a spam complaint button on a marketing blast. Transactional and account email: fine. Newsletter: exclude by default.

Checking the domains, not just the addresses

POST /v1/email/domain/health takes up to 500 domains and reports MX, SPF, and DMARC readiness. Run it over the distinct domains in your list — a 50,000-row list typically has a few thousand at most, so this is cheap.

Two uses.

Spot dead domains in bulk. Faster than inferring from per-address results, and it groups the failures so you can see that 400 addresses all belong to one acquired company.

Check your own sending domain. If you are about to send to a cleaned list, your own SPF and DMARC records matter as much as the recipients’. Missing or misconfigured records will hurt delivery regardless of list quality. MX, SPF, DKIM, and DMARC explained covers what each record does, and the DNS Analyzer shows the raw records if you need to debug one.

What the API deliberately does not tell you

Being clear about the limits, because vendors in this space are often not.

SMTP mailbox probing is not included. The API does not connect to the recipient’s mail server to ask whether a specific mailbox exists. That means it cannot distinguish [email protected] (real person, still employed) from [email protected] (valid syntax, valid domain, mailbox deleted last March).

That is a meaningful limit and worth understanding rather than glossing over. SMTP probing is also less reliable than it sounds — catch-all domains accept everything, major providers rate-limit or deliberately mislead probes, and aggressive probing can get your IP blocked. The checks here are the ones that are reliable.

Validation improves list quality; it does not guarantee delivery. A clean list can still land in spam if your sending reputation, authentication, or content is the problem.

What a healthy result looks like

Rough expectations from summary, so you can tell “this list is fine” from “this list is a liability”:

  • Recent, single-opt-in list: 95%+ valid, under 2% no-MX, disposables under 1%.
  • Two to three years old, unmaintained: 80–90% valid is normal. 10–20% attrition over that period is expected, not alarming.
  • Purchased or scraped list: frequently 50–70% valid, with high disposable and role-account counts.

That last case is worth stating plainly: if a list comes back at 60% valid, the cleanup is not the problem. Nobody on it asked to hear from you, and sending to it will damage the sending reputation you use for the lists that did.

After the pass

Keep the removals. Write rejected addresses to a suppression list rather than deleting them. If the same address re-enters through a form import next quarter, you want to know it already failed.

Store the verification date on the record. “Verified 2026-06-01” lets you re-verify on a schedule instead of re-verifying everything every time.

Warm up if the list has been dormant. Sending 40,000 messages to a list that has not been contacted in two years looks exactly like a spam run, even if every address is valid. Ramp volume over days.

Fix the intake. The cheapest cleanup is the one you never have to run — put validation at the signup form and the list stays clean by default.