Skip to content

Node.js SDK

The mailpush package wraps the API in a typed client. It has no dependency: it uses the runtime’s fetch and Web Crypto.

Terminal window
npm install mailpush

It needs Node.js 22 or later, Bun, Deno, or an edge runtime with fetch.

import { MailPush } from "mailpush";
const mailpush = new MailPush(process.env.MAILPUSH_API_KEY);
Option Default Description
baseUrl https://api.mailpush.app Where requests go.
maxRetries 2 Retries after a network error, a 5xx answer or rate_limited.
timeoutMs 30000 Time allowed for each attempt.
fetch The global fetch A replacement, for tests or a proxy.

mailpush.testMode is true when the key starts with mp_test_.

const { id } = await mailpush.emails.send({
from: "Acme <hello@example.com>",
to: ["customer@example.org", "billing@example.org"],
replyTo: "support@example.com",
subject: "Your order is ready",
html: "<p>Hello, your order is waiting for you.</p>",
});

The fields are those of POST /v1/emails, in camelCase: reply_to becomes replyTo.

Each call carries its own Idempotency-Key, which the SDK reuses when it retries, so a retry never sends twice. To also cover the retries of your own code, pass a key that identifies the email:

await mailpush.emails.send(email, { idempotencyKey: `order-${order.id}-confirmation` });
await mailpush.emails.send({
from: "Acme <hello@example.com>",
to: "customer@example.org",
template: "order-confirmation",
data: { firstName: "Camille", order: { id: "1234" } },
});

template is the alias or the id of a published template, and data holds the values of its variables. subject is optional and replaces the subject of the template. TypeScript refuses html or text next to template. This needs version 0.3.0 or later of the package.

const email = await mailpush.emails.get(id);
for (const recipient of email.recipients) {
console.log(recipient.email, recipient.status, recipient.lastResponse);
}

Dates such as createdAt, deliveredAt, openedAt and clickedAt are Date objects. openedAt and clickedAt need version 0.4.0 or later, like the tracking option of send: see Track opens and clicks.

Every failure is a MailPushError. Test its code, not its message.

import { MailPushError } from "mailpush";
try {
await mailpush.emails.send(email);
} catch (error) {
if (error instanceof MailPushError && error.code === "daily_quota_exceeded") {
console.log(`Quota reached, retry in ${error.retryAfter} seconds`);
} else {
throw error;
}
}
Property Description
code An API error code, or network_error, timeout, invalid_signature.
status The HTTP status, when the API answered.
retryAfter Seconds to wait, when the API sent Retry-After.

The SDK never retries daily_quota_exceeded or a 4xx other than rate_limited.

const event = await mailpush.webhooks.verify(request, process.env.MAILPUSH_WEBHOOK_SECRET);
if (event.type === "email.bounced") {
console.log(event.data.recipient, event.data.bounce.type);
}

request is the standard Request your handler receives. With Express or plain Node.js, pass the raw body and the headers, and import the function directly:

import { verifyWebhook } from "mailpush";
const event = await verifyWebhook({ payload: rawBody, headers: req.headers }, secret);

verify throws a MailPushError with the code invalid_signature when the signature does not match or the request is older than 5 minutes. Change the window with { toleranceSeconds: 600 } as a third argument. See Receive webhooks for the full flow.