Rust email verification

Verify emails in Rust with Verifly

Add a live email verification step to any Rust service using reqwest and serde. Verifly is an API-first, pay-as-you-go service — send an address, deserialize a strongly typed JSON verdict, and drop bad mailboxes before they ever reach your mailer.

Verify one email in RustAPI
use serde::Deserialize;

#[derive(Deserialize)]
struct Verdict {
    result: String,
    is_valid: bool,
}

#[tokio::main]
async fn main() -> Result<(), reqwest::Error> {
    let client = reqwest::Client::new();
    let verdict: Verdict = client
        .get("https://verifly.email/api/v1/verify")
        .query(&[("email", "user@example.com")])
        .bearer_auth("vf_YOUR_KEY")
        .send()
        .await?
        .json()
        .await?;

    println!("{} {}", verdict.result, verdict.is_valid);
    Ok(())
}

Real-time SMTP mailbox checks

Single, batch, and async bulk verification

Disposable, role account, and catch-all detection

Pay-as-you-go credits with no subscription lock-in

Search fit

Built for Rust email verification

Use Verifly when you need a simple API, predictable pricing, and clean JSON results before emails hit your product, CRM, or campaign tool.

Validate signups inside an Axum or Actix handler
Clean an imported contact list in a Tokio task
Filter leads in a CLI built with clap before a mail send
Gate a background worker so it only mails deliverable addresses

Email verification in Rust

Rust makes it easy to write a fast, correct email service, but the language cannot tell you whether a mailbox on the far side of the internet is real. Crates that parse and validate address syntax, or a hand-rolled check, only confirm that user@dead-domain.com is well-formed. Whether that address accepts mail is a runtime fact that lives behind an SMTP handshake, and Verifly performs that handshake for you over one HTTPS request.

Because Verifly is a plain REST API, you reach for the crates you already have: reqwest for the async HTTP call and serde for zero-boilerplate deserialization into a typed struct. You send a GET to /api/v1/verify with bearer_auth, and .json() gives you back a Verdict with a result field of deliverable, undeliverable, or risky plus boolean flags for disposable, role, catch_all, and smtp. Because it is strongly typed, the compiler holds you to handling the shape you declared.

The pay-as-you-go model fits both a long-lived Tokio service and a short CLI. Grab a key, spend credits from $2 per 1,000 checks down to $0.60 per 1,000 at volume, and the same key works from an Axum server, a Lambda built on the Rust runtime, or a local binary. Self-register for 100 free credits with no card at the autonomous register endpoint.

Batch-verify a list of emails in Rust

When you hold more than a handful of addresses, do not loop the single endpoint — POST them together to /api/v1/verify/batch. It takes a JSON body with an emails array and returns a results array in the same order, which deserializes cleanly into a Vec of typed structs. Serialize your request with a serde_json::json! macro and let serde do the rest on the way back.

Batch verification with reqwest and serde
use serde::Deserialize;
use serde_json::json;

#[derive(Deserialize)]
struct Item {
    email: String,
    result: String,
    #[serde(default)]
    disposable: bool,
    #[serde(default)]
    role: bool,
}

#[derive(Deserialize)]
struct Batch {
    results: Vec<Item>,
}

#[tokio::main]
async fn main() -> Result<(), reqwest::Error> {
    let client = reqwest::Client::new();
    let emails = ["ada@example.com", "info@acme.com", "noreply@disposable.tld"];

    let batch: Batch = client
        .post("https://verifly.email/api/v1/verify/batch")
        .bearer_auth("vf_YOUR_KEY")
        .json(&json!({ "emails": emails }))
        .send()
        .await?
        .json()
        .await?;

    let keep: Vec<&str> = batch
        .results
        .iter()
        .filter(|r| r.result == "deliverable" && !r.disposable && !r.role)
        .map(|r| r.email.as_str())
        .collect();

    println!("Keeping {} of {} addresses", keep.len(), batch.results.len());
    Ok(())
}

Wire it into an Axum signup handler

The most valuable place to verify in Rust is the moment a user registers. Share a reqwest::Client through your Axum or Actix application state, call Verifly with a short timeout, and reject disposable addresses before writing the account. Match on the Result so a network error returns a fail-open path rather than a 500 that blocks a legitimate signup.

Reject bad emails in an Axum handler
use axum::{http::StatusCode, Json};
use serde::Deserialize;
use std::time::Duration;

#[derive(Deserialize)]
struct Signup {
    email: String,
}

#[derive(Deserialize)]
struct Verdict {
    result: String,
    disposable: bool,
}

async fn register(Json(body): Json<Signup>) -> Result<StatusCode, StatusCode> {
    let client = reqwest::Client::builder()
        .timeout(Duration::from_secs(8))
        .build()
        .unwrap();

    match client
        .get("https://verifly.email/api/v1/verify")
        .query(&[("email", &body.email)])
        .bearer_auth("vf_YOUR_KEY")
        .send()
        .await
    {
        Ok(resp) => {
            let v: Verdict = resp.json().await.map_err(|_| StatusCode::BAD_GATEWAY)?;
            if v.disposable || v.result == "undeliverable" {
                return Err(StatusCode::BAD_REQUEST);
            }
        }
        Err(_) => { /* fail open: never block signup on a network error */ }
    }

    // create_user(&body.email).await;
    Ok(StatusCode::CREATED)
}

FAQ

Frequently asked questions

Which HTTP crate should I use with Verifly in Rust?

reqwest is the natural choice — its async client with bearer_auth and a serde json() call is only a few lines. If you prefer, hyper or ureq work too, since Verifly is a plain REST API returning standard JSON.

How do I deserialize the response with serde?

Declare a struct with the fields you care about — result: String, is_valid: bool, and optional disposable/role/catch_all/smtp bools — derive Deserialize, and call .json::<Verdict>() on the response. serde maps the JSON onto your type for free.

How do I batch-verify a Vec of emails in Rust?

POST them to /api/v1/verify/batch with a json!({ "emails": emails }) body and deserialize the results array into a Vec of typed items, then filter with an iterator on the result field. Batching beats looping the single endpoint on both speed and cost.

What fields does the JSON response contain?

Each result has result (deliverable, undeliverable, or risky), is_valid, and boolean flags disposable, role, catch_all, and smtp. Use #[serde(default)] on the flags so a missing field deserializes safely to false.

Can I call Verifly from an async Axum or Actix service?

Yes. Build one reqwest::Client, store it in application state, and reuse it across handlers. The same Bearer key also works from a Rust Lambda or a CLI. For agents, Verifly exposes an MCP server at verifly.email/mcp.

What does Rust email verification cost?

Pricing is pay-as-you-go from $2 per 1,000 checks down to $0.60 per 1,000 at volume, with 100 free credits when you self-register. There is no subscription to justify for an occasional cleaning binary.