Article content and detailed guides remain in English. The selected language applies to controls and quick instructions.

Back to articles

Real-Time Email Validation on Web Forms: Implementation Guide

On this page

Why Validate Email Addresses at Point of Capture

Every invalid email address that enters your system costs money downstream. Validating at the moment a user types their email on a form prevents bad data from reaching your CRM, email marketing platform or sales team:

Problem Cost of not validating at capture
Typos (gmial.com, yaho.com) Lost leads who never receive your emails
Disposable/temporary addresses Inflated list size; low engagement; wasted sends
Invalid syntax Bounced emails; damaged sender reputation
Non-existent mailboxes Hard bounces; potential blacklisting
Role-based addresses (info@, admin@) Low engagement; some providers penalise senders
Spam traps Deliverability damage; potential blacklisting

When to validate

Validation timing What it catches User experience impact
On blur (field loses focus) Immediate feedback; catches most issues Best for single-field forms
On submit All checks before form submission Acceptable; user waits briefly at submit
Delayed (async after submit) Deep checks (SMTP, mailbox verification) No form delay; follow-up if invalid
On keystroke Syntax only; too early for domain/mailbox checks Can feel intrusive; best limited to format hints

Validation Layers

Real-time validation works best as a layered system, from fast/cheap to slow/expensive:

Layer 1: Client-side syntax validation (instant)

Check What it catches Implementation
Basic format (contains @, has domain) Obviously invalid input HTML5 type="email" or regex
Domain has a dot Missing TLD (user@gmail) Regex or string check
No spaces Accidental spaces Trim and validate
Reasonable length Extremely long or short input Length check (3-254 characters)
No forbidden characters Characters that are never valid in email Regex
<!-- HTML5 basic validation -->
<input type="email" required
       pattern="[a-zA-Z0-9._%+\-]+@[a-zA-Z0-9.\-]+\.[a-zA-Z]{2,}"
       title="Enter a valid email address">
// Client-side validation function
function validateEmailSyntax(email) {
  const trimmed = email.trim();
  const errors = [];

  if (!trimmed) {
    errors.push('Email address is required.');
    return { valid: false, errors };
  }

  if (trimmed.length > 254) {
    errors.push('Email address is too long.');
    return { valid: false, errors };
  }

  const parts = trimmed.split('@');
  if (parts.length !== 2) {
    errors.push('Email must contain exactly one @ symbol.');
    return { valid: false, errors };
  }

  const [local, domain] = parts;

  if (!local || local.length > 64) {
    errors.push('The part before @ is missing or too long.');
  }

  if (!domain || !domain.includes('.')) {
    errors.push('Domain must include a dot (e.g., gmail.com).');
  }

  const domainParts = domain.split('.');
  const tld = domainParts[domainParts.length - 1];
  if (tld && tld.length < 2) {
    errors.push('Top-level domain must be at least 2 characters.');
  }

  return {
    valid: errors.length === 0,
    errors,
    normalised: trimmed.toLowerCase()
  };
}

Layer 2: Common typo detection (instant)

Typo pattern Correction Method
gmial.com, gmal.com, gamil.com gmail.com Domain dictionary + fuzzy matching
yaho.com, yahooo.com yahoo.com Domain dictionary + fuzzy matching
hotmal.com, hotmial.com hotmail.com Domain dictionary + fuzzy matching
outlok.com, outlool.com outlook.com Domain dictionary + fuzzy matching
.con instead of .com .com TLD dictionary
.cmo instead of .com .com TLD dictionary
// Common domain typo suggestions
const DOMAIN_CORRECTIONS = {
  'gmial.com': 'gmail.com',
  'gmal.com': 'gmail.com',
  'gamil.com': 'gmail.com',
  'gmaill.com': 'gmail.com',
  'gnail.com': 'gmail.com',
  'gmai.com': 'gmail.com',
  'yaho.com': 'yahoo.com',
  'yahooo.com': 'yahoo.com',
  'yhaoo.com': 'yahoo.com',
  'hotmal.com': 'hotmail.com',
  'hotmial.com': 'hotmail.com',
  'hotmai.com': 'hotmail.com',
  'outlok.com': 'outlook.com',
  'outloo.com': 'outlook.com',
  'outlool.com': 'outlook.com',
};

const TLD_CORRECTIONS = {
  'con': 'com',
  'cmo': 'com',
  'ocm': 'com',
  'coom': 'com',
  'nte': 'net',
  'ogr': 'org',
};

