Published on June 15, 2026 • By Kaiju Team
Email verification performance is bound by the network, not by your CPU. Every address you check triggers DNS and MX lookups, an SMTP handshake to a remote mail server, and a round-trip across the public internet — work measured in tens or hundreds of milliseconds that no amount of local optimization can shortcut. This guide is for developers who need to verify emails at scale and want to push throughput up while keeping latency and error rates down: what actually limits speed, when to switch from single calls to async batch jobs, and how to consume the API without tripping 429s.
job_id → poll or job.completed webhook → CSV) avoids HTTP timeouts on large lists.429s.If you profile a single verification, almost all the wall-clock time is spent waiting on a network response, not running code. Real verification is a chain of remote round-trips, and each link adds latency you cannot compress locally. Understanding the chain tells you where throughput is won or lost.
| Bottleneck | Impact on speed | Mitigation |
|---|---|---|
| DNS & MX lookups | A fresh lookup per domain costs a network round-trip before any SMTP work begins. | Cache MX records by domain; group a list by domain so one lookup serves many addresses. |
| SMTP handshake | TCP connect + EHLO + STARTTLS + RCPT TO is several serial round-trips to the recipient server. | Reuse connections per MX where possible; parallelize across domains, not within one server. |
| Greylisting | A 4xx defer forces a delayed retry, adding minutes to that address. | Queue deferrals and retry with backoff instead of blocking the whole batch. |
| Provider rate limits | Gmail, Microsoft 365 and Yahoo throttle probes per source IP, capping per-domain throughput. | Spread load across an IP pool and pace per recipient domain (the provider does this for you). |
| Network round-trips | Latency stacks: client → API + API → recipient MX on every call. | Batch to amortize the client → API hop; let the API parallelize the MX hop internally. |
The headline insight: a single accurate verification is inherently latency-bound, so you make a list fast through concurrency and avoidance — checking many addresses in parallel and skipping the ones you don't need to check at all. The deeper mechanics of the handshake itself are covered in the SMTP validation deep dive.
The most common throughput mistake is calling the single-verify endpoint in a tight loop to clean a list. Each iteration pays the full client → API round-trip plus the verification chain, serially, and it burns your rate-limit budget one address at a time. The single endpoint is built for real-time checks — a signup form, a checkout, a CRM record on save — where you need one answer immediately.
For volume, the batch and async APIs let the provider parallelize across its IP pool internally, so you submit once and let the service do the fan-out:
| Job | Slow approach | Fast approach |
|---|---|---|
| Verify one signup live | — | Single verify (real-time, one answer now) |
| Clean a large list | Thousands of single calls in a loop | One async job submission, then poll or webhook |
| Ongoing CRM hygiene | One call per record on every save | Queue records and submit a batch on a schedule |
If you'd rather not write any integration code at all, the bulk email cleaner takes a CSV upload and runs the same async pipeline behind a UI. For programmatic control, reach for the async jobs API described next, and consult the API reference for exact request shapes.
A synchronous request that verifies tens of thousands of addresses will exceed any reasonable HTTP timeout long before it finishes — load balancers and clients typically cut connections after 30 to 60 seconds. The async jobs API solves this by decoupling submission from completion. You hand the service the whole list in one request, get back a job_id immediately, and retrieve results once the job finishes. The lifecycle is straightforward:
dedupe flag) in a single POST. The response returns a job_id right away — no waiting on the verification work.job.completed webhook and let the service push you the notification.A minimal submit-then-wait flow in pseudocode:
# 1. Submit the list as one async job (dedupe server-side)
job = POST /api/v1/jobs {
"emails": unique_emails,
"dedupe": true
}
job_id = job["job_id"]
# 2a. Preferred: receive a signed job.completed webhook
# and skip polling entirely.
# 2b. Fallback: poll politely until done
while True:
status = GET /api/v1/jobs/{job_id}
if status["state"] in ("completed", "failed"):
break
sleep(poll_interval) # e.g. 5-15s, not a tight loop
# 3. Pull the results CSV
results = GET /api/v1/jobs/{job_id}/results
Webhooks are the lower-latency, lower-traffic option: instead of dozens of status polls, you get one inbound notification the moment the job is done. KaijuVerifier signs every webhook with an HMAC signature so you can verify authenticity before trusting the payload — see webhooks and real-time validation for the verification pattern.
Real-world lists are full of repeats: the same address appears across imports, form fills, and CRM exports. Every duplicate you submit is a verification you pay for and wait on twice. De-duplication is the single cheapest throughput win available, because the fastest verification is the one you never run.
Normalize before you compare so near-duplicates collapse correctly:
Jane <jane@acme.com> and jane@acme.com match.KaijuVerifier's async jobs accept an optional dedupe flag so the service collapses duplicates server-side before verifying — handy when you can't easily dedupe upstream. It's still worth doing a normalize-and-dedupe pass client-side first, because that also shrinks the payload you upload and the result set you process.
An address that verified valid last week is almost certainly still valid today. Re-verifying unchanged records on every run wastes both throughput and quota. A result cache keyed on the normalized address lets you answer most repeat checks locally, in microseconds, instead of paying for another network round-trip.
The art is in the TTL. Too long and you serve stale verdicts on addresses that have since gone dead; too short and you re-verify needlessly. Reasonable defaults:
Make the cache lookup idempotent: a retried request for the same address should hit the cache, never double-charge quota, and never fire a second probe. Combined with dedupe, caching is what keeps your effective API traffic — and therefore your latency and cost — low on recurring jobs.
Rate limits are not a punishment; they protect the verification pool from overload and keep response times fast for everyone. The way to run at full speed is to stay just under the ceiling, read the limit off the response, and recover gracefully when you do hit it. Never retry blindly.
X-RateLimit-Remaining tells you how much budget is left in the window; slow down as it nears zero. On a 429, honor Retry-After — wait at least that many seconds before retrying.Retry-After is present, use exponential backoff with a random offset so many clients failing at once don't all retry in the same instant.400 or 401 is a client-side bug that will fail identically forever; surface it instead.A robust retry wrapper looks like this:
attempt = 0
while attempt < MAX_RETRIES: # cap at 3-5
res = call_api(payload)
if res.status == 200:
return res
if res.status == 429 and res.headers["Retry-After"]:
sleep(int(res.headers["Retry-After"]))
elif res.status in (429, 500, 502, 503):
backoff = min(MAX_BACKOFF, BASE * 2 ** attempt)
sleep(random(0, backoff)) # full jitter
else:
raise ApiError(res) # 4xx other than 429 -> don't retry
attempt += 1
raise RateLimitExceeded(payload) # never drop silently
Batch and async jobs sidestep most of this entirely, because the provider paces the internal probes for you and you make far fewer HTTP calls. For the full treatment of headers, token buckets, and backoff math, see email verification API rate-limiting best practices.
When you do need to drive single-verify calls in parallel — for example, verifying a burst of live signups — concurrency is your lever, but an unbounded one is a self-inflicted denial of service. The goal is enough in-flight requests to hide network latency, without exceeding your plan's rate ceiling.
Tune by watching X-RateLimit-Remaining and your 429 rate: if you never see a 429, you have headroom to raise concurrency; if you see them regularly, lower it or move the workload to async batch. For one-off real-time checks, the single validator is the right tool; for anything list-shaped, prefer batch.
It is tempting to "just open a socket to port 25" and verify addresses yourself. At small volume that works; at scale it is both slow and self-defeating. The performance ceiling of a DIY SMTP probe is set by factors entirely outside your code:
RCPT TO probes they accept from a single IP per hour. Run a large verification from one host and you hit the wall in minutes, after which every check is deferred or refused.This is exactly the work a managed API absorbs: the IP pool, per-domain pacing, greylisting retries, and catch-all detection all happen behind a single call, so your throughput is limited by your plan rather than by one IP's reputation. The full reasoning is in the SMTP validation deep dive, and you can verify a single address against the live pipeline with the free email checker.
Why is verifying a single email so much slower than I expect?
Because an accurate verification waits on remote servers: a DNS/MX lookup followed by a multi-step SMTP handshake to the recipient's mail server. That is network latency you cannot optimize away locally. You speed up a list by checking many addresses concurrently and by skipping duplicates and cached results, not by making any one check faster.
When should I switch from single calls to the async jobs API?
As soon as a job is large enough to risk an HTTP timeout or burn through your rate limit in a loop — typically anything beyond a few hundred addresses. Submit the list once, get a job_id, and either poll politely or wait for a job.completed webhook, then download the results CSV. It avoids timeouts and lets the provider parallelize internally.
Does de-duplication and caching really make a difference?
Yes, often a large one. Lists carry many repeats, and recurring jobs re-check addresses that haven't changed. Normalizing and de-duplicating before you submit, plus caching verdicts with a sensible TTL, can cut real API traffic substantially — which lowers latency, keeps you under the rate limit, and stretches your quota further.
How do I avoid 429 errors when verifying at high volume?
Read X-RateLimit-Remaining and slow down as it nears zero, honor Retry-After on a 429, and fall back to exponential backoff with jitter when no header is present. Better still, prefer the batch and async jobs API for volume so the provider paces the internal probes and you make far fewer HTTP calls. See the rate-limiting guide for details.
Real-time single verify, async batch jobs with optional dedupe, and HMAC-signed webhooks — with clear rate-limit headers on every call. Start free.
View the API docs