Node.js SDK

@torvath/mail: the email API, typed. Works on Node 18+, Cloudflare Workers, Bun and Deno, with no dependencies.

Install

Terminal
npm install @torvath/mail
Set up
import { Torvath } from "@torvath/mail";

const torvath = new Torvath({ apiKey: process.env.TORVATH_API_KEY });
OptionDefault
apiKeyrequiredYour key from Admin → API.
maxRetries2Retries after rate limits, server errors and lost connections, waiting longer each time.
timeoutMs30000Per request. A long event fetch waits longer.
fetchglobal fetchAnother fetch, such as for tests or a proxy.

Send

js
const { id } = await torvath.emails.send({
  from: { email: "billing@yourcompany.in", name: "Your Company" },
  to: "customer@example.com",
  subject: "Invoice 1042",
  html: "<p>Your invoice is attached.</p>",
  attachments: [{ filename: "invoice-1042.pdf", content: pdf.toString("base64") }],
  tags: ["invoice"],
  metadata: { invoice: "1042" },
});

const email = await torvath.emails.get(id); // status, and each recipient's

Every send carries an Idempotency-Key, so a retry after a lost connection never sends twice. Pass your own to tie it to your records: send(email, { idempotencyKey: "invoice-1042" }). All fields are on Send an email.

Templates

js
await torvath.emails.send({
  from: "billing@yourcompany.in",
  to: "asha@example.com",
  template: "invoice-ready",
  variables: { name: "Asha", amount: "₹500" },
});

Events

js
const listener = torvath.events.listen({ types: ["email.bounced", "email.complained"] }, async (event) => {
  await markAddressBad(event.recipient, event.reason);
});
listen optionDefault
typesallOnly these types; others stay for other consumers.
concurrency5Events handled at once.
includeMachinefalseAlso pass opens and clicks that were likely machines. Otherwise they are acknowledged without calling you.
onErrornoneCalled when a fetch fails. Listening carries on, waiting longer each time.

listener.stop() stops fetching, lets running handlers finish and acknowledges them. listener.done settles when listening ends, and rejects on an error that can't clear by itself, such as a revoked key. More in Events queue.

Webhooks

js
import { verifyWebhook } from "@torvath/mail";

const event = await verifyWebhook(rawBody, request.headers, process.env.TORVATH_WEBHOOK_SECRET);

Throws WebhookVerificationError when the request wasn't signed with your secret or its timestamp is more than 5 minutes off. A full example is on Webhooks.

Errors

Every error is a TorvathError with status, a code that stays the same (see error codes) and a message.

ClassStatus
ValidationError400
AuthError401
PermissionError403
NotFoundError404
ConflictError409
RateLimitError429
ServerError5xx
ConnectionErrorNo answer
js
import { RateLimitError } from "@torvath/mail";

try {
  await torvath.emails.send(email);
} catch (err) {
  if (err instanceof RateLimitError && err.code === "monthly_limit") notifyBilling();
  else throw err;
}

© 2026 Torvath. Questions? support@torvathmail.com