Verify email deliverability on the server
Check email deliverability in Node, request metered verification explicitly, and preserve uncertain results.
On this page
Run the example
Download Nodeverify-email-server.node.mjs
Download the file, then run these commands in its folder using a macOS or Linux terminal. You need Node 18 or later and a secret API key.
npm install @parseapi/sdk
export PARSEAPI_KEY='YOUR_SECRET_API_KEY'
node verify-email-server.node.mjsThe first run uses the included sample. It checks email format; add --verify only when you intend to request a metered delivery check.
Check one mailbox
Check email deliverability in Node when your application needs more than format validation. The function starts with a core email check, requests delivery verification only when you enable it, and returns the original address with the result.
1. Start with core validation
Run the core check without a delivery flag:
node verify-email-server.node.mjs hello@example.comThe reusable function calls the core API and keeps the address, validity, domain flags, and spelling suggestion:
core = readCore(await parse.email(email, { retries: 0 }), email);
Object.assign(result, core);Running without a flag leaves verification.status as not_requested. A spelling suggestion remains in suggestion until the person confirms a correction.
2. Request verification deliberately
Set the shell variable EMAIL_TO_VERIFY to the exact address you intend to check, then add --verify:
node verify-email-server.node.mjs "$EMAIL_TO_VERIFY" --verifyThe flag adds a second request with deep: true after core validation succeeds. That request uses one metered delivery check. Review the Email reference for allowances and billing.
const data = await parse.email(email, { deep: true, retries: 0, timeoutMs: 30000 });To use it in a server handler, import verifyEmail from the download and reuse an SDK client. Call verifyEmail(submittedEmail, parse, true) only when that workflow is meant to request a delivery check.
Invalid core results skip verification. A malformed email can return HTTP 400, which stays a request error. The complete source validates the second response against the same address before reading its deep fields.
3. Read the verdict
const status = catchall === true ? 'catchall'
: deliverable === false ? 'undeliverable'
: deliverable === true && catchall === false ? 'deliverable' : 'unknown';
return { status, deliverable, catchall };If core validation succeeds, a run without --verify has this shape. These are selected fields; delivery is still untested:
{
"input": "hello@example.com",
"email": "hello@example.com",
"valid": true,
"verification": { "status": "not_requested", "deliverable": null, "catchall": null },
"error": null
}Read the delivery decision from verification.status; valid belongs to the core check:
deliverableorundeliverable: a conclusive answer from this check.catchall: the domain accepts arbitrary recipients; this mailbox remains uncertain.unknownorunavailable: no conclusive answer, including missing or emptydeep.request_failed: the request did not complete successfully.
Without the flag, the status is not_requested; rejected core input uses skipped_invalid. Nullable flags remain null, never false.
The complete script disables retries. Verification times out after 30 seconds and retains the core result on failure. A timeout can follow a completed check, so another attempt is an explicit decision and may consume another unit.
Exit 0 means a completed answer, 2 means an error or uncertain verification, and 1 means startup/input failed. JSON includes the error code, HTTP status, and request ID when available.
Attach the result to the exact submitted address and clear it when that address changes. Deliverability is not ownership verification or a guarantee of future inbox placement. The signup email tutorial covers browser-side core checks.
For a CSV or spreadsheet, verify an email list and download the results.
Complete example
The complete Node source. The script reads your API key from PARSEAPI_KEY.
View and copy the complete source
import { pathToFileURL } from 'node:url';
import { parseAPI, ParseAPIError } from '@parseapi/sdk';
function errorResult(error) {
return error instanceof ParseAPIError
? { code: error.code, status: error.status, request_id: error.requestId }
: { code: 'request_failed', status: null, request_id: null };
}
function readCore(data, input) {
if (!data || typeof data.valid !== 'boolean' || typeof data.email !== 'string' ||
data.email.toLowerCase() !== input.toLowerCase()) throw new Error('Unexpected email response.');
for (const field of ['domain_valid', 'disposable', 'role']) {
if (data[field] != null && typeof data[field] !== 'boolean') throw new Error('Unexpected email flag.');
}
return {
email: data.email, valid: data.valid, domain_valid: data.domain_valid ?? null,
disposable: data.disposable ?? null, role: data.role ?? null,
suggestion: typeof data.didyoumean === 'string' ? data.didyoumean : null
};
}
export function readVerification(data) {
if (data.deep == null || (typeof data.deep === 'object' && !Array.isArray(data.deep) && Object.keys(data.deep).length === 0)) {
return { status: 'unavailable', deliverable: null, catchall: null };
}
if (typeof data.deep !== 'object' || Array.isArray(data.deep)) throw new Error('Unexpected verification response.');
const { deliverable = null, catchall = null } = data.deep;
if (![deliverable, catchall].every(value => value === null || typeof value === 'boolean')) {
throw new Error('Unexpected verification flag.');
}
// example:result
const status = catchall === true ? 'catchall'
: deliverable === false ? 'undeliverable'
: deliverable === true && catchall === false ? 'deliverable' : 'unknown';
return { status, deliverable, catchall };
// /example:result
}
export async function verifyEmail(input, parse, verify = false) {
if (typeof verify !== 'boolean') throw new Error('Verification must be explicitly true or false.');
if (typeof input !== 'string' || !input.trim() || input.trim().length > 254 || /[\r\n]/.test(input)) {
throw new Error('Provide one email address of at most 254 characters.');
}
const email = input.trim();
const result = {
input, email: null, valid: null, domain_valid: null, disposable: null, role: null,
suggestion: null, verification: { status: 'not_requested', deliverable: null, catchall: null }, error: null
};
let core;
try {
// example:core
core = readCore(await parse.email(email, { retries: 0 }), email);
Object.assign(result, core);
// /example:core
} catch (error) {
result.error = errorResult(error);
return result;
}
if (!core.valid) {
result.verification.status = 'skipped_invalid';
return result;
}
if (verify !== true) return result;
// Reach this branch only after the caller explicitly requests verification.
try {
// example:request
const data = await parse.email(email, { deep: true, retries: 0, timeoutMs: 30000 });
// /example:request
readCore(data, email);
if (data.valid !== true) {
result.verification = { status: 'unknown', deliverable: null, catchall: null };
} else {
result.verification = readVerification(data);
}
} catch (error) {
result.verification = { status: 'request_failed', deliverable: null, catchall: null };
result.error = errorResult(error);
// A timeout can follow a completed check. Let the caller decide about another attempt.
}
return result;
}
async function main() {
const args = process.argv.slice(2);
if (args.includes('--help')) {
console.log('Use: node verify-email-server.node.mjs [email] [--verify]\nDefault: core validation of hello@example.com. --verify requests a metered check.');
return;
}
const verify = args.includes('--verify');
const values = args.filter(value => value !== '--verify');
if (values.length > 1 || args.filter(value => value === '--verify').length > 1 || values.some(value => value.startsWith('--'))) {
throw new Error('Use: node verify-email-server.node.mjs [email] [--verify]');
}
// Reads PARSEAPI_KEY. Keep the secret key in your server environment.
const parse = parseAPI(undefined, { timeoutMs: 10000, retries: 0 });
const result = await verifyEmail(values[0] ?? 'hello@example.com', parse, verify);
console.log(JSON.stringify(result, null, 2));
if (result.error || ['unavailable', 'unknown', 'catchall'].includes(result.verification.status)) process.exitCode = 2;
}
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
main().catch(error => { console.error(error instanceof ParseAPIError ? 'Could not initialize the API client.' : error.message); process.exitCode = 1; });
}
Need a key? Set up this server example. For every field and parameter, see Email API reference.