Framework guide
Send email from Node.js and Express
Why use this
The client is a zero-dependency TypeScript package that works in any Node process — a server, a worker, a one-off script. This guide covers the plain call and then a real endpoint around it.
For example
Someone signs up through your Express API and gets a welcome email before the response returns.
1. Install
npm install yourmail2. The shortest working send
Supplying both html and text is worth the extra line: some clients prefer the plain-text part, and its absence is a mild spam signal.
import { YourMail } from "yourmail";
const yourmail = new YourMail(process.env.YOURMAIL_API_KEY);
const { id } = await yourmail.send({
from: "Acme <hello@mail.acme.com>",
to: "customer@example.com",
subject: "Welcome aboard",
html: "<p>Thanks for signing up.</p>",
text: "Thanks for signing up.",
});
console.log("queued", id);3. An Express endpoint
import express from "express";
import { YourMail, ValidationError, RateLimitError } from "yourmail";
const app = express();
app.use(express.json());
const yourmail = new YourMail(process.env.YOURMAIL_API_KEY);
app.post("/signup", async (req, res, next) => {
const { email } = req.body;
try {
const { id } = await yourmail.send({
from: "Acme <hello@mail.acme.com>",
to: email,
subject: "Welcome aboard",
html: "<p>Thanks for signing up.</p>",
// Makes a retried signup safe: the same key returns the original id
// rather than mailing the customer twice.
idempotencyKey: `welcome:${email}`,
tags: [{ name: "type", value: "welcome" }],
});
res.json({ id });
} catch (err) {
if (err instanceof ValidationError) {
// Bad address, or a recipient on your suppression list. Retrying this
// unchanged will fail identically — tell the caller instead.
return res.status(422).json({ error: err.message });
}
if (err instanceof RateLimitError) {
return res.status(429).json({ error: "Try again shortly." });
}
next(err);
}
});
app.listen(3000);tags are optional labels that show up when you filter your message history later — useful once you are sending more than one kind of email.
4. Don't write a retry loop
This is the single most common way to make a working integration worse. The SDK already retries transient failures with backoff; wrapping it in your own loop retries the permanent failures too, which cannot succeed and merely burns your rate limit.
// Don't do this.
for (let attempt = 0; attempt < 5; attempt++) {
try {
await yourmail.send(options);
break;
} catch {
// A ValidationError will fail all five times, and a RateLimitError will
// fail harder the more you hammer it.
}
}// The SDK already retries what is worth retrying, with backoff.
// Handle the rest by type.
try {
await yourmail.send(options);
} catch (err) {
if (err instanceof ValidationError) {
// Permanent. Fix the input; don't retry.
logger.warn({ err }, "rejected by the API");
return;
}
// Transient (network, 5xx, rate limit) — hand it to your job queue and let
// that decide when to try again.
throw err;
}The rule is the error type, not the attempt count: a ValidationError is permanent and should never be retried, while a RateLimitError or ServerError is transient. The full list is in the errors reference.
Sending a lot at once
Looping over send() for a few hundred recipients works but is slow and wasteful. Use the batch endpoint instead — up to 100 messages per request, each with its own recipients and body.