# JavaScript & TypeScript SDK

URL: https://email-validation.org/docs/sdks/javascript/
Updated: 2026-10-20

> Documentation index: https://email-validation.org/llms.txt

The official JavaScript and TypeScript SDK has no dependencies: it uses only the standard `fetch`, `AbortSignal` and
Web Crypto APIs and ships its own TypeScript types. It is tested on Node.js 24 and needs Node.js 24 or later.

## Install

```bash
npm install @email-validation/sdk
```

## Validate an address

```ts
import { EmailValidation } from '@email-validation/sdk';

const ev = new EmailValidation({ apiKey: process.env.EV_API_KEY! });

const result = await ev.validate('accepted@sandbox.email-validation.org');
console.log(result.status, result.reason, result.action); // deliverable accepted send
```

Options: `ipAddress`, `timeout` (seconds the check may take, 5–15) and `signal` (an
`AbortSignal`). The result type lists every field with its type; `status`, `reason` and `action` are string unions,
so a `switch` over them is checked by the compiler.

## Handle errors

```ts
import {
  EmailValidationError,
  InsufficientCreditsError,
  RateLimitError,
} from '@email-validation/sdk';

try {
  await ev.validate(address);
} catch (err) {
  if (err instanceof InsufficientCreditsError) {
    // buy credits; nothing was charged
  } else if (err instanceof RateLimitError) {
    console.log(`retry in ${err.retryAfterS}s`);
  } else if (err instanceof EmailValidationError) {
    console.error(err.code, err.status, err.requestId);
  } else {
    throw err;
  }
}
```

Every error extends `EmailValidationError` with `status` (0 when no answer arrived), `code`, `requestId` and
`retryAfterS`. Subclasses: `InvalidRequestError`, `AuthenticationError`, `InsufficientCreditsError`,
`PermissionDeniedError`, `NotFoundError`, `ConflictError`, `RateLimitError`, `ServiceUnavailableError`,
`ServerError` and `ConnectionError`.

## Retries and timeouts

```ts
const ev = new EmailValidation({
  apiKey: process.env.EV_API_KEY!,
  timeoutMs: 30_000, // per attempt
  maxRetries: 2, // after 429 / 503 answers
  maxRetryWaitMs: 10_000, // longer Retry-After values are returned to you instead of waited out
});
```

`429` and `503` answers are retried with backoff, honouring `Retry-After`. A daily key cap is never retried, and a
validation is never retried after a network failure, because it may already have been charged.

## Credits, usage and status

```ts
const credits = await ev.credits(); // { balance, debt, pending, lots }
const usage = await ev.usage({ from: '2026-11-01', to: '2026-11-30', groupBy: 'day' });
const status = await ev.status();
```

## Bulk jobs

```ts
import { readFile } from 'node:fs/promises';

// From a list (JSON, up to 100,000 addresses) …
let job = await ev.bulk.create({ emails: ['accepted@sandbox.email-validation.org', 'catch_all@sandbox.email-validation.org'] });

// … or from a CSV file (up to 100 MB)
job = await ev.bulk.create({ file: await readFile('customers.csv') }, { column: 'email', hasHeader: true });

job = await ev.bulk.wait(job.id, { onProgress: (j) => console.log(j.status, j.processed) });

if (job.status === 'completed') {
  const res = await ev.bulk.results(job.id, { filter: ['deliverable', 'risky'] });
  console.log(await res.text()); // or stream res.body for large files
} else if (job.status === 'ready') {
  // it could not start: job.error_code is e.g. 'insufficient_credits' (buy credits, then ev.bulk.start(job.id))
}
```

`wait()` returns when the job is `completed`, `failed`, `cancelled` or `expired`, or when it is still `ready` with an
`error_code`: it could not start, for example `insufficient_credits`, and does not move on by itself. Pass
`timeoutMs` to give up earlier.

Also: `ev.bulk.get(id)`, `ev.bulk.start(id)` for jobs created with `start: false` (call it before `wait()`), and
`ev.bulk.cancel(id)`.

## Verify webhooks

```ts
import { verifyWebhook, WebhookVerificationError } from '@email-validation/sdk';

// In any fetch-style handler (a Request in, a Response out):
export default {
  async fetch(request: Request, env: { EV_WEBHOOK_SECRET: string }) {
    try {
      const event = await verifyWebhook(await request.text(), request.headers, env.EV_WEBHOOK_SECRET);
      if (event.type === 'bulk.completed') console.log('job done', event.data.job_id);
      return new Response(null, { status: 204 });
    } catch (err) {
      if (err instanceof WebhookVerificationError) return new Response('invalid signature', { status: 400 });
      throw err;
    }
  },
};
```

`verifyWebhook` accepts the raw body as a string or bytes, and headers as a `Headers` object or a plain object such
as Node's `req.headers`. `event.type` can also be `webhook.test`, sent when you press **Send test** in the dashboard:
answer it with a `2xx` like any other event. `signWebhook` creates a valid signature for local tests.

## Constants

`STATUSES`, `REASONS`, `REASONS_BY_STATUS`, `ACTIONS`, `ERROR_CODES`, `WEBHOOK_EVENTS` and
`sandboxAddress(reason)` mirror the API's vocabulary. See [statuses and reasons](https://email-validation.org/docs/statuses/) and
[test addresses](https://email-validation.org/docs/test-addresses/).
