Skip to main content

Email Verifiers (BYOK)

CronDB integrates with five email-verification vendors out of the box. Connect one or more of your own keys; the engine picks the cheapest active provider for each verification, falls through on transient errors, and caches results for 7 days per email. No CronDB-managed credits — you pay your vendor directly.

tip

Verification is opt-in. Without any active verifier keys, sequence enrollment uses regex + disposable-domain checks only (the existing default). Connect a key to gate enrollment on real deliverability.

Why multi-vendor?

What you getWhat you avoid
ChoicePick vendors based on price, accuracy, or existing relationships.Lock-in to one provider.
ResilienceWhen one vendor 429s or hits a quota, the next active key picks up.Manual failover.
Cost controlThe fallback chain is cheapest-first — your bill stays low automatically.Paying premium rates by default.

Supported vendors

ProviderList rate per verificationNotes
millionverifier$0.0049Bulk-friendly, prioritised first in the fallback chain.
reoon$0.005Real-time verification, fast.
neverbounce$0.008Higher accuracy, well-established.
zerobounce$0.008Premium accuracy, deepest sub-status set.
hunter$0.0098Has free tier (50/mo) — good for trying things out.

Numbers are list rates from each vendor's public pricing page. Your contracted rate may be lower.


Add a Verifier Key

The endpoint validates the key by hitting the vendor's credits/account endpoint before saving. Invalid keys never persist.

Endpoint

POST /v1/email-verifiers/providers

Body Parameters

ParameterTypeRequiredDescription
providerstringYesOne of reoon, zerobounce, hunter, neverbounce, millionverifier.
api_keystringYesThe vendor's API key. 8–512 chars.
labelstringNoFree-text label for the dashboard.

Example Request

curl -X POST \
-H "Authorization: Bearer cdb_your_api_key_here" \
-H "Content-Type: application/json" \
-d '{
"provider": "millionverifier",
"api_key": "mv_xxxxxxxxxxxxxxxx",
"label": "main"
}' \
"https://api.crondb.com/v1/email-verifiers/providers"

Response

{
"key": {
"id": 4,
"provider": "millionverifier",
"label": "main",
"is_active": true,
"key_preview": "mv_x…xxxx",
"credits_remaining": 9842,
"last_validated_at": "2026-05-05T08:14:02Z",
"created_at": "2026-05-05T08:14:02Z",
"updated_at": "2026-05-05T08:14:02Z"
},
"validation": {
"provider": "millionverifier",
"credits_remaining": 9842
}
}

key_preview is first4…last4; the full key is never returned after creation.

Error responses

StatusReason
400Unknown provider, or vendor rejected the key (auth / quota / network) — detail carries the upstream error.
500Unexpected validation failure.

List Keys

GET /v1/email-verifiers/providers
{
"keys": [
{
"id": 4,
"provider": "millionverifier",
"label": "main",
"is_active": true,
"key_preview": "mv_x…xxxx",
"credits_remaining": 9842,
"last_validated_at": "2026-05-05T08:14:02Z",
"created_at": "2026-05-05T08:14:02Z",
"updated_at": "2026-05-05T08:14:02Z"
},
{
"id": 5,
"provider": "zerobounce",
"label": "premium fallback",
"is_active": true,
"key_preview": "zb1…ab9c",
"credits_remaining": 200,
"last_validated_at": "2026-05-05T08:14:30Z",
"created_at": "2026-05-05T08:14:30Z",
"updated_at": "2026-05-05T08:14:30Z"
}
]
}

Fallback chain

When you have multiple active keys, the engine tries them in this order: millionverifier → reoon → neverbounce → zerobounce → hunter. Cheapest first; the next is tried only when the previous returns a transient error (network, 429, 5xx). Set is_active=false to take a key out of rotation without deleting it.


Update / Rotate

PUT /v1/email-verifiers/providers/{key_id}
Body FieldDescription
labelRename.
is_activeToggle whether the engine uses this key.
api_keyProvide to rotate the key. Omit to keep the existing one.
revalidateSet true to run a credits health-check against the (possibly rotated) key.

When revalidate=true, the response also includes a validation block.


Delete

DELETE /v1/email-verifiers/providers/{key_id}

Hard delete — the encrypted row is removed. Outstanding sequence enrollments fall through to the next-priority key. If no other keys remain, verification gates open (regex + disposable check only, current default behavior).


Verify a Single Email

Manually verify an address through your BYOK chain. Useful for spot-checks, debugging, or as the backend of a "verify before sending" UI button.

Endpoint

POST /v1/email-verifiers/verify

Body

{ "email": "jordan@acme.com" }

Response

{
"email": "jordan@acme.com",
"provider": "millionverifier",
"status": "valid",
"deliverability": "deliverable",
"is_disposable": false,
"is_role": false,
"is_catch_all": false,
"confidence": 0.9,
"raw_status": "ok",
"latency_ms": 412,
"cost_estimate_usd": 0.0049,
"_cache_hit": false
}

