Prices increase in 00D : 00H : 00M Upgrade now Pay less later.

Blog / Technical Guide

Email Verification API Integration: MailValid vs ZeroBounce, NeverBounce (2026 Guide)

Email Verification API Integration: MailValid vs ZeroBounce, NeverBounce (2026 Guide)

The Problem: Your Signup Form Is Bleeding Money

Every fake email that slips through your signup form costs you three ways:

  1. Direct cost: You're paying your ESP (SendGrid, Mailgun, AWS SES) to send to addresses that bounce.
  2. Reputation cost: Bounce rates above 2% trigger spam filters at Gmail, Outlook, and Yahoo. Once your domain reputation drops, even valid subscribers stop seeing your emails.
  3. Data cost: Fake users pollute your analytics, inflate your MAU numbers, and make cohort analysis meaningless.

The solution is dead simple: verify every email before it enters your database. But most developers either skip verification entirely or implement a naive regex check that catches only obvious typos.

This guide shows you how to integrate MailValid's real, documented API in under 10 minutes — with production-ready code, caching, retries, and webhook handling.


MailValid Pricing

Plan Monthly Price Credits Per-Email Cost
Free $0 100 (one-time) —
Starter $15/mo 10,000 $0.0015
Growth $50/mo 50,000 $0.001
Scale $75/mo 100,000+ $0.00075

All plans include syntax, MX, SMTP-level, disposable, catch-all, and role-based checks in a single API call. Credits never expire.

Start with 100 free verifications →


Quick Start: Verify an Email

MailValid's API uses a single endpoint for all verification types. One call gives you syntax, MX, SMTP, disposable, catch-all, and role-based checks.

Python

import requests

API_KEY = "your_mailvalid_api_key"
EMAIL = "user@example.com"

response = requests.post(
    "https://mailvalid.io/api/v1/verify/single",
    headers={
        "X-API-Key": API_KEY,
        "Content-Type": "application/json"
    },
    json={"email": EMAIL},
    timeout=10
)

result = response.json()["result"]
print(f"Status: {result['status']}")             # valid, invalid, catch_all, unknown, do_not_mail
print(f"Confidence: {result['confidence_score']}") # 0-100
print(f"Disposable: {result['is_disposable']}")
print(f"Free provider: {result['is_free_provider']}")

Node.js

