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.
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 get | What you avoid | |
|---|---|---|
| Choice | Pick vendors based on price, accuracy, or existing relationships. | Lock-in to one provider. |
| Resilience | When one vendor 429s or hits a quota, the next active key picks up. | Manual failover. |
| Cost control | The fallback chain is cheapest-first — your bill stays low automatically. | Paying premium rates by default. |
Supported vendors
| Provider | List rate per verification | Notes |
|---|---|---|
millionverifier | $0.0049 | Bulk-friendly, prioritised first in the fallback chain. |
reoon | $0.005 | Real-time verification, fast. |
neverbounce | $0.008 | Higher accuracy, well-established. |
zerobounce | $0.008 | Premium accuracy, deepest sub-status set. |
hunter | $0.0098 | Has 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
| Parameter | Type | Required | Description |
|---|---|---|---|
provider | string | Yes | One of reoon, zerobounce, hunter, neverbounce, millionverifier. |
api_key | string | Yes | The vendor's API key. 8–512 chars. |
label | string | No | Free-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
| Status | Reason |
|---|---|
| 400 | Unknown provider, or vendor rejected the key (auth / quota / network) — detail carries the upstream error. |
| 500 | Unexpected 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 Field | Description |
|---|---|
label | Rename. |
is_active | Toggle whether the engine uses this key. |
api_key | Provide to rotate the key. Omit to keep the existing one. |
revalidate | Set 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
| Field | Description |
|---|---|
status | Canonical CronDB label: valid, invalid, risky, or unknown. |
deliverability | Canonical mail outcome: deliverable, undeliverable, risky, or unknown. |
is_disposable / is_role / is_catch_all | True/false where the vendor reports it; null when unknown. |
confidence | Optional 0.0-1.0 score for vendors that supply one (Hunter, MillionVerifier). |
raw_status | Vendor-native status string — keep for audit/debugging. |
cost_estimate_usd | List-rate cost of this verification call. |
_cache_hit | true 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:
- Regex + disposable check (existing).
- Active enrollment + suppression check (existing).
- BYOK verification — call
verify_email_with_fallback. Cache hit short-circuits to the cached result. - If
status=invalidordeliverability=undeliverable→ contact is added toskipped_emailsand not enrolled. riskyandunknownresults still enroll — the verification still lands inemail_verification_logso 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 (
spamtrapfrom ZeroBounce,accept_allfrom Hunter, etc.) is preserved inraw_statusfor 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.