Email Verification API Tutorial with Code Examples | emails-wipes.com

Complete guide to integrating email verification APIs. Includes code examples in JavaScript, Python, PHP, Ruby. Real-time validation, bulk processing.

⏱️ 15 minute read | Published: February 10, 2026

Email Verification API Tutorial with Code Examples

TL;DR: Learn how to integrate email verification APIs into your applications with practical code examples in JavaScript, Python, PHP, and Ruby. Covers real-time validation, bulk processing, error handling, and best practices.

Why Use an Email Verification API?

Email verification APIs provide programmatic access to validation services, allowing you to:

  • Real-time validation - verify emails instantly during user signup
  • Bulk processing - validate thousands of emails in minutes (see our email list cleaning guide)
  • Automated workflows - integrate with CRMs, marketing tools, webhooks
  • Multi-layer checks - syntax, domain, SMTP, disposable detection
  • Detailed results - structured JSON responses with validation details

API Basics: RESTful Email Verification

Endpoint Structure

Most email verification APIs follow REST principles:

Base URL

https://emails-wipes.com/api/v1

Authentication

API key via header:

Authorization: Bearer YOUR_API_KEY

Common Endpoints

  • POST /verify - Single email verification
  • POST /verify/bulk - Batch verification (up to 10,000 emails)
  • GET /status/{job_id} - Check bulk job status
  • GET /credits - Check remaining API credits

Single Email Verification

Request Format

POST https://emails-wipes.com/api/v1/verify
Content-Type: application/json
Authorization: Bearer YOUR_API_KEY

{
  "email": "[email protected]"
}

Response Format

{
  "email": "[email protected]",
  "valid": true,
  "details": {
    "syntax": "valid",
    "domain": "valid",
    "smtp": "valid",
    "disposable": false,
    "role_based": false,
    "free_email": false,
    "catch_all": false
  },
  "quality_score": 95,
  "verification_time_ms": 1847,
  "timestamp": "2026-02-10T01:30:00Z"
}

Response Fields Explained

Field Type Description
email string The verified email address
valid boolean true = deliverable, false = invalid
syntax string valid | invalid (RFC 5322 compliance)
domain string valid | invalid (DNS + MX records)
smtp string valid | invalid | unknown (mailbox verification)
disposable boolean true = temporary email service (10minutemail, etc.)
role_based boolean true = generic address (info@, support@)
free_email boolean true = free provider (Gmail, Yahoo, Hotmail)
catch_all boolean true = domain accepts any email (uncertain deliverability)
quality_score number 0-100 (confidence score, higher = better)

Code Examples: Single Email Verification

JavaScript (Fetch API)

async function verifyEmail(email) {
  const response = await fetch('https://emails-wipes.com/api/v1/verify', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'Authorization': 'Bearer YOUR_API_KEY'
    },
    body: JSON.stringify({ email })
  });

  if (!response.ok) {
    throw new Error(`HTTP error ${response.status}`);
  }

  const result = await response.json();

  if (result.valid && result.quality_score >= 80) {
    console.log('✅ Email is valid and high quality');
  } else if (result.valid && result.catch_all) {
    console.log('⚠️ Email is valid but catch-all (uncertain)');
  } else {
    console.log('❌ Email is invalid');
  }

  return result;
}

// Usage
verifyEmail('[email protected]')
  .then(result => console.log(result))
  .catch(error => console.error('Verification failed:', error));

JavaScript (Node.js with Axios)

const axios = require('axios');

async function verifyEmail(email) {
  try {
    const response = await axios.post('https://emails-wipes.com/api/v1/verify', 
      { email },
      {
        headers: {
          'Authorization': 'Bearer YOUR_API_KEY'
        }
      }
    );

    const { valid, details, quality_score } = response.data;

    if (valid) {
      console.log(`✅ Email valid (score: ${quality_score})`);

      // Check for red flags
      if (details.disposable) {
        console.warn('⚠️ Disposable email detected');
      }
      if (details.role_based) {
        console.warn('⚠️ Role-based email (low engagement)');
      }
    } else {
      console.log('❌ Email invalid');
    }

    return response.data;

  } catch (error) {
    if (error.response) {
      console.error('API error:', error.response.status, error.response.data);
    } else {
      console.error('Request failed:', error.message);
    }
    throw error;
  }
}

