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

Back to articles

Email Validation for Web Forms: Client-Side and Server-Side Techniques

On this page

Why Form-Level Email Validation Matters

Every email address that enters your database through a web form affects your data quality, deliverability and marketing ROI. Validation at the point of entry prevents bad data from ever getting in:

Problem Cause Impact Prevention
Typos User types "gmial.com" instead of "gmail.com" Bounced emails; lost contacts Typo suggestion / autocorrect
Fake addresses User enters "asdf@asdf.com" to bypass a required field Inflated list; wasted sends; skewed metrics Real-time verification API
Disposable addresses User enters a throwaway address Contact disappears within hours/days Disposable domain detection
Role-based addresses User enters "info@" or "admin@" instead of personal Low engagement; potential spam traps Role-based detection
Syntax errors Missing @ symbol, spaces, invalid characters Form submission errors; data corruption Client-side validation
Duplicate entries Same person signs up multiple times Inflated counts; duplicate emails Server-side deduplication

Validation Layers

Email validation works best as a layered system. Each layer catches different types of errors:

Layer What it checks When it runs Catches
HTML5 built-in validation Basic format (requires @) On form submission Missing @, empty field
Client-side JavaScript Format, common typos, domain suggestions On blur (field exit) or keystroke Typos, syntax errors, obvious fakes
Server-side validation Format, domain MX records, business rules On form submission (server) Anything client-side missed; security
Real-time API verification Mailbox existence, disposable detection, risk scoring On blur or submission Invalid mailboxes, disposable, role-based
Double opt-in Human confirmation via email After form submission Fake addresses, typos, consent verification

Client-Side Validation

HTML5 built-in validation

The simplest validation is the HTML5 type="email" attribute, which browsers validate automatically:

<form>
  <label for="email">Email address</label>
  <input
    type="email"
    id="email"
    name="email"
    required
    placeholder="you@example.com"
  />
  <button type="submit">Subscribe</button>
</form>
What HTML5 type="email" checks What it does NOT check
Contains an @ symbol Whether the domain exists
Has text before and after @ Whether the mailbox exists
Prevents most obviously invalid formats Whether it is a disposable address
Shows browser-native error messages Common typos like "gmial.com"
Works without JavaScript Role-based addresses

JavaScript validation

For more control, add JavaScript validation that runs when the user leaves the email field:

function validateEmail(email) {
  const errors = [];

  // Basic format check
  const formatRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
  if (!formatRegex.test(email)) {
    errors.push('Please enter a valid email address.');
    return errors;
  }

  const [localPart, domain] = email.split('@');

  // Check local part length (RFC 5321: max 64)
  if (localPart.length > 64) {
    errors.push(
      'The part before @ is too long (max 64 characters).'
    );
  }

  // Check domain length (RFC 5321: max 255)
  if (domain.length > 255) {
    errors.push(
      'The domain name is too long (max 255 characters).'
    );
  }

  // Check for common typos
  const typoSuggestion = checkCommonTypos(domain);
  if (typoSuggestion) {
    errors.push(
      `Did you mean @${typoSuggestion}?`
    );
  }

  return errors;
}

function checkCommonTypos(domain) {
  const corrections = {
    'gmial.com': 'gmail.com',
    'gmaill.com': 'gmail.com',
    'gamil.com': 'gmail.com',
    'gmal.com': 'gmail.com',
    'gmail.co': 'gmail.com',
    'gmail.con': 'gmail.com',
    'gnail.com': 'gmail.com',
    'yahooo.com': 'yahoo.com',
    'yaho.com': 'yahoo.com',
    'yahoo.co': 'yahoo.com',
    'hotmial.com': 'hotmail.com',
    'hotmal.com': 'hotmail.com',
    'hotmail.co': 'hotmail.com',
    'outlok.com': 'outlook.com',
    'outloo.com': 'outlook.com',
    'outlook.co': 'outlook.com',
  };
  return corrections[domain.toLowerCase()] || null;
}

Common regex patterns

Pattern Strictness What it catches Limitations
/^[^\s@]+@[^\s@]+\.[^\s@]+$/ Loose No spaces; has @ and dot Allows some technically invalid formats
/^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/ Medium Alphanumeric + common special chars; TLD of 2+ chars Rejects some valid addresses (plus addressing edge cases)
Full RFC 5322 regex Strict Matches the email specification precisely Extremely complex; over 6,000 characters; not recommended

Recommendation

Use the loose regex for client-side validation. Overly strict regex patterns reject valid email addresses (addresses with plus signs, long TLDs, internationalised domain names). Let the server-side validation and real-time API do the heavy lifting.

Server-Side Validation

Server-side validation is required regardless of client-side validation, because client-side checks can be bypassed:

Python (Flask) example

import re
import dns.resolver

