Skip to Content
GuidesValidate Email

Validate Email

This guide covers best practices for integrating VerifNow’s email validation into your application — from simple form validation to advanced use cases.


Basic email validation

Send a POST request to /api/v1/validate/email with the value to validate:

POST https://api.verifnow.io/api/v1/validate/email X-API-KEY: your_api_key_here Content-Type: application/json { "value": "user@example.com" }

Example response

{ "valid": true, "message": "Email address is valid but has risk factors", "normalizedValue": "user@example.com", "originalValue": "user@example.com", "validationLevel": "PREMIUM", "emailDetails": { "signals": { "syntax_valid": true, "mx_valid": true, "typo_detected": false, "suggested_domain": null, "disposable": false, "role_based": false, "free_provider": false, "domain_age_days": 11554, "mx_provider": "google", "mx_quality_score": 0.9 }, "risk_score": 30, "risk_level": "MEDIUM", "deliverability": "RISKY", "applied_level": "PREMIUM" } }

What valid means

valid answers one question: can this address receive mail? It is false only for a hard fact:

Reasonmessage
Empty input"Email cannot be empty"
Invalid syntax"Invalid email format"
Domain has neither MX nor A records"Domain has no valid MX or A records"
Domain is on the blocklist"Domain reputation is blocked"

Everything else (typos, disposable providers, role addresses, suspicious mail servers, young domains) is a signal. Signals raise risk_score and shape deliverability, but they never turn valid to false. The verdict is the same on every plan: a higher plan returns more signals, never a different valid for the same address.

That leaves the acceptance decision with you. See Acceptance strategy below.


Understanding the email signals

syntax_valid

Validates the email against standard syntax rules. An invalid syntax will result in valid: false.

mx_valid

Checks whether the email domain can receive mail: it must resolve with MX (mail exchange) records, or fall back to an A record. When it has neither, mx_valid is false and so is valid.

disposable

Detects whether the address is from a known disposable/temporary email provider (e.g., Mailinator, Guerrilla Mail, 10MinuteMail).

// Example: disposable email detected { "valid": true, "emailDetails": { "signals": { "disposable": true }, "risk_level": "HIGH" } }

role_based

Flags email addresses that are typically shared by multiple people or auto-generated:

  • info@, admin@, support@
  • noreply@, no-reply@
  • postmaster@, webmaster@

A role address is not rejected: valid stays true when the domain accepts mail, and role_based raises the risk score. On a B2B signup form, contact@ or info@ is often the only mailbox a small company has, so blocking them is a decision to make deliberately, not a default.

typo_detected & suggested_domain

If VerifNow detects a likely typo (e.g., gmal.com instead of gmail.com), typo_detected is true and suggested_domain contains the correction:

{ "valid": true, "message": "Email address is valid but unlikely to be deliverable", "emailDetails": { "signals": { "mx_valid": true, "typo_detected": true, "suggested_domain": "gmail.com" }, "deliverability": "UNDELIVERABLE" } }

A typo domain can have mail records of its own, so valid can still be true. Check typo_detected whatever the value of valid, and prompt the user to correct their email before submitting.

domain_age_days

The age of the email domain in days. Very young domains may indicate spam or fraud.

mx_provider & mx_quality_score

Identifies the mail provider (e.g., google, apple, microsoft) and assigns a quality score between 0 and 1.

risk_score & risk_level

A 0–100 risk score and a categorical risk level (LOW, MEDIUM, HIGH). Lower scores mean less risk.

deliverability

An overall assessment built from the signals that were checked at the applied depth. It is returned on every plan.

ValueMeaning
DELIVERABLEThe domain accepts mail and no significant risk signal was found
RISKYThe domain accepts mail, but risk signals were found (for example a disposable provider)
UNDELIVERABLEThe address is invalid, or its risk signals add up to a high risk (for example a likely typo)
UNKNOWNNo assessment could be made. Not returned by email validation today; treat it as “not assessed” if it appears

UNDELIVERABLE does not imply valid: false. An address at a real domain with a likely typo is valid: true and UNDELIVERABLE: it can receive mail, but probably not the mail your user meant to get.


Validation depth by plan

Not every signal is returned on every plan. The depth actually applied is echoed back in emailDetails.applied_level, so you can always confirm which checks ran.

SignalFREESTARTERGROWTHPRO
applied_levelSTANDARDSTANDARDADVANCEDPREMIUM
syntax_valid✅✅✅✅
mx_valid (MX + A)✅✅✅✅
typo_detected & suggested_domain✅✅✅✅
role_based✅✅✅✅
disposable✅✅✅✅
risk_score✅✅✅✅
deliverability✅✅✅✅
free_provider❌❌✅✅
domain_age_days❌❌✅✅
mx_provider & mx_quality_score❌❌✅✅
risk_level❌❌✅✅

FREE and STARTER run identical checks — they differ only in monthly quota (250 vs 10,000). GROWTH adds MX pattern analysis, domain reputation and the categorical risk_level. Those extra signals can move risk_score and deliverability, but never valid: the same address gets the same verdict on every plan.

Write your integration against applied_level rather than against the plan name. If a field you rely on is missing, check that value first — it tells you exactly which depth was applied.


Acceptance strategy

Choose the right acceptance strategy based on your use case:

Strict — Block any risky or undeliverable email:

function isEmailAccepted(result) { return result.valid && result.emailDetails.deliverability === 'DELIVERABLE' && !result.emailDetails.signals.disposable && !result.emailDetails.signals.role_based }

Best for: paid signups, enterprise SaaS, financial services — note that it also turns away small businesses whose only address is contact@


Integration examples

React form validation

import { useState } from 'react' async function validateEmail(email: string) { const response = await fetch('/api/validate-email', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ value: email }), }) return response.json() } export function SignupForm() { const [email, setEmail] = useState('') const [error, setError] = useState('') const [suggestion, setSuggestion] = useState('') const [loading, setLoading] = useState(false) async function handleSubmit(e: React.FormEvent) { e.preventDefault() setLoading(true) setError('') setSuggestion('') const result = await validateEmail(email) if (result.emailDetails?.signals?.typo_detected && result.emailDetails?.signals?.suggested_domain) { const [local] = email.split('@') setSuggestion(`Did you mean ${local}@${result.emailDetails.signals.suggested_domain}?`) } if (!result.valid) { setError('Please enter a valid, reachable email address.') setLoading(false) return } // Continue with form submission... setLoading(false) } return ( <form onSubmit={handleSubmit}> <input type="email" value={email} onChange={e => setEmail(e.target.value)} placeholder="your@email.com" /> {suggestion && <p className="text-yellow-600">{suggestion}</p>} {error && <p className="text-red-600">{error}</p>} <button type="submit" disabled={loading}> {loading ? 'Validating...' : 'Sign Up'} </button> </form> ) }

Next.js API route (server-side proxy)

// app/api/validate-email/route.ts import { NextRequest, NextResponse } from 'next/server' export async function POST(req: NextRequest) { const body = await req.json() const { value } = body if (!value) { return NextResponse.json({ error: 'value is required' }, { status: 400 }) } const response = await fetch('https://api.verifnow.io/api/v1/validate/email', { method: 'POST', headers: { 'X-API-KEY': process.env.VERIFNOW_API_KEY!, 'Content-Type': 'application/json', }, body: JSON.stringify({ value }), }) const data = await response.json() return NextResponse.json(data) }

Security tip: Always proxy API calls through your backend to keep your API key secret. Never call the VerifNow API directly from client-side JavaScript.

Last updated on