module.exports = { verifyEmail };

Python (requests library)

import requests

def verify_email(email):
    """Verify a single email address"""

    url = 'https://emails-wipes.com/api/v1/verify'
    headers = {
        'Authorization': 'Bearer YOUR_API_KEY',
        'Content-Type': 'application/json'
    }
    payload = {'email': email}

    try:
        response = requests.post(url, json=payload, headers=headers, timeout=10)
        response.raise_for_status()  # Raise exception for 4xx/5xx

        result = response.json()

        if result['valid']:
            print(f"✅ {email} is valid (score: {result['quality_score']})")

            # Check for warnings
            if result['details']['disposable']:
                print("⚠️ Disposable email detected")
            if result['details']['catch_all']:
                print("⚠️ Catch-all domain (uncertain deliverability)")
        else:
            print(f"❌ {email} is invalid")

        return result

    except requests.exceptions.RequestException as e:
        print(f"❌ Verification failed: {e}")
        return None

# Usage
if __name__ == '__main__':
    verify_email('[email protected]')

PHP (cURL)

<?php

function verifyEmail($email) {
    $url = 'https://emails-wipes.com/api/v1/verify';
    $apiKey = 'YOUR_API_KEY';

    $data = json_encode(['email' => $email]);

    $ch = curl_init($url);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_POST, true);
    curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
    curl_setopt($ch, CURLOPT_HTTPHEADER, [
        'Content-Type: application/json',
        "Authorization: Bearer $apiKey"
    ]);

    $response = curl_exec($ch);
    $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    curl_close($ch);

    if ($httpCode !== 200) {
        error_log("API error: HTTP $httpCode");
        return null;
    }

    $result = json_decode($response, true);

    if ($result['valid']) {
        echo "✅ Email is valid (score: {$result['quality_score']})\n";

        // Check warnings
        if ($result['details']['disposable']) {
            echo "⚠️ Disposable email\n";
        }
    } else {
        echo "❌ Email is invalid\n";
    }

    return $result;
}

// Usage
verifyEmail('[email protected]');

Ruby

require 'net/http'
require 'json'
require 'uri'

def verify_email(email)
  url = URI('https://emails-wipes.com/api/v1/verify')
  api_key = 'YOUR_API_KEY'

  http = Net::HTTP.new(url.host, url.port)
  http.use_ssl = true

  request = Net::HTTP::Post.new(url)
  request['Authorization'] = "Bearer #{api_key}"
  request['Content-Type'] = 'application/json'
  request.body = { email: email }.to_json

  response = http.request(request)

  if response.code.to_i != 200
    puts "❌ API error: #{response.code}"
    return nil
  end

  result = JSON.parse(response.body)

  if result['valid']
    puts "✅ Email valid (score: #{result['quality_score']})"

    # Check warnings
    puts "⚠️ Disposable email" if result['details']['disposable']
    puts "⚠️ Catch-all domain" if result['details']['catch_all']
  else
    puts "❌ Email invalid"
  end

  result
end

# Usage
verify_email('[email protected]')

Bulk Email Verification

For validating large lists (1,000+), use the bulk API for better performance and cost efficiency.

Step 1: Submit Bulk Job

POST https://emails-wipes.com/api/v1/verify/bulk
Content-Type: application/json
Authorization: Bearer YOUR_API_KEY

{
  "emails": [
    "[email protected]",
    "[email protected]",
    "[email protected]"
    // ... up to 10,000 emails
  ],
  "webhook_url": "https://yoursite.com/webhook/verification-complete" // optional
}

Response: Job Created

