Lead validator template
- Next.js
- React
Score a lead from its email and phone number: throwaway inboxes, typos, VoIP and temporary numbers.
A Next.js app that checks a signup or contact-form lead before it reaches your CRM. Enter an email and a phone number, and it runs both through APIVerve at once, lists every check with a pass, warning or fail, and scores the lead out of 100. Deploy it as-is to screen leads by hand, or lift the API route into your own form.

What it does
- Catches fake emails
- Flags disposable inboxes, domains with no mail server, and shared addresses like info@ or sales@.
- Spots typos
- Warns when a domain looks misspelled, like gmial.com, and suggests the fix on paid plans.
- Checks the phone number
- Confirms the number is real for its country and flags temporary and VoIP numbers.
- Scores the lead
- Starts at 100, takes 40 off per fail and 15 per warning, so you can sort or filter leads by one number.
- Keeps your key private
- The browser only talks to your own API route, which holds the key and limits each visitor to 10 checks a minute.
How it works
The API route: it calls both APIs, turns each response into checks, and scores the result.
async function callApi(url, params) {
const res = await fetch(`${url}?${new URLSearchParams(params)}`, {
headers: { 'x-api-key': API_KEY },
cache: 'no-store'
});
const body = await res.json().catch(() => null);
if (!res.ok || body?.status !== 'ok') {
throw new Error(body?.error || `APIVerve returned ${res.status}`);
}
return body.data;
}
/**
* Each check is pass, warn or fail. A fail costs 40 points, a warning 15.
* Only free-plan fields are used, so the score works on every plan.
*/
function emailChecks(d) {
// suggestedCorrection (paid plans) can catch a typo hasTypo misses.
const typo = d.hasTypo || !!d.suggestedCorrection;
return [
{ label: 'Deliverable', status: d.isValid && d.isMxValid ? 'pass' : 'fail',
detail: d.isMxValid ? 'Domain accepts mail' : 'Domain has no mail server' },
{ label: 'Not disposable', status: d.isDisposable ? 'fail' : 'pass',
detail: d.isDisposable ? 'Throwaway inbox' : 'Permanent inbox' },
{ label: 'No typo', status: typo ? 'warn' : 'pass',
detail: typo ? (d.suggestedCorrection ? `Did they mean ${d.suggestedCorrection}?` : 'Looks like a misspelled domain') : 'Domain spelled correctly' },
{ label: 'Personal inbox', status: d.isRoleAccount ? 'warn' : 'pass',
detail: d.isRoleAccount ? 'Shared address (info@, sales@)' : 'Reaches a person' },
{ label: 'Work email', status: 'info',
detail: d.isCompanyEmail ? 'Company domain' : d.isFreeEmail ? 'Free provider (Gmail, Yahoo…)' : 'Unknown provider' }
];
}
function phoneChecks(d) {
return [
{ label: 'Valid number', status: d.isValid ? 'pass' : 'fail',
detail: d.isValid ? d.formatted?.international || 'Valid' : 'Not a real number for this country' },
{ label: 'Not disposable', status: d.isDisposable ? 'fail' : 'pass',
detail: d.isDisposable ? 'Temporary number' : 'Permanent number' },
{ label: 'Not VoIP', status: d.isVoip ? 'warn' : 'pass',
detail: d.isVoip ? 'Internet number, easy to create' : 'Carrier-issued' },
{ label: 'Line type', status: 'info',
detail: d.isMobile ? 'Mobile' : (d.type || 'unknown').replace(/_/g, ' ') }
];
}
function score(checks) {
const penalty = checks.reduce((n, c) => n + (c.status === 'fail' ? 40 : c.status === 'warn' ? 15 : 0), 0);
return Math.max(0, 100 - penalty);
}Deploy it
- Get a free API key
The free plan includes 200 credits a month, no card needed. Create your key.
- Deploy it
Click Deploy to Vercel. Vercel copies the repo to your GitHub account and asks for
APIVERVE_API_KEY: paste your key there. - Open your app
Vercel builds it and gives you a live URL, usually in about a minute. Every push to the repo redeploys it.
Try the Email Validator API — the call this template makes
No key required to try it. Get a key to use it in your app.
{
"status": "ok",
"error": null,
"data": {
"email": "[email protected]",
"domain": "myspace.com",
"username": "support",
"isRegexValid": true,
"hasTypo": false,
"isMxValid": true,
"isValid": true,
"isFreeEmail": false,
"isCompanyEmail": true,
"isDisposable": false,
"isRoleAccount": true,
"suggestedCorrection": null,
"riskScore": 10,
"riskLevel": "low"
}
}