guides

Server-side integration (Node client)

Use isBusinessEmail from a backend or a mail pipeline: the zero-dependency Node client with timeouts, retries, caching and fail-open checks.

View as Markdown

Backends call the API with a secret key (ibe_live_…). The official client is one file with no dependencies. It runs on Node 18+, Cloudflare Workers, Deno and Bun:

npm install @isbusinessemail/client
import { createClient, isPersonal } from '@isbusinessemail/client';

const ibe = createClient({ apiKey: process.env.ISBUSINESSEMAIL_API_KEY });

const r = await ibe.check('jane@acme.io');
r.category;                               // "business"
r.workspace.google_workspace.detected;    // true
isPersonal(r);                            // false

Secret keys are refused from browsers. For forms in the browser, use a publishable key.

Built for hot paths

The client was made for places like mail ingest, where every message is checked and a slow dependency must never hold up the pipeline:

Behaviour Default Option
Hard timeout per request 2 s timeoutMs
Retry on network errors and 5xx (never on 429) 1 retry retries
In-memory LRU of answers; degraded and deep answers are not cached 10 minutes, 10,000 entries cacheTtlMs, cacheSize
Base URL https://api.isbusinessemail.com baseUrl

Fail open with tryCheck

check() throws an IbeError with status, code (the problem+json error code), retryAfter and requestId. tryCheck() never throws. It returns null when there is no answer, so you fall back to your own data:

async function senderIsPersonal(address, ownListLookup) {
  const r = await ibe.tryCheck(address);
  const fromApi = isPersonal(r);          // true | false | null (unknown / no answer)
  return fromApi ?? (await ownListLookup(address));
}

isPersonal() is true only for the personal category (shared-domain providers such as gmail.com). It returns null for unknown and invalid and when there is no answer, so your fallback decides those cases.

Many addresses at once

checkBatch() keeps your input order and skips anything already in the cache. It sends the rest in chunks of 100 (the batch limit):

const results = await ibe.checkBatch(['bob@gmail.com', 'contoso.com', 'x@mailinator.com']);
results.map((r) => r.category);   // ["personal", "business", "disposable"]

Every item counts toward your daily quota. Use it for backfills, not for one message at a time.

Blocklists and your own lists

is_blocked is true when the domain, or for secret keys the exact address, is on our blocklist. Keep any blocklists of your own in your system and check them first. The API adds what you don’t have: shared-domain providers, disposable and relay services, Workspace and Microsoft 365 detection and lookalike domains.

Limits for a service

New accounts start at 5 checks a second and 100 a day. A mail pipeline usually needs more. Request higher limits from your dashboard and say roughly how many messages a day you process. Use GET /v1/usage (ibe.usage()) to watch your quota. Plan for 429 quota_exceeded: the client doesn’t retry it, and tryCheck() returns null, so the pipeline keeps going on your own data.

Reporting a wrong verdict

await ibe.feedback('jane@acme.io', 'business', 'Our customer, a real company');

Reports are reviewed by a person before any list changes. See Feedback.