{
  "job_id": "bulk_abc123xyz",
  "status": "processing",
  "total_emails": 10000,
  "estimated_completion_seconds": 1200,
  "created_at": "2026-02-10T01:30:00Z"
}

Step 2: Poll Job Status

GET https://emails-wipes.com/api/v1/status/bulk_abc123xyz
Authorization: Bearer YOUR_API_KEY

Response: Job Status

{
  "job_id": "bulk_abc123xyz",
  "status": "completed",
  "progress": 100,
  "total_emails": 10000,
  "processed_emails": 10000,
  "valid_emails": 8735,
  "invalid_emails": 1265,
  "processing_time_seconds": 1147,
  "download_url": "https://emails-wipes.com/api/v1/download/bulk_abc123xyz",
  "expires_at": "2026-02-17T01:30:00Z"
}

Step 3: Download Results

GET https://emails-wipes.com/api/v1/download/bulk_abc123xyz
Authorization: Bearer YOUR_API_KEY

Response: CSV file with validation results:

email,valid,syntax,domain,smtp,disposable,role_based,catch_all,quality_score
[email protected],true,valid,valid,valid,false,false,false,98
[email protected],false,valid,valid,invalid,false,false,false,25
[email protected],true,valid,valid,valid,false,false,true,75

Bulk Verification Code Example (Python)

import requests
import time
import csv

class EmailVerifier:
    def __init__(self, api_key):
        self.api_key = api_key
        self.base_url = 'https://emails-wipes.com/api/v1'
        self.headers = {
            'Authorization': f'Bearer {api_key}',
            'Content-Type': 'application/json'
        }

    def verify_bulk(self, emails, webhook_url=None):
        """Submit bulk verification job"""
        url = f'{self.base_url}/verify/bulk'
        payload = {'emails': emails}
        if webhook_url:
            payload['webhook_url'] = webhook_url

        response = requests.post(url, json=payload, headers=self.headers)
        response.raise_for_status()

        return response.json()

    def check_status(self, job_id):
        """Check bulk job status"""
        url = f'{self.base_url}/status/{job_id}'
        response = requests.get(url, headers=self.headers)
        response.raise_for_status()

        return response.json()

    def download_results(self, job_id, output_file='results.csv'):
        """Download verification results"""
        url = f'{self.base_url}/download/{job_id}'
        response = requests.get(url, headers=self.headers, stream=True)
        response.raise_for_status()

        with open(output_file, 'wb') as f:
            for chunk in response.iter_content(chunk_size=8192):
                f.write(chunk)

        print(f"✅ Results saved to {output_file}")
        return output_file

    def wait_for_completion(self, job_id, poll_interval=30):
        """Poll job status until completion"""
        while True:
            status_data = self.check_status(job_id)
            status = status_data['status']
            progress = status_data.get('progress', 0)

            print(f"Status: {status} | Progress: {progress}%")

            if status == 'completed':
                return status_data
            elif status == 'failed':
                raise Exception(f"Job failed: {status_data.get('error')}")

            time.sleep(poll_interval)

# Usage Example
if __name__ == '__main__':
    verifier = EmailVerifier('YOUR_API_KEY')

    # Read emails from file
    with open('emails.txt', 'r') as f:
        emails = [line.strip() for line in f if line.strip()]

    print(f"Submitting {len(emails)} emails for verification...")

    # Submit bulk job
    job = verifier.verify_bulk(emails)
    job_id = job['job_id']

    print(f"Job created: {job_id}")
    print(f"Estimated completion: {job['estimated_completion_seconds']} seconds")

    # Wait for completion
    result = verifier.wait_for_completion(job_id)

    print(f"\n✅ Verification complete!")
    print(f"Valid: {result['valid_emails']}")
    print(f"Invalid: {result['invalid_emails']}")

    # Download results
    verifier.download_results(job_id, 'verified_emails.csv')

Error Handling

Common HTTP Status Codes

