Skip to content

Email Validation API in Node.js: Syntax, MX and SMTP Checks

An email can look perfectly valid and still be a poor destination. alex@example.com passes a basic pattern check, but that tells you nothing about the domain’s mail servers, whether the mailbox appears reachable, whether the address is disposable, or whether the receiving server refuses to reveal mailbox status.

That is why a useful email validation API should return evidence—not merely true or false.

In this guide, we will build a Node.js validation workflow around four layers: syntax, domain and MX readiness, SMTP/mailbox signals, and address risk. We will also handle catch_all and unknown honestly, choose between fast and deep checks, and add a bulk-verification path for list cleaning.

The goal is risk reduction, not a guarantee. Verification cannot promise that a future message will be accepted, delivered, or placed in the inbox.

What an email validation API can actually tell you

“Valid email” is an overloaded phrase. It can mean at least four different things:

  1. The string has a plausible email format. A local parser can catch spaces, missing @ characters and other obvious input errors.
  2. The domain can receive mail. DNS MX records—or a valid fallback mail host—show that the domain is configured for email.
  3. The receiving infrastructure responds. An SMTP connection may reveal whether the server is reachable and whether it accepts the recipient probe.
  4. The address carries risk signals. Disposable providers, role accounts, catch-all domains, full mailboxes and other conditions affect how you should use the address.

These layers are related, but they are not interchangeable. A syntax-valid address can sit on a nonexistent domain. A domain with valid MX records can contain a nonexistent mailbox. A server can accept every recipient during an SMTP conversation and reject invalid mail later. A mailbox that is reachable today can be disabled tomorrow.

Treat the response as a snapshot of observable signals.

Quick mode or power mode?

The API used in this tutorial exposes two modes for single-address checks.

Mode Best fit Typical signals Important limitation
quick Signup forms and interactive flows Syntax, disposable status, role account, domain and MX readiness Does not establish individual mailbox existence
power CRM imports, campaign preparation and higher-risk decisions Quick-mode checks plus deeper SMTP/mailbox and catch-all signals where detectable Can take longer and may still return unknown

Use quick when a person is waiting for your form to respond and you mainly need to block obvious problems. Use power when the extra evidence justifies the latency.

Do not use quick mode to claim that an individual inbox exists. In a domain-level check, a plausible address at a healthy domain can look valid even when that specific mailbox does not exist.

Call the email verification API from Node.js

Modern Node.js releases include a stable global fetch(), so the integration does not require an HTTP package. Store the RapidAPI key in an environment variable; never commit it to source control.

const RAPIDAPI_HOST = "email-verify-api1.p.rapidapi.com";

export async function verifyEmail(email, mode = "power") {
  const response = await fetch(
    `https://${RAPIDAPI_HOST}/api/v1/verify`,
    {
      method: "POST",
      headers: {
        "content-type": "application/json",
        "x-rapidapi-host": RAPIDAPI_HOST,
        "x-rapidapi-key": process.env.RAPIDAPI_KEY,
      },
      body: JSON.stringify({ email, mode }),
      signal: AbortSignal.timeout(mode === "quick" ? 5_000 : 35_000),
    },
  );

  const payload = await response.json();
  if (!response.ok) {
    throw new Error(payload.error ?? `Verification failed: ${response.status}`);
  }
  return payload.data;
}

const result = await verifyEmail("person@example.com", "power");
console.log(result.status, result.overall_score, result.is_safe_to_send);

RapidAPI authentication uses two headers: X-RapidAPI-Key identifies the application, and X-RapidAPI-Host identifies the subscribed API. Copy the exact host from the marketplace code snippet. The API route accepts quick or power; the current wrapper defaults to power when mode is omitted.

Read the response as evidence

A Power-mode result can include fields such as these:

{
  "status": "safe",
  "overall_score": 98,
  "is_safe_to_send": true,
  "is_valid_syntax": true,
  "mx_accepts_mail": true,
  "can_connect_smtp": true,
  "is_deliverable": true,
  "is_disposable": false,
  "is_role_account": false,
  "is_catch_all": false,
  "has_inbox_full": false,
  "is_disabled": false,
  "is_spamtrap": false,
  "mx_records": ["mx.example.net"],
  "verification_mode": "power"
}

