Skip to content
guide

Validate an email address without sending a single message

| 7 min read
Email inbox with verification badges
Photo by Stephen Phillips on Unsplash

Your signup form collects an email address. You store it, send a welcome message, and the email bounces. The address was a typo. Or the domain doesn't exist. Or it was a throwaway @mailinator.com address that'll be dead in ten minutes. You now have a junk record in your database, a dinged sender reputation, and a user who'll never come back.

The traditional fix is sending a verification email: "Click this link to confirm your address." It works, but it adds friction. Conversion drops. And it doesn't catch the typo until after you've already sent a message to a bad address.

You can validate an email address before sending anything. Three API calls check syntax, MX records, and disposable domains. The whole process takes under 200ms and catches the problems that cause 90% of email delivery failures.

Three layers of email validation

Each layer catches a different class of bad email address. Use them together for a complete email verification pipeline, or pick the ones you need.

  • Syntax validation. Does the address follow a valid format? Does the domain have a known typo? The /v1/email/validate endpoint checks RFC 5322 compliance, detects role-based addresses (admin@, support@), and suggests corrections for common domain misspellings like gmial.com or hotmal.com.
  • MX record verification. Can the domain receive email at all? The /v1/email-mx/verify endpoint queries DNS for MX records and falls back to A records. If the domain has no mail server, the address is undeliverable; no amount of retrying will fix it.
  • Disposable email detection. Is the address from a throwaway service? The /v1/disposable-email/check endpoint checks against 700+ known disposable domains and pattern-matches variations. It also flags whether the domain is a free provider (Gmail, Outlook) so you can distinguish between personal and throwaway addresses.
Email validation checkmarks on a form
Photo by Brett Jordan on Unsplash

Check email syntax and catch typos

The /v1/email/validate endpoint does the most work in a single call. It validates format, looks up MX records, checks for disposable domains, and detects typos in popular provider domains.

curl -X POST https://api.botoi.com/v1/email/validate \
  -H "Content-Type: application/json" \
  -d '{"email": "jamie@gmial.com"}'

Response:

{
  "success": true,
  "data": {
    "email": "jamie@gmial.com",
    "is_valid": false,
    "format_valid": true,
    "mx_found": false,
    "mx_records": [],
    "is_disposable": false,
    "is_free_provider": false,
    "is_role_based": false,
    "domain": "gmial.com",
    "local_part": "jamie",
    "suggestion": "jamie@gmail.com"
  }
}

The email jamie@gmial.com has valid syntax, but the domain is a typo. The API returns is_valid: false because no MX records exist for gmial.com, and it suggests the corrected address jamie@gmail.com. Show this suggestion in your signup form and you save the user from a dead account.

Verify MX records to check if email is real

The /v1/email-mx/verify endpoint focuses on DNS. It returns the full list of MX records with priorities and a deliverable field that tells you whether the domain can receive mail.

curl -X POST https://api.botoi.com/v1/email-mx/verify \
  -H "Content-Type: application/json" \
  -d '{"email": "orders@acme-corp.com"}'

Response:

{
  "success": true,
  "data": {
    "email": "orders@acme-corp.com",
    "valid_format": true,
    "domain": "acme-corp.com",
    "has_mx": true,
    "mx_records": [
      { "priority": 1, "exchange": "aspmx.l.google.com" },
      { "priority": 5, "exchange": "alt1.aspmx.l.google.com" }
    ],
    "deliverable": "likely"
  }
}

The deliverable field returns one of three values: "likely" (MX records found), "unknown" (no MX but an A record exists, so the domain might accept mail), or "unlikely" (no MX and no A records). Use this for MX record validation when you need the raw DNS data.

Detect disposable and throwaway addresses

The /v1/disposable-email/check endpoint identifies temporary email services. These addresses work for minutes or hours, then stop accepting mail.

curl -X POST https://api.botoi.com/v1/disposable-email/check \
  -H "Content-Type: application/json" \
  -d '{"email": "signup-test@mailinator.com"}'

Response:

{
  "success": true,
  "data": {
    "email": "signup-test@mailinator.com",
    "domain": "mailinator.com",
    "is_disposable": true,
    "is_free": false,
    "provider": "Mailinator"
  }
}