Code Meaning Action
200 Success Parse response
400 Bad request (invalid email format) Check input validation
401 Unauthorized (invalid API key) Check API key
402 Insufficient credits Top up account
429 Rate limit exceeded Retry with exponential backoff
500 Server error Retry or contact support

solid Error Handling Example

async function verifyEmailWithRetry(email, maxRetries = 3) {
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
    try {
      const response = await fetch('https://emails-wipes.com/api/v1/verify', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          'Authorization': 'Bearer YOUR_API_KEY'
        },
        body: JSON.stringify({ email })
      });

      // Handle different HTTP status codes
      if (response.status === 200) {
        return await response.json();
      } else if (response.status === 400) {
        throw new Error('Invalid email format');
      } else if (response.status === 401) {
        throw new Error('Invalid API key');
      } else if (response.status === 402) {
        throw new Error('Insufficient credits');
      } else if (response.status === 429) {
        // Rate limited - exponential backoff
        const waitTime = Math.pow(2, attempt) * 1000; // 2s, 4s, 8s
        console.warn(`Rate limited, retrying in ${waitTime}ms...`);
        await new Promise(resolve => setTimeout(resolve, waitTime));
        continue;
      } else if (response.status >= 500) {
        // Server error - retry
        if (attempt < maxRetries) {
          console.warn(`Server error, attempt ${attempt}/${maxRetries}`);
          await new Promise(resolve => setTimeout(resolve, 2000));
          continue;
        }
        throw new Error('Server error after retries');
      } else {
        throw new Error(`Unexpected status: ${response.status}`);
      }

    } catch (error) {
      if (attempt === maxRetries) {
        throw error;
      }
      console.warn(`Attempt ${attempt} failed, retrying...`);
      await new Promise(resolve => setTimeout(resolve, 1000));
    }
  }
}

Real-Time Form Validation

Client-Side + Server-Side Pattern

<!-- HTML Form -->
<form id="signupForm">
  <input type="email" id="emailInput" placeholder="Enter your email" />
  <span id="validationMessage"></span>
  <button type="submit" id="submitButton" disabled>Sign Up</button>
</form>

<script>
const emailInput = document.getElementById('emailInput');
const validationMessage = document.getElementById('validationMessage');
const submitButton = document.getElementById('submitButton');

let debounceTimer;

emailInput.addEventListener('input', (e) => {
  const email = e.target.value;

  // Clear previous timer
  clearTimeout(debounceTimer);

  // Basic client-side syntax check (instant)
  const basicRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
  if (!basicRegex.test(email)) {
    validationMessage.textContent = '❌ Invalid email format';
    validationMessage.className = 'error';
    submitButton.disabled = true;
    return;
  }

  // Debounce API call (wait for user to stop typing)
  debounceTimer = setTimeout(async () => {
    validationMessage.textContent = '⏳ Verifying...';
    validationMessage.className = 'loading';

    try {
      const response = await fetch('/api/verify-email', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ email })
      });

      const result = await response.json();

      if (result.valid && result.quality_score >= 70) {
        validationMessage.textContent = '✅ Email is valid';
        validationMessage.className = 'success';
        submitButton.disabled = false;
      } else if (result.details.disposable) {
        validationMessage.textContent = '⚠️ Disposable email not allowed';
        validationMessage.className = 'warning';
        submitButton.disabled = true;
      } else {
        validationMessage.textContent = '❌ Email address is invalid';
        validationMessage.className = 'error';
        submitButton.disabled = true;
      }

    } catch (error) {
      console.error('Verification failed:', error);
      validationMessage.textContent = '⚠️ Could not verify (optional check)';
      validationMessage.className = 'warning';
      submitButton.disabled = false; // Allow signup if API fails
    }

  }, 800); // Wait 800ms after user stops typing
});
</script>

Backend Server Route (Express.js)

const express = require('express');
const axios = require('axios');

const app = express();
app.use(express.json());

