Key Takeaways

  • Verifying ten thousand addresses is a script. Verifying ten million is a distributed systems problem with queue design, rate control, and resumability at its core.
  • The architecture is an ingest queue feeding autoscaled worker nodes that call the API under a shared rate limit, writing results to a datastore keyed for idempotency.
  • Idempotency is non-negotiable at scale. Key results by email so retries and replays never double-process or double-charge.
  • Design for resumability from the start. A ten-million-address job will be interrupted; it must restart from where it stopped, not from zero.

There is a category difference between verifying a list and verifying a database. A marketing team cleaning fifty thousand contacts before a campaign runs a script and waits a few minutes. A data platform verifying ten million addresses across a customer base faces a genuine distributed systems problem: how to sustain throughput, respect rate limits, survive interruptions, and store results without double-processing. This guide covers the architecture patterns for email verification at scale, the kind of design that holds up when the input is measured in millions rather than thousands.

The naive approach (loop over the list, call the API, write the result) works until it does not. At a million addresses it is too slow. At ten million it falls over on the first network blip, having no way to resume. Scale requires architecture.

The Core Architecture

The pattern that scales is a queue-backed worker pool. It decouples ingestion from processing, allows horizontal scaling of workers, and survives failures at every stage. Five components make it work.

Pro Tip Batch addresses into the queue rather than enqueuing one per message. A hundred addresses per message cuts queue overhead by two orders of magnitude and lets each worker amortize its setup cost across a meaningful chunk of work.

Rate Strategy Across a Worker Fleet

The hardest part of scaling is rate control across multiple workers. Each worker individually respecting a rate limit does not prevent the fleet from collectively exceeding the API quota. Ten workers each sending 50 requests per second is 500 per second in aggregate, which will trigger throttling.

The solution is a shared rate limiter. A token bucket in Redis holds the global budget; every worker requests a token before each API call and blocks until one is available. The bucket refills at the sustainable rate, so the entire fleet collectively stays within the quota no matter how many workers are running. This decouples worker count from request rate, which is what makes autoscaling safe.

A shared token bucket lets you scale worker count without scaling request rate. Source: Distributed rate-limiting architecture pattern

When the API does return a rate-limit response, the worker should respect the backoff signal, return the batch to the queue, and let the shared limiter naturally throttle the fleet. Fighting the rate limit by retrying immediately makes throttling worse; backing off resolves it.

Idempotency and Deduplication

At scale, the same address will be submitted more than once. Lists overlap, retries replay batches, and failures cause reprocessing. Without idempotency, this wastes API quota and corrupts result counts. Every result must be keyed so that verifying the same address twice updates one record rather than creating two.

The pattern is an upsert keyed by the normalized email (lowercased, trimmed) or a hash of it. Before calling the API, the worker can check whether a recent result already exists in the store; if the address was verified within the cache window, it skips the call entirely. This deduplication often removes 10 to 30 percent of the API calls on overlapping lists, which is both a cost saving and a throughput gain.

Best Practice Store a checked_at timestamp with every result and treat results older than 30 to 90 days as stale. A verification from last year is not reliable; mailbox states change. The timestamp lets you re-verify selectively rather than re-running the entire job.

Designing for Resumability

A ten-million-address job runs for hours. Over that window, something will fail: a worker crashes, the network blips, a deploy restarts the fleet. If the job cannot resume from where it stopped, every failure means starting over, which at scale means a job that never finishes.

Resumability comes from the queue and the result store working together. Work that has been pulled but not acknowledged returns to the queue automatically after a visibility timeout. Work that completed is recorded in the result store. On restart, the system processes whatever remains in the queue, and the deduplication check skips anything already in the store. The job converges on completion regardless of how many times it is interrupted.

This is why the queue-backed design beats a single long-running script. The script holds all its state in memory and loses everything on failure. The distributed design holds state in durable infrastructure and treats any individual component failure as routine.

Result Storage and Downstream Use

The result store is not just a log; it is the asset the whole job produces. Design it for how results will be consumed. A typical schema stores the email, the status (passed, failed, unknown, transient), the sub_status, the boolean risk flags (isDisposable, isRoleAccount, isGibberish), and the checked_at timestamp.

Downstream systems query this store rather than re-verifying. The marketing platform pulls deliverable addresses for sends. The data team pulls verification status for segmentation. The fraud team pulls disposable and gibberish flags for signup scoring. One verification, many consumers, all reading from the same authoritative store. The email verification API documentation covers the full response schema for mapping these fields into your store.

For teams building this out, the integration patterns matter as much as the architecture. The email verification integrations hub has worker-side code for the major languages, and new accounts get 100 free email verification credits to validate the pipeline against a sample before committing to a full run. For volume pricing on multi-million-address jobs, the email verification pricing page covers the per-address economics at scale.

Frequently Asked Questions

How long does it take to verify a million addresses?

At a sustained 200 requests per second, a million addresses takes roughly 80 to 90 minutes of processing, less with deduplication on overlapping lists. Throughput scales with worker count up to the API rate limit, so the wall-clock time depends on the rate budget rather than the worker count.

Do I need a queue, or can I just use threads?

Threads work up to a few hundred thousand addresses on a single machine. Beyond that, a queue is necessary for resumability and horizontal scaling. The threshold is roughly where a single job exceeds what one machine can finish reliably in one run without risk of interruption.

How do I avoid hitting rate limits with many workers?

Use a shared rate limiter, typically a token bucket in Redis, that all workers draw from. This enforces a global request rate across the fleet regardless of how many workers run, which decouples worker count from request rate and makes autoscaling safe.

Should I re-verify the whole database every time?

No. Store a checked_at timestamp and re-verify selectively. Addresses verified within the last 30 to 90 days can be trusted; only re-verify stale records and newly added addresses. This turns a recurring ten-million-address job into a much smaller incremental one.