Rust Email Validation

Complete guide to email validation in Rust

What You'll Learn

  • Integrate VerifyForge API using reqwest
  • Create type-safe validation functions
  • Handle errors with Result types
  • Implement async validation
  • Best practices for production

Prerequisites

  • Rust 1.70+ installed
  • Basic knowledge of Rust
  • VerifyForge API key (get free key)

Installation

# Cargo.toml
[dependencies]
reqwest = { version = "0.11", features = ["json"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
tokio = { version = "1", features = ["full"] }

Quick Start

use reqwest::Client;
use serde::Deserialize;

#[derive(Deserialize)]
struct ValidationData {
    #[serde(rename = "isValid")]
    is_valid: bool,
    reachability: String,
}

#[derive(Deserialize)]
struct ValidationResponse {
    data: ValidationData,
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = Client::new();
    let api_key = "your_api_key";
    let email = "user@example.com";

    let response = client
        .get(&format!("https://verifyforge.com/api/validate?email={}", email))
        .header("X-API-Key", api_key)
        .send()
        .await?
        .json::<ValidationResponse>()
        .await?;

    println!("Valid: {}", response.data.is_valid);
    Ok(())
}

Email Validator Module

// src/email_validator.rs
use reqwest::{Client, Error};
use serde::{Deserialize, Serialize};

#[derive(Debug, Deserialize)]
pub struct ValidationResult {
    pub email: String,
    #[serde(rename = "isValid")]
    pub is_valid: bool,
    pub reachability: String,
    pub disposable: bool,
    #[serde(rename = "roleAccount")]
    pub role_account: bool,
    #[serde(rename = "freeProvider")]
    pub free_provider: bool,
    pub suggestion: Option<String>,
}

#[derive(Debug, Deserialize)]
pub struct ValidationResponse {
    pub success: bool,
    pub data: ValidationResult,
}

#[derive(Debug, Serialize)]
struct BulkRequest {
    emails: Vec<String>,
}

pub struct EmailValidator {
    client: Client,
    api_key: String,
    base_url: String,
}

impl EmailValidator {
    pub fn new(api_key: String) -> Self {
        Self {
            client: Client::new(),
            api_key,
            base_url: "https://verifyforge.com".to_string(),
        }
    }

    pub async fn validate(&self, email: &str) -> Result<ValidationResult, Error> {
        let url = format!("{}/api/validate?email={}", self.base_url, email);

        let response = self
            .client
            .get(&url)
            .header("X-API-Key", &self.api_key)
            .send()
            .await?
            .json::<ValidationResponse>()
            .await?;

        Ok(response.data)
    }

    pub async fn validate_bulk(&self, emails: Vec<String>) -> Result<Vec<ValidationResult>, Error> {
        let url = format!("{}/api/validate/bulk", self.base_url);
        let payload = BulkRequest { emails };

        let response = self
            .client
            .post(&url)
            .header("X-API-Key", &self.api_key)
            .header("Content-Type", "application/json")
            .json(&payload)
            .send()
            .await?
            .json::<serde_json::Value>()
            .await?;

        let results = response["data"]["results"]
            .as_array()
            .unwrap()
            .iter()
            .map(|v| serde_json::from_value(v.clone()).unwrap())
            .collect();

        Ok(results)
    }

    pub async fn is_valid(&self, email: &str) -> bool {
        match self.validate(email).await {
            Ok(result) => result.is_valid,
            Err(_) => false,
        }
    }
}

Actix Web Example

use actix_web::{post, web, App, HttpResponse, HttpServer, Responder};
use serde::{Deserialize, Serialize};
use std::sync::Arc;

mod email_validator;
use email_validator::EmailValidator;

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

#[derive(Serialize)]
struct ApiResponse {
    success: bool,
    data: Option<serde_json::Value>,
    error: Option<String>,
}

#[post("/api/validate")]
async fn validate(
    req: web::Json<ValidateRequest>,
    validator: web::Data<Arc<EmailValidator>>,
) -> impl Responder {
    match validator.validate(&req.email).await {
        Ok(result) => HttpResponse::Ok().json(ApiResponse {
            success: true,
            data: Some(serde_json::to_value(result).unwrap()),
            error: None,
        }),
        Err(_) => HttpResponse::InternalServerError().json(ApiResponse {
            success: false,
            data: None,
            error: Some("Validation failed".to_string()),
        }),
    }
}

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    let api_key = std::env::var("VERIFYFORGE_API_KEY").expect("API key not set");
    let validator = Arc::new(EmailValidator::new(api_key));

    HttpServer::new(move || {
        App::new()
            .app_data(web::Data::new(validator.clone()))
            .service(validate)
    })
    .bind(("127.0.0.1", 8080))?
    .run()
    .await
}

Concurrent Validation

use futures::future::join_all;

pub async fn validate_many(
    validator: &EmailValidator,
    emails: Vec<String>,
) -> Vec<(String, bool)> {
    let futures = emails.iter().map(|email| {
        let email = email.clone();
        async move {
            let is_valid = validator.is_valid(&email).await;
            (email, is_valid)
        }
    });

    join_all(futures).await
}

Next Steps