Verify
Check whether an email address is deliverable before you send to it — syntax, MX records, disposable/role-based detection, and a live SMTP probe, with results cached so repeat checks are free. Verify single addresses in real time, or submit a list as a bulk job for asynchronous processing.
Verify endpoints are scoped to your user account, not a project — they don't
require the X-Project-Id header. Single checks and bulk batches are recorded
against your currently active workspace automatically.
The verification object
{
"email": "jane@example.com",
"status": "valid",
"confidence": 92,
"reason": "smtp_accepted",
"checks": {
"syntax": true,
"mx": ["aspmx.l.google.com"],
"smtpConnectable": true,
"smtpAccepted": true,
"catchAll": false,
"disposable": false,
"roleBased": false,
"freeProvider": false,
"provider": "google",
"didYouMean": null,
"smtpCode": 250,
"smtpMessage": "2.1.5 OK"
},
"durationMs": 812,
"checkedAt": "2026-07-02T10:14:03.000Z",
"cached": false,
"retryable": false
}| Field | Description |
|---|---|
status | One of valid, invalid, risky, catch_all, unknown. |
confidence | 0–100 score. Used with status to decide whether the address is safe to send to. |
reason | Short machine-readable reason code (e.g. smtp_accepted, mailbox_not_found, disposable_domain). |
checks | The individual signals behind the status/confidence — syntax validity, MX records, SMTP connect/accept results, catch-all detection, disposable/role-based/free-provider flags, and the raw SMTP response. |
cached | true if this result was served from cache rather than a fresh SMTP probe. Cached results don't count against your quota. |
retryable | true if the check failed for a transient reason (e.g. SMTP timeout) and re-running with force: true may produce a different answer. |
Verify a single email
/verifyRuns (or reuses a cached) verification for one address. Cached results are returned instantly and are not billable; a fresh check consumes one verification from your plan's quota.
| Field | Type | Required | Description |
|---|---|---|---|
email | string | Yes | The address to verify. |
force | boolean | No | Skip the cache and run a fresh SMTP probe even if a cached result exists. |
curl https://app.warmerly.com/api/v1/verify \
-X POST \
-H "X-Api-Key: wmv_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{ "email": "jane@example.com" }'{
"result": {
"email": "jane@example.com",
"status": "valid",
"confidence": 92,
"reason": "smtp_accepted",
"checks": {
"syntax": true,
"mx": ["aspmx.l.google.com"],
"smtpConnectable": true,
"smtpAccepted": true,
"catchAll": false,
"disposable": false,
"roleBased": false,
"freeProvider": false,
"provider": "google",
"didYouMean": null,
"smtpCode": 250,
"smtpMessage": "2.1.5 OK"
},
"durationMs": 812,
"checkedAt": "2026-07-02T10:14:03.000Z",
"cached": false,
"retryable": false
},
"checkId": "b2b7c9a0-1e3f-4a2b-9c1d-4e8f0a2b3c4d"
}Every call — cached or not — is saved to your history as
checkId. Errors: 400 bad_request for an invalid body, 402 payment_required if you're
out of verification quota.
{ "error": { "code": "payment_required", "message": "quota_exceeded", "details": { "used": 5000, "limit": 5000 } } }Bulk verification
Submit a list of addresses as an asynchronous batch, then poll for results. Quota for the full batch size is checked up front, before any items are processed.
Submit a batch
/verify/bulk| Field | Type | Required | Description |
|---|---|---|---|
name | string | No | Optional label for the batch. |
emails | string[] | No | Array of addresses. |
text | string | No | A pasted blob of addresses separated by whitespace, commas, or semicolons. |
Provide emails, text, or both — they're merged and de-duplicated. A batch may
contain at most 50,000 addresses.
curl https://app.warmerly.com/api/v1/verify/bulk \
-X POST \
-H "X-Api-Key: wmv_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{ "name": "Q3 leads", "emails": ["jane@example.com", "john@example.com"] }'{
"batch": {
"id": "6f2c6c8e-9b1d-4e3a-9c7f-2d1e8b4a6c9e",
"total": 2,
"status": "processing"
}
}201 Created on success. Errors: 400 bad_request (no_valid_emails or
batch_too_large_max_50000), 402 payment_required
(quota_exceeded, with requested/used/limit/remaining in details).
Get batch status and results
/verify/bulk/{id}Returns the batch's progress and, for items that have been processed, their verification results.
curl https://app.warmerly.com/api/v1/verify/bulk/6f2c6c8e-9b1d-4e3a-9c7f-2d1e8b4a6c9e \
-H "X-Api-Key: wmv_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"{
"batch": {
"id": "6f2c6c8e-9b1d-4e3a-9c7f-2d1e8b4a6c9e",
"name": "Q3 leads",
"status": "processing",
"total": 2,
"processed": 1,
"createdAt": "2026-07-02T10:00:00.000Z",
"completedAt": null
},
"items": [
{
"email": "jane@example.com",
"recommendation": "Safe to send",
"status": "valid",
"confidence": 92,
"reason": "smtp_accepted"
},
{
"email": "john@example.com",
"recommendation": "Pending",
"status": null,
"confidence": null,
"reason": null
}
]
}batch.status is processing or completed. Each item's recommendation is
Safe to send, Send with caution, Do not send, Unknown, or Pending (not
yet processed) — a human-readable summary derived from status/confidence/checks.
404 not_found if the batch doesn't exist or belongs to another user.
Pass ?format=csv to download the results as a CSV file
(email,recommendation,status,confidence,reason) instead of JSON:
curl "https://app.warmerly.com/api/v1/verify/bulk/6f2c6c8e-9b1d-4e3a-9c7f-2d1e8b4a6c9e?format=csv" \
-H "X-Api-Key: wmv_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
-o results.csvVerification history
/verify/historyReturns your recent single verifications (from POST /verify), most recent
first, cursor-paginated.
| Query param | Type | Required | Description |
|---|---|---|---|
cursor | ISO 8601 timestamp | No | Fetch results older than this timestamp. Use the previous response's nextCursor. |
limit | integer | No | Page size, 1–100. Default 25. |
curl "https://app.warmerly.com/api/v1/verify/history?limit=25" \
-H "X-Api-Key: wmv_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"{
"checks": [
{
"id": "b2b7c9a0-1e3f-4a2b-9c1d-4e8f0a2b3c4d",
"email": "jane@example.com",
"status": "valid",
"confidence": 92,
"reason": "smtp_accepted",
"checks": { "...": "..." },
"confirmedStatus": null,
"sendTestStatus": null,
"createdAt": "2026-07-02T10:14:03.000Z"
}
],
"nextCursor": "2026-07-02T09:58:11.000Z"
}confirmedStatus and sendTestStatus reflect a follow-up bounce-probe test send
kicked off from the dashboard to confirm an uncertain SMTP result (valid or
invalid once resolved); both are null if no test was run. nextCursor is
null once you've reached the end of history.
Send test (deliverability-grade check)
Some addresses cannot be settled by SMTP probing alone — a catch-all domain accepts everything, so "accepted" proves nothing. A send test resolves those the only way that actually works: it sends one real message from one of your own warmup mailboxes and watches for a bounce.
POST /verify/send-test
GET /verify/send-test/{id}| Field | Type | Required | Description |
|---|---|---|---|
email | string | Yes | Address to test. |
checkId | string (UUID) | No | Links the result back to an earlier verification, so the original check is refined rather than duplicated. |
{
"test": {
"id": "9f1c2d3e-4a5b-4c6d-8e7f-0a1b2c3d4e5f",
"email": "sam@example.com",
"status": "awaiting_bounce",
"fromAccount": "jane@yourdomain.com",
"bounceCode": null,
"bounceMessage": null,
"failureReason": null,
"deadlineAt": "2026-09-12T10:15:00.000Z"
}
}GET /verify/send-test/{id} is the poll: each call does a throttled IMAP check for the
bounce and returns the current status — awaiting_bounce, valid, invalid or failed.
The watch window is about five minutes (deadlineAt). A bounce inside it makes the test
invalid. A reply from the address, or reaching deadlineAt with no bounce, makes it
valid. So valid here means "no bounce came back in the window", not proof the mailbox is
read. A poll made after deadlineAt is what settles the test, so keep polling until the
status is no longer awaiting_bounce.
Notes worth knowing before you wire this up:
- It requires a connected sending mailbox. With none, the test comes back immediately as
failedwithfailureReason: "no_account"rather than erroring. - It sends real email from your mailbox, so it affects that mailbox's reputation like any other send. Use it to settle a handful of ambiguous addresses, not as a bulk strategy.
- It is not billed as a separate verification — it refines a check that was already counted.
Usage / quota
/verify/usageReturns your current verification usage against your plan's limit for the billing period. Checking usage does not itself consume quota.
curl https://app.warmerly.com/api/v1/verify/usage \
-H "X-Api-Key: wmv_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"{
"usage": {
"used": 128,
"limit": 500,
"remaining": 372,
"plan": "growth"
}
}Summarize with AI

