A real-time email verification API sits between a user interaction and several unreliable external systems. Syntax checks are local and fast; DNS can time out; receiving mail servers can delay, block, or conceal recipient status. Good API design makes these differences visible instead of pretending every request can produce a permanent yes-or-no answer within a fixed number of milliseconds.
The central design task is to separate immediate evidence from deferred evidence. A signup form needs a quick, understandable response. A CRM import may tolerate background processing and retries. Both need stable result semantics, privacy controls, and an audit trail that says what was checked and when.
Start with a result contract, not an endpoint
Define the evidence model before choosing routes or infrastructure. A practical result can contain the original address, a cautiously normalized address, syntax status, domain status, mail-routing status, mailbox status, catch-all status, disposable and role signals, checked time, reason codes, and a top-level decision recommendation.
The top-level recommendation should be derived from a documented policy. It should not erase the underlying fields. A consumer may accept an unknown mailbox for a low-risk newsletter while requiring confirmation for an account-recovery address. The same evidence can legitimately produce different actions.
Use explicit status families
Model at least four broad outcomes: favorable, unfavorable, risky, and unknown. Then attach specific reasons such as malformed syntax, no domain, no mail route, explicit recipient rejection, catch-all, disposable provider, role mailbox, temporary failure, timeout, policy block, or unsupported address form.
Unknown must be a first-class result. It is not an internal error and it is not invalid. Remote systems intentionally hide mailbox status, and temporary network conditions are normal. APIs that force uncertainty into a Boolean encourage clients to block legitimate users or accept risky records without context.
Separate synchronous and asynchronous checks
Fast path
Run deterministic local checks immediately: input shape, maximum length, supported syntax, normalization, and perhaps cached domain intelligence. DNS checks can often fit within the request budget if bounded by strict timeouts and caching. Return correction hints only when confidence is high and require the user to approve any change.
Deferred path
Mailbox conversations, retries, provider enrichment, and deeper risk analysis can continue in a job. The synchronous response can include a stable verification ID and a status such as pending or partial. Clients can poll a resource, receive a signed webhook, or query later during CRM processing.
Do not hold an interactive request open indefinitely because a remote mail exchanger is slow. A predictable timeout with partial evidence is better than an unreliable user experience.
Design endpoints around resources
A simple interface might create a verification resource with a POST request and retrieve it with a GET request. Bulk jobs can be separate resources with item-level results. Keep idempotency keys so clients can safely retry a request without duplicating work. Return the same resource when the same key and payload are reused within the documented window.
Use conventional HTTP status codes for the API transaction, not for the mailbox verdict. A syntactically malformed JSON request can produce a client error, while a well-formed request whose email address is undeliverable should still return a successful API response containing an unfavorable verification result.
Make latency observable
Expose total processing time and, where useful, stage timing for syntax, DNS, and mailbox checks. Operators need to know whether latency comes from their own service, a resolver, a particular receiving domain, or a queue. Use percentiles by stage and receiving domain rather than relying only on an average.
Set a budget for each layer. Cancel work after the budget is exhausted and return a reason such as dns_timeout or smtp_timeout. Avoid hidden background activity that continues probing after the client has been told the request is complete.
Cache the right things
Domain existence, MX routing, disposable-provider classification, and catch-all behavior can be cached, but each has a different useful lifetime. Respect DNS time-to-live values where applicable and maintain separate freshness rules for derived intelligence. Cache negative results conservatively because a new domain or corrected DNS record can become valid quickly.
Mailbox-level results are more sensitive and more likely to change. Cache them only for a purpose-appropriate period, protect the keys, and avoid exposing one customer’s query history to another. A recent reply or successful delivery may be stronger evidence than a cached probe.
Handle internationalized addresses deliberately
Internationalized domain names can be represented in Unicode and ASCII-compatible forms. SMTPUTF8 extends the mail transport environment for internationalized mailbox names. The SMTPUTF8 specification describes negotiation for this capability. An API must declare whether it accepts Unicode local parts, how it normalizes domains, and what happens when the receiving server lacks support.
Do not return “invalid” merely because an implementation supports only ASCII. Use a reason such as unsupported_internationalized_local_part and allow the client to choose a fallback. Preserve the original display form while using a consistent comparison representation where standards permit it.
Protect the address and derived data
Collect the email address alone unless additional fields are necessary for the stated operation. Encrypt transport and storage, restrict operator access, and define retention for raw addresses, request logs, diagnostics, and webhooks. Avoid including the full address in URLs, metrics labels, error trackers, or ordinary application logs.
Hashing an address does not automatically make it anonymous because the input space is guessable. Use keyed hashing for controlled matching, rotate keys under a documented process, and still treat the result as sensitive. Provide deletion and retention controls that work across primary storage, caches, queues, and backups according to the service’s commitments.
Build safe SMTP behavior
Mailbox checks should use identified infrastructure, valid forward and reverse DNS, controlled rates, and monitored abuse contacts. Limit concurrency per receiving domain, use backoff, cache domain-wide behavior, and stop after bounded attempts. Never advise clients to rotate networks to defeat blocks.
Record the protocol stage and broad response class, but sanitize remote text before exposing it. Responses can include internal hostnames, recipient details, or untrusted strings. Separate a policy block against the checking network from an explicit nonexistent-recipient response.
Use structured errors and reason codes
API errors should contain a stable machine code, concise human message, request identifier, and field location when relevant. Verification outcomes need a separate namespace of result reasons. Do not make clients parse English text. Version reason semantics and announce deprecations.
A retryable flag can help, but it must be tied to a reason and a recommended delay. Syntax failure is not retryable without changed input. A DNS timeout may be retryable. A provider that consistently hides status may remain unknown no matter how many times the client asks.
Plan for rate limits and abuse
Verification endpoints can be misused for recipient enumeration. Require authentication, set per-tenant and global limits, monitor domain concentration, and restrict bulk access according to customer risk. Do not reveal more recipient detail than the customer’s legitimate workflow requires.
Return standard rate-limit metadata without exposing internal defenses. Offer bulk jobs for legitimate high-volume processing rather than encouraging thousands of interactive requests. Audit administrative overrides and changes to limits.
Make webhooks trustworthy
Sign webhook payloads with a documented scheme, include an event ID and timestamp, and let consumers reject stale or replayed events. Retry delivery with backoff and make events idempotent. Provide a way to retrieve the canonical resource so a consumer can recover after a missed webhook.
Avoid placing the raw address in verbose webhook logs. Let tenants choose the event types they need, rotate signing secrets, and keep a delivery history that operators can inspect without revealing data across tenants.
Version policy separately from evidence
The evidence schema and the recommendation policy evolve at different rates. Store a policy version with each result. A domain can have the same underlying signals while a customer changes how disposable or catch-all addresses are treated. Recomputing a recommendation should not require repeating every external check if the evidence is still fresh.
When fields change, prefer additive versioning and explicit deprecation. Keep old values meaningful for historical analysis. A client should never discover that “valid” silently acquired a new definition.
Test failure, not just success
Create fixtures for malformed input, Unicode addresses, NXDOMAIN, no MX, slow DNS, multiple MX hosts, temporary SMTP response, explicit rejection, catch-all, blocked probe, connection reset, TLS negotiation issue, and webhook replay. Inject latency and dependency failures. Confirm that cancellation stops work and that partial results remain internally consistent.
Contract tests should verify that every reason maps to documented fields and that clients can ignore unknown additive fields. Load tests should include concentration on one receiving domain because per-domain limits often matter more than total request rate.
A compact response example
{
"id": "ev_01J...",
"status": "unknown",
"reason": "smtp_policy_block",
"address": {
"normalized": "[email protected]",
"syntax": "valid"
},
"domain": {
"status": "mail_capable",
"catch_all": "unknown"
},
"mailbox": {
"status": "unknown",
"retryable": false
},
"checked_at": "2026-08-19T18:42:00Z",
"policy_version": "2026-08"
}
The example communicates uncertainty without turning it into failure. A real implementation should document every enum, redaction rule, and retention period.
Conclusion
A dependable verification API is explicit about what can be known quickly and what depends on external systems. It separates transport errors from mailbox outcomes, supports partial and asynchronous results, protects addresses, and treats unknown as legitimate. Stable reason codes, bounded retries, careful caching, and versioned policy give clients enough information to make their own proportionate decisions.