verifymailapi 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,89 +1,233 @@
1
1
  # verifymailapi
2
2
 
3
- Official Node / TypeScript SDK for the [VerifyMail API](https://verifymailapi.com) — disposable, throwaway, and abusive email detection.
3
+ [![npm version](https://img.shields.io/npm/v/verifymailapi.svg)](https://www.npmjs.com/package/verifymailapi) [![types](https://img.shields.io/npm/types/verifymailapi.svg)](https://www.npmjs.com/package/verifymailapi) [![license](https://img.shields.io/npm/l/verifymailapi.svg)](./LICENSE)
4
+
5
+ **Official Node / TypeScript SDK for the [VerifyMail API](https://verifymailapi.com)** — detect disposable, throwaway, and abusive emails before they reach your database.
4
6
 
5
7
  ```ts
6
8
  import { VerifyMail } from "verifymailapi";
7
9
 
8
10
  const vm = new VerifyMail({ apiKey: process.env.VERIFYMAIL_KEY! });
9
11
 
10
- const result = await vm.check("user@example.com");
11
-
12
- switch (result.verdict.recommendation) {
13
- case "block":
14
- throw new Error("This email cannot be used.");
15
- case "allow_with_flag":
16
- user.requires_email_verification = true;
17
- await sendVerificationEmail(user);
18
- break;
19
- case "allow":
20
- // Clean. Proceed.
21
- break;
22
- }
12
+ const { verdict } = await vm.check("user@example.com");
13
+ console.log(verdict.recommendation); // "allow" | "allow_with_flag" | "block"
23
14
  ```
24
15
 
16
+ ---
17
+
25
18
  ## Install
26
19
 
27
20
  ```bash
28
21
  npm install verifymailapi
29
22
  # or
30
23
  pnpm add verifymailapi
24
+ # or
25
+ yarn add verifymailapi
31
26
  ```
32
27
 
33
- Requires Node 18+ (uses native `fetch`). Works in any modern server runtime Node, Bun, Deno, Vercel Functions, Cloudflare Workers.
28
+ Works in any modern server runtime: **Node 18+, Bun, Deno, Vercel Edge / Functions, Cloudflare Workers, Railway, Fly.io**. Uses native `fetch` — zero dependencies.
29
+
30
+ > **Server-side only.** Never call this from a browser bundle — your API key would leak. Use it inside a server action, API route, or backend service.
31
+
32
+ ---
33
+
34
+ ## Production-ready example
35
+
36
+ This is what a real signup handler looks like. Copy-paste it into a Next.js server action, an Express route, or a Hono handler — the shape is the same everywhere.
37
+
38
+ ```ts
39
+ import {
40
+ VerifyMail,
41
+ QuotaExceededError,
42
+ RateLimitError,
43
+ VerifyMailError,
44
+ } from "verifymailapi";
45
+
46
+ // Instantiate once per process, reuse per request.
47
+ const vm = new VerifyMail({
48
+ apiKey: process.env.VERIFYMAIL_KEY!,
49
+ riskProfile: "balanced", // strict | balanced | permissive
50
+ });
51
+
52
+ export async function handleSignup(email: string, requestId: string) {
53
+ let result;
54
+ try {
55
+ result = await vm.check(email, {
56
+ // Idempotent — safe to retry a flaky network without double-charging.
57
+ idempotencyKey: `signup:${requestId}`,
58
+ });
59
+ } catch (err) {
60
+ if (err instanceof QuotaExceededError) {
61
+ // Out of credits — don't bounce real customers. Allow with a flag.
62
+ return { ok: true, action: "allow_with_flag", reason: "verifymail_unavailable" };
63
+ }
64
+ if (err instanceof RateLimitError) {
65
+ return { ok: false, error: "Please retry in a moment.", retryAfter: err.retryAfter };
66
+ }
67
+ if (err instanceof VerifyMailError) {
68
+ // Log the error and fail open — losing one signup hurts more than
69
+ // briefly skipping fraud detection.
70
+ console.error("VerifyMail:", err.code, err.message, err.requestId);
71
+ return { ok: true, action: "allow", reason: "verifymail_error" };
72
+ }
73
+ throw err;
74
+ }
75
+
76
+ switch (result.verdict.recommendation) {
77
+ case "block":
78
+ // Reject the signup. result.verdict.summary explains *why*.
79
+ return { ok: false, error: result.verdict.summary };
80
+ case "allow_with_flag":
81
+ // Pass through, but route this user through your verification step
82
+ // (email confirmation link, captcha, slower onboarding — whatever
83
+ // your app already has).
84
+ return { ok: true, action: "allow_with_flag" };
85
+ case "allow":
86
+ return { ok: true, action: "allow" };
87
+ }
88
+ }
89
+ ```
90
+
91
+ That's the canonical pattern. Everything below is reference detail.
92
+
93
+ ---
34
94
 
35
95
  ## API
36
96
 
37
97
  ### `new VerifyMail(options)`
38
98
 
39
- | Option | Default | Description |
99
+ | Option | Type | Default | Notes |
100
+ |---|---|---|---|
101
+ | `apiKey` | `string` | **required** | Your `dc_…` key from the [dashboard](https://verifymailapi.com/dashboard/keys). |
102
+ | `baseUrl` | `string` | `https://api.verifymailapi.com` | Override for staging or self-hosted. |
103
+ | `retries` | `number` | `2` | Retries on `429` / `5xx`. Set `0` to disable. |
104
+ | `timeoutMs` | `number` | `30000` | Per-request timeout. |
105
+ | `riskProfile` | `"strict" \| "balanced" \| "permissive"` | server default | Per-call `riskProfile` overrides this. |
106
+ | `fetch` | `typeof fetch` | `globalThis.fetch` | Inject for tests or non-Node runtimes. |
107
+
108
+ ### Methods
109
+
110
+ | Method | What it does |
111
+ |---|---|
112
+ | **`vm.check(email, opts?)`** | Check one email. Charges 1 credit. Returns the full 5-block response. |
113
+ | **`vm.checkDomain(domain, opts?)`** | Check a domain only (no local part). Charges 1 credit. |
114
+ | **`vm.checkBulk(emails[], opts?)`** | 1–100 emails per call. Charges N up front, all-or-nothing. Order preserved. |
115
+ | **`vm.checkBulkStream(emails[], onEvent, opts?)`** | NDJSON streaming version of `checkBulk`. Use for 5k+ batches. |
116
+ | **`vm.checkAsync({ email, webhookUrl, webhookSecret? }, opts?)`** | Returns 202 immediately with a preliminary verdict. The final result is POSTed to your webhook. |
117
+ | **`vm.report({ domain, outcome, notes? })`** | Tell us when a domain turned out to be confirmed throwaway / legitimate / suspected. |
118
+ | **`vm.usage()`** | Current-period totals + credit balance — same shape your dashboard reads. |
119
+ | **`vm.status()`** | Component health (Redis / Postgres / DNS). Always returns 200. |
120
+
121
+ Every method that costs credits accepts `{ idempotencyKey: true }` (auto-generated UUID) or `{ idempotencyKey: "your-string" }`.
122
+
123
+ ---
124
+
125
+ ## Verdicts — what to do with each
126
+
127
+ ```ts
128
+ switch (result.verdict.recommendation) {
129
+ case "block": // High confidence: abuse, dead address, or a disposable provider.
130
+ case "allow_with_flag": // Suspicious. Route through your verification step.
131
+ case "allow": // Clean. Proceed normally.
132
+ }
133
+ ```
134
+
135
+ **The most important rule:** map `allow_with_flag` to `user.requires_email_verification = true` (or whatever your friction-step is called). Most B2B apps already have email verification — that one line costs zero new code and catches the vast majority of bot signups.
136
+
137
+ If your app doesn't have email verification, add one. It's the cheapest fraud defense in existence and the API is designed around the assumption that you have it.
138
+
139
+ ---
140
+
141
+ ## Errors
142
+
143
+ All HTTP failures become typed errors so you can branch cleanly:
144
+
145
+ ```ts
146
+ import {
147
+ VerifyMailError,
148
+ InvalidApiKeyError,
149
+ QuotaExceededError,
150
+ RateLimitError,
151
+ IdempotencyConflictError,
152
+ ValidationError,
153
+ ServiceDegradedError,
154
+ } from "verifymailapi";
155
+
156
+ try {
157
+ await vm.check(email);
158
+ } catch (err) {
159
+ if (err instanceof QuotaExceededError) return showBilling(err.upgradeUrl);
160
+ if (err instanceof RateLimitError) return retryAfter(err.retryAfter); // seconds
161
+ if (err instanceof InvalidApiKeyError) return alertOps("VerifyMail key rotated?");
162
+ if (err instanceof VerifyMailError) return logAndFallback(err);
163
+ throw err;
164
+ }
165
+ ```
166
+
167
+ Every error carries:
168
+
169
+ | Property | Type | Example |
40
170
  |---|---|---|
41
- | `apiKey` | (required) | Your `dc_…` API key from the dashboard. |
42
- | `baseUrl` | `https://api.verifymailapi.com` | Override for staging. |
43
- | `retries` | `2` | Retries on 429 / 5xx with backoff. Set to 0 to disable. |
44
- | `timeoutMs` | `30000` | Per-request timeout. |
45
- | `riskProfile` | (server default) | `"strict"` / `"balanced"` / `"permissive"`. Per-call override available. |
46
- | `fetch` | `globalThis.fetch` | Inject a custom fetch (tests, edge runtimes). |
171
+ | `code` | `string` | `"too_many_requests"`, `"quota_exceeded"`, |
172
+ | `status` | `number` | HTTP status (`429`, `402`, …) |
173
+ | `requestId` | `string \| undefined` | Pass this when filing support tickets. |
174
+ | `docsUrl` | `string \| undefined` | Direct link to the relevant docs section. |
175
+ | `message` | `string` | Human-readable. |
176
+
177
+ Specific subclasses add specific fields — `RateLimitError.retryAfter`, `QuotaExceededError.upgradeUrl`, etc.
178
+
179
+ ---
47
180
 
48
- ### `vm.check(email, opts?)`
181
+ ## Idempotency
49
182
 
50
- Checks a single email. Returns a `CheckResponse` (the five-block schema).
183
+ `POST` endpoints that charge credits all accept an `Idempotency-Key` header. Replay the same key within **24 hours** and you get the cached response back — no duplicate work, no duplicate charge.
51
184
 
52
185
  ```ts
53
- const r = await vm.check("user@example.com", { idempotencyKey: true });
54
- console.log(r.verdict.recommendation, r.score.value);
186
+ // Auto-generate a UUID v4
187
+ await vm.check(email, { idempotencyKey: true });
188
+
189
+ // Or pass your own (correlate with your own request ID)
190
+ await vm.check(email, { idempotencyKey: `signup:${requestId}` });
55
191
  ```
56
192
 
57
- ### `vm.checkDomain(domain, opts?)`
193
+ Re-using the same key with a different request body throws `IdempotencyConflictError` (HTTP 409). Pick a new key or send the original payload.
58
194
 
59
- Domain-only check (skips syntax validation). Same 1-credit cost.
195
+ ---
60
196
 
61
- ### `vm.checkBulk(emails[], opts?)`
197
+ ## Bulk processing
62
198
 
63
- Submit 1–100 emails. Charges N credits up front, all-or-nothing.
199
+ ### Small batches (≤ 100 emails)
64
200
 
65
201
  ```ts
66
- const { items, summary } = await vm.checkBulk(["a@x.com", "b@x.com"]);
202
+ const { items, summary } = await vm.checkBulk(["a@x.com", "b@x.com", "c@x.com"]);
203
+
67
204
  // items[i] matches emails[i] (order preserved).
205
+ items.forEach((r, i) => console.log(r.meta.domain, "→", r.verdict.recommendation));
206
+ console.log(`charged ${summary.credits_charged} credits in ${summary.elapsed_ms}ms`);
68
207
  ```
69
208
 
70
- ### `vm.checkBulkStream(emails[], onEvent, opts?)`
209
+ ### Large batches (5k–100k addresses)
71
210
 
72
- For large batches (5k–100k addresses). Calls `onEvent` once per finished row + once with a `{ event: "summary" }` final line. Results arrive in finish order; correlate via `index`.
211
+ Stream results as each check completes process rows immediately instead of waiting for the whole batch:
73
212
 
74
213
  ```ts
75
- await vm.checkBulkStream(emails, (e) => {
76
- if ("event" in e) {
77
- console.log("done — credits remaining:", e.credits_remaining);
78
- } else {
79
- processRow(e.index, e.result);
214
+ await vm.checkBulkStream(emails, (event) => {
215
+ if ("event" in event) {
216
+ console.log("done — credits remaining:", event.credits_remaining);
217
+ return;
80
218
  }
219
+ // event = { index: number, result: CheckResponse }
220
+ await processRow(event.index, event.result);
81
221
  });
82
222
  ```
83
223
 
84
- ### `vm.checkAsync({ email, webhookUrl, webhookSecret? })`
224
+ Results arrive in **finish order**, not input order — correlate via `event.index`.
225
+
226
+ ---
85
227
 
86
- Two-phase verification. Returns a 202 immediately with a preliminary verdict; the final result is POSTed to your webhook URL after the deep SMTP probe completes.
228
+ ## Async deep checks (webhooks)
229
+
230
+ For workflows where the user can wait for an email but not a synchronous SMTP probe:
87
231
 
88
232
  ```ts
89
233
  const { request_id, preliminary } = await vm.checkAsync({
@@ -91,9 +235,12 @@ const { request_id, preliminary } = await vm.checkAsync({
91
235
  webhookUrl: "https://your-app.example/webhooks/verifymail",
92
236
  webhookSecret: process.env.VERIFYMAIL_WEBHOOK_SECRET,
93
237
  });
238
+
239
+ // `preliminary` is the fast-path verdict you can act on immediately.
240
+ // The deep SMTP probe runs in the background; the final result lands at your webhook.
94
241
  ```
95
242
 
96
- In your webhook handler, verify the signature with `verifyWebhook()`:
243
+ ### Verifying the webhook signature
97
244
 
98
245
  ```ts
99
246
  import express from "express";
@@ -101,6 +248,7 @@ import { verifyWebhook } from "verifymailapi";
101
248
 
102
249
  app.post(
103
250
  "/webhooks/verifymail",
251
+ // IMPORTANT: raw body, not json — verifyWebhook needs the original bytes.
104
252
  express.raw({ type: "application/json" }),
105
253
  (req, res) => {
106
254
  const sig = req.header("X-VerifyMail-Signature") ?? "";
@@ -108,52 +256,133 @@ app.post(
108
256
  return res.status(401).send("bad signature");
109
257
  }
110
258
  const event = JSON.parse(req.body.toString("utf8"));
111
- // event.result is the final CheckResponse.
259
+ // event.result is the final CheckResponse with the deep verdict.
260
+ await handleFinalVerdict(event);
112
261
  res.status(200).end();
113
262
  },
114
263
  );
115
264
  ```
116
265
 
117
- ### `vm.report({ domain, outcome, notes? })`
266
+ Same idea in Hono / Fastify / Next.js Route Handlers just keep the body bytes raw until after `verifyWebhook` returns true.
118
267
 
119
- Tell us when a domain turned out to be confirmed throwaway / legitimate / suspected.
268
+ ---
120
269
 
121
- ### `vm.usage()`
270
+ ## Framework recipes
122
271
 
123
- Returns the same shape the dashboard reads current-period totals + remaining credit balance.
272
+ ### Next.jsServer Action
124
273
 
125
- ### `vm.status()`
274
+ ```ts
275
+ // app/actions.ts
276
+ "use server";
126
277
 
127
- Per-component health (Redis, Postgres, DNS). Always 200; read individual fields.
278
+ import { VerifyMail, QuotaExceededError } from "verifymailapi";
128
279
 
129
- ## Errors
280
+ const vm = new VerifyMail({ apiKey: process.env.VERIFYMAIL_KEY! });
281
+
282
+ export async function signupAction(_: unknown, formData: FormData) {
283
+ const email = String(formData.get("email"));
284
+ try {
285
+ const r = await vm.check(email);
286
+ if (r.verdict.recommendation === "block") {
287
+ return { ok: false, error: r.verdict.summary };
288
+ }
289
+ await db.user.create({
290
+ data: { email, requires_verification: r.verdict.recommendation === "allow_with_flag" },
291
+ });
292
+ return { ok: true };
293
+ } catch (err) {
294
+ if (err instanceof QuotaExceededError) return { ok: true }; // fail open
295
+ throw err;
296
+ }
297
+ }
298
+ ```
299
+
300
+ ### Express — middleware
130
301
 
131
302
  ```ts
132
- import {
133
- VerifyMailError,
134
- InvalidApiKeyError,
135
- QuotaExceededError,
136
- RateLimitError,
137
- IdempotencyConflictError,
138
- ValidationError,
139
- ServiceDegradedError,
140
- } from "verifymailapi";
303
+ import { VerifyMail } from "verifymailapi";
141
304
 
142
- try {
143
- await vm.check(email);
144
- } catch (e) {
145
- if (e instanceof QuotaExceededError) return showBilling(e.upgradeUrl);
146
- if (e instanceof RateLimitError) return retryLater(e.retryAfter);
147
- if (e instanceof VerifyMailError) return logAndShowGeneric(e);
148
- throw e;
149
- }
305
+ const vm = new VerifyMail({ apiKey: process.env.VERIFYMAIL_KEY! });
306
+
307
+ app.post("/signup", async (req, res) => {
308
+ const r = await vm.check(req.body.email);
309
+ if (r.verdict.recommendation === "block") {
310
+ return res.status(422).json({ error: r.verdict.summary });
311
+ }
312
+ const user = await createUser({
313
+ ...req.body,
314
+ requires_verification: r.verdict.recommendation === "allow_with_flag",
315
+ });
316
+ res.json(user);
317
+ });
150
318
  ```
151
319
 
152
- All errors carry `code`, `status`, `requestId`, `docsUrl`, and the original `body` payload when available.
320
+ ### Cloudflare Workers / Edge
153
321
 
154
- ## Idempotency
322
+ ```ts
323
+ import { VerifyMail } from "verifymailapi";
324
+
325
+ export default {
326
+ async fetch(req: Request, env: Env) {
327
+ const vm = new VerifyMail({ apiKey: env.VERIFYMAIL_KEY });
328
+ const { email } = await req.json();
329
+ const r = await vm.check(email);
330
+ return Response.json(r);
331
+ },
332
+ };
333
+ ```
334
+
335
+ ---
336
+
337
+ ## Rate limits
338
+
339
+ The API enforces **600 requests / minute per key** by default (configurable for paying customers). The SDK automatically:
340
+
341
+ - Reads `Retry-After` on `429` responses.
342
+ - Backs off and retries up to `retries` times (default `2`).
343
+ - Surfaces the limit info on the thrown `RateLimitError` if all retries fail.
344
+
345
+ Look at the response headers `X-RateLimit-Remaining` / `X-RateLimit-Reset` if you want to throttle preemptively in your own queue.
346
+
347
+ ---
348
+
349
+ ## TypeScript
350
+
351
+ The package ships full `.d.ts` types. Every response field is typed; every verdict / risk level / signal direction is a literal union you can `switch` over exhaustively.
352
+
353
+ ```ts
354
+ import type {
355
+ CheckResponse,
356
+ Recommendation,
357
+ RiskLevel,
358
+ Signal,
359
+ BulkCheckResponse,
360
+ CheckCompletedEvent,
361
+ } from "verifymailapi";
362
+ ```
363
+
364
+ Strict mode + `noUncheckedIndexedAccess` clean. Works with `"moduleResolution": "Bundler"`, `"node16"`, and classic `"node"`.
365
+
366
+ ---
367
+
368
+ ## Configuration via environment
369
+
370
+ This SDK doesn't read env vars itself — you pass them. Recommended names:
371
+
372
+ | Var | Purpose |
373
+ |---|---|
374
+ | `VERIFYMAIL_KEY` | Your `dc_…` API key |
375
+ | `VERIFYMAIL_WEBHOOK_SECRET` | Optional shared secret for `vm.checkAsync(...)` |
376
+ | `VERIFYMAIL_API_URL` | Optional override of `baseUrl` for staging |
377
+
378
+ ---
379
+
380
+ ## Links
155
381
 
156
- Pass `idempotencyKey: true` to auto-generate a UUID v4, or pass a fixed string you choose. Within 24 hours the same key replays the cached response with no duplicate charge. Re-using a key with a different body throws `IdempotencyConflictError`.
382
+ - **Full API docs:** https://verifymailapi.com/docs
383
+ - **Dashboard / API keys:** https://verifymailapi.com/dashboard/keys
384
+ - **Issues / discussions:** https://github.com/jt1402/verifymail-js
385
+ - **Pricing:** https://verifymailapi.com/pricing
157
386
 
158
387
  ## License
159
388
 
package/dist/index.cjs CHANGED
@@ -159,7 +159,7 @@ var HttpClient = class {
159
159
  const h = new Headers(opts.headers);
160
160
  h.set("X-API-Key", this.apiKey);
161
161
  h.set("Accept", "application/json");
162
- h.set("User-Agent", "verifymailapi-js/0.1.0");
162
+ h.set("User-Agent", "verifymailapi-js/0.1.1");
163
163
  if (opts.body !== void 0) h.set("Content-Type", "application/json");
164
164
  const profile = opts.riskProfile ?? this.defaultRiskProfile;
165
165
  if (profile) h.set("X-Risk-Profile", profile);
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/errors.ts","../src/client.ts","../src/webhooks.ts"],"sourcesContent":["/**\n * Official SDK for the VerifyMail API.\n *\n * import { VerifyMail } from \"verifymailapi\";\n * const vm = new VerifyMail({ apiKey: process.env.VERIFYMAIL_KEY! });\n * const r = await vm.check(\"user@example.com\");\n * if (r.verdict.recommendation === \"block\") { ... }\n *\n * Docs: https://verifymailapi.com/docs\n */\n\nimport { HttpClient, type ClientOptions, type RequestOptions } from \"./client.js\";\nimport type {\n AsyncCheckResponse,\n BulkCheckResponse,\n BulkStreamEvent,\n CheckResponse,\n ReportRequest,\n ReportResponse,\n StatusResponse,\n UsageMeResponse,\n} from \"./types.js\";\n\nexport * from \"./types.js\";\nexport * from \"./errors.js\";\nexport { verifyWebhook } from \"./webhooks.js\";\n\nexport interface CheckOptions {\n /** Override the SDK-wide risk profile for this call. */\n riskProfile?: \"strict\" | \"balanced\" | \"permissive\";\n /** Make the call idempotent. Pass `true` to auto-generate a UUID, or a fixed string. */\n idempotencyKey?: string | boolean;\n}\n\nexport interface AsyncCheckArgs {\n email: string;\n webhookUrl: string;\n /** Optional HMAC-SHA256 key used to sign the final webhook payload. */\n webhookSecret?: string;\n}\n\nexport class VerifyMail {\n private readonly http: HttpClient;\n\n constructor(opts: ClientOptions) {\n this.http = new HttpClient(opts);\n }\n\n /** Check a single email. Charges 1 credit. */\n check(email: string, opts: CheckOptions = {}): Promise<CheckResponse> {\n return this.http.json<CheckResponse>({\n path: \"/v1/check\",\n body: { email },\n idempotencyKey: opts.idempotencyKey,\n riskProfile: opts.riskProfile,\n });\n }\n\n /** Check a domain only (no local part). Charges 1 credit. */\n checkDomain(domain: string, opts: CheckOptions = {}): Promise<CheckResponse> {\n return this.http.json<CheckResponse>({\n path: \"/v1/check/domain\",\n body: { domain },\n idempotencyKey: opts.idempotencyKey,\n riskProfile: opts.riskProfile,\n });\n }\n\n /** Bulk check 1–100 emails. Charges N credits up front (all-or-nothing). */\n checkBulk(emails: string[], opts: CheckOptions = {}): Promise<BulkCheckResponse> {\n if (emails.length === 0 || emails.length > 100) {\n throw new RangeError(\"checkBulk requires 1–100 emails.\");\n }\n return this.http.json<BulkCheckResponse>({\n path: \"/v1/check/bulk\",\n body: { emails },\n idempotencyKey: opts.idempotencyKey,\n riskProfile: opts.riskProfile,\n });\n }\n\n /**\n * Stream bulk-check results as each row completes. Calls `onEvent` once\n * per result (in finish-order) plus once with a final `{event: \"summary\"}`.\n *\n * await vm.checkBulkStream(emails, (e) => {\n * if (\"event\" in e) console.log(\"done\", e);\n * else processRow(e.index, e.result);\n * });\n */\n async checkBulkStream(\n emails: string[],\n onEvent: (e: BulkStreamEvent) => void | Promise<void>,\n opts: Omit<CheckOptions, \"idempotencyKey\"> = {},\n ): Promise<void> {\n if (emails.length === 0) throw new RangeError(\"checkBulkStream needs at least one email.\");\n\n const req: RequestOptions = {\n path: \"/v1/check/bulk/stream\",\n body: { emails },\n riskProfile: opts.riskProfile,\n // Streaming requests aren't safe to auto-retry — would re-charge credits\n // and re-stream rows. Customer must retry explicitly.\n noRetry: true,\n };\n const res = await this.http.raw(req);\n if (!res.body) throw new Error(\"Streaming response had no body.\");\n\n const reader = res.body.getReader();\n const decoder = new TextDecoder();\n let buf = \"\";\n while (true) {\n const { value, done } = await reader.read();\n if (done) break;\n buf += decoder.decode(value, { stream: true });\n let nl: number;\n // Drain complete lines; keep the partial tail in buf for the next chunk.\n while ((nl = buf.indexOf(\"\\n\")) !== -1) {\n const line = buf.slice(0, nl).trim();\n buf = buf.slice(nl + 1);\n if (!line) continue;\n try {\n await onEvent(JSON.parse(line) as BulkStreamEvent);\n } catch (err) {\n throw new Error(`Failed to parse stream line: ${line}`);\n }\n }\n }\n // Final flush in case the server didn't terminate with newline.\n const tail = buf.trim();\n if (tail) await onEvent(JSON.parse(tail) as BulkStreamEvent);\n }\n\n /**\n * Async deep check. Returns 202 immediately with a preliminary verdict;\n * VerifyMail POSTs the final result to your webhook URL once the deep\n * SMTP probe completes. Verify the signature with `verifyWebhook()` in\n * your handler before trusting the payload.\n */\n checkAsync(args: AsyncCheckArgs, opts: CheckOptions = {}): Promise<AsyncCheckResponse> {\n return this.http.json<AsyncCheckResponse>({\n path: \"/v1/check/async\",\n body: {\n email: args.email,\n webhook_url: args.webhookUrl,\n webhook_secret: args.webhookSecret,\n },\n idempotencyKey: opts.idempotencyKey,\n riskProfile: opts.riskProfile,\n });\n }\n\n /** File a /v1/report for a domain outcome (feedback loop). */\n report(req: ReportRequest): Promise<ReportResponse> {\n return this.http.json<ReportResponse>({\n path: \"/v1/report\",\n body: req,\n });\n }\n\n /** Programmatic equivalent of the dashboard's Usage summary. */\n usage(): Promise<UsageMeResponse> {\n return this.http.json<UsageMeResponse>({\n path: \"/v1/usage/me\",\n method: \"GET\",\n });\n }\n\n /** Component health (Redis / Postgres / DNS). Always returns 200. */\n status(): Promise<StatusResponse> {\n return this.http.json<StatusResponse>({\n path: \"/v1/status\",\n method: \"GET\",\n });\n }\n}\n\nexport default VerifyMail;\n","/**\n * Error class hierarchy. All HTTP errors from the API are translated into one\n * of these — customers can `instanceof` them to branch cleanly:\n *\n * try { await vm.check(email) }\n * catch (e) {\n * if (e instanceof QuotaExceededError) return showBilling();\n * if (e instanceof RateLimitError) return retryLater(e.retryAfter);\n * if (e instanceof VerifyMailError) return logAndShowGeneric(e);\n * throw e;\n * }\n */\n\nexport interface ErrorBody {\n code: string;\n http_status: number;\n message: string;\n request_id?: string;\n docs_url?: string;\n // Rate-limit-specific\n limit?: number;\n reset_at?: string;\n // Quota-specific\n upgrade_url?: string;\n}\n\nexport class VerifyMailError extends Error {\n readonly code: string;\n readonly status: number;\n readonly requestId: string | undefined;\n readonly docsUrl: string | undefined;\n readonly body: ErrorBody | undefined;\n\n constructor(message: string, opts: {\n code?: string;\n status?: number;\n requestId?: string;\n docsUrl?: string;\n body?: ErrorBody;\n } = {}) {\n super(message);\n this.name = \"VerifyMailError\";\n this.code = opts.code ?? \"verifymail_error\";\n this.status = opts.status ?? 0;\n this.requestId = opts.requestId;\n this.docsUrl = opts.docsUrl;\n this.body = opts.body;\n }\n}\n\nexport class InvalidApiKeyError extends VerifyMailError {\n constructor(opts: NonNullable<ConstructorParameters<typeof VerifyMailError>[1]>) {\n super(\"API key is missing or invalid.\", opts);\n this.name = \"InvalidApiKeyError\";\n }\n}\n\nexport class QuotaExceededError extends VerifyMailError {\n readonly upgradeUrl: string | undefined;\n constructor(opts: NonNullable<ConstructorParameters<typeof VerifyMailError>[1]>) {\n super(\"Out of credits. Buy a bundle to keep going.\", opts);\n this.name = \"QuotaExceededError\";\n this.upgradeUrl = opts.body?.upgrade_url;\n }\n}\n\nexport class RateLimitError extends VerifyMailError {\n readonly retryAfter: number;\n readonly limit: number | undefined;\n readonly resetAt: string | undefined;\n\n constructor(opts: NonNullable<ConstructorParameters<typeof VerifyMailError>[1]> & {\n retryAfter: number;\n }) {\n super(`Rate limit hit; retry after ${opts.retryAfter}s.`, opts);\n this.name = \"RateLimitError\";\n this.retryAfter = opts.retryAfter;\n this.limit = opts.body?.limit;\n this.resetAt = opts.body?.reset_at;\n }\n}\n\nexport class IdempotencyConflictError extends VerifyMailError {\n constructor(opts: NonNullable<ConstructorParameters<typeof VerifyMailError>[1]>) {\n super(\n \"Idempotency-Key was reused with a different request body. Use a new key or resend the original payload.\",\n opts,\n );\n this.name = \"IdempotencyConflictError\";\n }\n}\n\nexport class ValidationError extends VerifyMailError {\n constructor(message: string, opts: NonNullable<ConstructorParameters<typeof VerifyMailError>[1]>) {\n super(message, opts);\n this.name = \"ValidationError\";\n }\n}\n\nexport class ServiceDegradedError extends VerifyMailError {\n constructor(opts: NonNullable<ConstructorParameters<typeof VerifyMailError>[1]>) {\n super(\"A component is degraded; retry shortly.\", opts);\n this.name = \"ServiceDegradedError\";\n }\n}\n\n/**\n * Map an HTTP response to the right error class. The backend's error envelope\n * is `{ error: { code, http_status, message, ... } }`.\n */\nexport function errorFromResponse(\n status: number,\n body: { error?: ErrorBody } | unknown,\n retryAfterHeader?: string | null,\n): VerifyMailError {\n const envelope =\n body && typeof body === \"object\" && \"error\" in body\n ? (body as { error?: ErrorBody }).error\n : undefined;\n const code = envelope?.code ?? \"verifymail_error\";\n const opts = {\n code,\n status,\n requestId: envelope?.request_id,\n docsUrl: envelope?.docs_url,\n body: envelope,\n };\n\n if (status === 401) return new InvalidApiKeyError(opts);\n if (status === 402) return new QuotaExceededError(opts);\n if (status === 409 && code === \"invalid_idempotency_key\")\n return new IdempotencyConflictError(opts);\n if (status === 422) return new ValidationError(envelope?.message ?? \"Validation error.\", opts);\n if (status === 429) {\n const retryAfter = Number(retryAfterHeader ?? envelope?.limit ?? 1);\n return new RateLimitError({ ...opts, retryAfter: Number.isFinite(retryAfter) ? retryAfter : 1 });\n }\n if (status === 503 || status === 504) return new ServiceDegradedError(opts);\n return new VerifyMailError(envelope?.message ?? `HTTP ${status}`, opts);\n}\n","import { errorFromResponse, RateLimitError, ServiceDegradedError, VerifyMailError } from \"./errors.js\";\n\nexport interface ClientOptions {\n apiKey: string;\n /** Defaults to https://api.verifymailapi.com */\n baseUrl?: string;\n /** Max retry attempts on retryable failures (429, 5xx). Default 2 (so up to 3 total tries). */\n retries?: number;\n /** Per-request timeout in ms. Default 30000. */\n timeoutMs?: number;\n /** Override fetch — useful for tests or non-Node runtimes. */\n fetch?: typeof fetch;\n /** Default risk profile sent as X-Risk-Profile on every call (overridable per request). */\n riskProfile?: \"strict\" | \"balanced\" | \"permissive\";\n}\n\nexport interface RequestOptions {\n method?: \"GET\" | \"POST\";\n path: string;\n query?: Record<string, string | number | undefined>;\n body?: unknown;\n headers?: Record<string, string>;\n /** Provide an Idempotency-Key for POSTs. Auto-generated UUID if `true`. */\n idempotencyKey?: string | boolean;\n /** Override the default risk profile for this call. */\n riskProfile?: \"strict\" | \"balanced\" | \"permissive\";\n /** Skip retry on transient errors for this call. */\n noRetry?: boolean;\n /** Override timeout for this call. */\n timeoutMs?: number;\n}\n\nconst RETRYABLE_STATUS = new Set([408, 429, 500, 502, 503, 504]);\n\nfunction uuidv4(): string {\n // Node 19+ has globalThis.crypto.randomUUID. Polyfill for older Node.\n const c = globalThis.crypto;\n if (c?.randomUUID) return c.randomUUID();\n const buf = new Uint8Array(16);\n for (let i = 0; i < 16; i++) buf[i] = Math.floor(Math.random() * 256);\n buf[6] = (buf[6]! & 0x0f) | 0x40;\n buf[8] = (buf[8]! & 0x3f) | 0x80;\n const hex = Array.from(buf, (b) => b.toString(16).padStart(2, \"0\")).join(\"\");\n return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;\n}\n\nexport class HttpClient {\n private readonly apiKey: string;\n private readonly baseUrl: string;\n private readonly retries: number;\n private readonly timeoutMs: number;\n private readonly fetchImpl: typeof fetch;\n private readonly defaultRiskProfile: string | undefined;\n\n constructor(opts: ClientOptions) {\n if (!opts.apiKey) throw new VerifyMailError(\"apiKey is required.\");\n this.apiKey = opts.apiKey;\n this.baseUrl = (opts.baseUrl ?? \"https://api.verifymailapi.com\").replace(/\\/+$/, \"\");\n this.retries = opts.retries ?? 2;\n this.timeoutMs = opts.timeoutMs ?? 30_000;\n this.fetchImpl = opts.fetch ?? globalThis.fetch;\n this.defaultRiskProfile = opts.riskProfile;\n if (!this.fetchImpl) {\n throw new VerifyMailError(\n \"No fetch available. Pass `fetch` in the options, or use Node 18+.\",\n );\n }\n }\n\n /** Build the headers shared by every request. */\n private buildHeaders(opts: RequestOptions): Headers {\n const h = new Headers(opts.headers);\n h.set(\"X-API-Key\", this.apiKey);\n h.set(\"Accept\", \"application/json\");\n h.set(\"User-Agent\", \"verifymailapi-js/0.1.0\");\n if (opts.body !== undefined) h.set(\"Content-Type\", \"application/json\");\n\n const profile = opts.riskProfile ?? this.defaultRiskProfile;\n if (profile) h.set(\"X-Risk-Profile\", profile);\n\n if (opts.idempotencyKey) {\n h.set(\n \"Idempotency-Key\",\n typeof opts.idempotencyKey === \"string\" ? opts.idempotencyKey : uuidv4(),\n );\n }\n return h;\n }\n\n private buildUrl(path: string, query?: RequestOptions[\"query\"]): string {\n const url = new URL(`${this.baseUrl}${path}`);\n if (query) {\n for (const [k, v] of Object.entries(query)) {\n if (v !== undefined) url.searchParams.set(k, String(v));\n }\n }\n return url.toString();\n }\n\n /** Send and decode a JSON request. Retries on 429 / 5xx up to `retries` times. */\n async json<T>(opts: RequestOptions): Promise<T> {\n const raw = await this.raw(opts);\n if (raw.status === 204) return undefined as T;\n const text = await raw.text();\n try {\n return JSON.parse(text) as T;\n } catch {\n throw new VerifyMailError(`Non-JSON response from ${opts.path}.`, {\n status: raw.status,\n });\n }\n }\n\n /**\n * Send a request and return the raw Response. Handles retries + error mapping.\n * Used internally; exposed for the streaming bulk method to consume the body.\n */\n async raw(opts: RequestOptions): Promise<Response> {\n const url = this.buildUrl(opts.path, opts.query);\n const headers = this.buildHeaders(opts);\n const body = opts.body !== undefined ? JSON.stringify(opts.body) : undefined;\n const method = opts.method ?? (body ? \"POST\" : \"GET\");\n const maxTries = opts.noRetry ? 1 : this.retries + 1;\n const timeoutMs = opts.timeoutMs ?? this.timeoutMs;\n\n let lastError: VerifyMailError | undefined;\n for (let attempt = 0; attempt < maxTries; attempt++) {\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), timeoutMs);\n let res: Response;\n try {\n res = await this.fetchImpl(url, {\n method,\n headers,\n body,\n signal: controller.signal,\n });\n } catch (err) {\n clearTimeout(timer);\n // Network-level failure — retry if attempts remain.\n lastError = new VerifyMailError(\n err instanceof Error ? err.message : \"Network error\",\n { code: \"network_error\" },\n );\n if (attempt < maxTries - 1) {\n await sleep(backoffMs(attempt));\n continue;\n }\n throw lastError;\n }\n clearTimeout(timer);\n\n if (res.ok) return res;\n\n // Parse error body (best-effort) so we can build a typed error.\n let parsed: unknown = undefined;\n try {\n parsed = await res.clone().json();\n } catch {\n /* ignore non-JSON error bodies */\n }\n const err = errorFromResponse(res.status, parsed, res.headers.get(\"Retry-After\"));\n\n const isRetryable =\n !opts.noRetry &&\n RETRYABLE_STATUS.has(res.status) &&\n attempt < maxTries - 1;\n\n if (isRetryable) {\n const wait =\n err instanceof RateLimitError\n ? err.retryAfter * 1000\n : err instanceof ServiceDegradedError\n ? backoffMs(attempt)\n : backoffMs(attempt);\n await sleep(wait);\n lastError = err;\n continue;\n }\n\n throw err;\n }\n\n throw lastError ?? new VerifyMailError(\"Request failed after retries.\");\n }\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((r) => setTimeout(r, ms));\n}\n\nfunction backoffMs(attempt: number): number {\n // 250ms, 750ms, 2.25s — typical exponential with a small jitter floor.\n return Math.min(250 * Math.pow(3, attempt), 10_000);\n}\n","import { createHmac, timingSafeEqual } from \"node:crypto\";\n\n/**\n * Verify an incoming webhook signature from a `/v1/check/async` completion.\n *\n * const ok = verifyWebhook(rawBody, req.header(\"X-VerifyMail-Signature\"), secret);\n * if (!ok) return res.status(401).end();\n *\n * Pass the *raw* request body bytes — not the parsed JSON. In Express:\n * app.post(\"/webhook\", express.raw({ type: \"application/json\" }), handler)\n */\nexport function verifyWebhook(\n rawBody: Buffer | Uint8Array | string,\n signatureHeader: string | null | undefined,\n secret: string,\n): boolean {\n if (!signatureHeader) return false;\n const body =\n typeof rawBody === \"string\" ? Buffer.from(rawBody, \"utf8\") : Buffer.from(rawBody);\n const expected =\n \"sha256=\" + createHmac(\"sha256\", secret).update(body).digest(\"hex\");\n const a = Buffer.from(signatureHeader);\n const b = Buffer.from(expected);\n return a.length === b.length && timingSafeEqual(a, b);\n}\n"],"mappings":"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","names":["err"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/errors.ts","../src/client.ts","../src/webhooks.ts"],"sourcesContent":["/**\n * Official SDK for the VerifyMail API.\n *\n * import { VerifyMail } from \"verifymailapi\";\n * const vm = new VerifyMail({ apiKey: process.env.VERIFYMAIL_KEY! });\n * const r = await vm.check(\"user@example.com\");\n * if (r.verdict.recommendation === \"block\") { ... }\n *\n * Docs: https://verifymailapi.com/docs\n */\n\nimport { HttpClient, type ClientOptions, type RequestOptions } from \"./client.js\";\nimport type {\n AsyncCheckResponse,\n BulkCheckResponse,\n BulkStreamEvent,\n CheckResponse,\n ReportRequest,\n ReportResponse,\n StatusResponse,\n UsageMeResponse,\n} from \"./types.js\";\n\nexport * from \"./types.js\";\nexport * from \"./errors.js\";\nexport { verifyWebhook } from \"./webhooks.js\";\n\nexport interface CheckOptions {\n /** Override the SDK-wide risk profile for this call. */\n riskProfile?: \"strict\" | \"balanced\" | \"permissive\";\n /** Make the call idempotent. Pass `true` to auto-generate a UUID, or a fixed string. */\n idempotencyKey?: string | boolean;\n}\n\nexport interface AsyncCheckArgs {\n email: string;\n webhookUrl: string;\n /** Optional HMAC-SHA256 key used to sign the final webhook payload. */\n webhookSecret?: string;\n}\n\nexport class VerifyMail {\n private readonly http: HttpClient;\n\n constructor(opts: ClientOptions) {\n this.http = new HttpClient(opts);\n }\n\n /** Check a single email. Charges 1 credit. */\n check(email: string, opts: CheckOptions = {}): Promise<CheckResponse> {\n return this.http.json<CheckResponse>({\n path: \"/v1/check\",\n body: { email },\n idempotencyKey: opts.idempotencyKey,\n riskProfile: opts.riskProfile,\n });\n }\n\n /** Check a domain only (no local part). Charges 1 credit. */\n checkDomain(domain: string, opts: CheckOptions = {}): Promise<CheckResponse> {\n return this.http.json<CheckResponse>({\n path: \"/v1/check/domain\",\n body: { domain },\n idempotencyKey: opts.idempotencyKey,\n riskProfile: opts.riskProfile,\n });\n }\n\n /** Bulk check 1–100 emails. Charges N credits up front (all-or-nothing). */\n checkBulk(emails: string[], opts: CheckOptions = {}): Promise<BulkCheckResponse> {\n if (emails.length === 0 || emails.length > 100) {\n throw new RangeError(\"checkBulk requires 1–100 emails.\");\n }\n return this.http.json<BulkCheckResponse>({\n path: \"/v1/check/bulk\",\n body: { emails },\n idempotencyKey: opts.idempotencyKey,\n riskProfile: opts.riskProfile,\n });\n }\n\n /**\n * Stream bulk-check results as each row completes. Calls `onEvent` once\n * per result (in finish-order) plus once with a final `{event: \"summary\"}`.\n *\n * await vm.checkBulkStream(emails, (e) => {\n * if (\"event\" in e) console.log(\"done\", e);\n * else processRow(e.index, e.result);\n * });\n */\n async checkBulkStream(\n emails: string[],\n onEvent: (e: BulkStreamEvent) => void | Promise<void>,\n opts: Omit<CheckOptions, \"idempotencyKey\"> = {},\n ): Promise<void> {\n if (emails.length === 0) throw new RangeError(\"checkBulkStream needs at least one email.\");\n\n const req: RequestOptions = {\n path: \"/v1/check/bulk/stream\",\n body: { emails },\n riskProfile: opts.riskProfile,\n // Streaming requests aren't safe to auto-retry — would re-charge credits\n // and re-stream rows. Customer must retry explicitly.\n noRetry: true,\n };\n const res = await this.http.raw(req);\n if (!res.body) throw new Error(\"Streaming response had no body.\");\n\n const reader = res.body.getReader();\n const decoder = new TextDecoder();\n let buf = \"\";\n while (true) {\n const { value, done } = await reader.read();\n if (done) break;\n buf += decoder.decode(value, { stream: true });\n let nl: number;\n // Drain complete lines; keep the partial tail in buf for the next chunk.\n while ((nl = buf.indexOf(\"\\n\")) !== -1) {\n const line = buf.slice(0, nl).trim();\n buf = buf.slice(nl + 1);\n if (!line) continue;\n try {\n await onEvent(JSON.parse(line) as BulkStreamEvent);\n } catch (err) {\n throw new Error(`Failed to parse stream line: ${line}`);\n }\n }\n }\n // Final flush in case the server didn't terminate with newline.\n const tail = buf.trim();\n if (tail) await onEvent(JSON.parse(tail) as BulkStreamEvent);\n }\n\n /**\n * Async deep check. Returns 202 immediately with a preliminary verdict;\n * VerifyMail POSTs the final result to your webhook URL once the deep\n * SMTP probe completes. Verify the signature with `verifyWebhook()` in\n * your handler before trusting the payload.\n */\n checkAsync(args: AsyncCheckArgs, opts: CheckOptions = {}): Promise<AsyncCheckResponse> {\n return this.http.json<AsyncCheckResponse>({\n path: \"/v1/check/async\",\n body: {\n email: args.email,\n webhook_url: args.webhookUrl,\n webhook_secret: args.webhookSecret,\n },\n idempotencyKey: opts.idempotencyKey,\n riskProfile: opts.riskProfile,\n });\n }\n\n /** File a /v1/report for a domain outcome (feedback loop). */\n report(req: ReportRequest): Promise<ReportResponse> {\n return this.http.json<ReportResponse>({\n path: \"/v1/report\",\n body: req,\n });\n }\n\n /** Programmatic equivalent of the dashboard's Usage summary. */\n usage(): Promise<UsageMeResponse> {\n return this.http.json<UsageMeResponse>({\n path: \"/v1/usage/me\",\n method: \"GET\",\n });\n }\n\n /** Component health (Redis / Postgres / DNS). Always returns 200. */\n status(): Promise<StatusResponse> {\n return this.http.json<StatusResponse>({\n path: \"/v1/status\",\n method: \"GET\",\n });\n }\n}\n\nexport default VerifyMail;\n","/**\n * Error class hierarchy. All HTTP errors from the API are translated into one\n * of these — customers can `instanceof` them to branch cleanly:\n *\n * try { await vm.check(email) }\n * catch (e) {\n * if (e instanceof QuotaExceededError) return showBilling();\n * if (e instanceof RateLimitError) return retryLater(e.retryAfter);\n * if (e instanceof VerifyMailError) return logAndShowGeneric(e);\n * throw e;\n * }\n */\n\nexport interface ErrorBody {\n code: string;\n http_status: number;\n message: string;\n request_id?: string;\n docs_url?: string;\n // Rate-limit-specific\n limit?: number;\n reset_at?: string;\n // Quota-specific\n upgrade_url?: string;\n}\n\nexport class VerifyMailError extends Error {\n readonly code: string;\n readonly status: number;\n readonly requestId: string | undefined;\n readonly docsUrl: string | undefined;\n readonly body: ErrorBody | undefined;\n\n constructor(message: string, opts: {\n code?: string;\n status?: number;\n requestId?: string;\n docsUrl?: string;\n body?: ErrorBody;\n } = {}) {\n super(message);\n this.name = \"VerifyMailError\";\n this.code = opts.code ?? \"verifymail_error\";\n this.status = opts.status ?? 0;\n this.requestId = opts.requestId;\n this.docsUrl = opts.docsUrl;\n this.body = opts.body;\n }\n}\n\nexport class InvalidApiKeyError extends VerifyMailError {\n constructor(opts: NonNullable<ConstructorParameters<typeof VerifyMailError>[1]>) {\n super(\"API key is missing or invalid.\", opts);\n this.name = \"InvalidApiKeyError\";\n }\n}\n\nexport class QuotaExceededError extends VerifyMailError {\n readonly upgradeUrl: string | undefined;\n constructor(opts: NonNullable<ConstructorParameters<typeof VerifyMailError>[1]>) {\n super(\"Out of credits. Buy a bundle to keep going.\", opts);\n this.name = \"QuotaExceededError\";\n this.upgradeUrl = opts.body?.upgrade_url;\n }\n}\n\nexport class RateLimitError extends VerifyMailError {\n readonly retryAfter: number;\n readonly limit: number | undefined;\n readonly resetAt: string | undefined;\n\n constructor(opts: NonNullable<ConstructorParameters<typeof VerifyMailError>[1]> & {\n retryAfter: number;\n }) {\n super(`Rate limit hit; retry after ${opts.retryAfter}s.`, opts);\n this.name = \"RateLimitError\";\n this.retryAfter = opts.retryAfter;\n this.limit = opts.body?.limit;\n this.resetAt = opts.body?.reset_at;\n }\n}\n\nexport class IdempotencyConflictError extends VerifyMailError {\n constructor(opts: NonNullable<ConstructorParameters<typeof VerifyMailError>[1]>) {\n super(\n \"Idempotency-Key was reused with a different request body. Use a new key or resend the original payload.\",\n opts,\n );\n this.name = \"IdempotencyConflictError\";\n }\n}\n\nexport class ValidationError extends VerifyMailError {\n constructor(message: string, opts: NonNullable<ConstructorParameters<typeof VerifyMailError>[1]>) {\n super(message, opts);\n this.name = \"ValidationError\";\n }\n}\n\nexport class ServiceDegradedError extends VerifyMailError {\n constructor(opts: NonNullable<ConstructorParameters<typeof VerifyMailError>[1]>) {\n super(\"A component is degraded; retry shortly.\", opts);\n this.name = \"ServiceDegradedError\";\n }\n}\n\n/**\n * Map an HTTP response to the right error class. The backend's error envelope\n * is `{ error: { code, http_status, message, ... } }`.\n */\nexport function errorFromResponse(\n status: number,\n body: { error?: ErrorBody } | unknown,\n retryAfterHeader?: string | null,\n): VerifyMailError {\n const envelope =\n body && typeof body === \"object\" && \"error\" in body\n ? (body as { error?: ErrorBody }).error\n : undefined;\n const code = envelope?.code ?? \"verifymail_error\";\n const opts = {\n code,\n status,\n requestId: envelope?.request_id,\n docsUrl: envelope?.docs_url,\n body: envelope,\n };\n\n if (status === 401) return new InvalidApiKeyError(opts);\n if (status === 402) return new QuotaExceededError(opts);\n if (status === 409 && code === \"invalid_idempotency_key\")\n return new IdempotencyConflictError(opts);\n if (status === 422) return new ValidationError(envelope?.message ?? \"Validation error.\", opts);\n if (status === 429) {\n const retryAfter = Number(retryAfterHeader ?? envelope?.limit ?? 1);\n return new RateLimitError({ ...opts, retryAfter: Number.isFinite(retryAfter) ? retryAfter : 1 });\n }\n if (status === 503 || status === 504) return new ServiceDegradedError(opts);\n return new VerifyMailError(envelope?.message ?? `HTTP ${status}`, opts);\n}\n","import { errorFromResponse, RateLimitError, ServiceDegradedError, VerifyMailError } from \"./errors.js\";\n\nexport interface ClientOptions {\n apiKey: string;\n /** Defaults to https://api.verifymailapi.com */\n baseUrl?: string;\n /** Max retry attempts on retryable failures (429, 5xx). Default 2 (so up to 3 total tries). */\n retries?: number;\n /** Per-request timeout in ms. Default 30000. */\n timeoutMs?: number;\n /** Override fetch — useful for tests or non-Node runtimes. */\n fetch?: typeof fetch;\n /** Default risk profile sent as X-Risk-Profile on every call (overridable per request). */\n riskProfile?: \"strict\" | \"balanced\" | \"permissive\";\n}\n\nexport interface RequestOptions {\n method?: \"GET\" | \"POST\";\n path: string;\n query?: Record<string, string | number | undefined>;\n body?: unknown;\n headers?: Record<string, string>;\n /** Provide an Idempotency-Key for POSTs. Auto-generated UUID if `true`. */\n idempotencyKey?: string | boolean;\n /** Override the default risk profile for this call. */\n riskProfile?: \"strict\" | \"balanced\" | \"permissive\";\n /** Skip retry on transient errors for this call. */\n noRetry?: boolean;\n /** Override timeout for this call. */\n timeoutMs?: number;\n}\n\nconst RETRYABLE_STATUS = new Set([408, 429, 500, 502, 503, 504]);\n\nfunction uuidv4(): string {\n // Node 19+ has globalThis.crypto.randomUUID. Polyfill for older Node.\n const c = globalThis.crypto;\n if (c?.randomUUID) return c.randomUUID();\n const buf = new Uint8Array(16);\n for (let i = 0; i < 16; i++) buf[i] = Math.floor(Math.random() * 256);\n buf[6] = (buf[6]! & 0x0f) | 0x40;\n buf[8] = (buf[8]! & 0x3f) | 0x80;\n const hex = Array.from(buf, (b) => b.toString(16).padStart(2, \"0\")).join(\"\");\n return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;\n}\n\nexport class HttpClient {\n private readonly apiKey: string;\n private readonly baseUrl: string;\n private readonly retries: number;\n private readonly timeoutMs: number;\n private readonly fetchImpl: typeof fetch;\n private readonly defaultRiskProfile: string | undefined;\n\n constructor(opts: ClientOptions) {\n if (!opts.apiKey) throw new VerifyMailError(\"apiKey is required.\");\n this.apiKey = opts.apiKey;\n this.baseUrl = (opts.baseUrl ?? \"https://api.verifymailapi.com\").replace(/\\/+$/, \"\");\n this.retries = opts.retries ?? 2;\n this.timeoutMs = opts.timeoutMs ?? 30_000;\n this.fetchImpl = opts.fetch ?? globalThis.fetch;\n this.defaultRiskProfile = opts.riskProfile;\n if (!this.fetchImpl) {\n throw new VerifyMailError(\n \"No fetch available. Pass `fetch` in the options, or use Node 18+.\",\n );\n }\n }\n\n /** Build the headers shared by every request. */\n private buildHeaders(opts: RequestOptions): Headers {\n const h = new Headers(opts.headers);\n h.set(\"X-API-Key\", this.apiKey);\n h.set(\"Accept\", \"application/json\");\n h.set(\"User-Agent\", \"verifymailapi-js/0.1.1\");\n if (opts.body !== undefined) h.set(\"Content-Type\", \"application/json\");\n\n const profile = opts.riskProfile ?? this.defaultRiskProfile;\n if (profile) h.set(\"X-Risk-Profile\", profile);\n\n if (opts.idempotencyKey) {\n h.set(\n \"Idempotency-Key\",\n typeof opts.idempotencyKey === \"string\" ? opts.idempotencyKey : uuidv4(),\n );\n }\n return h;\n }\n\n private buildUrl(path: string, query?: RequestOptions[\"query\"]): string {\n const url = new URL(`${this.baseUrl}${path}`);\n if (query) {\n for (const [k, v] of Object.entries(query)) {\n if (v !== undefined) url.searchParams.set(k, String(v));\n }\n }\n return url.toString();\n }\n\n /** Send and decode a JSON request. Retries on 429 / 5xx up to `retries` times. */\n async json<T>(opts: RequestOptions): Promise<T> {\n const raw = await this.raw(opts);\n if (raw.status === 204) return undefined as T;\n const text = await raw.text();\n try {\n return JSON.parse(text) as T;\n } catch {\n throw new VerifyMailError(`Non-JSON response from ${opts.path}.`, {\n status: raw.status,\n });\n }\n }\n\n /**\n * Send a request and return the raw Response. Handles retries + error mapping.\n * Used internally; exposed for the streaming bulk method to consume the body.\n */\n async raw(opts: RequestOptions): Promise<Response> {\n const url = this.buildUrl(opts.path, opts.query);\n const headers = this.buildHeaders(opts);\n const body = opts.body !== undefined ? JSON.stringify(opts.body) : undefined;\n const method = opts.method ?? (body ? \"POST\" : \"GET\");\n const maxTries = opts.noRetry ? 1 : this.retries + 1;\n const timeoutMs = opts.timeoutMs ?? this.timeoutMs;\n\n let lastError: VerifyMailError | undefined;\n for (let attempt = 0; attempt < maxTries; attempt++) {\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), timeoutMs);\n let res: Response;\n try {\n res = await this.fetchImpl(url, {\n method,\n headers,\n body,\n signal: controller.signal,\n });\n } catch (err) {\n clearTimeout(timer);\n // Network-level failure — retry if attempts remain.\n lastError = new VerifyMailError(\n err instanceof Error ? err.message : \"Network error\",\n { code: \"network_error\" },\n );\n if (attempt < maxTries - 1) {\n await sleep(backoffMs(attempt));\n continue;\n }\n throw lastError;\n }\n clearTimeout(timer);\n\n if (res.ok) return res;\n\n // Parse error body (best-effort) so we can build a typed error.\n let parsed: unknown = undefined;\n try {\n parsed = await res.clone().json();\n } catch {\n /* ignore non-JSON error bodies */\n }\n const err = errorFromResponse(res.status, parsed, res.headers.get(\"Retry-After\"));\n\n const isRetryable =\n !opts.noRetry &&\n RETRYABLE_STATUS.has(res.status) &&\n attempt < maxTries - 1;\n\n if (isRetryable) {\n const wait =\n err instanceof RateLimitError\n ? err.retryAfter * 1000\n : err instanceof ServiceDegradedError\n ? backoffMs(attempt)\n : backoffMs(attempt);\n await sleep(wait);\n lastError = err;\n continue;\n }\n\n throw err;\n }\n\n throw lastError ?? new VerifyMailError(\"Request failed after retries.\");\n }\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((r) => setTimeout(r, ms));\n}\n\nfunction backoffMs(attempt: number): number {\n // 250ms, 750ms, 2.25s — typical exponential with a small jitter floor.\n return Math.min(250 * Math.pow(3, attempt), 10_000);\n}\n","import { createHmac, timingSafeEqual } from \"node:crypto\";\n\n/**\n * Verify an incoming webhook signature from a `/v1/check/async` completion.\n *\n * const ok = verifyWebhook(rawBody, req.header(\"X-VerifyMail-Signature\"), secret);\n * if (!ok) return res.status(401).end();\n *\n * Pass the *raw* request body bytes — not the parsed JSON. In Express:\n * app.post(\"/webhook\", express.raw({ type: \"application/json\" }), handler)\n */\nexport function verifyWebhook(\n rawBody: Buffer | Uint8Array | string,\n signatureHeader: string | null | undefined,\n secret: string,\n): boolean {\n if (!signatureHeader) return false;\n const body =\n typeof rawBody === \"string\" ? Buffer.from(rawBody, \"utf8\") : Buffer.from(rawBody);\n const expected =\n \"sha256=\" + createHmac(\"sha256\", secret).update(body).digest(\"hex\");\n const a = Buffer.from(signatureHeader);\n const b = Buffer.from(expected);\n return a.length === b.length && timingSafeEqual(a, b);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;AC0BO,IAAM,kBAAN,cAA8B,MAAM;AAAA,EAChC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EAET,YAAY,SAAiB,OAMzB,CAAC,GAAG;AACN,UAAM,OAAO;AACb,SAAK,OAAO;AACZ,SAAK,OAAO,KAAK,QAAQ;AACzB,SAAK,SAAS,KAAK,UAAU;AAC7B,SAAK,YAAY,KAAK;AACtB,SAAK,UAAU,KAAK;AACpB,SAAK,OAAO,KAAK;AAAA,EACnB;AACF;AAEO,IAAM,qBAAN,cAAiC,gBAAgB;AAAA,EACtD,YAAY,MAAqE;AAC/E,UAAM,kCAAkC,IAAI;AAC5C,SAAK,OAAO;AAAA,EACd;AACF;AAEO,IAAM,qBAAN,cAAiC,gBAAgB;AAAA,EAC7C;AAAA,EACT,YAAY,MAAqE;AAC/E,UAAM,+CAA+C,IAAI;AACzD,SAAK,OAAO;AACZ,SAAK,aAAa,KAAK,MAAM;AAAA,EAC/B;AACF;AAEO,IAAM,iBAAN,cAA6B,gBAAgB;AAAA,EACzC;AAAA,EACA;AAAA,EACA;AAAA,EAET,YAAY,MAET;AACD,UAAM,+BAA+B,KAAK,UAAU,MAAM,IAAI;AAC9D,SAAK,OAAO;AACZ,SAAK,aAAa,KAAK;AACvB,SAAK,QAAQ,KAAK,MAAM;AACxB,SAAK,UAAU,KAAK,MAAM;AAAA,EAC5B;AACF;AAEO,IAAM,2BAAN,cAAuC,gBAAgB;AAAA,EAC5D,YAAY,MAAqE;AAC/E;AAAA,MACE;AAAA,MACA;AAAA,IACF;AACA,SAAK,OAAO;AAAA,EACd;AACF;AAEO,IAAM,kBAAN,cAA8B,gBAAgB;AAAA,EACnD,YAAY,SAAiB,MAAqE;AAChG,UAAM,SAAS,IAAI;AACnB,SAAK,OAAO;AAAA,EACd;AACF;AAEO,IAAM,uBAAN,cAAmC,gBAAgB;AAAA,EACxD,YAAY,MAAqE;AAC/E,UAAM,2CAA2C,IAAI;AACrD,SAAK,OAAO;AAAA,EACd;AACF;AAMO,SAAS,kBACd,QACA,MACA,kBACiB;AACjB,QAAM,WACJ,QAAQ,OAAO,SAAS,YAAY,WAAW,OAC1C,KAA+B,QAChC;AACN,QAAM,OAAO,UAAU,QAAQ;AAC/B,QAAM,OAAO;AAAA,IACX;AAAA,IACA;AAAA,IACA,WAAW,UAAU;AAAA,IACrB,SAAS,UAAU;AAAA,IACnB,MAAM;AAAA,EACR;AAEA,MAAI,WAAW,IAAK,QAAO,IAAI,mBAAmB,IAAI;AACtD,MAAI,WAAW,IAAK,QAAO,IAAI,mBAAmB,IAAI;AACtD,MAAI,WAAW,OAAO,SAAS;AAC7B,WAAO,IAAI,yBAAyB,IAAI;AAC1C,MAAI,WAAW,IAAK,QAAO,IAAI,gBAAgB,UAAU,WAAW,qBAAqB,IAAI;AAC7F,MAAI,WAAW,KAAK;AAClB,UAAM,aAAa,OAAO,oBAAoB,UAAU,SAAS,CAAC;AAClE,WAAO,IAAI,eAAe,EAAE,GAAG,MAAM,YAAY,OAAO,SAAS,UAAU,IAAI,aAAa,EAAE,CAAC;AAAA,EACjG;AACA,MAAI,WAAW,OAAO,WAAW,IAAK,QAAO,IAAI,qBAAqB,IAAI;AAC1E,SAAO,IAAI,gBAAgB,UAAU,WAAW,QAAQ,MAAM,IAAI,IAAI;AACxE;;;AC3GA,IAAM,mBAAmB,oBAAI,IAAI,CAAC,KAAK,KAAK,KAAK,KAAK,KAAK,GAAG,CAAC;AAE/D,SAAS,SAAiB;AAExB,QAAM,IAAI,WAAW;AACrB,MAAI,GAAG,WAAY,QAAO,EAAE,WAAW;AACvC,QAAM,MAAM,IAAI,WAAW,EAAE;AAC7B,WAAS,IAAI,GAAG,IAAI,IAAI,IAAK,KAAI,CAAC,IAAI,KAAK,MAAM,KAAK,OAAO,IAAI,GAAG;AACpE,MAAI,CAAC,IAAK,IAAI,CAAC,IAAK,KAAQ;AAC5B,MAAI,CAAC,IAAK,IAAI,CAAC,IAAK,KAAQ;AAC5B,QAAM,MAAM,MAAM,KAAK,KAAK,CAAC,MAAM,EAAE,SAAS,EAAE,EAAE,SAAS,GAAG,GAAG,CAAC,EAAE,KAAK,EAAE;AAC3E,SAAO,GAAG,IAAI,MAAM,GAAG,CAAC,CAAC,IAAI,IAAI,MAAM,GAAG,EAAE,CAAC,IAAI,IAAI,MAAM,IAAI,EAAE,CAAC,IAAI,IAAI,MAAM,IAAI,EAAE,CAAC,IAAI,IAAI,MAAM,EAAE,CAAC;AAC1G;AAEO,IAAM,aAAN,MAAiB;AAAA,EACL;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EAEjB,YAAY,MAAqB;AAC/B,QAAI,CAAC,KAAK,OAAQ,OAAM,IAAI,gBAAgB,qBAAqB;AACjE,SAAK,SAAS,KAAK;AACnB,SAAK,WAAW,KAAK,WAAW,iCAAiC,QAAQ,QAAQ,EAAE;AACnF,SAAK,UAAU,KAAK,WAAW;AAC/B,SAAK,YAAY,KAAK,aAAa;AACnC,SAAK,YAAY,KAAK,SAAS,WAAW;AAC1C,SAAK,qBAAqB,KAAK;AAC/B,QAAI,CAAC,KAAK,WAAW;AACnB,YAAM,IAAI;AAAA,QACR;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAAA;AAAA,EAGQ,aAAa,MAA+B;AAClD,UAAM,IAAI,IAAI,QAAQ,KAAK,OAAO;AAClC,MAAE,IAAI,aAAa,KAAK,MAAM;AAC9B,MAAE,IAAI,UAAU,kBAAkB;AAClC,MAAE,IAAI,cAAc,wBAAwB;AAC5C,QAAI,KAAK,SAAS,OAAW,GAAE,IAAI,gBAAgB,kBAAkB;AAErE,UAAM,UAAU,KAAK,eAAe,KAAK;AACzC,QAAI,QAAS,GAAE,IAAI,kBAAkB,OAAO;AAE5C,QAAI,KAAK,gBAAgB;AACvB,QAAE;AAAA,QACA;AAAA,QACA,OAAO,KAAK,mBAAmB,WAAW,KAAK,iBAAiB,OAAO;AAAA,MACzE;AAAA,IACF;AACA,WAAO;AAAA,EACT;AAAA,EAEQ,SAAS,MAAc,OAAyC;AACtE,UAAM,MAAM,IAAI,IAAI,GAAG,KAAK,OAAO,GAAG,IAAI,EAAE;AAC5C,QAAI,OAAO;AACT,iBAAW,CAAC,GAAG,CAAC,KAAK,OAAO,QAAQ,KAAK,GAAG;AAC1C,YAAI,MAAM,OAAW,KAAI,aAAa,IAAI,GAAG,OAAO,CAAC,CAAC;AAAA,MACxD;AAAA,IACF;AACA,WAAO,IAAI,SAAS;AAAA,EACtB;AAAA;AAAA,EAGA,MAAM,KAAQ,MAAkC;AAC9C,UAAM,MAAM,MAAM,KAAK,IAAI,IAAI;AAC/B,QAAI,IAAI,WAAW,IAAK,QAAO;AAC/B,UAAM,OAAO,MAAM,IAAI,KAAK;AAC5B,QAAI;AACF,aAAO,KAAK,MAAM,IAAI;AAAA,IACxB,QAAQ;AACN,YAAM,IAAI,gBAAgB,0BAA0B,KAAK,IAAI,KAAK;AAAA,QAChE,QAAQ,IAAI;AAAA,MACd,CAAC;AAAA,IACH;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,IAAI,MAAyC;AACjD,UAAM,MAAM,KAAK,SAAS,KAAK,MAAM,KAAK,KAAK;AAC/C,UAAM,UAAU,KAAK,aAAa,IAAI;AACtC,UAAM,OAAO,KAAK,SAAS,SAAY,KAAK,UAAU,KAAK,IAAI,IAAI;AACnE,UAAM,SAAS,KAAK,WAAW,OAAO,SAAS;AAC/C,UAAM,WAAW,KAAK,UAAU,IAAI,KAAK,UAAU;AACnD,UAAM,YAAY,KAAK,aAAa,KAAK;AAEzC,QAAI;AACJ,aAAS,UAAU,GAAG,UAAU,UAAU,WAAW;AACnD,YAAM,aAAa,IAAI,gBAAgB;AACvC,YAAM,QAAQ,WAAW,MAAM,WAAW,MAAM,GAAG,SAAS;AAC5D,UAAI;AACJ,UAAI;AACF,cAAM,MAAM,KAAK,UAAU,KAAK;AAAA,UAC9B;AAAA,UACA;AAAA,UACA;AAAA,UACA,QAAQ,WAAW;AAAA,QACrB,CAAC;AAAA,MACH,SAASA,MAAK;AACZ,qBAAa,KAAK;AAElB,oBAAY,IAAI;AAAA,UACdA,gBAAe,QAAQA,KAAI,UAAU;AAAA,UACrC,EAAE,MAAM,gBAAgB;AAAA,QAC1B;AACA,YAAI,UAAU,WAAW,GAAG;AAC1B,gBAAM,MAAM,UAAU,OAAO,CAAC;AAC9B;AAAA,QACF;AACA,cAAM;AAAA,MACR;AACA,mBAAa,KAAK;AAElB,UAAI,IAAI,GAAI,QAAO;AAGnB,UAAI,SAAkB;AACtB,UAAI;AACF,iBAAS,MAAM,IAAI,MAAM,EAAE,KAAK;AAAA,MAClC,QAAQ;AAAA,MAER;AACA,YAAM,MAAM,kBAAkB,IAAI,QAAQ,QAAQ,IAAI,QAAQ,IAAI,aAAa,CAAC;AAEhF,YAAM,cACJ,CAAC,KAAK,WACN,iBAAiB,IAAI,IAAI,MAAM,KAC/B,UAAU,WAAW;AAEvB,UAAI,aAAa;AACf,cAAM,OACJ,eAAe,iBACX,IAAI,aAAa,MACjB,eAAe,uBACf,UAAU,OAAO,IACjB,UAAU,OAAO;AACvB,cAAM,MAAM,IAAI;AAChB,oBAAY;AACZ;AAAA,MACF;AAEA,YAAM;AAAA,IACR;AAEA,UAAM,aAAa,IAAI,gBAAgB,+BAA+B;AAAA,EACxE;AACF;AAEA,SAAS,MAAM,IAA2B;AACxC,SAAO,IAAI,QAAQ,CAAC,MAAM,WAAW,GAAG,EAAE,CAAC;AAC7C;AAEA,SAAS,UAAU,SAAyB;AAE1C,SAAO,KAAK,IAAI,MAAM,KAAK,IAAI,GAAG,OAAO,GAAG,GAAM;AACpD;;;AClMA,yBAA4C;AAWrC,SAAS,cACd,SACA,iBACA,QACS;AACT,MAAI,CAAC,gBAAiB,QAAO;AAC7B,QAAM,OACJ,OAAO,YAAY,WAAW,OAAO,KAAK,SAAS,MAAM,IAAI,OAAO,KAAK,OAAO;AAClF,QAAM,WACJ,gBAAY,+BAAW,UAAU,MAAM,EAAE,OAAO,IAAI,EAAE,OAAO,KAAK;AACpE,QAAM,IAAI,OAAO,KAAK,eAAe;AACrC,QAAM,IAAI,OAAO,KAAK,QAAQ;AAC9B,SAAO,EAAE,WAAW,EAAE,cAAU,oCAAgB,GAAG,CAAC;AACtD;;;AHiBO,IAAM,aAAN,MAAiB;AAAA,EACL;AAAA,EAEjB,YAAY,MAAqB;AAC/B,SAAK,OAAO,IAAI,WAAW,IAAI;AAAA,EACjC;AAAA;AAAA,EAGA,MAAM,OAAe,OAAqB,CAAC,GAA2B;AACpE,WAAO,KAAK,KAAK,KAAoB;AAAA,MACnC,MAAM;AAAA,MACN,MAAM,EAAE,MAAM;AAAA,MACd,gBAAgB,KAAK;AAAA,MACrB,aAAa,KAAK;AAAA,IACpB,CAAC;AAAA,EACH;AAAA;AAAA,EAGA,YAAY,QAAgB,OAAqB,CAAC,GAA2B;AAC3E,WAAO,KAAK,KAAK,KAAoB;AAAA,MACnC,MAAM;AAAA,MACN,MAAM,EAAE,OAAO;AAAA,MACf,gBAAgB,KAAK;AAAA,MACrB,aAAa,KAAK;AAAA,IACpB,CAAC;AAAA,EACH;AAAA;AAAA,EAGA,UAAU,QAAkB,OAAqB,CAAC,GAA+B;AAC/E,QAAI,OAAO,WAAW,KAAK,OAAO,SAAS,KAAK;AAC9C,YAAM,IAAI,WAAW,uCAAkC;AAAA,IACzD;AACA,WAAO,KAAK,KAAK,KAAwB;AAAA,MACvC,MAAM;AAAA,MACN,MAAM,EAAE,OAAO;AAAA,MACf,gBAAgB,KAAK;AAAA,MACrB,aAAa,KAAK;AAAA,IACpB,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWA,MAAM,gBACJ,QACA,SACA,OAA6C,CAAC,GAC/B;AACf,QAAI,OAAO,WAAW,EAAG,OAAM,IAAI,WAAW,2CAA2C;AAEzF,UAAM,MAAsB;AAAA,MAC1B,MAAM;AAAA,MACN,MAAM,EAAE,OAAO;AAAA,MACf,aAAa,KAAK;AAAA;AAAA;AAAA,MAGlB,SAAS;AAAA,IACX;AACA,UAAM,MAAM,MAAM,KAAK,KAAK,IAAI,GAAG;AACnC,QAAI,CAAC,IAAI,KAAM,OAAM,IAAI,MAAM,iCAAiC;AAEhE,UAAM,SAAS,IAAI,KAAK,UAAU;AAClC,UAAM,UAAU,IAAI,YAAY;AAChC,QAAI,MAAM;AACV,WAAO,MAAM;AACX,YAAM,EAAE,OAAO,KAAK,IAAI,MAAM,OAAO,KAAK;AAC1C,UAAI,KAAM;AACV,aAAO,QAAQ,OAAO,OAAO,EAAE,QAAQ,KAAK,CAAC;AAC7C,UAAI;AAEJ,cAAQ,KAAK,IAAI,QAAQ,IAAI,OAAO,IAAI;AACtC,cAAM,OAAO,IAAI,MAAM,GAAG,EAAE,EAAE,KAAK;AACnC,cAAM,IAAI,MAAM,KAAK,CAAC;AACtB,YAAI,CAAC,KAAM;AACX,YAAI;AACF,gBAAM,QAAQ,KAAK,MAAM,IAAI,CAAoB;AAAA,QACnD,SAAS,KAAK;AACZ,gBAAM,IAAI,MAAM,gCAAgC,IAAI,EAAE;AAAA,QACxD;AAAA,MACF;AAAA,IACF;AAEA,UAAM,OAAO,IAAI,KAAK;AACtB,QAAI,KAAM,OAAM,QAAQ,KAAK,MAAM,IAAI,CAAoB;AAAA,EAC7D;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,WAAW,MAAsB,OAAqB,CAAC,GAAgC;AACrF,WAAO,KAAK,KAAK,KAAyB;AAAA,MACxC,MAAM;AAAA,MACN,MAAM;AAAA,QACJ,OAAO,KAAK;AAAA,QACZ,aAAa,KAAK;AAAA,QAClB,gBAAgB,KAAK;AAAA,MACvB;AAAA,MACA,gBAAgB,KAAK;AAAA,MACrB,aAAa,KAAK;AAAA,IACpB,CAAC;AAAA,EACH;AAAA;AAAA,EAGA,OAAO,KAA6C;AAClD,WAAO,KAAK,KAAK,KAAqB;AAAA,MACpC,MAAM;AAAA,MACN,MAAM;AAAA,IACR,CAAC;AAAA,EACH;AAAA;AAAA,EAGA,QAAkC;AAChC,WAAO,KAAK,KAAK,KAAsB;AAAA,MACrC,MAAM;AAAA,MACN,QAAQ;AAAA,IACV,CAAC;AAAA,EACH;AAAA;AAAA,EAGA,SAAkC;AAChC,WAAO,KAAK,KAAK,KAAqB;AAAA,MACpC,MAAM;AAAA,MACN,QAAQ;AAAA,IACV,CAAC;AAAA,EACH;AACF;AAEA,IAAO,gBAAQ;","names":["err"]}
package/dist/index.js CHANGED
@@ -123,7 +123,7 @@ var HttpClient = class {
123
123
  const h = new Headers(opts.headers);
124
124
  h.set("X-API-Key", this.apiKey);
125
125
  h.set("Accept", "application/json");
126
- h.set("User-Agent", "verifymailapi-js/0.1.0");
126
+ h.set("User-Agent", "verifymailapi-js/0.1.1");
127
127
  if (opts.body !== void 0) h.set("Content-Type", "application/json");
128
128
  const profile = opts.riskProfile ?? this.defaultRiskProfile;
129
129
  if (profile) h.set("X-Risk-Profile", profile);
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/errors.ts","../src/client.ts","../src/webhooks.ts","../src/index.ts"],"sourcesContent":["/**\n * Error class hierarchy. All HTTP errors from the API are translated into one\n * of these — customers can `instanceof` them to branch cleanly:\n *\n * try { await vm.check(email) }\n * catch (e) {\n * if (e instanceof QuotaExceededError) return showBilling();\n * if (e instanceof RateLimitError) return retryLater(e.retryAfter);\n * if (e instanceof VerifyMailError) return logAndShowGeneric(e);\n * throw e;\n * }\n */\n\nexport interface ErrorBody {\n code: string;\n http_status: number;\n message: string;\n request_id?: string;\n docs_url?: string;\n // Rate-limit-specific\n limit?: number;\n reset_at?: string;\n // Quota-specific\n upgrade_url?: string;\n}\n\nexport class VerifyMailError extends Error {\n readonly code: string;\n readonly status: number;\n readonly requestId: string | undefined;\n readonly docsUrl: string | undefined;\n readonly body: ErrorBody | undefined;\n\n constructor(message: string, opts: {\n code?: string;\n status?: number;\n requestId?: string;\n docsUrl?: string;\n body?: ErrorBody;\n } = {}) {\n super(message);\n this.name = \"VerifyMailError\";\n this.code = opts.code ?? \"verifymail_error\";\n this.status = opts.status ?? 0;\n this.requestId = opts.requestId;\n this.docsUrl = opts.docsUrl;\n this.body = opts.body;\n }\n}\n\nexport class InvalidApiKeyError extends VerifyMailError {\n constructor(opts: NonNullable<ConstructorParameters<typeof VerifyMailError>[1]>) {\n super(\"API key is missing or invalid.\", opts);\n this.name = \"InvalidApiKeyError\";\n }\n}\n\nexport class QuotaExceededError extends VerifyMailError {\n readonly upgradeUrl: string | undefined;\n constructor(opts: NonNullable<ConstructorParameters<typeof VerifyMailError>[1]>) {\n super(\"Out of credits. Buy a bundle to keep going.\", opts);\n this.name = \"QuotaExceededError\";\n this.upgradeUrl = opts.body?.upgrade_url;\n }\n}\n\nexport class RateLimitError extends VerifyMailError {\n readonly retryAfter: number;\n readonly limit: number | undefined;\n readonly resetAt: string | undefined;\n\n constructor(opts: NonNullable<ConstructorParameters<typeof VerifyMailError>[1]> & {\n retryAfter: number;\n }) {\n super(`Rate limit hit; retry after ${opts.retryAfter}s.`, opts);\n this.name = \"RateLimitError\";\n this.retryAfter = opts.retryAfter;\n this.limit = opts.body?.limit;\n this.resetAt = opts.body?.reset_at;\n }\n}\n\nexport class IdempotencyConflictError extends VerifyMailError {\n constructor(opts: NonNullable<ConstructorParameters<typeof VerifyMailError>[1]>) {\n super(\n \"Idempotency-Key was reused with a different request body. Use a new key or resend the original payload.\",\n opts,\n );\n this.name = \"IdempotencyConflictError\";\n }\n}\n\nexport class ValidationError extends VerifyMailError {\n constructor(message: string, opts: NonNullable<ConstructorParameters<typeof VerifyMailError>[1]>) {\n super(message, opts);\n this.name = \"ValidationError\";\n }\n}\n\nexport class ServiceDegradedError extends VerifyMailError {\n constructor(opts: NonNullable<ConstructorParameters<typeof VerifyMailError>[1]>) {\n super(\"A component is degraded; retry shortly.\", opts);\n this.name = \"ServiceDegradedError\";\n }\n}\n\n/**\n * Map an HTTP response to the right error class. The backend's error envelope\n * is `{ error: { code, http_status, message, ... } }`.\n */\nexport function errorFromResponse(\n status: number,\n body: { error?: ErrorBody } | unknown,\n retryAfterHeader?: string | null,\n): VerifyMailError {\n const envelope =\n body && typeof body === \"object\" && \"error\" in body\n ? (body as { error?: ErrorBody }).error\n : undefined;\n const code = envelope?.code ?? \"verifymail_error\";\n const opts = {\n code,\n status,\n requestId: envelope?.request_id,\n docsUrl: envelope?.docs_url,\n body: envelope,\n };\n\n if (status === 401) return new InvalidApiKeyError(opts);\n if (status === 402) return new QuotaExceededError(opts);\n if (status === 409 && code === \"invalid_idempotency_key\")\n return new IdempotencyConflictError(opts);\n if (status === 422) return new ValidationError(envelope?.message ?? \"Validation error.\", opts);\n if (status === 429) {\n const retryAfter = Number(retryAfterHeader ?? envelope?.limit ?? 1);\n return new RateLimitError({ ...opts, retryAfter: Number.isFinite(retryAfter) ? retryAfter : 1 });\n }\n if (status === 503 || status === 504) return new ServiceDegradedError(opts);\n return new VerifyMailError(envelope?.message ?? `HTTP ${status}`, opts);\n}\n","import { errorFromResponse, RateLimitError, ServiceDegradedError, VerifyMailError } from \"./errors.js\";\n\nexport interface ClientOptions {\n apiKey: string;\n /** Defaults to https://api.verifymailapi.com */\n baseUrl?: string;\n /** Max retry attempts on retryable failures (429, 5xx). Default 2 (so up to 3 total tries). */\n retries?: number;\n /** Per-request timeout in ms. Default 30000. */\n timeoutMs?: number;\n /** Override fetch — useful for tests or non-Node runtimes. */\n fetch?: typeof fetch;\n /** Default risk profile sent as X-Risk-Profile on every call (overridable per request). */\n riskProfile?: \"strict\" | \"balanced\" | \"permissive\";\n}\n\nexport interface RequestOptions {\n method?: \"GET\" | \"POST\";\n path: string;\n query?: Record<string, string | number | undefined>;\n body?: unknown;\n headers?: Record<string, string>;\n /** Provide an Idempotency-Key for POSTs. Auto-generated UUID if `true`. */\n idempotencyKey?: string | boolean;\n /** Override the default risk profile for this call. */\n riskProfile?: \"strict\" | \"balanced\" | \"permissive\";\n /** Skip retry on transient errors for this call. */\n noRetry?: boolean;\n /** Override timeout for this call. */\n timeoutMs?: number;\n}\n\nconst RETRYABLE_STATUS = new Set([408, 429, 500, 502, 503, 504]);\n\nfunction uuidv4(): string {\n // Node 19+ has globalThis.crypto.randomUUID. Polyfill for older Node.\n const c = globalThis.crypto;\n if (c?.randomUUID) return c.randomUUID();\n const buf = new Uint8Array(16);\n for (let i = 0; i < 16; i++) buf[i] = Math.floor(Math.random() * 256);\n buf[6] = (buf[6]! & 0x0f) | 0x40;\n buf[8] = (buf[8]! & 0x3f) | 0x80;\n const hex = Array.from(buf, (b) => b.toString(16).padStart(2, \"0\")).join(\"\");\n return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;\n}\n\nexport class HttpClient {\n private readonly apiKey: string;\n private readonly baseUrl: string;\n private readonly retries: number;\n private readonly timeoutMs: number;\n private readonly fetchImpl: typeof fetch;\n private readonly defaultRiskProfile: string | undefined;\n\n constructor(opts: ClientOptions) {\n if (!opts.apiKey) throw new VerifyMailError(\"apiKey is required.\");\n this.apiKey = opts.apiKey;\n this.baseUrl = (opts.baseUrl ?? \"https://api.verifymailapi.com\").replace(/\\/+$/, \"\");\n this.retries = opts.retries ?? 2;\n this.timeoutMs = opts.timeoutMs ?? 30_000;\n this.fetchImpl = opts.fetch ?? globalThis.fetch;\n this.defaultRiskProfile = opts.riskProfile;\n if (!this.fetchImpl) {\n throw new VerifyMailError(\n \"No fetch available. Pass `fetch` in the options, or use Node 18+.\",\n );\n }\n }\n\n /** Build the headers shared by every request. */\n private buildHeaders(opts: RequestOptions): Headers {\n const h = new Headers(opts.headers);\n h.set(\"X-API-Key\", this.apiKey);\n h.set(\"Accept\", \"application/json\");\n h.set(\"User-Agent\", \"verifymailapi-js/0.1.0\");\n if (opts.body !== undefined) h.set(\"Content-Type\", \"application/json\");\n\n const profile = opts.riskProfile ?? this.defaultRiskProfile;\n if (profile) h.set(\"X-Risk-Profile\", profile);\n\n if (opts.idempotencyKey) {\n h.set(\n \"Idempotency-Key\",\n typeof opts.idempotencyKey === \"string\" ? opts.idempotencyKey : uuidv4(),\n );\n }\n return h;\n }\n\n private buildUrl(path: string, query?: RequestOptions[\"query\"]): string {\n const url = new URL(`${this.baseUrl}${path}`);\n if (query) {\n for (const [k, v] of Object.entries(query)) {\n if (v !== undefined) url.searchParams.set(k, String(v));\n }\n }\n return url.toString();\n }\n\n /** Send and decode a JSON request. Retries on 429 / 5xx up to `retries` times. */\n async json<T>(opts: RequestOptions): Promise<T> {\n const raw = await this.raw(opts);\n if (raw.status === 204) return undefined as T;\n const text = await raw.text();\n try {\n return JSON.parse(text) as T;\n } catch {\n throw new VerifyMailError(`Non-JSON response from ${opts.path}.`, {\n status: raw.status,\n });\n }\n }\n\n /**\n * Send a request and return the raw Response. Handles retries + error mapping.\n * Used internally; exposed for the streaming bulk method to consume the body.\n */\n async raw(opts: RequestOptions): Promise<Response> {\n const url = this.buildUrl(opts.path, opts.query);\n const headers = this.buildHeaders(opts);\n const body = opts.body !== undefined ? JSON.stringify(opts.body) : undefined;\n const method = opts.method ?? (body ? \"POST\" : \"GET\");\n const maxTries = opts.noRetry ? 1 : this.retries + 1;\n const timeoutMs = opts.timeoutMs ?? this.timeoutMs;\n\n let lastError: VerifyMailError | undefined;\n for (let attempt = 0; attempt < maxTries; attempt++) {\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), timeoutMs);\n let res: Response;\n try {\n res = await this.fetchImpl(url, {\n method,\n headers,\n body,\n signal: controller.signal,\n });\n } catch (err) {\n clearTimeout(timer);\n // Network-level failure — retry if attempts remain.\n lastError = new VerifyMailError(\n err instanceof Error ? err.message : \"Network error\",\n { code: \"network_error\" },\n );\n if (attempt < maxTries - 1) {\n await sleep(backoffMs(attempt));\n continue;\n }\n throw lastError;\n }\n clearTimeout(timer);\n\n if (res.ok) return res;\n\n // Parse error body (best-effort) so we can build a typed error.\n let parsed: unknown = undefined;\n try {\n parsed = await res.clone().json();\n } catch {\n /* ignore non-JSON error bodies */\n }\n const err = errorFromResponse(res.status, parsed, res.headers.get(\"Retry-After\"));\n\n const isRetryable =\n !opts.noRetry &&\n RETRYABLE_STATUS.has(res.status) &&\n attempt < maxTries - 1;\n\n if (isRetryable) {\n const wait =\n err instanceof RateLimitError\n ? err.retryAfter * 1000\n : err instanceof ServiceDegradedError\n ? backoffMs(attempt)\n : backoffMs(attempt);\n await sleep(wait);\n lastError = err;\n continue;\n }\n\n throw err;\n }\n\n throw lastError ?? new VerifyMailError(\"Request failed after retries.\");\n }\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((r) => setTimeout(r, ms));\n}\n\nfunction backoffMs(attempt: number): number {\n // 250ms, 750ms, 2.25s — typical exponential with a small jitter floor.\n return Math.min(250 * Math.pow(3, attempt), 10_000);\n}\n","import { createHmac, timingSafeEqual } from \"node:crypto\";\n\n/**\n * Verify an incoming webhook signature from a `/v1/check/async` completion.\n *\n * const ok = verifyWebhook(rawBody, req.header(\"X-VerifyMail-Signature\"), secret);\n * if (!ok) return res.status(401).end();\n *\n * Pass the *raw* request body bytes — not the parsed JSON. In Express:\n * app.post(\"/webhook\", express.raw({ type: \"application/json\" }), handler)\n */\nexport function verifyWebhook(\n rawBody: Buffer | Uint8Array | string,\n signatureHeader: string | null | undefined,\n secret: string,\n): boolean {\n if (!signatureHeader) return false;\n const body =\n typeof rawBody === \"string\" ? Buffer.from(rawBody, \"utf8\") : Buffer.from(rawBody);\n const expected =\n \"sha256=\" + createHmac(\"sha256\", secret).update(body).digest(\"hex\");\n const a = Buffer.from(signatureHeader);\n const b = Buffer.from(expected);\n return a.length === b.length && timingSafeEqual(a, b);\n}\n","/**\n * Official SDK for the VerifyMail API.\n *\n * import { VerifyMail } from \"verifymailapi\";\n * const vm = new VerifyMail({ apiKey: process.env.VERIFYMAIL_KEY! });\n * const r = await vm.check(\"user@example.com\");\n * if (r.verdict.recommendation === \"block\") { ... }\n *\n * Docs: https://verifymailapi.com/docs\n */\n\nimport { HttpClient, type ClientOptions, type RequestOptions } from \"./client.js\";\nimport type {\n AsyncCheckResponse,\n BulkCheckResponse,\n BulkStreamEvent,\n CheckResponse,\n ReportRequest,\n ReportResponse,\n StatusResponse,\n UsageMeResponse,\n} from \"./types.js\";\n\nexport * from \"./types.js\";\nexport * from \"./errors.js\";\nexport { verifyWebhook } from \"./webhooks.js\";\n\nexport interface CheckOptions {\n /** Override the SDK-wide risk profile for this call. */\n riskProfile?: \"strict\" | \"balanced\" | \"permissive\";\n /** Make the call idempotent. Pass `true` to auto-generate a UUID, or a fixed string. */\n idempotencyKey?: string | boolean;\n}\n\nexport interface AsyncCheckArgs {\n email: string;\n webhookUrl: string;\n /** Optional HMAC-SHA256 key used to sign the final webhook payload. */\n webhookSecret?: string;\n}\n\nexport class VerifyMail {\n private readonly http: HttpClient;\n\n constructor(opts: ClientOptions) {\n this.http = new HttpClient(opts);\n }\n\n /** Check a single email. Charges 1 credit. */\n check(email: string, opts: CheckOptions = {}): Promise<CheckResponse> {\n return this.http.json<CheckResponse>({\n path: \"/v1/check\",\n body: { email },\n idempotencyKey: opts.idempotencyKey,\n riskProfile: opts.riskProfile,\n });\n }\n\n /** Check a domain only (no local part). Charges 1 credit. */\n checkDomain(domain: string, opts: CheckOptions = {}): Promise<CheckResponse> {\n return this.http.json<CheckResponse>({\n path: \"/v1/check/domain\",\n body: { domain },\n idempotencyKey: opts.idempotencyKey,\n riskProfile: opts.riskProfile,\n });\n }\n\n /** Bulk check 1–100 emails. Charges N credits up front (all-or-nothing). */\n checkBulk(emails: string[], opts: CheckOptions = {}): Promise<BulkCheckResponse> {\n if (emails.length === 0 || emails.length > 100) {\n throw new RangeError(\"checkBulk requires 1–100 emails.\");\n }\n return this.http.json<BulkCheckResponse>({\n path: \"/v1/check/bulk\",\n body: { emails },\n idempotencyKey: opts.idempotencyKey,\n riskProfile: opts.riskProfile,\n });\n }\n\n /**\n * Stream bulk-check results as each row completes. Calls `onEvent` once\n * per result (in finish-order) plus once with a final `{event: \"summary\"}`.\n *\n * await vm.checkBulkStream(emails, (e) => {\n * if (\"event\" in e) console.log(\"done\", e);\n * else processRow(e.index, e.result);\n * });\n */\n async checkBulkStream(\n emails: string[],\n onEvent: (e: BulkStreamEvent) => void | Promise<void>,\n opts: Omit<CheckOptions, \"idempotencyKey\"> = {},\n ): Promise<void> {\n if (emails.length === 0) throw new RangeError(\"checkBulkStream needs at least one email.\");\n\n const req: RequestOptions = {\n path: \"/v1/check/bulk/stream\",\n body: { emails },\n riskProfile: opts.riskProfile,\n // Streaming requests aren't safe to auto-retry — would re-charge credits\n // and re-stream rows. Customer must retry explicitly.\n noRetry: true,\n };\n const res = await this.http.raw(req);\n if (!res.body) throw new Error(\"Streaming response had no body.\");\n\n const reader = res.body.getReader();\n const decoder = new TextDecoder();\n let buf = \"\";\n while (true) {\n const { value, done } = await reader.read();\n if (done) break;\n buf += decoder.decode(value, { stream: true });\n let nl: number;\n // Drain complete lines; keep the partial tail in buf for the next chunk.\n while ((nl = buf.indexOf(\"\\n\")) !== -1) {\n const line = buf.slice(0, nl).trim();\n buf = buf.slice(nl + 1);\n if (!line) continue;\n try {\n await onEvent(JSON.parse(line) as BulkStreamEvent);\n } catch (err) {\n throw new Error(`Failed to parse stream line: ${line}`);\n }\n }\n }\n // Final flush in case the server didn't terminate with newline.\n const tail = buf.trim();\n if (tail) await onEvent(JSON.parse(tail) as BulkStreamEvent);\n }\n\n /**\n * Async deep check. Returns 202 immediately with a preliminary verdict;\n * VerifyMail POSTs the final result to your webhook URL once the deep\n * SMTP probe completes. Verify the signature with `verifyWebhook()` in\n * your handler before trusting the payload.\n */\n checkAsync(args: AsyncCheckArgs, opts: CheckOptions = {}): Promise<AsyncCheckResponse> {\n return this.http.json<AsyncCheckResponse>({\n path: \"/v1/check/async\",\n body: {\n email: args.email,\n webhook_url: args.webhookUrl,\n webhook_secret: args.webhookSecret,\n },\n idempotencyKey: opts.idempotencyKey,\n riskProfile: opts.riskProfile,\n });\n }\n\n /** File a /v1/report for a domain outcome (feedback loop). */\n report(req: ReportRequest): Promise<ReportResponse> {\n return this.http.json<ReportResponse>({\n path: \"/v1/report\",\n body: req,\n });\n }\n\n /** Programmatic equivalent of the dashboard's Usage summary. */\n usage(): Promise<UsageMeResponse> {\n return this.http.json<UsageMeResponse>({\n path: \"/v1/usage/me\",\n method: \"GET\",\n });\n }\n\n /** Component health (Redis / Postgres / DNS). Always returns 200. */\n status(): Promise<StatusResponse> {\n return this.http.json<StatusResponse>({\n path: \"/v1/status\",\n method: \"GET\",\n });\n }\n}\n\nexport default VerifyMail;\n"],"mappings":"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","names":["err"]}
1
+ {"version":3,"sources":["../src/errors.ts","../src/client.ts","../src/webhooks.ts","../src/index.ts"],"sourcesContent":["/**\n * Error class hierarchy. All HTTP errors from the API are translated into one\n * of these — customers can `instanceof` them to branch cleanly:\n *\n * try { await vm.check(email) }\n * catch (e) {\n * if (e instanceof QuotaExceededError) return showBilling();\n * if (e instanceof RateLimitError) return retryLater(e.retryAfter);\n * if (e instanceof VerifyMailError) return logAndShowGeneric(e);\n * throw e;\n * }\n */\n\nexport interface ErrorBody {\n code: string;\n http_status: number;\n message: string;\n request_id?: string;\n docs_url?: string;\n // Rate-limit-specific\n limit?: number;\n reset_at?: string;\n // Quota-specific\n upgrade_url?: string;\n}\n\nexport class VerifyMailError extends Error {\n readonly code: string;\n readonly status: number;\n readonly requestId: string | undefined;\n readonly docsUrl: string | undefined;\n readonly body: ErrorBody | undefined;\n\n constructor(message: string, opts: {\n code?: string;\n status?: number;\n requestId?: string;\n docsUrl?: string;\n body?: ErrorBody;\n } = {}) {\n super(message);\n this.name = \"VerifyMailError\";\n this.code = opts.code ?? \"verifymail_error\";\n this.status = opts.status ?? 0;\n this.requestId = opts.requestId;\n this.docsUrl = opts.docsUrl;\n this.body = opts.body;\n }\n}\n\nexport class InvalidApiKeyError extends VerifyMailError {\n constructor(opts: NonNullable<ConstructorParameters<typeof VerifyMailError>[1]>) {\n super(\"API key is missing or invalid.\", opts);\n this.name = \"InvalidApiKeyError\";\n }\n}\n\nexport class QuotaExceededError extends VerifyMailError {\n readonly upgradeUrl: string | undefined;\n constructor(opts: NonNullable<ConstructorParameters<typeof VerifyMailError>[1]>) {\n super(\"Out of credits. Buy a bundle to keep going.\", opts);\n this.name = \"QuotaExceededError\";\n this.upgradeUrl = opts.body?.upgrade_url;\n }\n}\n\nexport class RateLimitError extends VerifyMailError {\n readonly retryAfter: number;\n readonly limit: number | undefined;\n readonly resetAt: string | undefined;\n\n constructor(opts: NonNullable<ConstructorParameters<typeof VerifyMailError>[1]> & {\n retryAfter: number;\n }) {\n super(`Rate limit hit; retry after ${opts.retryAfter}s.`, opts);\n this.name = \"RateLimitError\";\n this.retryAfter = opts.retryAfter;\n this.limit = opts.body?.limit;\n this.resetAt = opts.body?.reset_at;\n }\n}\n\nexport class IdempotencyConflictError extends VerifyMailError {\n constructor(opts: NonNullable<ConstructorParameters<typeof VerifyMailError>[1]>) {\n super(\n \"Idempotency-Key was reused with a different request body. Use a new key or resend the original payload.\",\n opts,\n );\n this.name = \"IdempotencyConflictError\";\n }\n}\n\nexport class ValidationError extends VerifyMailError {\n constructor(message: string, opts: NonNullable<ConstructorParameters<typeof VerifyMailError>[1]>) {\n super(message, opts);\n this.name = \"ValidationError\";\n }\n}\n\nexport class ServiceDegradedError extends VerifyMailError {\n constructor(opts: NonNullable<ConstructorParameters<typeof VerifyMailError>[1]>) {\n super(\"A component is degraded; retry shortly.\", opts);\n this.name = \"ServiceDegradedError\";\n }\n}\n\n/**\n * Map an HTTP response to the right error class. The backend's error envelope\n * is `{ error: { code, http_status, message, ... } }`.\n */\nexport function errorFromResponse(\n status: number,\n body: { error?: ErrorBody } | unknown,\n retryAfterHeader?: string | null,\n): VerifyMailError {\n const envelope =\n body && typeof body === \"object\" && \"error\" in body\n ? (body as { error?: ErrorBody }).error\n : undefined;\n const code = envelope?.code ?? \"verifymail_error\";\n const opts = {\n code,\n status,\n requestId: envelope?.request_id,\n docsUrl: envelope?.docs_url,\n body: envelope,\n };\n\n if (status === 401) return new InvalidApiKeyError(opts);\n if (status === 402) return new QuotaExceededError(opts);\n if (status === 409 && code === \"invalid_idempotency_key\")\n return new IdempotencyConflictError(opts);\n if (status === 422) return new ValidationError(envelope?.message ?? \"Validation error.\", opts);\n if (status === 429) {\n const retryAfter = Number(retryAfterHeader ?? envelope?.limit ?? 1);\n return new RateLimitError({ ...opts, retryAfter: Number.isFinite(retryAfter) ? retryAfter : 1 });\n }\n if (status === 503 || status === 504) return new ServiceDegradedError(opts);\n return new VerifyMailError(envelope?.message ?? `HTTP ${status}`, opts);\n}\n","import { errorFromResponse, RateLimitError, ServiceDegradedError, VerifyMailError } from \"./errors.js\";\n\nexport interface ClientOptions {\n apiKey: string;\n /** Defaults to https://api.verifymailapi.com */\n baseUrl?: string;\n /** Max retry attempts on retryable failures (429, 5xx). Default 2 (so up to 3 total tries). */\n retries?: number;\n /** Per-request timeout in ms. Default 30000. */\n timeoutMs?: number;\n /** Override fetch — useful for tests or non-Node runtimes. */\n fetch?: typeof fetch;\n /** Default risk profile sent as X-Risk-Profile on every call (overridable per request). */\n riskProfile?: \"strict\" | \"balanced\" | \"permissive\";\n}\n\nexport interface RequestOptions {\n method?: \"GET\" | \"POST\";\n path: string;\n query?: Record<string, string | number | undefined>;\n body?: unknown;\n headers?: Record<string, string>;\n /** Provide an Idempotency-Key for POSTs. Auto-generated UUID if `true`. */\n idempotencyKey?: string | boolean;\n /** Override the default risk profile for this call. */\n riskProfile?: \"strict\" | \"balanced\" | \"permissive\";\n /** Skip retry on transient errors for this call. */\n noRetry?: boolean;\n /** Override timeout for this call. */\n timeoutMs?: number;\n}\n\nconst RETRYABLE_STATUS = new Set([408, 429, 500, 502, 503, 504]);\n\nfunction uuidv4(): string {\n // Node 19+ has globalThis.crypto.randomUUID. Polyfill for older Node.\n const c = globalThis.crypto;\n if (c?.randomUUID) return c.randomUUID();\n const buf = new Uint8Array(16);\n for (let i = 0; i < 16; i++) buf[i] = Math.floor(Math.random() * 256);\n buf[6] = (buf[6]! & 0x0f) | 0x40;\n buf[8] = (buf[8]! & 0x3f) | 0x80;\n const hex = Array.from(buf, (b) => b.toString(16).padStart(2, \"0\")).join(\"\");\n return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;\n}\n\nexport class HttpClient {\n private readonly apiKey: string;\n private readonly baseUrl: string;\n private readonly retries: number;\n private readonly timeoutMs: number;\n private readonly fetchImpl: typeof fetch;\n private readonly defaultRiskProfile: string | undefined;\n\n constructor(opts: ClientOptions) {\n if (!opts.apiKey) throw new VerifyMailError(\"apiKey is required.\");\n this.apiKey = opts.apiKey;\n this.baseUrl = (opts.baseUrl ?? \"https://api.verifymailapi.com\").replace(/\\/+$/, \"\");\n this.retries = opts.retries ?? 2;\n this.timeoutMs = opts.timeoutMs ?? 30_000;\n this.fetchImpl = opts.fetch ?? globalThis.fetch;\n this.defaultRiskProfile = opts.riskProfile;\n if (!this.fetchImpl) {\n throw new VerifyMailError(\n \"No fetch available. Pass `fetch` in the options, or use Node 18+.\",\n );\n }\n }\n\n /** Build the headers shared by every request. */\n private buildHeaders(opts: RequestOptions): Headers {\n const h = new Headers(opts.headers);\n h.set(\"X-API-Key\", this.apiKey);\n h.set(\"Accept\", \"application/json\");\n h.set(\"User-Agent\", \"verifymailapi-js/0.1.1\");\n if (opts.body !== undefined) h.set(\"Content-Type\", \"application/json\");\n\n const profile = opts.riskProfile ?? this.defaultRiskProfile;\n if (profile) h.set(\"X-Risk-Profile\", profile);\n\n if (opts.idempotencyKey) {\n h.set(\n \"Idempotency-Key\",\n typeof opts.idempotencyKey === \"string\" ? opts.idempotencyKey : uuidv4(),\n );\n }\n return h;\n }\n\n private buildUrl(path: string, query?: RequestOptions[\"query\"]): string {\n const url = new URL(`${this.baseUrl}${path}`);\n if (query) {\n for (const [k, v] of Object.entries(query)) {\n if (v !== undefined) url.searchParams.set(k, String(v));\n }\n }\n return url.toString();\n }\n\n /** Send and decode a JSON request. Retries on 429 / 5xx up to `retries` times. */\n async json<T>(opts: RequestOptions): Promise<T> {\n const raw = await this.raw(opts);\n if (raw.status === 204) return undefined as T;\n const text = await raw.text();\n try {\n return JSON.parse(text) as T;\n } catch {\n throw new VerifyMailError(`Non-JSON response from ${opts.path}.`, {\n status: raw.status,\n });\n }\n }\n\n /**\n * Send a request and return the raw Response. Handles retries + error mapping.\n * Used internally; exposed for the streaming bulk method to consume the body.\n */\n async raw(opts: RequestOptions): Promise<Response> {\n const url = this.buildUrl(opts.path, opts.query);\n const headers = this.buildHeaders(opts);\n const body = opts.body !== undefined ? JSON.stringify(opts.body) : undefined;\n const method = opts.method ?? (body ? \"POST\" : \"GET\");\n const maxTries = opts.noRetry ? 1 : this.retries + 1;\n const timeoutMs = opts.timeoutMs ?? this.timeoutMs;\n\n let lastError: VerifyMailError | undefined;\n for (let attempt = 0; attempt < maxTries; attempt++) {\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), timeoutMs);\n let res: Response;\n try {\n res = await this.fetchImpl(url, {\n method,\n headers,\n body,\n signal: controller.signal,\n });\n } catch (err) {\n clearTimeout(timer);\n // Network-level failure — retry if attempts remain.\n lastError = new VerifyMailError(\n err instanceof Error ? err.message : \"Network error\",\n { code: \"network_error\" },\n );\n if (attempt < maxTries - 1) {\n await sleep(backoffMs(attempt));\n continue;\n }\n throw lastError;\n }\n clearTimeout(timer);\n\n if (res.ok) return res;\n\n // Parse error body (best-effort) so we can build a typed error.\n let parsed: unknown = undefined;\n try {\n parsed = await res.clone().json();\n } catch {\n /* ignore non-JSON error bodies */\n }\n const err = errorFromResponse(res.status, parsed, res.headers.get(\"Retry-After\"));\n\n const isRetryable =\n !opts.noRetry &&\n RETRYABLE_STATUS.has(res.status) &&\n attempt < maxTries - 1;\n\n if (isRetryable) {\n const wait =\n err instanceof RateLimitError\n ? err.retryAfter * 1000\n : err instanceof ServiceDegradedError\n ? backoffMs(attempt)\n : backoffMs(attempt);\n await sleep(wait);\n lastError = err;\n continue;\n }\n\n throw err;\n }\n\n throw lastError ?? new VerifyMailError(\"Request failed after retries.\");\n }\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((r) => setTimeout(r, ms));\n}\n\nfunction backoffMs(attempt: number): number {\n // 250ms, 750ms, 2.25s — typical exponential with a small jitter floor.\n return Math.min(250 * Math.pow(3, attempt), 10_000);\n}\n","import { createHmac, timingSafeEqual } from \"node:crypto\";\n\n/**\n * Verify an incoming webhook signature from a `/v1/check/async` completion.\n *\n * const ok = verifyWebhook(rawBody, req.header(\"X-VerifyMail-Signature\"), secret);\n * if (!ok) return res.status(401).end();\n *\n * Pass the *raw* request body bytes — not the parsed JSON. In Express:\n * app.post(\"/webhook\", express.raw({ type: \"application/json\" }), handler)\n */\nexport function verifyWebhook(\n rawBody: Buffer | Uint8Array | string,\n signatureHeader: string | null | undefined,\n secret: string,\n): boolean {\n if (!signatureHeader) return false;\n const body =\n typeof rawBody === \"string\" ? Buffer.from(rawBody, \"utf8\") : Buffer.from(rawBody);\n const expected =\n \"sha256=\" + createHmac(\"sha256\", secret).update(body).digest(\"hex\");\n const a = Buffer.from(signatureHeader);\n const b = Buffer.from(expected);\n return a.length === b.length && timingSafeEqual(a, b);\n}\n","/**\n * Official SDK for the VerifyMail API.\n *\n * import { VerifyMail } from \"verifymailapi\";\n * const vm = new VerifyMail({ apiKey: process.env.VERIFYMAIL_KEY! });\n * const r = await vm.check(\"user@example.com\");\n * if (r.verdict.recommendation === \"block\") { ... }\n *\n * Docs: https://verifymailapi.com/docs\n */\n\nimport { HttpClient, type ClientOptions, type RequestOptions } from \"./client.js\";\nimport type {\n AsyncCheckResponse,\n BulkCheckResponse,\n BulkStreamEvent,\n CheckResponse,\n ReportRequest,\n ReportResponse,\n StatusResponse,\n UsageMeResponse,\n} from \"./types.js\";\n\nexport * from \"./types.js\";\nexport * from \"./errors.js\";\nexport { verifyWebhook } from \"./webhooks.js\";\n\nexport interface CheckOptions {\n /** Override the SDK-wide risk profile for this call. */\n riskProfile?: \"strict\" | \"balanced\" | \"permissive\";\n /** Make the call idempotent. Pass `true` to auto-generate a UUID, or a fixed string. */\n idempotencyKey?: string | boolean;\n}\n\nexport interface AsyncCheckArgs {\n email: string;\n webhookUrl: string;\n /** Optional HMAC-SHA256 key used to sign the final webhook payload. */\n webhookSecret?: string;\n}\n\nexport class VerifyMail {\n private readonly http: HttpClient;\n\n constructor(opts: ClientOptions) {\n this.http = new HttpClient(opts);\n }\n\n /** Check a single email. Charges 1 credit. */\n check(email: string, opts: CheckOptions = {}): Promise<CheckResponse> {\n return this.http.json<CheckResponse>({\n path: \"/v1/check\",\n body: { email },\n idempotencyKey: opts.idempotencyKey,\n riskProfile: opts.riskProfile,\n });\n }\n\n /** Check a domain only (no local part). Charges 1 credit. */\n checkDomain(domain: string, opts: CheckOptions = {}): Promise<CheckResponse> {\n return this.http.json<CheckResponse>({\n path: \"/v1/check/domain\",\n body: { domain },\n idempotencyKey: opts.idempotencyKey,\n riskProfile: opts.riskProfile,\n });\n }\n\n /** Bulk check 1–100 emails. Charges N credits up front (all-or-nothing). */\n checkBulk(emails: string[], opts: CheckOptions = {}): Promise<BulkCheckResponse> {\n if (emails.length === 0 || emails.length > 100) {\n throw new RangeError(\"checkBulk requires 1–100 emails.\");\n }\n return this.http.json<BulkCheckResponse>({\n path: \"/v1/check/bulk\",\n body: { emails },\n idempotencyKey: opts.idempotencyKey,\n riskProfile: opts.riskProfile,\n });\n }\n\n /**\n * Stream bulk-check results as each row completes. Calls `onEvent` once\n * per result (in finish-order) plus once with a final `{event: \"summary\"}`.\n *\n * await vm.checkBulkStream(emails, (e) => {\n * if (\"event\" in e) console.log(\"done\", e);\n * else processRow(e.index, e.result);\n * });\n */\n async checkBulkStream(\n emails: string[],\n onEvent: (e: BulkStreamEvent) => void | Promise<void>,\n opts: Omit<CheckOptions, \"idempotencyKey\"> = {},\n ): Promise<void> {\n if (emails.length === 0) throw new RangeError(\"checkBulkStream needs at least one email.\");\n\n const req: RequestOptions = {\n path: \"/v1/check/bulk/stream\",\n body: { emails },\n riskProfile: opts.riskProfile,\n // Streaming requests aren't safe to auto-retry — would re-charge credits\n // and re-stream rows. Customer must retry explicitly.\n noRetry: true,\n };\n const res = await this.http.raw(req);\n if (!res.body) throw new Error(\"Streaming response had no body.\");\n\n const reader = res.body.getReader();\n const decoder = new TextDecoder();\n let buf = \"\";\n while (true) {\n const { value, done } = await reader.read();\n if (done) break;\n buf += decoder.decode(value, { stream: true });\n let nl: number;\n // Drain complete lines; keep the partial tail in buf for the next chunk.\n while ((nl = buf.indexOf(\"\\n\")) !== -1) {\n const line = buf.slice(0, nl).trim();\n buf = buf.slice(nl + 1);\n if (!line) continue;\n try {\n await onEvent(JSON.parse(line) as BulkStreamEvent);\n } catch (err) {\n throw new Error(`Failed to parse stream line: ${line}`);\n }\n }\n }\n // Final flush in case the server didn't terminate with newline.\n const tail = buf.trim();\n if (tail) await onEvent(JSON.parse(tail) as BulkStreamEvent);\n }\n\n /**\n * Async deep check. Returns 202 immediately with a preliminary verdict;\n * VerifyMail POSTs the final result to your webhook URL once the deep\n * SMTP probe completes. Verify the signature with `verifyWebhook()` in\n * your handler before trusting the payload.\n */\n checkAsync(args: AsyncCheckArgs, opts: CheckOptions = {}): Promise<AsyncCheckResponse> {\n return this.http.json<AsyncCheckResponse>({\n path: \"/v1/check/async\",\n body: {\n email: args.email,\n webhook_url: args.webhookUrl,\n webhook_secret: args.webhookSecret,\n },\n idempotencyKey: opts.idempotencyKey,\n riskProfile: opts.riskProfile,\n });\n }\n\n /** File a /v1/report for a domain outcome (feedback loop). */\n report(req: ReportRequest): Promise<ReportResponse> {\n return this.http.json<ReportResponse>({\n path: \"/v1/report\",\n body: req,\n });\n }\n\n /** Programmatic equivalent of the dashboard's Usage summary. */\n usage(): Promise<UsageMeResponse> {\n return this.http.json<UsageMeResponse>({\n path: \"/v1/usage/me\",\n method: \"GET\",\n });\n }\n\n /** Component health (Redis / Postgres / DNS). Always returns 200. */\n status(): Promise<StatusResponse> {\n return this.http.json<StatusResponse>({\n path: \"/v1/status\",\n method: \"GET\",\n });\n }\n}\n\nexport default VerifyMail;\n"],"mappings":"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","names":["err"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "verifymailapi",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Official SDK for the VerifyMail API — disposable, throwaway, and abusive email detection.",
5
5
  "license": "MIT",
6
6
  "homepage": "https://verifymailapi.com",