Email verification
Email verification
The verify_emails module checks whether the addresses of a list can receive mail. It checks syntax, spots obvious typos, resolves MX records, opens a probe against the receiving server, and filters out disposable domains and suspect prefixes (test@, noreply@, abuse@ and similar).
Reduced functionality during the alpha, for technical cost reasons. Syntax, typo, disposable-domain, suspect-prefix and MX checks run for every address. The mailbox probe (SMTP) runs from our server with retries, catch-all and greylisting detection. Large consumer mail providers (Gmail, Outlook, Yahoo, Orange, Free, SFR, La Poste, iCloud and others) refuse such probes: their addresses come back as
skipped. Some receiving servers do not answer our server clearly, which givesunknownrows. If the probe port is closed on our side, the job switches to MX-only mode and marks the rowsunknownwith themx_onlycategory.
It runs after an enrichment step that produced emails, or against an imported list. The job is parallel: it does not consume a slot on the multi-proxy queue and can run alongside extraction jobs.
Disposable domains are detected, while privacy alias services (Apple, DuckDuckGo, Proton) are recognised as real mailboxes and never rejected as disposable. Catch-all domains are surfaced as cases where acceptance is not a guarantee.
Inputs
| Field | Required | Source |
|---|---|---|
email |
yes | from a previous emails job, or imported |
nom |
no | passed through to the output |
telephone |
no | passed through |
site_web |
no | passed through |
needs: ['email']. Items missing an @ are discarded at job creation. Duplicates are deduplicated on the lowercased address. The job accepts between 1 and 10000 items per call.
source_job_id is optional and points to the emails job whose output is being verified. The UI surfaces this picker as from_jobs_of_type: 'emails'.
Outputs
The job produces a verification report. Each row carries the verified address and the verdict; the input columns are kept.
| Column | Type | Meaning |
|---|---|---|
email |
string | Lowercased address as submitted |
status |
string | Deliverability verdict (see values below) |
category |
string | How the verdict was reached: smtp, syntax, disposable, suspect, big_provider, no_mx, mx_only, error |
reason |
string | Human-readable explanation of the verdict |
suggested_fix |
string | Suggested correction for an obvious typo, when detected (optional) |
smtp_code |
string | Raw SMTP response code from the probe, when an SMTP check ran |
catch_all |
string | yes, no or unknown: whether the domain accepts any address |
nom |
string | Passed through from input |
telephone |
string | Passed through from input |
site_web |
string | Passed through from input |
status takes one of:
| Value | Meaning |
|---|---|
valid |
The receiving server accepted the address |
valid_catch_all |
Accepted, but the domain is catch-all so acceptance is not a guarantee |
invalid |
The server rejected the address |
greylisted |
Temporarily deferred by the server; can be retried later |
unknown |
Could not be determined (timeout, blocked probe, MX-only mode) |
filtered |
Rejected before the SMTP step: bad syntax, disposable, suspect prefix or no MX (see category) |
skipped |
Large consumer mail provider (Gmail, Outlook, Orange, and so on) that refuses probes: the mailbox cannot be checked |
The signal columns (status, category) are declared in the module registry under produces. They are the columns downstream filter and sort nodes branch on.
Lifecycle
Standard job states. See Jobs lifecycle. Progress is reported per email (progress_unit: 'emails').
Pipeline
The module declares the following contract:
| Property | Value |
|---|---|
needs |
email |
produces |
status, category |
category |
verify |
pipelinable |
true |
supports_veille |
false |
In a pipeline, verify_emails accepts any upstream node that emits email (typically emails or import). Its verified output can be wired into a downstream filter or sort node: filter on status to keep only accepted rows (for example status in valid, valid_catch_all). The pipeline builder does not accept enrichment nodes after verify_emails.
Endpoints
Create a job
POST /api/jobs/verify-emails
Body:
{
"items": [
{ "email": "alex@example.com", "nom": "Alex", "site_web": "https://example.com" },
{ "email": "contact@example.org" }
],
"source_job_id": "f3c2…"
}
source_job_id is optional. items is required, with at least one record and at most 10000.
Response: JobPublic (the standard job envelope).
Read a job
The standard job endpoints apply:
GET /api/jobs/{id}
GET /api/jobs/{id}/events # SSE stream
GET /api/jobs/{id}/download # CSV
For per-job and per-account caps, see Limits. Throughput is capped at 15 verifications per second across 4 workers, with up to 3 SMTP attempts per address, so the outbound IP does not get flagged by mail providers. In MX-only mode (no SMTP probe) the cap rises to 80 per second. Concurrency: the job runs on the parallel worker pool, so a verify_emails job never blocks a scraping job and is never blocked by one.
Errors
| Code | Condition |
|---|---|
| 400 | Aucun email valide dans la liste: items empty, every entry missing @, or all entries are duplicates |
| 400 | Quota dépassé: estimated cost exceeds MAX_EF_PER_JOB |
| 401 | Caller is not an active user |
| 422 | Payload does not match VerifyEmailsJobCreateRequest |
Per-item failures (timeout on MX, refused SMTP probe, etc.) do not fail the job. The row is written with status = unknown and the job advances.
What's next
- delivery_check: checks where a test message from your own domain lands (inbox, tabs, spam) in a set of test mailboxes.
- filter: keep only accepted rows by filtering on
status(valid,valid_catch_all), then export the result.