app.post('/api/verify-email', async (req, res) => {
  const { email } = req.body;

  if (!email) {
    return res.status(400).json({ error: 'Email required' });
  }

  try {
    const response = await axios.post('https://emails-wipes.com/api/v1/verify',
      { email },
      {
        headers: {
          'Authorization': `Bearer ${process.env.EMAIL_VERIFY_API_KEY}`
        },
        timeout: 5000 // 5 second timeout
      }
    );

    res.json(response.data);

  } catch (error) {
    console.error('Email verification error:', error.message);
    res.status(500).json({ error: 'Verification service unavailable' });
  }
});

app.listen(3000, () => console.log('Server running on port 3000'));

Best Practices

✅ Do This

  • Cache results - store validation results for 30-90 days, don't re-verify the same email
  • Implement rate limiting - use exponential backoff if you hit API rate limits
  • Set timeouts - 5-10 seconds max per API call to prevent slow responses from blocking users
  • Graceful degradation - if API fails, allow signup but flag for manual review
  • Use bulk API for large lists - 10x faster and cheaper than individual calls
  • Monitor API usage - track credits/costs to avoid unexpected bills
  • Log validation results - helps debug issues and track quality trends

❌ Don't Do This

  • Expose API keys client-side - always proxy through your backend
  • Block signups on API failure - prioritize user experience over perfect validation
  • Verify on every keystroke - debounce input (500-800ms) to reduce API calls
  • Ignore catch-all emails - treat as "uncertain", not "invalid" (learn about catch-all detection)
  • Hard-reject disposable emails - depends on your use case (some are legitimate)
  • Forget double opt-in - API validation + email confirmation = best security

Rate Limiting & Cost Optimization

Caching Strategy

// Redis cache example (Node.js)
const redis = require('redis');
const client = redis.createClient();

async function verifyCached(email) {
  // Check cache first
  const cached = await client.get(`email:${email}`);
  if (cached) {
    console.log('✅ Cache hit');
    return JSON.parse(cached);
  }

  // Cache miss - call API
  console.log('⏳ Cache miss - calling API');
  const result = await verifyEmail(email);

  // Store in cache (30 days)
  await client.setex(`email:${email}`, 30 * 24 * 60 * 60, JSON.stringify(result));

  return result;
}

Request Batching

// Batch multiple emails into single bulk request
class EmailVerificationQueue {
  constructor(apiKey, batchSize = 100, flushInterval = 5000) {
    this.apiKey = apiKey;
    this.batchSize = batchSize;
    this.flushInterval = flushInterval;
    this.queue = [];
    this.timer = null;
  }

  add(email) {
    return new Promise((resolve, reject) => {
      this.queue.push({ email, resolve, reject });

      // Flush if batch size reached
      if (this.queue.length >= this.batchSize) {
        this.flush();
      } else if (!this.timer) {
        // Set timer to flush after interval
        this.timer = setTimeout(() => this.flush(), this.flushInterval);
      }
    });
  }

  async flush() {
    if (this.queue.length === 0) return;

    clearTimeout(this.timer);
    this.timer = null;

    const batch = this.queue.splice(0, this.batchSize);
    const emails = batch.map(item => item.email);

    try {
      const response = await axios.post('https://emails-wipes.com/api/v1/verify/bulk',
        { emails },
        { headers: { 'Authorization': `Bearer ${this.apiKey}` } }
      );

      const jobId = response.data.job_id;
      const results = await this.waitForResults(jobId);

      // Resolve individual promises
      batch.forEach((item, index) => {
        item.resolve(results[index]);
      });

    } catch (error) {
      batch.forEach(item => item.reject(error));
    }
  }

  async waitForResults(jobId) {
    // Poll until completion (simplified)
    while (true) {
      const status = await axios.get(`https://emails-wipes.com/api/v1/status/${jobId}`,
        { headers: { 'Authorization': `Bearer ${this.apiKey}` } }
      );

      if (status.data.status === 'completed') {
        const download = await axios.get(status.data.download_url,
          { headers: { 'Authorization': `Bearer ${this.apiKey}` } }
        );
        return download.data; // Assuming JSON response
      }

      await new Promise(resolve => setTimeout(resolve, 5000));
    }
  }
}

