Email verification
Email verification
The verify_emails module validates the deliverability of a list of email addresses before a campaign is sent. It checks syntax, resolves MX records, opens a probe against the receiving server, and flags addresses that are disposable or shaped like a role mailbox.
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 and alias domains are detected, including modern providers (Apple, DuckDuckGo, ProtonMail) that lookalike tools incorrectly reject. Catch-all domains are surfaced as cases where deliverability cannot be guaranteed.
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, the verdict, and a passthrough of identifying fields from the input.
| 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, 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 / 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 — deliverable |
valid_catch_all |
Accepted, but the domain is catch-all so acceptance is not a guarantee |
invalid |
The server rejected the address — do not send |
greylisted |
Temporarily deferred by the server; can be retried later |
unknown |
Could not be determined (timeout, blocked probe…) |
filtered |
Rejected before the SMTP step — bad syntax, disposable, role/suspect, or no MX (see category) |
skipped |
Big free provider (Gmail, Outlook…) that rejects probes; treat as deliverable |
The signal columns (status, category) are declared in the module registry under produces. They are the columns campaigns and downstream filter / 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 deliverable rows (e.g. status in valid, valid_catch_all). Enrichment nodes cannot run after verify_emails: the verification report does not carry the full POI columns they need.
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 throttled to roughly five verifications per second so the outbound IP does not get flagged by mail providers. 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 — confirms the receiving server actually accepted a test delivery, beyond the SMTP handshake.
- filter — keep only deliverable rows by filtering on
status(valid/valid_catch_all), then hand the result to a campaign or export.