Response Fields

FieldDescription
statusCanonical CronDB label: valid, invalid, risky, or unknown.
deliverabilityCanonical mail outcome: deliverable, undeliverable, risky, or unknown.
is_disposable / is_role / is_catch_allTrue/false where the vendor reports it; null when unknown.
confidenceOptional 0.0-1.0 score for vendors that supply one (Hunter, MillionVerifier).
raw_statusVendor-native status string — keep for audit/debugging.
cost_estimate_usdList-rate cost of this verification call.
_cache_hittrue if served from the 7-day Redis cache. Cache is keyed (user_id, email).

Returns 400 when no verifier keys are configured; 502 when every active vendor returned an error.


Bulk Verification

Verify many emails in parallel. Chunks of 10 concurrent vendor calls — keeps wall-clock low without crushing per-second rate limits. Cache hits are free.

Endpoint

POST /v1/email-verifiers/verify-bulk

Body

{ "emails": ["jordan@acme.com", "sam@globex.com", "..."] }

emails accepts 1-500 entries.

Response

{
"count": 2,
"results": [
{
"email": "jordan@acme.com",
"provider": "millionverifier",
"status": "valid",
"deliverability": "deliverable",
"...": "...",
"_cache_hit": false
},
{
"email": "sam@globex.com",
"error": "millionverifier: HTTP 429: Rate limit exceeded"
}
]
}

Successful entries carry the same shape as /verify. Failed entries carry an error string. Mix of successes and failures is the expected shape — vendor errors on individual emails don't fail the whole call.

For a 1000-email list, expect ~100s wall-clock when uncached (10 chunks × ~10s each); cached emails return ~instantly.


Behaviour in Sequence Enrollment

When POST /v1/sequences/{id}/enroll runs and the user has at least one active verifier key:

  1. Regex + disposable check (existing).
  2. Active enrollment + suppression check (existing).
  3. BYOK verification — call verify_email_with_fallback. Cache hit short-circuits to the cached result.
  4. If status=invalid or deliverability=undeliverable → contact is added to skipped_emails and not enrolled.
  5. risky and unknown results still enroll — the verification still lands in email_verification_log so the user can review later and decide whether to tighten the gate.

When no verifier keys are configured, step 3 is a no-op — existing behavior is preserved exactly. So adding verification is purely additive.

When all configured vendors fail (auth/network/quota), enrollment fails open — the contact gets enrolled. Better to enrol a contact and sort out the vendor problem than to silently drop a campaign because Hunter is having a bad afternoon.


Usage & Cost

GET /v1/email-verifiers/usage?days=30

days accepts 1-365, default 30.

{
"days": 30,
"since": "2026-04-05T08:14:02Z",
"total_calls": 1842,
"total_cost_usd": 9.0258,
"by_provider": [
{ "provider": "millionverifier", "calls": 1604, "cost_usd": 7.860 },
{ "provider": "zerobounce", "calls": 238, "cost_usd": 1.904 }
],
"by_action_type": [
{ "action_type": "sequence_enrollment", "calls": 1742, "cost_usd": 8.502 },
{ "action_type": "manual", "calls": 100, "cost_usd": 0.524 }
],
"by_deliverability": {
"deliverable": 1488,
"risky": 184,
"undeliverable": 142,
"unknown": 28
}
}

Sort by_action_type to identify what's burning credits. Sort by_provider to confirm the fallback chain is doing its job (cheap vendors should dominate).


Public Helper

GET /v1/email-verifiers/known-providers

No-auth helper for UI dropdowns — returns the list of supported vendors and their list-rate per-verification cost. Use for "Which vendor should I sign up for?" comparison UIs.


Notes

  • All keys encrypted at rest via the same Fernet/SECRET_KEY-derived path used for OAuth/SMTP/AI-provider tokens.
  • Verification cache TTL is 7 days, keyed on (user_id, email). Same email being re-enrolled within the window costs $0.
  • The cache is invalidated implicitly when you delete the verifier key — adding a fresh key triggers fresh verifications. There is no manual cache-flush endpoint.
  • Vendor latency varies: MillionVerifier and Reoon typically <500ms; Hunter and ZeroBounce 800-1500ms. Sequence enrollment at scale (1000-row CSV) adds non-trivial wall-clock — consider running enrollment in chunks or off-peak.
  • Sub-status from each vendor (spamtrap from ZeroBounce, accept_all from Hunter, etc.) is preserved in raw_status for vendor-specific reporting needs.

Next Steps

  • Sequences — Where verification gates take effect (enrollment loop).
  • AI Providers — The same BYOK shape for LLM keys.
  • Send Accounts — Connect mailboxes once verification is in place.