The is_free field separates disposable domains from free providers. A Gmail address returns is_free: true and is_disposable: false. A Mailinator address returns is_disposable: true and is_free: false. This distinction matters; blocking all free providers would lock out most of the internet.

Build a validation pipeline in Node.js

Combine all three checks into a single function. This pipeline runs each check sequentially and short-circuits on the first failure.

async function validateEmail(email) {
  const headers = { "Content-Type": "application/json" };
  const body = JSON.stringify({ email });

  // Step 1: Syntax validation + typo detection
  const syntaxRes = await fetch("https://api.botoi.com/v1/email/validate", {
    method: "POST", headers, body,
  });
  const syntax = await syntaxRes.json();

  if (!syntax.data.format_valid) {
    return { valid: false, reason: "Invalid email format" };
  }

  if (syntax.data.suggestion) {
    return {
      valid: false,
      reason: `Did you mean ${syntax.data.suggestion}?`,
    };
  }

  // Step 2: MX record verification
  const mxRes = await fetch("https://api.botoi.com/v1/email-mx/verify", {
    method: "POST", headers, body,
  });
  const mx = await mxRes.json();

  if (!mx.data.has_mx) {
    return { valid: false, reason: "Domain cannot receive email" };
  }

  // Step 3: Disposable email detection
  const dispRes = await fetch("https://api.botoi.com/v1/disposable-email/check", {
    method: "POST", headers, body,
  });
  const disp = await dispRes.json();

  if (disp.data.is_disposable) {
    return {
      valid: false,
      reason: `Disposable email detected (${disp.data.provider})`,
    };
  }

  return { valid: true, reason: null };
}

// Usage
const result = await validateEmail("test@mailinator.com");
console.log(result);
// { valid: false, reason: "Disposable email detected (Mailinator)" }

The sequential approach is readable and exits early. If syntax fails, you skip the network calls for MX and disposable checks. If you prefer speed over early termination, run all three in parallel:

async function validateEmail(email) {
  const headers = { "Content-Type": "application/json" };
  const body = JSON.stringify({ email });

  // Run all three checks in parallel
  const [syntaxRes, mxRes, dispRes] = await Promise.all([
    fetch("https://api.botoi.com/v1/email/validate", {
      method: "POST", headers, body,
    }),
    fetch("https://api.botoi.com/v1/email-mx/verify", {
      method: "POST", headers, body,
    }),
    fetch("https://api.botoi.com/v1/disposable-email/check", {
      method: "POST", headers, body,
    }),
  ]);

  const [syntax, mx, disp] = await Promise.all([
    syntaxRes.json(), mxRes.json(), dispRes.json(),
  ]);

  const errors = [];

  if (!syntax.data.format_valid) errors.push("Invalid format");
  if (syntax.data.suggestion) errors.push(`Typo: try ${syntax.data.suggestion}`);
  if (!mx.data.has_mx) errors.push("No MX records");
  if (disp.data.is_disposable) errors.push("Disposable domain");

  return {
    valid: errors.length === 0,
    errors,
    details: { syntax: syntax.data, mx: mx.data, disposable: disp.data },
  };
}

The parallel version fires all three requests simultaneously using Promise.all. Total latency equals the slowest request instead of the sum. For most cases, all three complete in under 100ms.

Python validation pipeline

The same three-step validation in Python. This example uses the requests library for clarity.

import requests

API_BASE = "https://api.botoi.com/v1"

def validate_email(email: str) -> dict:
    headers = {"Content-Type": "application/json"}
    payload = {"email": email}

    # Step 1: Syntax + typo check
    syntax = requests.post(
        f"{API_BASE}/email/validate", json=payload, headers=headers
    ).json()

    if not syntax["data"]["format_valid"]:
        return {"valid": False, "reason": "Invalid email format"}

    suggestion = syntax["data"].get("suggestion")
    if suggestion:
        return {"valid": False, "reason": f"Did you mean {suggestion}?"}

    # Step 2: MX record check
    mx = requests.post(
        f"{API_BASE}/email-mx/verify", json=payload, headers=headers
    ).json()

    if not mx["data"]["has_mx"]:
        return {"valid": False, "reason": "Domain cannot receive email"}

    # Step 3: Disposable check
    disp = requests.post(
        f"{API_BASE}/disposable-email/check", json=payload, headers=headers
    ).json()

    if disp["data"]["is_disposable"]:
        provider = disp["data"]["provider"]
        return {"valid": False, "reason": f"Disposable email ({provider})"}

    return {"valid": True, "reason": None}