The values above illustrate the documented response shape. Live results depend on the address and on what its receiving system reveals.

  • Input evidence: is_valid_syntax, parsed username and domain.
  • Domain evidence: mx_accepts_mail and mx_records.
  • Mailbox evidence: can_connect_smtp, is_deliverable, has_inbox_full and is_disabled.
  • Risk evidence: is_disposable, is_role_account, is_catch_all and is_spamtrap.
  • Summary: status, overall_score and is_safe_to_send.

The summary fields are convenient, but keep the underlying signals. They make audits and future policy changes possible.

Build a tri-state decision policy

The most common integration mistake is forcing every result into a Boolean. Real SMTP evidence is not always conclusive. A better application-level contract is allow, review, or block.

export function decideEmail(result) {
  const status = String(result?.status ?? "unknown").toLowerCase();

  if (
    result?.is_valid_syntax === false ||
    result?.is_disposable === true ||
    result?.is_spamtrap === true ||
    ["invalid", "disabled", "disposable", "spamtrap"].includes(status)
  ) {
    return {
      decision: "block",
      reason: `high-risk verification result: ${status}`,
    };
  }

  if (
    result?.is_safe_to_send === true &&
    result?.is_catch_all !== true &&
    status === "safe"
  ) {
    return { decision: "allow", reason: "positive mailbox and risk signals" };
  }

  return {
    decision: "review",
    reason: `verification was not conclusive: ${status}`,
  };
}

This is a conservative example, not a universal rule. A newsletter signup, password-recovery flow and B2B sales import have different costs for false positives and false negatives. For a signup form, review might mean accepting registration but requiring a confirmation link. For a campaign import, it could mean placing the address in a lower-priority segment.

Why unknown is not invalid

SMTP servers do not owe verifiers a definitive mailbox answer. Firewalls, greylisting, temporary load, rate limits and anti-enumeration controls can all reduce available evidence. RFC 5321 also allows operators to restrict address-verification behavior for security reasons.

  • invalid means the available evidence supports rejection;
  • unknown means the verifier could not establish enough evidence;
  • catch_all means the domain may accept arbitrary recipients, so a specific inbox is not proven.

Converting all three into “bad email” destroys useful information and can reject legitimate users. Keep the categories separate and set policy at the application layer. Recheck a temporary unknown later with exponential backoff rather than looping immediately.

Put verification behind a resilient service

External network calls fail. A vendor timeout should not automatically become a user-visible rejection.

export async function assessEmail(email, options = {}) {
  const mode = options.mode ?? "quick";

  try {
    const evidence = await verifyEmail(email, mode);
    return {
      checked: true,
      ...decideEmail(evidence),
      evidence,
      checkedAt: new Date().toISOString(),
    };
  } catch {
    return {
      checked: false,
      decision: "review",
      reason: "verification service unavailable",
      errorCode: "EMAIL_VERIFICATION_UNAVAILABLE",
      checkedAt: new Date().toISOString(),
    };
  }
}

This design fails into review instead of pretending a network error proves an address is invalid.

In production, also consider:

  • hashing or redacting addresses in logs;
  • short-lived caching based on your risk model;
  • request correlation IDs for support and audits;
  • per-user rate limits to prevent address enumeration;
  • explicit retry rules for 429 and transient 5xx responses;
  • metrics for latency, upstream failures and status distribution.

Verify a list with the bulk workflow

The API exposes an asynchronous bulk path. The public wrapper accepts 10 to 50 valid, unique addresses per task. It trims entries and removes duplicates before submission.

export async function createBulkVerification(emails, name = "crm-import") {
  const response = await fetch(
    `https://${RAPIDAPI_HOST}/api/v1/verify/bulk`,
    {
      method: "POST",
      headers: {
        "content-type": "application/json",
        "x-rapidapi-host": RAPIDAPI_HOST,
        "x-rapidapi-key": process.env.RAPIDAPI_KEY,
      },
      body: JSON.stringify({ emails, name }),
      signal: AbortSignal.timeout(65_000),
    },
  );

  const payload = await response.json();
  if (!response.ok) throw new Error(payload.error ?? "Bulk task failed");
  return payload;
}

The task identifier is returned inside the response’s data object. Poll the result endpoint with a delay and an overall deadline:

const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));