function suggestCorrection(email) {
  const parts = email.toLowerCase().split('@');
  if (parts.length !== 2) return null;

  const domain = parts[1];

  // Check full domain
  if (DOMAIN_CORRECTIONS[domain]) {
    return parts[0] + '@' + DOMAIN_CORRECTIONS[domain];
  }

  // Check TLD
  const domainParts = domain.split('.');
  const tld = domainParts[domainParts.length - 1];
  if (TLD_CORRECTIONS[tld]) {
    domainParts[domainParts.length - 1] = TLD_CORRECTIONS[tld];
    return parts[0] + '@' + domainParts.join('.');
  }

  return null;
}

Layer 3: API-based validation (100-500ms)

Check What it catches Requires API call
MX record lookup Domains that cannot receive email Yes
Disposable email detection Temporary/throwaway addresses Yes
Role-based detection info@, admin@, support@ addresses Yes (or local list)
SMTP verification Whether the specific mailbox exists Yes (slower)
Spam trap detection Known spam trap addresses Yes
Catch-all domain detection Domains that accept all addresses Yes
// API-based validation (on blur or submit)
async function validateEmailAPI(email) {
  try {
    const response = await fetch('/api/validate-email', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ email })
    });

    const result = await response.json();

    return {
      valid: result.is_valid,
      disposable: result.is_disposable,
      role_based: result.is_role_based,
      catch_all: result.is_catch_all,
      suggestion: result.did_you_mean,
      risk: result.risk_level  // low, medium, high
    };
  } catch (error) {
    // If API is down, allow the submission
    // (validate later in batch)
    return { valid: true, api_error: true };
  }
}

Server-side validation endpoint

# Flask example: /api/validate-email
import dns.resolver
import requests
from flask import Flask, request, jsonify

app = Flask(__name__)

DISPOSABLE_DOMAINS = set()  # Load from a maintained list

ROLE_PREFIXES = {
    'admin', 'info', 'support', 'sales', 'contact',
    'help', 'billing', 'abuse', 'postmaster', 'webmaster',
    'noreply', 'no-reply', 'mailer-daemon'
}

def check_mx_records(domain):
    """Check if the domain has MX records."""
    try:
        mx_records = dns.resolver.resolve(domain, 'MX')
        return len(mx_records) > 0
    except (dns.resolver.NXDOMAIN,
            dns.resolver.NoAnswer,
            dns.resolver.NoNameservers):
        return False
    except Exception:
        return True  # Assume valid on DNS errors

def is_disposable(domain):
    """Check if the domain is a known disposable email provider."""
    return domain.lower() in DISPOSABLE_DOMAINS

def is_role_based(local_part):
    """Check if the local part is a role-based address."""
    return local_part.lower().split('+')[0] in ROLE_PREFIXES

@app.route('/api/validate-email', methods=['POST'])
def validate_email():
    data = request.get_json()
    email = data.get('email', '').strip().lower()

    if not email or '@' not in email:
        return jsonify({
            'is_valid': False,
            'reason': 'Invalid format'
        })

    local, domain = email.rsplit('@', 1)

    result = {
        'is_valid': True,
        'is_disposable': is_disposable(domain),
        'is_role_based': is_role_based(local),
        'is_catch_all': False,
        'risk_level': 'low'
    }

    # Check MX records
    if not check_mx_records(domain):
        result['is_valid'] = False
        result['reason'] = 'Domain cannot receive email'
        result['risk_level'] = 'high'
        return jsonify(result)

    # Assess risk
    if result['is_disposable']:
        result['risk_level'] = 'high'
    elif result['is_role_based']:
        result['risk_level'] = 'medium'

    return jsonify(result)

UX Patterns for Real-Time Validation

Showing validation results

Pattern When to use Implementation
Inline error below field Syntax errors, invalid domains Red text below input field
Suggestion prompt ("Did you mean...?") Detected typos Clickable suggestion below field
Green checkmark Valid email confirmed Icon inside or beside input field
Yellow warning Valid but risky (disposable, role-based) Warning text; allow submission
Loading spinner API validation in progress Small spinner inside input field

UX best practices

Practice Why it matters
Validate on blur, not on every keystroke Keystroke validation is disruptive; user has not finished typing
Show errors only after the user has finished Premature errors feel like the form is fighting the user
Make "Did you mean...?" clickable One click to accept the correction is better than retyping
Allow override for warnings Some users intentionally use role-based or uncommon addresses
Do not block submission on API timeout Validation is best-effort; a slow API should not break the form
Show a specific error, not "invalid email" "This domain cannot receive email" is more helpful than "invalid"
Trim whitespace automatically Do not show an error for a leading space; just remove it
Make the field wide enough Narrow fields cause truncation; users cannot see what they typed

Accessibility considerations

