Verify email deliverability on the server

Check one email on your server, request deliverability explicitly, and return a clear result.

NodeLast tested Get the complete example ↓
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.

Install
npm install @parseapi/sdk
API key
export PARSEAPI_KEY='YOUR_SECRET_API_KEY'
Run
node verify-email-server.node.mjs
Use this example

Check 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:

Terminal
node verify-email-server.node.mjs hello@example.com

The reusable function calls the core API and keeps the address, validity, domain flags, and spelling suggestion:

JavaScript
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:

Terminal
node verify-email-server.node.mjs address-you-intend-to-check@example.com --verify

The 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.

JavaScript
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

JavaScript
const status = catchall === true ? 'catchall'
  : deliverable === false ? 'undeliverable'
  : deliverable === true && catchall === false ? 'deliverable' : 'unknown';
return { status, deliverable, catchall };

Use verification.status:

  • deliverable or undeliverable: a conclusive answer from this check.
  • catchall: the domain accepts arbitrary recipients; this mailbox remains uncertain.
  • unknown or unavailable: no conclusive answer, including missing or empty deep.
  • 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.

Download Node
View and copy the complete source
verify-email-server.node.mjs
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.

Build something else

All tutorials →