Validate email on a signup form with a server-side API
By the 1-email.com editorial team · Reviewed October 9, 2026 · Editorial policy
Connect a browser form to a local Node.js backend without putting a live API key in browser code.
Download and run locally
Node.js 22+. No package installation is needed. Download every file below into the same directory, then run:
node signup-server.mjs
Open http://127.0.0.1:3000. The browser calls your local backend; the backend calls the API. This example creates no accounts and stores no contacts.
Source files
verify.mjs Download
// Node.js 22+: server code. Only the public sandbox key is supplied by default.
export class VerificationError extends Error {
constructor(status, message, retryAfter = null) {
super(message); this.status = status; this.retryAfter = retryAfter;
}
}
export async function verifyEmail(email, {key = process.env.EEV_API_KEY || 'eev_sandbox_key', fetchFn = fetch} = {}) {
const url = new URL('https://api.easyemailverification.com/v1/verify');
url.searchParams.set('email', email);
let response;
try {
response = await fetchFn(url, {
headers: {'X-API-Key': key, Accept: 'application/json'},
signal: AbortSignal.timeout(30000)
});
} catch {
throw new VerificationError(503, 'No reliable result; review the request before retrying.');
}
if (!response.ok) {
const messages = {402: 'No credits: check your account before retrying.',
429: 'Rate limited: wait before another request; respect Retry-After.'};
throw new VerificationError(response.status, messages[response.status] || 'Verification request failed.', response.headers.get('Retry-After'));
}
let data;
try { data = await response.json(); } catch { throw new VerificationError(502, 'Invalid JSON response.'); }
if (!data || !['valid','invalid','unknown'].includes(data.result)) {
throw new VerificationError(502, 'Unexpected API response; do not classify the address.');
}
return data;
}
export function classify(data) {
if (data.result === 'invalid') return 'invalid';
if (data.result === 'unknown' || data.disposable || data.accept_all || data.safe_to_send !== true) return 'review';
return 'passed_checks';
}
signup-server.mjs Download
// Local teaching example; creates no accounts and stores no submitted addresses.
import {createServer} from 'node:http';
import {readFile} from 'node:fs/promises';
import {verifyEmail, classify} from './verify.mjs';
const form = await readFile(new URL('./signup.html', import.meta.url));
const server = createServer(async (req, res) => {
const send = (status, body) => {
res.writeHead(status, {'Content-Type': 'application/json', 'Cache-Control':'no-store'});
res.end(JSON.stringify(body));
};
if (req.method === 'GET' && req.url === '/') {
res.writeHead(200, {'Content-Type':'text/html; charset=utf-8'}); return res.end(form);
}
if (req.method !== 'POST' || req.url !== '/verify') return send(404, {message:'Not found'});
if ((req.headers['content-type'] || '').split(';')[0] !== 'application/json') return send(415, {message:'Use JSON'});
let body = ''; let bytes = 0;
try {
for await (const chunk of req) {
bytes += chunk.length;
if (bytes > 2048) return send(413, {message:'Request too large'});
body += chunk.toString('utf8');
}
let input;
try { input = JSON.parse(body); } catch { return send(400, {message:'Invalid JSON'}); }
if (typeof input?.email !== 'string' || input.email.length > 254 || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(input.email)) {
return send(400, {message:'Enter one complete address'});
}
const data = await verifyEmail(input.email);
// Keep unknown and catch-all separate from a confirmed rejection.
send(200, {decision: classify(data), result: data.result, reason: data.reason,
did_you_mean: data.did_you_mean || null});
} catch (error) {
if (!res.headersSent) send(error.status || 503, {message:error.message || 'Check unavailable'});
}
});
server.requestTimeout = 35000;
server.listen(3000, '127.0.0.1', () => console.log('Demo: http://127.0.0.1:3000'));
signup.html Download
<!doctype html><html lang="en"><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
<title>Signup verification sandbox demo</title>
<style>body{font:18px system-ui;max-width:650px;margin:3rem auto;padding:1rem}input,button{font:inherit;padding:.6rem}pre{white-space:pre-wrap}</style>
<h1>Try a signup check</h1><p>This local demo creates no accounts. Start with a documented sandbox address.</p>
<form id="form"><label for="email">Email address</label><input id="email" name="email" type="email" required value="valid@sandbox.easyemailverification.com"><button>Check</button></form>
<pre id="result" role="status" aria-live="polite"></pre>
<script>
const form = document.getElementById('form'), output = document.getElementById('result');
form.addEventListener('submit', async event => {
event.preventDefault(); const button = form.querySelector('button'); button.disabled = true;
output.textContent = 'Checking…';
try {
const response = await fetch('/verify', {method:'POST', headers:{'Content-Type':'application/json'},
body:JSON.stringify({email:document.getElementById('email').value})});
const result = await response.json();
output.textContent = `HTTP ${response.status}\n${JSON.stringify(result, null, 2)}`;
} catch { output.textContent = 'Check unavailable. This is not proof of an invalid address.'; }
finally { button.disabled = false; }
});
</script></html>
Test success, uncertainty and failures
Replace the default test address using the command-line argument (Python/Node) or the form field (signup). Use valid, invalid, unknown, catchall, disposable, quota and ratelimit at sandbox.easyemailverification.com. The special typo@gmial.com fixture gives a correction suggestion.
Expect unknown and risk flags to produce review rather than rejection. Quota should stop the request with 402; a rate limit returns 429. Network or JSON failures should never create a valid or invalid mailbox classification.
Before production
Set EEV_API_KEY in the server environment. Keep the endpoint fixed, set a timeout, limit request size and add abuse controls. For a real signup flow add origin/CSRF protection, your own rate limit and consent handling. Do not retry an uncertain request automatically or silently replace a typo.
Primary source: Easy API reference. Download the example instructions.
Ready for real addresses?
We run Easy Email Verification. Create a free account for a live key after testing your integration.
Get a free live key