Spaw Node.js SDK: verify emails from JavaScript

updated · September 3, 2026

The official spaw package for Node.js 18 and newer wraps every Spaw endpoint with typed results, TypeScript declarations and one error class per API code.

Mechanism
Official client, zero dependencies, ESM
Status
Built, awaiting publication on npm as spaw
Auth
Secret API key passed to the constructor
Cost
1 credit per answered lookup, undeliverable answers free
Requires
Node.js 18 or newer

The Node.js client is a thin wrapper over the REST API: one method per endpoint, the API's data and meta blocks returned exactly as documented, TypeScript declarations, and an error class for every code. It has no dependencies, because it uses the fetch built into Node.js 18 and newer, and it is an ES module.

The package is built and will be published on npm as spaw. Until the listing is live, a plain fetch against the API reference does the same job. For code that runs in a browser, use the form helper with a publishable key instead; the secret key this client needs must never ship to a page.

Install and verify one address

  1. Create a secret key on the API keys page and put it in an environment variable.
  2. Install the package once it is published:
npm install spaw
  1. Verify an address:
import Spaw from 'spaw';

const spaw = new Spaw({ apiKey: process.env.SPAW_API_KEY });

const result = await spaw.verifyEmail('[email protected]');
console.log(result.data.deliverable);  // "deliverable", "risky" or "undeliverable"
console.log(result.data.reason);       // e.g. "catch_all", or null when deliverable
console.log(result.data.risk_score);   // 0–100, a sum of published weights
console.log(result.meta.credits_used); // 1 for a fresh answered lookup, 0 otherwise

result.data carries all 27 documented fields, including did_you_mean, mx_provider and smtp_checked_at, and every reason value is explained on the verdict reasons pages.

Everything the client does

Method Endpoint
verifyEmail(email, options) POST /email, with callbackUrl and callbackSecret for async settling
verifyEmails(emails) POST /email/batch, up to 50
domain(domain) GET /email/domain/{domain}
createBulkJob(emails, { webhookUrl }), getBulkJob(id), cancelBulkJob(id), downloadBulkResults(id, { variant }) the bulk endpoints, up to 100,000 addresses
listSuppressions({ page }), addSuppressions(emails), removeSuppression(id) the suppression list
reportFeedback(items), feedbackSummary() delivery outcomes and measured accuracy
account() the live credit balance
validatePhone(phone, { country }), lookupIp(ip) the phone and IP endpoints

A batch keeps the API's shape: data.results holds one { data, meta } per address in input order, and meta.stopped_reason is set instead of throwing when the balance runs out part-way, so paid results are never lost. A bulk job returns its webhook_secret once; keep it, because it signs the completion webhook.

Handle errors

Every failure throws a subclass of SpawError carrying code, message, httpStatus and requestId; no credits are spent on a failed request.

import Spaw, { InsufficientCreditsError, RateLimitedError, SpawError } from 'spaw';

try {
    const result = await spaw.verifyEmail('[email protected]');
} catch (error) {
    if (error instanceof InsufficientCreditsError) {
        // nothing ran and nothing was billed
    } else if (error instanceof RateLimitedError) {
        // error.retryAfter seconds, from the Retry-After header
    } else if (error instanceof SpawError) {
        console.error(error.code, error.httpStatus, error.requestId);
    }
}

AuthenticationError maps UNAUTHENTICATED, ValidationError maps VALIDATION_FAILED with errors keyed by field, NotFoundError covers a bulk job or suppression entry that is not on the account, and SpawConnectionError means the API could not be reached. Pass maxRetries: 2 to the constructor to retry 429 and 5xx answers automatically, honouring Retry-After.

What to do with the verdict

Branch on deliverable. Keep deliverable addresses, drop undeliverable ones, and route risky ones by reason: refuse disposable and likely_typo, decide role per use case, and treat catch_all as an address whose mailbox cannot be confirmed rather than a bad one, using mailbox_confidence as the threshold. When did_you_mean is set, offer it back to the user.

Cost

One credit per fresh deliverable or risky verdict. Undeliverable verdicts, invalid input, repeats within seven days and the spaw.test test addresses cost nothing, and every account gets 10 free lookups a month. Point your test suite at [email protected] and its siblings: fixed answers, no credits, never logged.

Related

markdown version: /integrations/node-sdk.md

Verify addresses the same way

The Spaw API runs every check described here on each lookup, with an SMTP handshake that never sends mail. 10 free lookups a month, no card required.

Get your API key

More integrations