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:
| Reason | message |
|---|---|
| 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.
| Value | Meaning |
|---|---|
DELIVERABLE | The domain accepts mail and no significant risk signal was found |
RISKY | The domain accepts mail, but risk signals were found (for example a disposable provider) |
UNDELIVERABLE | The address is invalid, or its risk signals add up to a high risk (for example a likely typo) |
UNKNOWN | No 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.
| Signal | FREE | STARTER | GROWTH | PRO |
|---|---|---|---|---|
applied_level | STANDARD | STANDARD | ADVANCED | PREMIUM |
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
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.