truval
v0.4.4
Published
Email verification API and API infrastructure for AI agents
Maintainers
Readme
truval.dev sdk
Production email verification built for apps and AI agents — one HTTP API, rich structured results (not just “valid/invalid”), and a small MIT-licensed client so you can ship with confidence.
truval.dev verifies addresses with layered checks (syntax, disposable signals, MX, SMTP where possible) and returns ordinal confidence, catch-all and smtp_blocked signals, and actionable errors so your product — or your LLM — can make the right call.
Why this SDK is open source
- Trust — Inspect exactly what runs in your stack: thin
fetchwrapper, no hidden telemetry in the client. - Agents — Public code trains and assists coding tools; copy-paste examples “just work” in Cursor, Copilot, and Claude.
- Integration — TypeScript types match the live API; pair with our OpenAPI and docs for audits and codegen.
The verification engine runs on truval.dev infrastructure (EU-hosted API). This repo is the official client — the part you embed in your app.
Features
| Capability | What you get |
|------------|----------------|
| Single verify | verify() or createClient().verify() → full VerifyResult |
| Batch | verifyBatch() — auto-chunks to 50 emails per request, sequential calls to avoid rate-limit spikes; results in input order |
| Streaming | verifyStream() — NDJSON stream, results as they finish (completion order, not input order) |
| Async + webhook | verifyAsync() — 202 + job_id; result POSTed to your HTTPS webhook |
| Rate limits | Optional retryOnRateLimit using API reset_at |
| Management API | createManagementClient() — account, keys, usage, Stripe billing portal (provisioning key) |
| Runtime | Global fetch (Node 18+, Bun, Deno, Cloudflare Workers, etc.). API keys must stay server-side — see warning below. |
Warning — never expose API keys in the browser
sk_live_...andsk_mgmt_...are secret. Do not putTRUVAL_API_KEY(or provisioning keys) in React/Vue/Svelte client components, public Next.js"use client"bundles, or any code shipped to the browser — keys can be extracted and your quota billing and account are at risk.
Call this SDK only from trusted server environments: HTTP APIs, Server Actions / Route Handlers / API routes, Edge Functions, Workers, cron jobs, etc. If the browser must trigger a check, add a backend endpoint you control that holds the key and proxies to truval.dev (or use async verify + webhook so the client never sees the key).
truval.dev does not offer a separate “publishable” or domain-restricted browser key; treat standard keys like database passwords.
Installation
npm install truvalpnpm add truval
yarn add truvalTypeScript: types are included. Node: use v18+ (native fetch).
Quick start
import { createClient } from 'truval'
import type { VerifyResult } from 'truval'
const truval = createClient(process.env.TRUVAL_API_KEY!, {
retryOnRateLimit: true,
maxRetries: 3,
})
const result: VerifyResult = await truval.verify('[email protected]')
console.log(result.status, result.confidence, result.smtp_blocked)Get an API key at dash.truval.dev.
TypeScript types
Use the exported types in your own signatures without digging through node_modules:
import type { VerifyResult, TruvalError, ApiError, AsyncVerifyResult } from 'truval'Other useful exports include TruvalClient, TruvalClientOptions, and management response types from the same entry.
Usage
One-off call (no client instance)
import { verify } from 'truval'
const result = await verify('[email protected]', process.env.TRUVAL_API_KEY!)
console.log(result.status, result.confidence)Client instance
import { createClient } from 'truval'
const apiKey = process.env.TRUVAL_API_KEY!
const truval = createClient(apiKey, {
// Optional: on 429, wait until reset_at then retry
retryOnRateLimit: true,
maxRetries: 3,
// Optional: default https://api.truval.dev
// baseUrl: 'https://api.truval.dev',
})
const result = await truval.verify('[email protected]')
console.log(result.status) // 'deliverable' | 'undeliverable' | 'unknown' | 'catch_all' | 'invalid'
console.log(result.valid) // do not use alone (see agent decision guide below)
console.log(result.confidence) // ordinal 0–1 (not a probability)
console.log(result.smtp_blocked) // true for Gmail/Outlook/Yahoo (expected)
console.log(result.catch_all) // true when the domain accepts any recipientBatch and streaming
import { createClient } from 'truval'
const truval = createClient(process.env.TRUVAL_API_KEY!)
const results = await truval.verifyBatch(['[email protected]', '[email protected]']) // auto-chunked (50 max per request)
for (const r of results) console.log(r.email, r.status)
for await (const r of truval.verifyStream(['[email protected]', '[email protected]'])) {
// Yields in completion order (not necessarily input order)
console.log(r.email, r.status)
}Async verification (webhook)
import { createClient } from 'truval'
const truval = createClient(process.env.TRUVAL_API_KEY!)
const job = await truval.verifyAsync('[email protected]', 'https://your-app.com/truval-webhook')
console.log(job.job_id, job.status) // { status: "pending" }Response shape (VerifyResult)
The API returns structured data so agents and humans can reason beyond a single boolean.
| Field | Type | Description |
|------|------|-------------|
| email | string | Address that was verified |
| valid | boolean | true for deliverable/catch-all paths; false also for many “unknown” cases — use status + confidence |
| status | string | deliverable · undeliverable · unknown · catch_all · invalid |
| confidence | number | Ordinal 0–1 (not P(mailbox exists)). Typical: ~0.97 deliverable, ~0.75 major providers with smtp_blocked, ~0.65 catch-all, ~0.50 unknown with MX but SMTP inconclusive, ~0.02 undeliverable |
| failed_check | string \| null | Same as the API: syntax · disposable · no_mx · smtp · smtp_timeout, or null when verification finished without failing a layer (deliverable, catch-all, unknown including smtp_blocked, inconclusive SMTP). See Verification layers in the docs. Enum matches OpenAPI VerifyEmailResult. |
| disposable | boolean | Throwaway domain |
| role | boolean | Role-style local part (admin@, info@, …) |
| free_provider | boolean | Large free-mail domains |
| catch_all | boolean | Domain accepts arbitrary recipients |
| smtp_blocked | boolean | Provider blocks SMTP probe — expected for Gmail/Outlook/Yahoo; use confidence |
| mx_found | boolean | MX records present |
| mx_host | string \| null | Primary MX host |
| suggestion | string \| null | Typo hint (e.g. gmail.com for gnail.com) |
| latency_ms | number | Round-trip processing time |
Agent decision guide (recommended)
Do not gate UX only on valid. Suggested order:
suggestionset → offer a corrected address.disposable→ reject.invalid/undeliverable→ reject or ask for another address.catch_all→ treat as higher risk; confirm with user or policy.smtp_blocked→ often ~0.75 confidence — acceptable for many signups.unknown+mx_found+ notsmtp_blocked→ SMTP inconclusive (common on org/M365); ~0.50 — confirm with user, not auto-reject.
Full matrix: Agent decision guide.
Error handling
Non-success HTTP responses throw TruvalError (extends Error). On 429, resetAt is set when the body includes reset_at.
import { createClient, TruvalError } from 'truval'
const truval = createClient(process.env.TRUVAL_API_KEY!)
try {
const result = await truval.verify('[email protected]')
console.log(result.status)
} catch (err) {
if (err instanceof TruvalError) {
console.error(err.status, err.resetAt, err.apiError?.error)
}
throw err
}Management API client
For provisioning keys (sk_mgmt_...): lifecycle for standard keys (sk_live_...), usage, and billing portal.
import { createManagementClient } from 'truval'
const mgmt = createManagementClient(process.env.TRUVAL_PROVISIONING_KEY!)
const account = await mgmt.account.get()
const keys = await mgmt.keys.list()
const created = await mgmt.keys.create({ label: 'CI bot' })
await mgmt.keys.revoke('key_...')
const daily = await mgmt.usage.daily()
const summary = await mgmt.usage.summary() // free tier: quota-based calls_remaining; paid: billable
const { url } = await mgmt.billing.portal({ return_url: 'https://your-app.com/billing' })Ecosystem
| Resource | URL | |----------|-----| | Docs | docs.truval.dev | | Dashboard & keys | dash.truval.dev | | OpenAPI 3.1 | api.truval.dev/openapi.json | | MCP server (Claude / Cursor) | docs.truval.dev/integrations/mcp | | npm package | npmjs.com/package/truval |
Issues
- This SDK (types, docs,
fetchclient, npm packaging): open a GitHub issue on truval-dev/truval-sdk (also linked from npm). - API behavior, quotas, verification results, or downtime: see documentation and status first; use product support channels there if documented.
Include SDK version, runtime (Node/Bun/etc.), and a minimal code snippet when reporting bugs.
Pull requests
Fork the repo and create a short-lived branch from
main(e.g.fix/readme-typo,feat/better-error-message).Keep each PR focused — one logical change; split unrelated edits.
In the description, say what changed and why, and link
Fixes #123if it closes an issue.Verify locally (from a checkout that contains this package):
pnpm install pnpm run build pnpm run test(Adjust if you use npm:
npm run build/npm test.)Match existing style (TypeScript, formatting, tone of docs). Avoid drive-by refactors outside the PR scope.
Contributing
- Contributions are welcome under the same MIT license as this package; by opening a PR you agree your changes are licensed under MIT.
- Improvements to types, README, error messages, and small ergonomic fixes are especially appreciated.
- Verification logic and API semantics live on truval.dev’s servers; client PRs should not claim to change server behavior — use issues/docs for product feedback.