// Usage
const queue = new EmailVerificationQueue('YOUR_API_KEY');

// Individual requests are automatically batched
const result1 = await queue.add('[email protected]');
const result2 = await queue.add('[email protected]');
// ... batched together if within 5 seconds

Security Considerations

🔒 API Key Security

  • Never expose keys client-side - API calls must go through your backend
  • Use environment variables - store keys in .env files, not code
  • Rotate keys regularly - change API keys every 3-6 months
  • Implement rate limiting - prevent abuse of your proxy endpoint
  • Validate input - sanitize user-provided emails before passing to API

Monitoring & Logging

// Structured logging example
const winston = require('winston');

const logger = winston.createLogger({
  level: 'info',
  format: winston.format.json(),
  transports: [
    new winston.transports.File({ filename: 'email-verification.log' })
  ]
});

async function verifyEmailWithLogging(email) {
  const startTime = Date.now();

  try {
    const result = await verifyEmail(email);
    const duration = Date.now() - startTime;

    logger.info({
      event: 'email_verification',
      email: email.replace(/^(.{2}).*(@.*)$/, '$1***$2'), // Partially redact
      valid: result.valid,
      quality_score: result.quality_score,
      duration_ms: duration,
      timestamp: new Date().toISOString()
    });

    return result;

  } catch (error) {
    const duration = Date.now() - startTime;

    logger.error({
      event: 'email_verification_failed',
      email: email.replace(/^(.{2}).*(@.*)$/, '$1***$2'),
      error: error.message,
      duration_ms: duration,
      timestamp: new Date().toISOString()
    });

    throw error;
  }
}

Testing

Unit Test Example (Jest)

const { verifyEmail } = require('./email-verifier');
const nock = require('nock');

describe('Email Verification', () => {
  beforeEach(() => {
    nock.cleanAll();
  });

  test('should return valid for existing email', async () => {
    nock('https://emails-wipes.com')
      .post('/api/v1/verify', { email: '[email protected]' })
      .reply(200, {
        email: '[email protected]',
        valid: true,
        details: { syntax: 'valid', domain: 'valid', smtp: 'valid' },
        quality_score: 95
      });

    const result = await verifyEmail('[email protected]');

    expect(result.valid).toBe(true);
    expect(result.quality_score).toBeGreaterThan(90);
  });

  test('should return invalid for non-existent email', async () => {
    nock('https://emails-wipes.com')
      .post('/api/v1/verify', { email: '[email protected]' })
      .reply(200, {
        email: '[email protected]',
        valid: false,
        details: { syntax: 'valid', domain: 'invalid', smtp: 'invalid' },
        quality_score: 15
      });

    const result = await verifyEmail('[email protected]');

    expect(result.valid).toBe(false);
  });

  test('should handle API errors gracefully', async () => {
    nock('https://emails-wipes.com')
      .post('/api/v1/verify')
      .reply(500, { error: 'Internal server error' });

    await expect(verifyEmail('[email protected]')).rejects.toThrow();
  });
});

Key Takeaways

  • Always proxy API calls through backend - never expose API keys client-side
  • Implement caching - 30-day cache reduces costs by 90%+
  • Use bulk API for large lists - 10x faster, more cost-effective
  • Graceful degradation - don't block users if API fails
  • Debounce input validation - 500-800ms delay reduces unnecessary calls
  • Monitor usage and costs - track API calls and credit consumption
  • Combine with double opt-in - API validation + email confirmation = best practice

🚀 Get Your API Key

Start verifying emails with our fast, accurate API

✅ 98.5% accuracy | ⚡ < 2 seconds per email | 🔒 Zero data retention

$0.75 per 1,000 emails with code REDDIT50

Get API Access →

Related Articles