# Verimail — full reference for agents Generated from https://verimail.io and https://api.verimail.io/v3/codes. Verimail is email verification that shows its work. It asks each address's mail server whether the mailbox exists and returns what the server said: a conclusive verdict with its reason, or an honest `unknown` that costs nothing when the server won't say. Two ways to ask, one engine: a real-time API for signup forms, checkout, lead forms, newsletter capture and free-trial abuse checks, and file verification for lists. Verifying email since 2019; operated by Verimail Ltd, UK. The promise: we don't guess. Catch-all domains are labeled catch-all and never upgraded to "valid". The SMTP evidence is available on request. ## Request ``` POST https://api.verimail.io/v3/verify Authorization: Bearer Content-Type: application/x-www-form-urlencoded email=emma.wilson@acme-corp.com ``` Parameters (query string or form body): - `email` (required). A missing or blank value answers HTTP 200 with a syntax_error result, not a 400. - `key` — the API key, if not sent as a Bearer header. - `timeout` — milliseconds, 1000–45000, default 45000. Out of range answers HTTP 400. If it runs out before the mail server answers, the verdict is `unknown` with reason `timeout`, not billed. - `evidence` — `true` to attach the evidence object. GET works too: `GET https://api.verimail.io/v3/verify?email=...` (the address then appears in request logs for 30 days; POST is preferred). A free key: https://verimail.io/signup — 100 verifications a month, no card. Keys live at https://verimail.io/app/account. ## Response ```json { "email": "emma.wilson@acme-corp.com", "verdict": "deliverable", "reason": "mailbox_exists", "billed": true, "suggested_action": "accept", "did_you_mean": "", "flags": { "disposable": false, "role_account": false, "free_provider": false, "catch_all": false }, "provider": "google.com", "latency_ms": 412, "status": "success", "result": "deliverable", "deliverable": true, "user": "emma.wilson", "domain": "acme-corp.com", "code": 200 } ``` An unknown, with evidence requested: ```json { "email": "j.okafor@some-bank.ng", "verdict": "unknown", "reason": "greylisted", "billed": false, "suggested_action": "accept_and_confirm", "did_you_mean": "", "flags": { "disposable": false, "role_account": false, "free_provider": false, "catch_all": false }, "provider": "some-bank.ng", "latency_ms": 802, "evidence": { "smtp": [ { "step": "connect", "reply": "220 mx1.some-bank.ng ESMTP" }, { "step": "helo", "reply": "250 OK" }, { "step": "mail_from", "reply": "250 OK" }, { "step": "rcpt_to", "reply": "451 4.7.1 Greylisted, please try again later" } ], "mx_host": "mx1.some-bank.ng", "timings_ms": { "dns": 38, "smtp": 610, "total": 802 } }, "status": "success", "result": "softbounce", "deliverable": false, "user": "j.okafor", "domain": "some-bank.ng", "code": 404 } ``` Fields: - `verdict`: exactly one of `deliverable`, `undeliverable`, `risky`, `unknown`. - `reason`: snake_case name, one per code (table below). - `billed`: whether this verdict cost a credit. `unknown` and syntax errors never do. - `suggested_action`: `accept` (deliverable), `reject` (undeliverable, and any disposable address), `accept_and_confirm` (risky, unknown). - `did_you_mean`: a corrected address when the domain looks like a mistyped free provider, else empty. - `flags`: `disposable`, `role_account` (admin@, info@, …), `free_provider`, `catch_all` (the domain accepts any address). Facts, not verdicts. - `provider`: registrable domain of the domain's MX host (google.com, outlook.com, yahoodns.net). - `latency_ms`: end-to-end time. - `evidence` (on request): `smtp` = the server's replies in dialogue order with steps connect / helo / mail_from / rcpt_to_random (the catch-all probe) / rcpt_to (your address); `mx_host`; `tls`; `error` (timeout_read, refused, reset, tls — when the dialogue did not complete); `timings_ms`. - Original fields, unchanged since 2019: `status` (`success`, or `error` for syntax errors and unknowns), `email`, `result` (deliverable, catch_all, hardbounce, softbounce, inbox_full, undeliverable, disposable, unknown, syntax_error), `deliverable` (boolean; also true for catch_all and disposable), `user`, `domain`, `code` (numeric reason, 100–500). Errors (non-200): `{ "status": "error", "code": , "type": ..., "message": ... }`. 400 malformed parameter; 401 bad or missing key; 402 monthly limit reached and no credits; 403 key inactive; 500 our failure. ## Handling results ```js switch (data.verdict) { case 'deliverable': return accept(email); case 'undeliverable': return reject(email, data.reason); case 'risky': case 'unknown': return data.suggested_action === 'reject' ? reject(email, data.reason) : acceptAndConfirm(email); } ``` Signup form: reject only `undeliverable` (offer `did_you_mean` first); create the account on everything else and confirm by email when the verdict is not `deliverable`; reject `flags.disposable` on free trials. Checkout: reject only `undeliverable`; never block a purchase on a maybe. Lead form: accept everything, route by `verdict` and `flags`. ## Result codes (GET https://api.verimail.io/v3/codes) | Code | Verdict | Billed | Reason | Meaning | Action | |---|---|---|---|---|---| | 100 | undeliverable | no | empty_address | Empty address. | reject | | 101 | undeliverable | no | address_too_long | Longer than 255 characters. | reject | | 102 | undeliverable | no | missing_at_sign | No @ sign, or nothing before it. | reject | | 103 | undeliverable | no | local_part_too_long | The part before the @ is longer than 64 characters. | reject | | 104 | undeliverable | no | invalid_domain | The domain is not a valid host name. | reject | | 105 | undeliverable | no | invalid_local_part | The part before the @ contains characters no mail server accepts. | reject | | 200 | deliverable | yes | mailbox_exists | The mail server accepted the mailbox. | accept | | 204 | deliverable | yes | mailbox_accepted | Accepted with a non-standard 2xx reply. | accept | | 300 | risky | yes | provider_catch_all | A provider known to accept every address. | accept and confirm | | 301 | risky | yes | domain_catch_all_known | The domain was found to accept any address within the last hour; this mailbox was accepted too. | accept and confirm | | 302 | risky | yes | domain_catch_all | A random address and this mailbox were both accepted. | accept and confirm | | 400 | undeliverable | yes | no_mail_server | The domain has no mail server: no MX and no A record, or a null MX. | reject | | 404 | unknown | no | greylisted | The mail server temporarily refused the mailbox (4xx), typically greylisting. | accept and confirm | | 405 | undeliverable | yes | mailbox_rejected | The mail server rejected the mailbox permanently (5xx). | reject | | 406 | risky | yes | mailbox_full | The mailbox is over quota (552 or enhanced status x.2.2). | accept and confirm | | 407 | undeliverable | yes | dropped_at_rcpt | The server dropped the connection when the mailbox was named. | reject | | 408 | unknown | no | no_verdict | The dialogue ended after the greeting without a verdict. | accept and confirm | | 409 | undeliverable | yes | greeting_rejected | The server refused our greeting. | reject | | 410 | undeliverable | yes | no_smtp_service | No SMTP service: connection refused, unreachable, or a 5xx banner. | reject | | 411 | unknown | no | accepts_unverified | The server accepts without verifying (252). | accept and confirm | | 412 | unknown | no | relay_blocked | The server blocked our checking IP on policy (5.7.x); every relay we tried was refused. | accept and confirm | | 413 | unknown | no | server_busy | The server is shedding load (4xx greeting). | accept and confirm | | 414 | unknown | no | tarpit | The server stalled after accepting the sender. | accept and confirm | | 415 | unknown | no | no_greeting | No greeting for a transient reason: timeout, reset, or TLS failure. | accept and confirm | | 416 | unknown | no | relay_deferred | The server deferred our checking IP at EHLO or MAIL FROM (volume deferral, greylisting). | accept and confirm | | 417 | undeliverable | yes | mailbox_gone | The domain accepts any address, but rejected this mailbox specifically: it is gone. | reject | | 418 | unknown | no | dns_unavailable | DNS resolution was unavailable; try again. | accept and confirm | | 419 | unknown | no | timeout | The time you allowed ran out before the mail server answered. | accept and confirm | | 500 | unknown | no | no_relay_available | No verification relay could be reached: a failure on our side, not a fact about the address. | accept and confirm | | disposable | risky | yes | disposable_domain | A mailbox on a disposable-address provider. | reject | | file: greylisted | unknown | no | greylisted | The mail server asked us to come back and had not answered by the time the file was written. | accept and confirm | | file: not-found | unknown | no | not_verified | The file was stopped before this row was verified. | accept and confirm | | file: pending | unknown | no | pending | Not verified yet (a download taken while the file was still verifying). | accept and confirm | ## When you should not trust a verdict - A catch-all domain accepts a random address as readily as a real one: `risky`, reason `domain_catch_all` / `provider_catch_all`. We test the real address too, so `mailbox_full` and `mailbox_gone` still surface on such domains. We do not resolve catch-all; nobody honestly can. - A server that answers 252 (`accepts_unverified`) will try to deliver and decide later: `unknown`. - Policy blocks on our IPs (`relay_blocked`) and deferrals naming our host (`relay_deferred`) say nothing about the mailbox; we retry through other relays in the same request; if all are refused: `unknown`. Most common on Microsoft's consumer namespace (outlook.com, hotmail.com). - Greylisting (`greylisted`) is a first-contact stall. A file retries on the server's schedule; a single API call answers `unknown`. - `mailbox_exists` is what the server said now. Re-verify old addresses before relying on them. - The disposable list is rebuilt daily (~75,000 domains); a brand-new throwaway domain can pass. - `dns_unavailable`, `no_relay_available` and `timeout` are about us or about time, never about the address: `unknown`, unbilled, retry. ## Timing On the current API the median verification takes about 0.6 s; the slowest 5% take over 6 s (3,000 calls), because some mail servers stall on first contact. Use `timeout` when a person is waiting; 2000 ms is reasonable for a signup form. ## Pricing (https://verimail.io/pricing) Free tier 100 verifications a month, no card. No minimum purchase (1,000 credits for $4). Credits never expire. Unknowns and syntax errors are never billed. From $4 per 1,000 credits down to $0.30 per 1,000 at a million a month; plans from $15 a month. ## Files (https://verimail.io/) Upload a CSV of any size in the dashboard; it comes back with `verimail_verdict`, `verimail_reason`, `verimail_status`, `verimail_code`, `verimail_deliverable`, `verimail_did_you_mean` columns. Same rules: syntax errors, duplicates, unknown and deferred rows, rows still waiting on a greylisting server, and rows never reached are credited back. Files and results are deleted 30 days after upload or when deleted by the customer. ## Data Addresses sent to the API are not stored; they appear in request logs for 30 days. GDPR: Verimail Ltd, UK; data processing agreement on request (https://verimail.io/gdpr).