@aradox/mailer
v1.0.0
Published
Node.js SDK for the Mailer email service
Maintainers
Readme
@aradox/mailer
Node.js SDK for the Mailer email service.
Zero runtime deps — uses the global fetch (Node 18+).
Install
npm install @aradox/mailerQuick start
import { Mailer } from "@aradox/mailer";
const mailer = new Mailer({
apiKey: process.env.MAILER_API_KEY!, // app_xxxxxxxxxxxxxx
baseURL: process.env.MAILER_URL, // defaults to https://mailer.example.com
});
// Single send
const { id } = await mailer.emails.send({
from: "Acme <[email protected]>",
to: "[email protected]",
subject: "Welcome",
html: "<p>Hi!</p>",
text: "Hi!",
});Sending
// Send by template + variables
await mailer.emails.send({
from: "Acme <[email protected]>",
to: "[email protected]",
template: "welcome",
variables: { FirstName: "Sam", ResetURL: "https://app.example.com/r/abc" },
subscription_group: "marketing",
});
// Batch (up to 500)
await mailer.emails.batch([
{ from, to: "[email protected]", subject: "a", text: "a" },
{ from, to: "[email protected]", subject: "b", text: "b" },
]);
// Attachments (Buffer or base64 string)
import { readFile } from "node:fs/promises";
await mailer.emails.send({
from,
to: "[email protected]",
subject: "Your invoice",
html: "<p>See attached.</p>",
attachments: [
{
filename: "invoice.pdf",
content: await readFile("./invoice.pdf"), // Buffer auto-base64'd
content_type: "application/pdf",
},
],
});
// Inspect
const events = await mailer.emails.events(id);Idempotency
Pass idempotency_key and the SDK forwards it both as the JSON field and
the Idempotency-Key HTTP header. Calling twice with the same key returns
the same email — safe to retry on network blips, useful when wiring up
order-confirmation flows.
await mailer.emails.send({
from, to: "[email protected]", subject: "Welcome", html: "<p>Hi</p>",
idempotency_key: "order-123-welcome",
});Automatic retries
Transient failures (HTTP 429, 5xx, network errors) are retried up to 3
times by default with exponential backoff + full jitter, capped at 8s
per delay. The Retry-After header is honoured when present.
const mailer = new Mailer({
apiKey: "...",
maxRetries: 5, // default 3
retryBaseDelayMs: 500, // default 250
});Webhook signature verification
Inbound webhooks are signed with HMAC-SHA256. Verify them before trusting the payload:
import { Mailer, WebhookVerificationError } from "@aradox/mailer";
// Express / Fastify / etc.
app.post("/mailer-webhook", async (req, res) => {
const rawBody = req.rawBody as string; // capture raw body — see below
try {
const event = mailer.webhooks.verify({
body: rawBody,
secret: process.env.WEBHOOK_SECRET!,
headers: req.headers,
});
// event.event_type === "email.delivered" | "email.bounced" | ...
await handleEvent(event);
res.sendStatus(204);
} catch (e) {
if (e instanceof WebhookVerificationError) return res.status(400).send(e.message);
throw e;
}
});Important: pass the raw request body string. If your framework JSON-parses the body before you see it, the signature will fail. In Express, use
express.raw({ type: "application/json" })and capturereq.body.toString("utf8").
The verifier rejects:
- Missing or malformed
X-Mailer-Signatureheader - Timestamps outside a 5-minute tolerance (configurable via
toleranceSeconds) - Signatures that don't match
- Non-JSON bodies
Errors
Failed requests throw a MailerError carrying status, body, code
(when present), and requestId (echoed from X-Request-Id).
import { MailerError } from "@aradox/mailer";
try {
await mailer.emails.send({ from: "bad", to: "[email protected]" });
} catch (e) {
if (e instanceof MailerError) {
console.error("send failed", { status: e.status, code: e.code, requestId: e.requestId });
if (e.status === 429) /* rate limited — retry later (or rely on auto-retry) */;
if (e.status === 422 && e.code === "suppressed") /* recipient is on the suppression list */;
}
}Tests
npm test