const response = await fetch('https://mailvalid.io/api/v1/verify/single', {
  method: 'POST',
  headers: {
    'X-API-Key': process.env.MAILVALID_API_KEY,
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({ email: 'user@example.com' })
});

const { result } = await response.json();
console.log(result.status);            // valid | invalid | catch_all | unknown | do_not_mail
console.log(result.confidence_score);  // 0-100

cURL

curl -X POST https://mailvalid.io/api/v1/verify/single \
  -H "X-API-Key: your_api_key" \
  -H "Content-Type: application/json" \
  -d '{"email": "user@example.com"}'

Documented response:

{
  "success": true,
  "credits_used": 1,
  "result": {
    "email": "user@example.com",
    "status": "valid",
    "is_valid": true,
    "syntax_valid": true,
    "domain": "example.com",
    "domain_valid": true,
    "has_mx": true,
    "mx_records": [{"priority": 10, "host": "mail.example.com"}],
    "smtp_checked": true,
    "smtp_response_code": 250,
    "is_disposable": false,
    "is_role_based": false,
    "is_catch_all": false,
    "is_free_provider": true,
    "confidence_score": 95,
    "status_reason": "mailbox_confirmed",
    "provider": "google",
    "verification_time_ms": 234,
    "cached": false
  }
}

Production Integration: Signup Form Pipeline

The quick-start examples above are fine for testing. In production, you need caching, retries, and graceful degradation.

Step 1: Pre-Validation (Client-Side)

Don't waste API calls on obviously invalid emails. Check syntax in the browser first.

function preValidate(email) {
  const pattern = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
  if (!pattern.test(email) || email.length > 254) {
    return { valid: false, reason: 'invalid_syntax' };
  }
  const [local] = email.split('@');
  if (local.length > 64) {
    return { valid: false, reason: 'local_part_too_long' };
  }
  return { valid: true };
}

Step 2: Server-Side Verification with Caching

Cache results to avoid re-verifying the same email repeatedly. Note MailValid's own API also caches recent results server-side and charges 0 credits for cached: true repeats.

import requests
import hashlib
import json
import redis

class EmailVerifier:
    def __init__(self, api_key: str, cache_ttl: int = 86400):
        self.api_key = api_key
        self.cache_ttl = cache_ttl
        self.cache = redis.Redis(host='localhost', port=6379, db=0, decode_responses=True)
        self.base_url = "https://mailvalid.io/api/v1"

    def _cache_key(self, email: str) -> str:
        return f"ev:{hashlib.sha256(email.lower().encode()).hexdigest()[:16]}"

    def verify(self, email: str, fresh: bool = False) -> dict:
        email = email.lower().strip()
        cache_key = self._cache_key(email)

        if not fresh:
            cached = self.cache.get(cache_key)
            if cached:
                return json.loads(cached)

        response = requests.post(
            f"{self.base_url}/verify/single",
            headers={
                "X-API-Key": self.api_key,
                "Content-Type": "application/json"
            },
            json={"email": email},
            timeout=10
        )

        if response.status_code == 429:
            raise Exception("Rate limit exceeded — implement exponential backoff")

        result = response.json()["result"]
        self.cache.setex(cache_key, self.cache_ttl, json.dumps(result))
        return result

    def should_accept(self, email: str) -> tuple[bool, str]:
        result = self.verify(email)

        if not result["is_valid"]:
            return False, result["status_reason"]
        if result["is_disposable"]:
            return False, "disposable_email"
        if result["confidence_score"] < 50:
            return False, "low_confidence"

        return True, "accepted"

Step 3: Retry Logic with Exponential Backoff

Network failures happen. Don't lose a signup because of a transient error.

from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry

def create_session(api_key: str):
    session = requests.Session()
    session.headers.update({"X-API-Key": api_key})

    retry = Retry(
        total=5,
        backoff_factor=1,
        status_forcelist=[429, 500, 502, 503, 504],
        allowed_methods=["POST"]
    )
    adapter = HTTPAdapter(max_retries=retry)
    session.mount("https://", adapter)
    return session

Step 4: Real-Time Form Validation (JavaScript)

class EmailVerifier {
  constructor(apiKey) {
    this.apiKey = apiKey;
    this.timer = null;
    this.cache = new Map();
  }

  async verify(email, onResult) {
    clearTimeout(this.timer);

    if (!this.preValidate(email)) {
      onResult({ status: 'invalid', reason: 'syntax_error' });
      return;
    }

    if (this.cache.has(email)) {
      onResult(this.cache.get(email));
      return;
    }

    this.timer = setTimeout(async () => {
      try {
        const res = await fetch('https://mailvalid.io/api/v1/verify/single', {
          method: 'POST',
          headers: {
            'X-API-Key': this.apiKey,
            'Content-Type': 'application/json'
          },
          body: JSON.stringify({ email })
        });
        const { result } = await res.json();
        this.cache.set(email, result);
        setTimeout(() => this.cache.delete(email), 5 * 60 * 1000);
        onResult(result);
      } catch (err) {
        onResult({ status: 'unknown', reason: 'api_error' });
      }
    }, 500);
  }

  preValidate(email) {
    return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email) && email.length <= 254;
  }
}

const verifier = new EmailVerifier('your_api_key');
document.getElementById('email').addEventListener('blur', (e) => {
  verifier.verify(e.target.value, (result) => {
    if (result.status === 'valid') {
      showSuccess('Valid email address');
    } else if (result.status === 'catch_all' || result.status === 'unknown') {
      showWarning('This email may not receive messages');
    } else {
      showError('Please enter a valid email address');
    }
  });
});

Bulk Verification: Clean a List in One Call

For list cleaning before campaigns, use the bulk endpoint (up to 10,000 emails per request) instead of verifying one by one.