export async function waitForBulkResult(taskId, maxAttempts = 30) {
  for (let attempt = 0; attempt < maxAttempts; attempt += 1) {
    const response = await fetch(
      `https://${RAPIDAPI_HOST}/api/v1/verify/bulk/${encodeURIComponent(taskId)}`,
      {
        headers: {
          "x-rapidapi-host": RAPIDAPI_HOST,
          "x-rapidapi-key": process.env.RAPIDAPI_KEY,
        },
        signal: AbortSignal.timeout(35_000),
      },
    );

    const payload = await response.json();
    if (!response.ok) throw new Error(payload.error ?? "Bulk lookup failed");
    const status = payload.data?.status;
    if (status === "completed") return payload.data;
    if (!["waiting", "running"].includes(status)) {
      throw new Error(`Bulk task stopped with status: ${status}`);
    }
    await delay(Math.min(2_000 * 2 ** attempt, 30_000));
  }
  throw new Error("Bulk verification did not finish before the deadline");
}

Do this polling in a job worker, not during a web request that must stay open. Persist the task ID, current state and timestamps so a worker can resume without creating a duplicate task.

Verification belongs in a larger deliverability system

An email verification API can reduce obvious data-quality problems, but it cannot replace the rest of your sending discipline.

  • Consent and lawful use: permission to send is not inferred from deliverability.
  • Confirmation: double opt-in proves that a user controls the inbox at that moment.
  • Suppression: never re-add addresses that unsubscribed or hard-bounced merely because a later check looks positive.
  • Bounce processing: treat provider events as fresher evidence than an earlier verification snapshot.
  • Reputation and authentication: SPF, DKIM and DMARC protect sending identity; they solve a different problem.
  • Engagement policy: a reachable mailbox is not necessarily an interested recipient.

The best workflow combines verification evidence with first-party events and an explicit business policy.

Common mistakes to avoid

Using a regular expression as “verification”

A regex catches obvious input errors. It cannot query DNS, observe SMTP behavior or identify a disposable provider.

Blocking every role account

Addresses such as support@, billing@ and security@ can be entirely legitimate. Role status is context, not automatic proof of abuse.

Treating catch-all as safe

A catch-all domain can accept mail sent to nonexistent recipients. Route it to review or a policy-specific segment.

Retrying deep verification in a tight loop

Repeated SMTP probes increase cost and can resemble abusive enumeration. Back off, cap attempts and respect rate limits.

Logging raw addresses everywhere

Email addresses are personal data. Minimize storage, restrict access, redact logs and define retention periods.

Test the workflow safely

Start with controlled addresses that you own. Exercise each application path:

  • a well-formed address on a working domain;
  • malformed input rejected locally;
  • a disposable address, if your policy blocks it;
  • an address that returns unknown or catch_all;
  • upstream timeout and non-2xx responses;
  • a bulk task containing duplicates and fewer than ten valid unique addresses.

Assert your decision contract rather than a vendor-specific score. A test should verify that unknown becomes review, not that an external score stays unchanged forever.

Try the API

The Email Verification API on RapidAPI supports single quick or power checks plus asynchronous bulk verification. The Basic plan includes 50 requests per month, enough to test response handling and decision rules before choosing a paid plan.

Keep your first integration small: verify controlled addresses, inspect the full evidence, and tune the allow/review/block policy for your use case.

Frequently asked questions

What is the difference between email validation and email verification?

Teams often use the terms interchangeably. A useful distinction is that validation checks structure and domain readiness, while verification attempts deeper mailbox-level evidence. Good APIs return both layers in one structured response.

Can an email validation API guarantee delivery?

No. A result is a point-in-time estimate based on available evidence. Provider policy, rate limits, mailbox state, sender reputation and content can all affect later delivery.

Is SMTP email verification always conclusive?

No. Servers may limit recipient probing, accept all addresses, time out or reveal too little evidence. Preserve unknown and catch_all instead of converting them to a Boolean.

Which mode should I use for signup forms?

Quick mode is usually the better latency fit. Combine it with a confirmation email when inbox ownership matters. Use power mode asynchronously when deeper evidence is worth the delay.

How many addresses can the bulk endpoint accept?

This RapidAPI wrapper accepts 10–50 valid, unique addresses per task. Split larger collections into multiple jobs and retrieve each task asynchronously.

Sources and further reading

Disclosure: StadiaSoft publishes the Email Verification API discussed in this guide. The technical limitations and decision guidance above apply regardless of provider.

Build with StadiaSoft

Explore practical APIs and software products designed for real workflows, with clear documentation and room to scale.

Explore live products