def validate_email_server(email):
    """Validate email on the server side."""
    errors = []

    # 1. Format check
    pattern = r'^[^\s@]+@[^\s@]+\.[^\s@]+$'
    if not re.match(pattern, email):
        errors.append('Invalid email format.')
        return errors

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

    # 2. Length checks (RFC 5321)
    if len(local_part) > 64:
        errors.append('Local part exceeds 64 characters.')
    if len(domain) > 255:
        errors.append('Domain exceeds 255 characters.')
    if len(email) > 254:
        errors.append(
            'Email address exceeds 254 characters.'
        )

    # 3. DNS MX record check
    try:
        mx_records = dns.resolver.resolve(
            domain, 'MX'
        )
        if not mx_records:
            errors.append(
                'Domain does not accept email.'
            )
    except (
        dns.resolver.NoAnswer,
        dns.resolver.NXDOMAIN,
        dns.resolver.NoNameservers,
    ):
        errors.append('Domain does not exist.')
    except Exception:
        pass  # DNS timeout; allow through

    # 4. Disposable domain check
    disposable_domains = load_disposable_domains()
    if domain.lower() in disposable_domains:
        errors.append(
            'Please use a non-disposable email address.'
        )

    # 5. Role-based check
    role_prefixes = [
        'info', 'admin', 'support', 'sales',
        'contact', 'help', 'webmaster', 'postmaster',
        'noreply', 'no-reply',
    ]
    if local_part.lower() in role_prefixes:
        errors.append(
            'Please use a personal email address.'
        )

    return errors

Node.js (Express) example

const dns = require('dns').promises;

async function validateEmailServer(email) {
  const errors = [];

  // 1. Format check
  const formatRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
  if (!formatRegex.test(email)) {
    errors.push('Invalid email format.');
    return errors;
  }

  const [localPart, domain] = email.split('@');

  // 2. Length checks
  if (localPart.length > 64) {
    errors.push('Local part too long.');
  }
  if (domain.length > 255) {
    errors.push('Domain too long.');
  }

  // 3. MX record check
  try {
    const mxRecords = await dns.resolveMx(domain);
    if (!mxRecords || mxRecords.length === 0) {
      errors.push('Domain does not accept email.');
    }
  } catch (err) {
    if (err.code === 'ENOTFOUND') {
      errors.push('Domain does not exist.');
    }
    // Timeout: allow through
  }

  return errors;
}

Real-Time API Verification

For the highest accuracy, use a third-party verification API that checks whether the mailbox actually exists:

Verification API providers

Provider Real-time API Features Pricing
ZeroBounce Yes Mailbox check, disposable detection, abuse scoring, AI scoring $0.008-$0.01/verification
NeverBounce Yes Mailbox check, disposable, accept-all detection $0.008-$0.01/verification
Kickbox Yes Mailbox check, disposable, role-based, free email detection $0.008-$0.01/verification
Emailable Yes Mailbox check, disposable, accept-all, MX check $0.007-$0.01/verification
Abstract API Yes Mailbox check, disposable, format, MX, SMTP Free tier; $0.01/verification
Hunter.io Yes Mailbox check, format, domain info $0.01/verification
Mailgun (verify) Yes Mailbox check, disposable, role-based Included with Mailgun plans
Debounce Yes Mailbox check, disposable, role-based, catch-all $0.005-$0.01/verification

When to call the API

Trigger Pros Cons
On field blur (user leaves field) Immediate feedback; best UX More API calls (user may edit field multiple times)
On form submission Fewer API calls; validates final input Slower submission experience
On field blur with debounce (300-500ms) Balances UX and API usage Slight delay before feedback
After client-side validation passes Only calls API for well-formed emails; saves cost Slight delay after initial validation

UX Best Practices

Practice Why Implementation
Show errors inline (next to the field) Users see the error immediately Red border + error text below field
Use suggestion, not rejection for typos Reduces frustration "Did you mean @gmail.com?" with a clickable fix
Validate on blur, not on every keystroke Keystroke validation is annoying mid-typing Add event listener to 'blur', not 'input'
Allow valid but unusual addresses Plus addressing, long TLDs, hyphens are all valid Do not over-restrict with regex
Show success state Confirms the email was accepted Green checkmark or border
Make error messages specific "Invalid email" is unhelpful "The domain 'gmial.com' doesn't exist. Did you mean gmail.com?"
Do not block form for DNS timeouts DNS can be slow; do not punish the user Allow through; verify asynchronously
Offer "use a different email" If verification fails, let the user try again easily Clear the field; refocus

Common Validation Mistakes

Mistake Problem Fix
Overly strict regex Rejects valid addresses (plus signs, new TLDs, international characters) Use loose regex; rely on MX and API checks
Client-side only validation Can be bypassed entirely; no security Always validate server-side
Blocking on API timeout User cannot submit form if API is slow Set timeout (3-5 seconds); allow through on timeout
Not checking MX records Accepts addresses at non-existent domains Add server-side MX lookup
Rejecting all free email providers Blocks legitimate users Only block if business email is truly required
Case-sensitive comparison Email local parts are technically case-sensitive, but almost no mail server enforces it Normalise to lowercase for comparison and storage
Not trimming whitespace Leading/trailing spaces cause validation failure email.trim() before validation
Not handling plus addressing "user+tag@example.com" is valid Do not strip or reject the + portion

Extracting and Validating Email Lists from Form Submissions

When you collect email addresses through multiple web forms (signup forms, contact forms, lead magnets, checkout flows, event registrations), export the submissions and upload the files to Email Extractor to extract and deduplicate email addresses across all form sources. This consolidates contacts collected through different forms into a single clean list and removes duplicates from users who submitted multiple forms.

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)