Requirement Implementation
Error messages linked to field Use aria-describedby to connect error text to the input
Status announcements Use aria-live="polite" for validation feedback
Colour is not the only indicator Add text and/or icons alongside colour changes
Keyboard navigation Suggestion corrections must be keyboard-accessible
Screen reader support Error and success states must be announced
<div class="form-group">
  <label for="email">Email address</label>
  <input type="email" id="email" name="email"
         aria-describedby="email-feedback"
         aria-invalid="false">
  <div id="email-feedback" aria-live="polite" role="status">
    <!-- Validation feedback appears here -->
  </div>
</div>

Validation API Provider Comparison

Provider Real-time API Checks included Pricing (approx.) Speed
ZeroBounce Yes Syntax, MX, SMTP, disposable, catch-all, abuse, spam trap From $0.008/verification 100-300ms
NeverBounce Yes Syntax, DNS, SMTP, disposable From $0.008/verification 100-500ms
Bouncer Yes Syntax, MX, SMTP, disposable, catch-all From $0.008/verification 100-300ms
Kickbox Yes Syntax, MX, SMTP, disposable, role, free provider From $0.01/verification 100-500ms
Emailable Yes Syntax, MX, SMTP, disposable, role, catch-all From $0.005/verification 100-300ms
Hunter.io Yes Syntax, MX, SMTP, disposable Free tier available; paid from $0.01 200-500ms
Abstract API Yes Syntax, MX, SMTP, disposable, catch-all Free tier available; paid plans 100-300ms

Choosing a provider

Factor Consideration
Accuracy Test with known valid, invalid, disposable and catch-all addresses
Speed Under 300ms for real-time form validation; over 500ms feels slow
Uptime 99.9%+ uptime SLA for production forms
Pricing model Per-verification vs subscription; watch for overage charges
Free tier Useful for development and low-volume sites
Integration SDKs for your language/framework; webhook support
Data coverage How frequently is the disposable domain list updated?
Privacy Does the provider store the email addresses you validate?

Integration Patterns

Pattern 1: Client-side only (no API)

Pros Cons
Free; no API dependency Cannot verify mailbox existence
Instant feedback Cannot detect disposable domains (without a bundled list)
Works offline No SMTP or MX verification
No privacy concerns Catches only obvious errors

Best for: Low-traffic forms, personal projects, situations where a bounced email is acceptable.

Pattern 2: Client-side + server-side API

Pros Cons
Comprehensive validation API cost per validation
Good user experience API latency (100-500ms)
Catches most invalid addresses API dependency (needs fallback)
Detects disposable and role-based Privacy: sending emails to third party

Best for: Business forms, lead capture, signup flows where email quality matters.

Pattern 3: Client-side + delayed batch validation

Pros Cons
No form delay Invalid emails enter the system temporarily
Lower API cost (batch pricing) Requires follow-up process for invalid addresses
Good for high-volume forms Delay between capture and validation
Allows API failures gracefully More complex implementation

Best for: High-volume signups, event registrations, situations where form conversion rate is prioritised over immediate validation.

Handling Edge Cases

Edge case How to handle
Catch-all domains Accept but flag; cannot verify individual mailbox
New TLDs (.xyz, .io, .dev) Accept; do not reject based on unfamiliar TLD
Internationalised domains (IDN) Accept; validate using punycode conversion
Plus addressing (user+tag@domain.com) Accept; it is valid and commonly used for filtering
Subdomains (user@mail.company.com) Accept; validate the full domain for MX records
Very long local parts Accept up to 64 characters (RFC limit)
IP-based domains (user@[192.168.1.1]) Technically valid but unusual; flag for review
Government and military domains (.gov, .mil) Accept; do not block based on domain type
API timeout Accept the submission; validate in batch later
Rate limiting from validation API Queue validations; validate remaining in batch

Measuring Validation Effectiveness

Metric What to track Target
Invalid capture rate Percentage of form submissions with invalid emails before validation Baseline measurement
Post-validation invalid rate Percentage of invalid emails that get through validation Under 2%
False positive rate Valid emails incorrectly rejected Under 0.5%
Form abandonment rate Users who leave the form after validation Monitor for increases
Typo correction acceptance rate Users who click "Did you mean...?" Track to validate suggestion quality
API response time (p95) 95th percentile response time for validation API Under 500ms
API error rate Percentage of API calls that fail Under 1%
Bounce rate (post-validation) Email bounces from addresses that passed validation Under 2%

Processing Validated Emails

After collecting validated email addresses through your forms, you may need to consolidate them with emails from other sources. Upload form exports, CRM downloads or other files to Email Extractor to extract and deduplicate email addresses across sources. The tool's case-insensitive deduplication ensures that john@example.com and John@Example.com are treated as one address.

Extract emails

Explore tools

Verify emails

Check address validity before using your list.

ZeroBounce

Email Verification

Verifies email lists and provides tools for monitoring deliverability.

Useful when list cleaning and sender health belong in one workflow.

Explore ZeroBounce (opens in a new tab)