Guide
How to send transactional emails with Next.js (App Router)
Send transactional email from a Next.js App Router server action or route handler.
In short
Create a Sendpository account, verify your domain, put your API key inSENDPOSITORY_API_KEY, and send with the code below - one HTTPS request per email, from server-side code only.Swipe the diagram to see the rest
1. Verify your sending domain
Add your domain in the dashboard and publish the three DNS records it shows - SPF and a bounce record on an sp. subdomain, and a DKIM key on your apex. Your existing records are never edited. Verification usually lands in a few minutes, and you can set up the code below while you wait.
2. Create a key
Create an API key with the sending permission. It is shown once, so put it straight into your secrets. Add SENDPOSITORY_API_KEY to .env.local. Never prefix it with NEXT_PUBLIC_ - it must stay on the server.
3. Install
npm install sendpository
4. Send an email
// app/actions.ts
"use server";
import { Sendpository } from "sendpository";
const sendpository = new Sendpository(process.env.SENDPOSITORY_API_KEY);
export async function sendWelcome(email: string) {
const { id } = await sendpository.emails.send({
from: "Your App <hello@mail.yourdomain.com>",
to: [email],
subject: "Welcome aboard",
html: "<p>Thanks for signing up.</p>",
});
return id;
}A success returns { "id": "..." } - the message’s id for looking it up later. A failure returns { "error": { "type", "message" } } with a 4xx or 5xx status.
Where the send belongs
In a Server Action or a Route Handler - never a Client Component, because the key would ship to the browser. Mark the module `server-only` so an accidental import from a client file fails the build instead of leaking at runtime.
If the user does not need the result, call `after()` from `next/server`. The response goes back as soon as the work is queued and the send finishes afterwards, so a slow upstream never shows up as a slow form submit.
Testing it
- Set `SENDPOSITORY_API_KEY` in `.env.local` and run `next dev`. Send to an address you control first - a real delivery you can open is the only proof the whole path works.
- On Vercel, add the key to the environment and redeploy - a value added without a redeploy is not in the running build.
Things to know
- Call it from a server action or a route handler - never from a client component, or the key ships to the browser.
- Using React Email? Render the component to a string with its render function and pass it as html.
- On Vercel, set SENDPOSITORY_API_KEY in Project Settings → Environment Variables.
Put email behind one server-only module
Keep the client and every email your app sends in one file that can only ever run on the server. The server-only package makes the build fail if a client component imports it, so the API key can't leak by accident.
import "server-only";
import { Sendpository } from "sendpository";
const sendpository = new Sendpository(process.env.SENDPOSITORY_API_KEY);
const FROM = "Acme <hello@mail.acme.com>";
export async function sendWelcome(user: { id: string; email: string; name: string }) {
return sendpository.emails.send(
{
from: FROM,
to: [user.email],
subject: `Welcome to Acme, ${user.name}`,
html: `<p>Hi ${user.name}, your account is ready.</p>`,
text: `Hi ${user.name}, your account is ready.`,
tags: { type: "welcome" },
},
// Retrying the job can never send two welcomes.
{ idempotencyKey: `welcome-${user.id}` },
);
}Send from a Server Action
A Server Action runs on the server and can be called straight from a form. Sending after the response with after() keeps the form fast; the email goes out a moment later.
"use server";
import { after } from "next/server";
import { createUser } from "@/lib/users";
import { sendWelcome } from "@/lib/email";
export async function signUp(formData: FormData) {
const user = await createUser(formData);
after(() => sendWelcome(user)); // runs once the response has been sent
return { ok: true };
}Render templates with React Email
Sendpository sends whatever HTML you give it, so any template library works. With React Email, render the component to HTML and pass the result as html.
import { render } from "@react-email/render";
import WelcomeEmail from "@/emails/welcome";
const html = await render(<WelcomeEmail name={user.name} />);
const text = await render(<WelcomeEmail name={user.name} />, { plainText: true });Receive delivery events in a Route Handler
Create an endpoint on the Webhooks page, store its secret as SENDPOSITORY_WEBHOOK_SECRET, and verify every request. Read the body with req.text() - the signature covers the raw bytes, so parsing first would break it.
import { verifyWebhook } from "sendpository";
export async function POST(req: Request) {
let event;
try {
event = verifyWebhook({
body: await req.text(),
signature: req.headers.get("sendpository-signature") ?? "",
secret: process.env.SENDPOSITORY_WEBHOOK_SECRET!,
});
} catch {
return new Response("invalid signature", { status: 401 });
}
if (event.type === "email.bounced" || event.type === "email.complained") {
await markUndeliverable(event.data.to);
}
return new Response("ok");
}Deploying
- Set
SENDPOSITORY_API_KEYandSENDPOSITORY_WEBHOOK_SECRETin your host's environment variables (on Vercel: Project Settings → Environment Variables) - never with aNEXT_PUBLIC_prefix. - Use a separate API key for preview deployments, so a test branch can be revoked without touching production.
- Route handlers run on the Node.js runtime by default, which is what the SDK expects.
More on what happens after the send: how transactional email works and how email webhooks work.
Questions
Can I send email from a Client Component?
No - the key would be bundled into the browser. Put the send in a Server Action or Route Handler and mark the module `server-only`, so an import from a client file fails at build time rather than leaking a key at runtime.
How do I send email with Next.js?
Verify your sending domain, create an API key, put it in an environment variable, and POST the message as JSON to https://api.sendpository.com/v1/emails from server-side Next.js code. The example on this page runs as written once the domain is verified.
Can I send email with Next.js without SMTP?
Yes. This is plain HTTPS, so Next.js needs no mail library and no SMTP port - which matters on hosts that block outbound 25 and 587. Failures come back as a status code on the call instead of as a bounce message hours later.
More guides
Send email with Node.js
Send email from any Node.js app with the typed SDK or plain fetch.
Send email with Express
Send email from an Express route with fetch - no SDK needed.
Send your first email in minutes
6,000 emails a month (up to 200 a day), free forever. No card needed.