Node.js SDK
@padimail/node: every endpoint, typed, with retries, React Email and webhook verification.
npm install @padimail/nodeThe SDK uses only fetch and Web Crypto, so it runs in Node 20+, Bun, Deno, Cloudflare Workers, Vercel Edge and Supabase Edge Functions. It's typed from the API's OpenAPI document: your editor knows every field and response.
Server only
The SDK refuses to start in a browser, because anyone who opens the page could read your key. Call PadiMail from your server.
Set up
import { PadiMail } from "@padimail/node";
const pm = new PadiMail(process.env.PADIMAIL_API_KEY, {
maxRetries: 2, // extra attempts on 429, 5xx and network errors; 0 turns retries off
timeout: 30_000, // per attempt, in milliseconds
baseUrl: "https://api.padimail.app", // or set PADIMAIL_BASE_URL
});With no arguments, new PadiMail() reads PADIMAIL_API_KEY.
Methods
| Resource | Methods |
|---|---|
pm.emails | send, batch, list, get, events, content, update, cancel |
pm.domains | create, list, get, verify, update, remove |
pm.apiKeys | create, list, remove |
pm.suppressions | list, create, remove |
pm.webhooks | create, list, get, update, remove, rotateSecret, test, deliveries.list, deliveries.retry, verify |
pm.templates | create, list, get, update, remove, publish, render, versions |
pm.account | get, usage |
pm.health() | Is the API up? |
Each method matches an endpoint in the API reference, which shows the SDK call next to cURL and Python. Every method takes a last options argument: { idempotencyKey, maxRetries, timeout, signal, headers }.
Errors
Every failure throws a PadiMailError with the API's error fields:
import { PadiMailError } from "@padimail/node";
try {
await pm.emails.send(email);
} catch (err) {
if (err instanceof PadiMailError) {
console.log(err.status, err.code, err.message, err.fields, err.docUrl, err.requestId);
}
}Branch on err.code (see Errors). Network failures and timeouts have status: 0 and code connection_error or timeout.
Retries and idempotency
The SDK retries network errors, 429 and 5xx (except 501), backing off from half a second and honouring Retry-After up to 60 seconds. It doesn't retry quota limits. Every POST gets an Idempotency-Key, the same across retries, so a retry can't send twice. Pass your own to also cover your app's retries:
await pm.emails.send(receipt, { idempotencyKey: `receipt-${order.id}` });The same request with cURL:
curl -X POST https://api.padimail.app/v1/emails -H "Authorization: Bearer $PADIMAIL_API_KEY" -H "Idempotency-Key: receipt-1042" -H "Content-Type: application/json" -d @receipt.jsonPagination
List methods return one page, { data, has_more, next }. To walk every item:
for await (const email of pm.paginate(pm.emails.list, { status: "bounced" })) {
console.log(email.id, email.to);
}React Email
Install @react-email/render, then pass a component as react. See React Email.
await pm.emails.send({ from, to, subject: "Your receipt", react: <ReceiptEmail orderNo="1042" /> });Webhooks
import { verifyWebhook } from "@padimail/node";
const event = await verifyWebhook({ payload: rawBody, headers: req.headers, secret: process.env.PADIMAIL_WEBHOOK_SECRET! });See Verify signatures. WEBHOOK_EVENT_TYPES lists every event type, and signWebhookHeaders signs a test body the way we do, for testing your handler.
Types
Request and response types are exported:
import type { Email, EmailSummary, Domain, SendEmailRequest, WebhookEvent } from "@padimail/node";