Verify email deliverability on the server
Check one email on your server, request deliverability explicitly, and return a clear result.
On this page
Run the example
Download the complete source below. In a macOS or Linux terminal, install the SDK, set your secret API key, then run the file.
npm install @parseapi/sdkexport PARSEAPI_KEY='YOUR_SECRET_API_KEY'node verify-email-server.node.mjsCheck one mailbox
This Node function checks an email's format, then requests deliverability only when you explicitly enable it. It returns one JSON result with the original address and the check's outcome.
Use Node 18 or later. Install the SDK and set your secret PARSEAPI_KEY with the setup above.
1. Start with core validation
Start with core validation:
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
For an address you intend to verify, add --verify:
node verify-email-server.node.mjs address-you-intend-to-check@example.com --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 });Import verifyEmail from the downloaded file to use the same function in your server. Reuse an SDK client and pass true as its third argument only when your workflow authorizes the 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 };Use verification.status:
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.