# Usage
result = validate_email("jamie@gmial.com")
print(result)
# {'valid': False, 'reason': 'Did you mean jamie@gmail.com?'}

When to validate email addresses

Different touchpoints call for different validation depths. Here's where each check fits.

Touchpoint Syntax MX check Disposable Why
Signup form Yes Yes Yes Catch typos, dead domains, and throwaway accounts before creating a record
Checkout / payment Yes Yes Optional Receipts need a deliverable address; disposable check depends on your refund policy
Contact form Yes Optional No Basic format check prevents obvious junk; MX check is a bonus
Newsletter subscribe Yes Yes Yes Protects sender reputation; high bounce rates get you flagged by ESPs
Lead import / CRM Yes Yes Yes Cleaning data before it enters your pipeline saves downstream costs
Internal tools Yes No No Syntax check catches data entry errors; internal users are trusted

Key points

  • Validate before storing. Every bad email in your database costs you: storage, failed sends, bounce processing, and sender reputation damage. Catch problems at the point of entry.
  • Typo detection saves users. The /v1/email/validate endpoint catches common domain misspellings and returns a suggestion. Surfacing "Did you mean jamie@gmail.com?" prevents a lost user.
  • MX checks are fast and free. DNS lookups resolve in milliseconds. There's no reason to skip them. An address with no MX records will never receive your email.
  • Disposable detection protects trial abuse. If you offer a free trial or freemium plan, disposable email detection stops the same person from creating unlimited accounts with throwaway addresses.
  • No API key needed to start. All three endpoints work anonymously at 5 requests per minute. Test the full pipeline without signing up. Add an Authorization: Bearer YOUR_KEY header when you need higher throughput.
  • Run checks in parallel for speed. The three endpoints are independent. Using Promise.all cuts total latency to the slowest single request instead of the sum of all three.

Full documentation for all three endpoints is in the interactive API docs. Each endpoint accepts a JSON body with an email field and returns results in under 100ms from Cloudflare's edge network.

Frequently asked questions

Can you validate an email address without sending an email?
Yes. You can check three things without sending anything: whether the address follows valid syntax (RFC 5322), whether the domain has MX records configured to receive mail, and whether the domain belongs to a known disposable email provider. These three checks catch the majority of bad addresses at signup time.
Is the botoi email validation API free?
Yes. Anonymous access is available at 5 requests per minute with IP-based rate limiting. No API key, no signup, no credit card required. For higher throughput, paid plans start at $9/mo and include access to all 150+ API endpoints.
What is the difference between MX record validation and SMTP verification?
MX record validation checks whether a domain has mail servers configured to accept email. It confirms the domain can receive mail in general. SMTP verification goes further by connecting to the mail server and asking if a specific mailbox exists. MX checks are fast (under 100ms) and free. SMTP checks are slower, can be blocked by mail servers, and may trigger rate limits or spam flags.
How accurate is disposable email detection?
The botoi disposable email endpoint checks against 700+ known disposable domains and uses pattern matching to catch variations (domains containing "temp", "trash", "throwaway", etc.). It also distinguishes between disposable domains and free providers like Gmail or Outlook. No detection list is 100% complete, but it catches all widely used throwaway services.
Should I validate emails on the client or the server?
Both. Run a basic format check on the client for instant feedback. Run MX and disposable checks on the server before writing to your database. Client-side validation improves the user experience; server-side validation protects your data. Never trust client-side checks alone because they can be bypassed.

Try this API

Email Validation API — interactive playground and code examples

More guide posts

Start building with botoi

150+ API endpoints for lookup, text processing, image generation, and developer utilities. Free tier, no credit card.