Skip to content
Finowl

Node.js · Email breach checks

Check email breaches in Node.js with fetch

In Node.js the email breach check is a natural fit for an Express signup route or a background job that walks your users table. The endpoint responds with breached and an array of breach objects whose keys are PascalCase, such as Title, BreachDate and DataClasses. Because the response is plain JSON, you can map it straight into the shape your frontend or notification template expects.

The script below runs on its own with node check-email.mjs and an optional address argument. It uses node:timers/promises for a clean await on Retry-After, so a rate-limited request pauses without blocking the event loop. Validation failures and bad keys throw immediately, while quota_exceeded is surfaced as an error so a batch job stops spending effort when credits run out.

Steps

  1. 1

    Confirm you are on Node.js 18 or newer by running node --version.

  2. 2

    Export FINOWL_API_KEY with a key from the Finowl dashboard.

  3. 3

    Save the example as check-email.mjs and run node check-email.mjs alex@example.com.

  4. 4

    Map each breach's Title, BreachDate and DataClasses into your UI or email template.

  5. 5

    Call checkEmail from your signup route or job runner once the output looks right.

Check email breaches in Node.js with fetch
// check-email.mjs (Node.js 18+, no dependencies)
import { setTimeout as sleep } from "node:timers/promises";

const API_URL = "https://api.finowl.co/v1/breaches/email";

async function checkEmail(email, attempts = 3) {
  for (let i = 0; i < attempts; i++) {
    const res = await fetch(API_URL, {
      method: "POST",
      headers: {
        Authorization: "Bearer " + process.env.FINOWL_API_KEY,
        "Content-Type": "application/json",
      },
      body: JSON.stringify({ email }),
      signal: AbortSignal.timeout(10_000),
    });
    if (res.ok) return res.json();

    const payload = await res.json().catch(() => ({}));
    const code = payload?.error?.code ?? "unknown_error";
    if (res.status === 400) throw new Error("Invalid email address (" + code + ")");
    if (res.status === 401) throw new Error("Unauthorized: check FINOWL_API_KEY");
    if (res.status === 429 && code === "rate_limited") {
      const seconds = Number(res.headers.get("retry-after")) || 1;
      await sleep(seconds * 1000);
      continue;
    }
    throw new Error("Finowl error " + res.status + ": " + code);
  }
  throw new Error("Still rate limited after " + attempts + " attempts");
}

const result = await checkEmail(process.argv[2] ?? "alex@example.com");

if (!result.breached) {
  console.log(result.email + " does not appear in any known breach");
}
for (const breach of result.breaches) {
  const flag = breach.IsVerified ? "" : " [unverified]";
  console.log(breach.Title + " (" + breach.BreachDate + ")" + flag);
  console.log("  exposed: " + breach.DataClasses.join(", "));
}

IN PRODUCTION

Production tips

  • Run the check after you create the account and respond to the client, for example from a queue job, so a slow lookup never adds latency to the signup request itself.

  • If you process many users, use a small concurrency limit such as p-limit set to a few workers, and keep total throughput below 60 requests per minute for your account.

  • Show breach titles and dates to users but keep the longer Description text for internal views, and treat it as untrusted content that you escape or sanitize before rendering.

Each lookup costs 1 credit, with 100 free credits every month. See pricing and error codes.

A FEW GOOD QUESTIONS

Common questions

Check email breaches in Node.js with fetch

Grab an API key, paste the example and run it. 100 free credits every month.