Bulk Email Verification API
Bulk verification submits a batch of addresses to POST /v1/email/bulk, which returns a job ID immediately (202 Accepted) and processes the batch asynchronously in the background. Results are available either by polling the job status endpoint or, more efficiently for larger batches, by registering a webhook that fires when the job completes.
Why bulk checking is asynchronous
A list of a few thousand addresses, each potentially involving DNS lookups and (in full mode) SMTP handshakes, takes far longer than any reasonable synchronous HTTP request timeout. Bulk submission returns immediately with a job reference, and the actual checking happens in the background — the same design any real batch-processing system needs, rather than holding a connection open or forcing you to chunk the list into many small synchronous calls yourself.
Submitting a batch
curl https://api.emailriskradar.com/v1/email/bulk \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{"emails": ["a@example.com", "b@example.com", "c@example.com"], "mode": "standard", "context": "crm"}'{ "job_id": "job_9f3a2e", "status": "queued", "total": 3 }Polling for status
const status = await fetch(`https://api.emailriskradar.com/v1/email/bulk/${jobId}`, {
headers: { Authorization: `Bearer ${process.env.EMAIL_RISK_API_KEY}` },
}).then((r) => r.json());
// { job_id, status: "queued" | "processing" | "completed" | "failed",
// total, processed, allow, review, block, errors }
if (status.status === "completed") {
const results = await fetch(`https://api.emailriskradar.com/v1/email/bulk/${jobId}/results?page=1`, {
headers: { Authorization: `Bearer ${process.env.EMAIL_RISK_API_KEY}` },
}).then((r) => r.json());
// { job_id, page, page_size, total, data: [{ row, email, status, decision, risk_score, risk_level, error }] }
}Polling works fine for smaller batches or interactive tools, but for larger batches — or any workflow where you don't want to keep a process alive just to check status — a webhook is the better fit.
Getting notified via webhook instead
Register a webhook endpoint once, and it's called automatically when a bulk job finishes — either bulk.completed or bulk.failed — with a payload summarizing the outcome. Each delivery includes an X-Webhook-Signature header (an HMAC-SHA256 signature of the request body, signed with the secret returned when you created the endpoint) so you can verify the request genuinely came from this API before trusting it.
{ "event": "bulk.completed", "job_id": "job_9f3a2e", "total": 3, "allow": 2, "review": 1, "block": 0 }import crypto from "node:crypto";
function isValidSignature(rawBody: string, signature: string, secret: string): boolean {
const expected = crypto.createHmac("sha256", secret).update(rawBody).digest("hex");
return crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected));
}Who can use the bulk endpoint
Bulk access is either included on plans with bulk enabled, gated by the plan's monthly quota, or available as a one-time bulk pass for accounts that just need to clean a single list without committing to a recurring bulk-enabled plan — useful for a one-off purchased-list cleanup or a single CRM import rather than ongoing bulk traffic.
FAQ
Batch size limits and monthly quotas depend on your plan — check the batch response for any rejection details if a submission exceeds your available quota.
Yes, mode applies the same way to bulk as to single checks — full mode adds a live SMTP handshake per address, which meaningfully increases processing time for a large batch.
It's still counted in the results with its own row, typically classified as block with an error explaining why, rather than failing the entire batch.
No — a webhook delivery replaces the need to poll. Polling is still useful as a fallback if a webhook delivery is missed, or for simple scripts that don't want to run a listener.