JavaScript & TypeScript SDK
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
npm install @email-validation/sdkValidate an address
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 sendOptions: 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
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
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
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
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
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 and test addresses.