import time
import requests

response = requests.post(
    "https://mailvalid.io/api/v1/verify/bulk",
    headers={"X-API-Key": api_key, "Content-Type": "application/json"},
    json={
        "emails": ["user1@example.com", "user2@test.com"],  # up to 10,000
        "webhook_url": "https://yourapp.com/webhooks/verification"
    }
)

job = response.json()
print(f"Job ID: {job['job_id']}, Status: {job['status']}")

while True:
    status = requests.get(
        f"https://mailvalid.io/api/v1/verify/bulk/{job['job_id']}",
        headers={"X-API-Key": api_key}
    ).json()

    if status["status"] == "completed":
        valid = [r for r in status["results"] if r["status"] == "valid"]
        invalid = [r for r in status["results"] if r["status"] == "invalid"]
        print(f"Done: {len(valid)} valid, {len(invalid)} invalid")
        break

    time.sleep(5)

Webhook Integration: Get Results Without Polling

from flask import Flask, request, jsonify
import hmac
import hashlib

app = Flask(__name__)
WEBHOOK_SECRET = "your_webhook_secret"  # from your dashboard or GET /api/auth/stats

@app.route('/webhooks/verification', methods=['POST'])
def handle_webhook():
    raw = request.get_data()  # sign the raw bytes, do not use request.json
    expected = hmac.new(WEBHOOK_SECRET.encode(), raw, hashlib.sha256).hexdigest()
    signature = request.headers.get('X-Mailvalid-Signature', '')

    if not hmac.compare_digest(expected, signature):
        return "Invalid signature", 401

    payload = request.json
    if payload["event"] == "job.completed":
        for result in payload["data"].get("results", []):
            if result["status"] == "invalid":
                add_to_suppression_list(result["email"])
            elif result["status"] == "valid":
                mark_as_verified(result["email"])

    return jsonify({"received": True}), 200

Common Integration Mistakes (And How to Avoid Them)

Mistake 1: Verifying at Send Time Instead of Capture Time

Better approach: Verify once at signup, then cache the result. Only re-verify if the email later bounces or your list has aged significantly.

Mistake 2: Blocking All "Catch-All" or "Unknown" Emails

A catch-all or unknown result isn't always bad — it means the mailbox couldn't be confirmed, not that it's invalid. Flag these for monitoring rather than auto-rejecting.

if result["status"] in ("catch_all", "unknown"):
    # Allow signup but flag for review
    user.flags.append("unconfirmed_email")
    user.save()

Mistake 3: Ignoring Role Accounts

Role accounts (info@, support@, admin@) are shared mailboxes with low engagement. Filter them out for cold email campaigns, but they're fine for transactional emails.

if result["is_role_based"] and campaign_type == "cold_outreach":
    return False, "role_based_address"

Mistake 4: No Fallback on API Failure

If your verification API goes down, your signup form shouldn't break. Implement a fail-open strategy:

try:
    result = verifier.verify(email)
except Exception:
    user.flags.append("unverified_email")
    return True, "api_unavailable"

Conclusion: Start Verifying Today

Email verification isn't optional infrastructure — it's profit protection. Every invalid email you block at signup saves you ESP costs, protects your sender reputation, and keeps your analytics clean.

MailValid gets you started fast:

  • From $0.0006 to $0.0015 per email depending on plan
  • 100 free verifications, no credit card required
  • Single endpoint — one call gets you syntax, MX, SMTP, disposable, catch-all, and role-based detection
  • 99.9% uptime SLA
curl -X POST https://mailvalid.io/api/v1/verify/single \
  -H "X-API-Key: your_free_key" \
  -H "Content-Type: application/json" \
  -d '{"email": "test@example.com"}'

Get 100 free credits →

Last Updated - 21 September 2026

M

MailValid Team

Email verification experts

Share:

Join teams that verify before they send

Stop letting bad emails hurt your deliverability

100 free credits. From $0.0008/email after. Credits never expire. No credit card required.

More from MailValid

Verify 100 emails free Start Free