Real-time email verification follows a short path:
request → verification → response
Large jobs are different.
Once your application needs to verify tens of thousands of contacts, keeping one HTTP request open until every address has been processed is the wrong model.
A better pattern is:
submit batch → store batch ID → process in background → detect completion → retrieve results
Bouncer’s bulk email verification flow supports programmatic batch processing, status polling and webhook callbacks.
This guide focuses on the architecture around that workflow.

When should verification become asynchronous?
Use real-time verification when the application needs a result close to data entry:
- – signup
- – checkout
- – lead form
- – invitation
- – profile update
Use asynchronous batch verification for jobs such as:
- – CRM cleanup
- – nightly database hygiene
- – campaign preparation
- – uploaded contact lists
- – migrations
- – scheduled re-verification
Bouncer’s Email Verification API supports real-time and batch workflows.
The architectural difference is simple: a user-facing check belongs in a request-response flow. A 100,000-address job belongs in background processing.
The basic batch architecture
A typical system looks like this:
Your application
↓
Create internal verification job
↓
Submit Bouncer batch
↓
Store provider batch ID
↓
Bouncer processes addresses
↓
Webhook callback
↓
Queue result processing
↓
Retrieve and store results
↓
CRM / database / suppression rules
Add a reconciliation worker beside it:
Reconciliation worker
↓
Find stale processing jobs
↓
Check batch status
↓
Recover missed callbacks
That second path matters. A webhook should speed up completion detection, not become the only way your application can ever finish a job.
Step 1: create your own job first
Before calling an external API, create an internal job record.
verification_jobs
id
provider_batch_id
status
submitted_at
completed_at
callback_received_at
retry_count
source
record_count
results_processed_at
A simple state machine could use:
created
submitted
processing
completed
failed
results_processed
The names are less important than persistence.
Your application must be able to restart without forgetting what Bouncer is still processing.
Step 2: submit the batch and save the Bouncer batch ID
Bouncer’s Batch API returns an identifier that lets you track the external job.
Store it next to your own ID:
internal_job_id: 84291
provider_batch_id: abc-123
Do not use one as a replacement for the other.
Your ID belongs to your application.
The provider ID identifies the Bouncer batch.
That distinction becomes useful during retries, reconciliation and support investigations.
How large should a batch be?
The current Bouncer API page recommends a smaller operating range of 1,000–10,000 addresses per batch even though larger jobs are supported.
That is a useful engineering hint.
One giant job creates one large failure domain.
Smaller jobs are easier to:
- – retry
- – monitor
- – reconcile
- – process independently
- – re-run when a subset fails
Do not design around the maximum number just because it exists.
For a broader comparison of file, real-time and batch workflows, see Bouncer’s guide to a bulk validator with API and CSV upload support.
Step 3: add a webhook callback
A webhook lets the verification system tell your application when a background job reaches the next stage.
Your handler should be small.
A good pattern is:
Bouncer
↓
callback endpoint
↓
identify batch
↓
record callback
↓
enqueue result-processing task
↓
return 2xx
Do not turn the callback request itself into a long-running import pipeline.
Queue the heavier work after you have recorded the event.
Bouncer’s API best-practices guide is useful for the wider verification-layer design.
Webhook vs polling
Both patterns are useful.
Webhook
Good for:
- – low-latency completion signals
- – fewer status checks
- – event-driven pipelines
Tradeoffs:
- – needs an inbound endpoint
- – callback processing must be safe
- – missed delivery needs recovery
Polling
Good for:
- – simpler control flow
- – environments that cannot accept inbound callbacks
- – reconciliation
Tradeoffs:
- – more API traffic
- – completion detection waits for the next poll
- – aggressive intervals waste requests
A strong production pattern is:
webhook for the normal path + low-frequency polling for recovery.
If the callback arrives, mark the job and stop unnecessary polling.
If it never arrives, the reconciliation worker can still detect that the batch completed.
Make webhook handling idempotent
Assume the same completion event can be processed more than once.
Bad flow:
callback
→ blindly insert results
→ send notification
If the callback repeats, you may insert data twice and notify twice.
Better flow:
callback
→ find provider_batch_id
→ check current state
→ record callback
→ enqueue only if processing is still needed
→ return success
Useful database protections include:
UNIQUE(provider_batch_id)
and a guard such as:
results_processed_at IS NULL
Idempotency should also cover your result importer.
If a worker crashes halfway through and runs again, it should continue safely rather than duplicate every completed action.
Keep transport failures separate from verification statuses
These are different classes of event.
Transport or API failure
Examples:
- – timeout
- – network error
- – authentication error
- – 5xx response
- – rate limit
Verification outcome
Examples:
- – deliverable
- – risky
- – undeliverable
- – unknown
Do not put all of these under one failed label.
An unknown verification status is a result from the verification process. A network timeout is a failure to complete the request.
That difference matters for retries.
The Bouncer FAQ describes the current verification status and reason terminology.
Build a bounded retry policy
Retry temporary technical failures.
Do not retry every result.
A simple policy can look like this:
attempt 1 → wait 2s
attempt 2 → wait 5s
attempt 3 → wait 15s
attempt 4 → move to dead-letter/manual review
For larger systems, add jitter so many failed jobs do not retry at the same moment.
Keep separate rules for:
- – network errors
- – 5xx responses
- – rate limits
- – authentication failures
- – invalid payloads
An undeliverable email is not a retryable infrastructure error.
Avoid retry storms
Suppose many batch submissions fail together.
Without backoff:
100 jobs
× several immediate retries
× multiple workers
You can create a traffic spike when the provider or network is already under stress.
Use:
- – exponential backoff
- – jitter
- – provider-level concurrency limits
- – retry caps
- – dead-letter handling
- – alerts
Your queue should control the pace.
Design for a callback that never arrives
Webhook delivery can fail.
Your application can be unavailable.
A configuration can be wrong.
That should not leave a verification job stuck forever.
Run a reconciliation job periodically:
find jobs where:
status = processing
AND submitted_at < threshold
Then check their remote status.
If the Bouncer batch is complete, move the local job forward.
Polling is now a recovery mechanism rather than the primary driver.
Design for application restarts
A restart should be boring.
After the application starts again:
- 1. load pending jobs
- 2. resume reconciliation
- 3. process queued callbacks
- 4. continue incomplete result imports
Do not depend on one in-memory process surviving from batch submission to completion.
Persist state.
Store useful result data
Avoid reducing the batch output to:
valid=true
Keep the information your system needs, for example:
email
status
reason
verified_at
batch_id
You may also keep selected domain or account flags.
Bouncer’s main email verification page shows why deliverable, risky, undeliverable and unknown should remain separate.
Decide what each result does
A basic post-processing policy can be:
| Result | Example action |
|---|---|
| Deliverable | Keep |
| Undeliverable | Suppress |
| Risky | Segment or review |
| Unknown | Recheck or apply a separate policy |
Do not delete every risky address automatically.
Your sending model matters. A warm opt-in list and cold outbound list can have different risk tolerance.
The bulk email verification guide goes deeper into what to do with each category after a batch finishes.
Separate verification from business actions
Keep two layers.
Verification layer
Responsible for:
- – creating jobs
- – submitting batches
- – storing provider IDs
- – receiving callbacks
- – checking status
- – retrieving results
Business-action layer
Responsible for:
- – suppressing addresses
- – updating CRM contacts
- – segmenting risky records
- – preparing campaign lists
- – notifying users
This keeps the verification pipeline reusable.
The same job system can support CRM hygiene, pre-campaign cleaning and customer-uploaded lists without hard-coding campaign rules into the Bouncer client.
What about Batch Sync?
Not every bulk workflow needs a fully asynchronous job architecture.
Bouncer’s email verification buyer's guide also discusses a Batch Sync option for workloads that need batch processing but prefer a synchronous-style integration model.
Choose based on the job:
- – real-time for one immediate decision
- – Batch Sync for bounded batch work where the request model still fits
- – asynchronous Batch for larger background jobs with lifecycle tracking
Where MCP fits
Bouncer also exposes bulk verification through its Email Verification MCP Server.
That workflow lets a compatible assistant create a batch, check its status and retrieve results.
It is useful for AI-assisted operations, but it does not replace the REST Batch API architecture described here when you are building a custom backend pipeline.
Production checklist
Before launch, check that:
- – every submitted batch has an internal job ID
- – provider batch IDs are persisted
- – callbacks are idempotent
- – callback handlers return quickly
- – heavy result processing runs outside the callback request
- – transient failures use bounded retries
- – retry delays use backoff and jitter
- – stale jobs are reconciled
- – large workloads are split into manageable batches
- – verification statuses stay distinct
- – rate limits are monitored
- – secrets stay out of logs
- – failed jobs can be replayed safely
- – completed jobs cannot be processed twice
FAQ
Does Bouncer support asynchronous batch email verification?
Yes. Bouncer supports batch workflows where applications submit larger datasets, track the batch and retrieve results after processing.
Should I use webhooks or polling?
Use webhooks for the normal completion signal and polling as a recovery path when possible. That keeps API traffic lower without making callback delivery a single point of failure.
What should happen if the webhook arrives twice?
Webhook processing should be idempotent. Check the stored batch state before queueing result processing again.
Should unknown email results be retried?
Treat unknown as a verification outcome, not a transport error. Whether and when to re-verify depends on your policy and the reason behind the result.
How large should my batches be?
Use the current Bouncer API documentation as the source of truth. Bouncer currently recommends a working batch range of 1,000–10,000 even though larger batches are supported.
When should I use Batch instead of real-time verification?
Use Batch when verification becomes background data processing: CRM cleanup, imports, scheduled hygiene or campaign preparation.
Final thought
Batch email verification is not just real-time verification with a longer list.
It is a background-job problem.
Persist state. Track both internal and provider IDs. Treat callbacks as repeatable events. Keep a polling fallback. Separate transport failures from verification results.
That architecture gives you a workflow that can recover from missed callbacks, restarts and temporary API failures without turning every batch into a manual repair job.

