Next.js guide
How to send email from Next.js
Send invites, receipts and password resets from an App Router server action or route handler. One SDK call, your own domain, and 3,000 free emails a month.
Setup
Set up email in your Next.js app.
Do this once. After that, sending is one function call from any server code.
Step 1: Verify your sending domain
Add your domain in the Sendaro dashboard and publish its DKIM, SPF and DMARC records. Mail from a verified domain lands in the inbox instead of spam. See domain setup.
Step 2: Install the SDK and store your key
Create an API key with send access and add it to your server environment. Without the
NEXT_PUBLIC_prefix, Next.js keeps it out of the browser.Terminalnpm install sendaro.env.local# .env.local (never prefix it with NEXT_PUBLIC_) SENDARO_API_KEY=rf_your_api_keyStep 3: Create one shared client
One module that only server code can import. Every action and route uses it.
lib/email.ts// lib/email.ts import "server-only"; import { Sendaro } from "sendaro"; export const sendaro = new Sendaro(); // reads SENDARO_API_KEY
Server action
Send email from a server action.
A form posts straight to a function on the server. No API route, no client-side fetch.
// app/invite/actions.ts
"use server";
import { sendaro } from "@/lib/email";
import { requireUser } from "@/lib/auth"; // your own auth check
export async function sendInvite(formData: FormData) {
const user = await requireUser();
const email = String(formData.get("email"));
await sendaro.send(
{
from: "Acme <team@yourdomain.com>",
to: email,
subject: `${user.name} invited you to Acme`,
html: "<p>Accept your invite to join the team.</p>",
text: "Accept your invite to join the team.",
},
{ idempotencyKey: `invite-${user.id}-${email}` },
);
}// app/invite/page.tsx
import { sendInvite } from "./actions";
export default function InvitePage() {
return (
<form action={sendInvite}>
<input name="email" type="email" required />
<button type="submit">Send invite</button>
</form>
);
}Server actions can be called by anyone who can load the page, so check the user first. The idempotencyKey means a double click or a retry within 24 hours returns the first email instead of sending a second.
Route handler
Send email from a route handler.
For emails triggered from outside the page: payment webhooks, cron jobs, other services.
// app/api/receipts/route.ts
import { sendaro } from "@/lib/email";
export async function POST(request: Request) {
// Check who is calling before you send anything.
const { orderId, email } = await request.json();
const { id } = await sendaro.send(
{
from: "Acme <receipts@yourdomain.com>",
to: email,
subject: `Receipt for order ${orderId}`,
html: `<p>Thanks for order ${orderId}.</p>`,
},
{ idempotencyKey: `receipt-${orderId}` },
);
return Response.json({ id });
}Never expose a route that sends mail to any address a stranger posts: it becomes a spam relay on your domain. Authenticate the caller, or only send to addresses you already have on file.
Webhooks
Know when an email is delivered or bounced.
Sendaro posts signed events like email.delivered and email.bounced to your app. Verify the signature before you trust the body.
// app/api/sendaro/route.ts
import { verifyWebhook } from "sendaro";
export async function POST(request: Request) {
// Use the raw body. Parsing and re-serialising JSON breaks the signature.
const payload = await request.text();
const valid = verifyWebhook(
payload,
request.headers.get("rf-signature"),
process.env.SENDARO_WEBHOOK_SECRET!,
request.headers.get("rf-timestamp"),
);
if (!valid) return new Response("Invalid signature", { status: 401 });
const event = JSON.parse(payload);
if (event.type === "email.bounced") {
// Flag the address in your own database.
}
return new Response(null, { status: 204 });
}FAQ
Next.js email questions.
npm install server-only.Send your first email today.
Free includes 3,000 emails every month. No card needed.