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

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. Run the check

Start with core validation:

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

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 requests one metered delivery check after core validation succeeds. Review the Email reference for allowances and billing. Running without the flag never requests delivery verification.

2. Request verification deliberately

JavaScript
// Reach this branch only after the caller explicitly requests verification.
try {
  const data = await parse.email(email, { deep: true, retries: 0, timeoutMs: 30000 });
  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.
}

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. Spelling suggestions remain in suggestion until the person confirms a correction.

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.

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 {
    core = readCore(await parse.email(email, { retries: 0 }), email);
    Object.assign(result, core);
  } catch (error) {
    result.error = errorResult(error);
    return result;
  }
  if (!core.valid) {
    result.verification.status = 'skipped_invalid';
    return result;
  }
  if (verify !== true) return result;

  // example:request
  // Reach this branch only after the caller explicitly requests verification.
  try {
    const data = await parse.email(email, { deep: true, retries: 0, timeoutMs: 30000 });
    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.
  }
  // /example:request
  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? Create a secret API key. For every field and parameter, see Email API reference.

Build something else

All tutorials →