sortance.js
One file, no dependencies. Copy it into your project and call isValidEmail. It runs on Node 18 or later, Bun, Deno and Cloudflare Workers. It is for your server only: it needs your secret API key, which must never reach a browser.
Install
curl -o sortance.js https://sortance.com/sdk/sortance.js
curl -o sortance.d.ts https://sortance.com/sdk/sortance.d.ts # optional, for TypeScript
export SORTANCE_API_KEY=sk_live_...Create the key on the API keys screen (Settings) of the dashboard. The SDK reads SORTANCE_API_KEY by default.
Use
const sortance = require('./sortance');
if (!(await sortance.isValidEmail(email))) {
return res.status(400).json({ error: 'Use an address that can receive mail.' });
}
const provider = await sortance.getEmailProvider(email); // 'gmail', 'outlook', … or null
// Work addresses only (no Gmail, Outlook, Yahoo, Apple or other free providers)
if (!(await sortance.isBusinessEmail(email))) {
return res.status(400).json({ error: 'Use your work email address.' });
}
// Block a competitor's domain for your account only
await sortance.blockDomains(['competitor.example'], 'no trials');In an ES module project ("type": "module"), save the file as sortance.cjs instead and import it: import { isValidEmail } from './sortance.cjs'. Deno and Workers bundlers take the same .cjs file. For a second key or different settings, create a client:
const client = sortance.createClient({
apiKey: process.env.OTHER_KEY,
timeoutMs: 3000, // default 5000
failOpen: true, // default; see below
onError: (err, helper) => console.warn(helper, err.code),
});Drop-in functions
These keep the names and return values of a typical email-utils.js, so replacing one is a change to the import line. They never throw on a Sortance outage: a validation service that is down must not block your sign-ups. That includes a wrong or revoked key, so set onError to log what was swallowed, or pass failOpen: false to make them throw instead.
| Function | Returns | Calls | If Sortance cannot be reached |
|---|---|---|---|
isValidEmail(email) | boolean | POST /v1/check | Local syntax check (true when well formed) |
isDisposableEmail(email) | boolean | POST /v1/domains/check | false |
isDomainDisposable(domain) | boolean | POST /v1/domains/check | false |
getEmailProvider(email) | 'gmail' | 'outlook' | … | 'unknown' | null | POST /v1/check | null |
isBusinessEmail(email) | boolean | POST /v1/check | true (an outage must not turn away a work address) |
emailType(email) | 'business' | 'free' | 'role' | 'disposable' | 'relay' | 'unknown' | POST /v1/check | 'unknown' |
isValidContactListEmail(email) | boolean | none, runs locally | not applicable |
isValidEmail is true when the syntax is valid, the domain has mail servers, and the domain is neither disposable nor on your domain or address blocklist. It does not open an SMTP connection; for a bounce probability, use verifyEmail. isDisposableEmail is true for Sortance’s disposable list and for your own blocklist, and false for anything on your allowlist. isValidContactListEmail is a strict local check for list imports: it also rejects +tags, all-digit names and common provider typos such as gmial.com.
emailType returns the address’s email_type: business (Google Workspace, Microsoft 365 or the company’s own mail server), free (Gmail, Outlook, Hotmail, Yahoo, Apple iCloud and other consumer providers, sometimes called social email), role (a shared mailbox such as info@ or sales@), disposable, relay (forwarding services such as Apple Hide My Email) or unknown (no mail servers, or nothing matched). When several apply, the first match in this order wins: disposable, relay, role, free, business. So info@gmail.com is role. isBusinessEmail is true only for business. Broken syntax answers locally, with no call: false and 'unknown'.
Full answers
These return the API’s JSON and throw a SortanceError (with status, code and traceId) when a call fails.
| Function | Calls | What it is for |
|---|---|---|
checkEmail(email) | POST /v1/check | Syntax, MX, disposable, your domain and address rules and email_type, with reason codes. Free; rate-limited. |
verifyEmail(email, context?) | POST /v1/verify | Bounce probability and recommendation. Counts against your monthly quota. |
screenEmail(email, name?) | POST /v1/screen | Typo, placeholder and fake-name screening. Counts against the screening quota. |
checkDomain(domain) | POST /v1/domains/check | What happens to one domain on your account: blocked, allowed, disposable, suspected or none. |
blockDomains(domains, note?) | POST /v1/domains/blocklist | Add to your blocklist. Batches of 1,000 are sent for you. |
unblockDomains(domains) | DELETE /v1/domains/blocklist | Remove from your blocklist. |
allowDomains(domains, note?) | POST /v1/domains/allowlist | Add to your allowlist. |
unallowDomains(domains) | DELETE /v1/domains/allowlist | Remove from your allowlist. |
listBlockedDomains() | GET /v1/domains/blocklist | Your blocklist. |
listAllowedDomains() | GET /v1/domains/allowlist | Your allowlist. |
checkAddress(email) | POST /v1/addresses/check | What happens to one address on your account: its rule, your domain rule and the effective outcome. |
blockAddresses(emails, note?) | POST /v1/addresses/blocklist | Block single addresses. Batches of 1,000 are sent for you. Stored as a keyed hash and a masked form. |
unblockAddresses(emails) | DELETE /v1/addresses/blocklist | Remove addresses from your blocklist. |
allowAddresses(emails, note?) | POST /v1/addresses/allowlist | Allow single addresses past the disposable lists and your domain blocks. |
unallowAddresses(emails) | DELETE /v1/addresses/allowlist | Remove addresses from your allowlist. |
listBlockedAddresses(page?) | GET /v1/addresses/blocklist | Your blocked addresses, masked (j•••@acme.com), newest first. Pass { limit, cursor } for the next page. |
listAllowedAddresses(page?) | GET /v1/addresses/allowlist | Your allowed addresses, masked, paged the same way. |
verifyList(csv, { name, policy, allowPartial }?) | POST /v1/bulk/jobs, PUT …/upload, POST …/start | Bulk verification: uploads a CSV (string or Buffer, up to 50 MB and 100,000 rows) and starts it. Returns { jobId, job }. Each unique address counts as one verification. |
waitForList(jobId, { pollMs, timeoutMs }?) | GET /v1/bulk/jobs/:id | Polls (every 5 s by default) until the job is done, failed, cancelled or paused on quota. Returns the job. |
downloadList(jobId, segment?) | GET /v1/bulk/jobs/:id/results.csv | One results file as a string: all (default), deliverable, undeliverable, review, business, or allow / deny with a policy. |
The three list functions never fail open: a bulk job has no safe default, so an outage, a missing email column (no_email_column) or a list that does not fit this month’s quota (402 quota_exceeded, unless allowPartial) throws.
const { jobId } = await sortance.verifyList(fs.readFileSync('leads.csv'), { name: 'leads.csv' })
const job = await sortance.waitForList(jobId)
if (job.state === 'done') {
fs.writeFileSync('leads.deliverable.csv', await sortance.downloadList(jobId, 'deliverable'))
}Reason codes are listed in the email check reference, and blocklist behaviour in Blocklists.
What the SDK sends
The address always travels in a POST body, never in a URL, so it does not end up in access logs. Sortance does not store raw addresses, with one exception you opt into: a CSV sent with verifyList and its results are kept as files for 7 days, then deleted (see trust).