Send emails with Next.js
Install
npm install mailstein
Put the key in .env.local. It must not be NEXT_PUBLIC_ — anything with that
prefix is compiled into the browser bundle, and a sending key in a browser
bundle is a key anyone can use to send as you.
MAILSTEIN_API_KEY=ms_...
A Route Handler
// app/api/send/route.ts
import { Mailstein } from "mailstein";
import { NextResponse } from "next/server";
const mailstein = new Mailstein(process.env.MAILSTEIN_API_KEY);
export async function POST(request: Request) {
const { email, name } = await request.json();
const { data, error } = await mailstein.emails.send({
from: "hello@yourdomain.com",
to: email,
subject: "Welcome",
html: `<p>Hello ${name}, thanks for signing up.</p>`,
// Derived from the thing being emailed about, so a retry of this
// signup does not send a second welcome. See /idempotency.
tags: ["welcome"],
});
if (error) {
// Log the detail, return something the user can act on. The message may
// name an internal domain or an id, so it is not for the browser.
console.error(error);
return NextResponse.json({ error: "Could not send" }, { status: 502 });
}
return NextResponse.json({ id: data.emailId });
}
A Server Action
// app/actions.ts
"use server";
import { Mailstein } from "mailstein";
const mailstein = new Mailstein(process.env.MAILSTEIN_API_KEY);
export async function notify(formData: FormData) {
const email = String(formData.get("email"));
const { error } = await mailstein.emails.send({
from: "hello@yourdomain.com",
to: email,
subject: "Thanks",
html: "<p>We got your message.</p>",
});
if (error) throw new Error(error.message);
}
Do not send from the Edge runtime on a user request
A send takes a few hundred milliseconds and can fail. Doing it inline means the user waits for it, and a transient failure becomes a failed signup.
Return the response first and send afterwards — a queue, a background job, or
after() from next/server:
import { after } from "next/server";
export async function POST(request: Request) {
const { email } = await request.json();
after(async () => {
await mailstein.emails.send({ /* ... */ });
});
return NextResponse.json({ ok: true });
}
React Email
If you write your templates as React components, render them to HTML and pass that:
import { render } from "@react-email/render";
import WelcomeEmail from "@/emails/welcome";
const html = await render(<WelcomeEmail name={name} />);
await mailstein.emails.send({ from, to, subject: "Welcome", html });
Something here wrong or missing? It is generated from the running API —
tell us and we will fix the source.