Skip to main content
← Back to blog

Bulk Email Verification API: How to Clean an Email List Safely

PushMail Team··3 min read

Bulk email verification is the process of classifying an existing list before you import it into a CRM, start a campaign, or migrate between email providers.

The goal is not to make every row green. The goal is to separate confirmed failures from acceptable addresses and uncertain cases without deleting legitimate subscribers.

When to verify an email list

Run a bulk check when:

  • A list has not been contacted for several months.
  • You are importing contacts from a legacy CRM.
  • Multiple forms or partners feed the same database.
  • A recent campaign produced an unusual number of hard bounces.
  • You are moving to a new sending domain or dedicated IP and want a clean first audience.

Verification does not turn purchased or scraped contacts into permission-based subscribers. Google's current email sender guidelines explicitly advise against purchasing addresses and recommend sending only to people who subscribed.

Submit up to 100 addresses per request

PushMail's validation endpoint accepts a JSON array:

curl -X POST https://pushmail.dev/api/v1/validate \
  -H "Authorization: Bearer pm_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "emails": [
      "alice@example.com",
      "bob@example.net",
      "test@mailinator.com"
    ]
  }'

Each result includes the normalized address, validity, individual checks, and—when available—status and confidence. The batch response also reports how many validations were free, how many were paid, and the total cost.

A production JavaScript batching pattern

For a large list, split it into groups of 100 and process a limited number concurrently. Unlimited concurrency creates avoidable rate-limit errors and makes retries harder.

const BATCH_SIZE = 100;

function chunks(items, size) {
  const output = [];
  for (let index = 0; index < items.length; index += size) {
    output.push(items.slice(index, index + size));
  }
  return output;
}

async function verifyBatch(emails) {
  const response = await fetch("https://pushmail.dev/api/v1/validate", {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.PUSHMAIL_API_KEY}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({ emails }),
  });

  if (response.status === 429) {
    throw new Error("RATE_LIMITED");
  }

  const body = await response.json();
  if (!response.ok) throw new Error(body.error || "Verification failed");
  return body.data.results;
}

export async function verifyList(emails) {
  const results = [];
  for (const batch of chunks(emails, BATCH_SIZE)) {
    results.push(...await verifyBatch(batch));
    await new Promise(resolve => setTimeout(resolve, 750));
  }
  return results;
}

For durable processing, store a job ID, current batch number, and results after every successful request. If the process stops, resume from the last completed batch instead of restarting the list.

Do not reduce results to valid and invalid

A useful cleaning export keeps at least four categories:

CategoryRecommended action
DeliverableKeep, assuming the contact has consented
UndeliverableSuppress; do not send
RiskyKeep separate and send cautiously based on the reason
UnknownRetry later or preserve without sending

Within risky, retain the underlying signal. A disposable address, role mailbox, and catch-all domain represent different product decisions.

For example, support@customer.com may be the correct recipient for a B2B ticketing product. Rejecting every role address would remove a legitimate customer. A disposable address may be acceptable for a privacy tool but inappropriate for a one-account-per-person promotion.

Preserve the original data

Never overwrite the only copy of an imported list. Produce a new export with columns such as:

email,verification_status,confidence,reason,checked_at

This gives you an audit trail and lets you change policy later without paying to re-verify every address.

Also deduplicate normalized addresses before submitting them. Lowercase the domain, trim whitespace, and preserve the original input in a separate column. Avoid aggressive rewriting of the local part because mailbox providers do not all treat case, dots, and aliases the same way.

Retry temporary failures

DNS timeouts, provider throttling, and SMTP 4xx responses are temporary conditions. They should not become permanent suppressions.

Use exponential backoff for 429 and server errors, cap the number of attempts, and place repeatedly failing rows in an unknown segment for later review. This is especially important when cleaning a list during a provider outage.

Clean before warming a new IP

The first traffic from a new dedicated IP helps mailbox providers form its reputation. Start with recently engaged, permission-based, verified recipients. Do not use an old unverified list as warm-up traffic.

Verification is only one part of deliverability. You still need SPF, DKIM, DMARC, valid reverse DNS, gradual volume increases, and complaint monitoring. Google recommends keeping spam rates below 0.1% and avoiding 0.3% or higher in Postmaster Tools.

Next steps

Read the email verification API guide for typed response handling and the explanation of verification without sending an email before defining your cleaning policy. The first 100 PushMail validations each month are free; create an account or review the complete email validation API documentation.