Next.js
Send email with Next.js Server Actions, Route Handlers, React Email, and the server-only samva SDK.
Use the samva
SDK directly from Next.js server code. Server Actions work well for forms, Route
Handlers work well for JSON endpoints, and both keep your API key server-side.
Samva sends from the verified sender configured on your account, so the
payload has no from field.
Install
bun add samva server-onlyCreate a server-only client module:
import "server-only";
import { createClient } from "samva";
const apiKey = process.env.SAMVA_API_KEY;
if (!apiKey) {
throw new Error("SAMVA_API_KEY is not set.");
}
export const samva = createClient({ apiKey });Server Action form
Next.js Server Actions let a form call server code directly:
"use server";
import { samva } from "../../lib/samva";
export async function sendContactEmail(formData: FormData) {
const email = String(formData.get("email") ?? "").trim();
const message = String(formData.get("message") ?? "").trim();
if (!email || !message) {
throw new Error("Email and message are required.");
}
const safeMessage = message.replaceAll("<", "<").replaceAll(">", ">");
await samva.messages.send({
to: [{ email }],
channel: "email",
email: {
subject: "Thanks for contacting us",
html: `<p>Thanks for reaching out.</p><p>${safeMessage}</p>`,
text: `Thanks for reaching out.\n\n${message}`,
},
});
}Use useActionState and
useFormStatus when the UI needs
pending and error state.
Route Handler endpoint
Use an App Router Route Handler for JSON clients:
import { samva } from "../../../lib/samva";
export const runtime = "edge";
export async function POST(request: Request) {
const body = (await request.json()) as { email?: unknown; message?: unknown };
const email = typeof body.email === "string" ? body.email.trim() : "";
const message = typeof body.message === "string" ? body.message.trim() : "";
if (!email || !message) {
return Response.json({ ok: false, error: "email and message are required" }, { status: 400 });
}
const safeMessage = message.replaceAll("<", "<").replaceAll(">", ">");
let result;
try {
result = await samva.messages.send({
to: [{ email }],
channel: "email",
email: {
subject: "Thanks for contacting us",
html: `<p>Thanks for reaching out.</p><p>${safeMessage}</p>`,
text: `Thanks for reaching out.\n\n${message}`,
},
});
} catch {
return Response.json({ ok: false, error: "Failed to send message." }, { status: 502 });
}
return Response.json({ ok: true, result });
}The SDK is fetch-based, so this send path can run in the Next.js Edge runtime. Choose Node instead when the surrounding handler needs Node-only database drivers, filesystem access, or other Node built-ins.
React Email
Render React Email to HTML, then send the strings with Samva:
import { render, toPlainText } from "react-email";
import { samva } from "./lib/samva";
import WelcomeEmail from "./emails/welcome";
const html = await render(<WelcomeEmail name="Ada" />);
await samva.messages.send({
to: [{ email: "ada@example.com" }],
channel: "email",
email: { subject: "Welcome", html, text: toPlainText(html) },
});See the React Email integration for the full templating workflow.
Full cookbook and example
The Next.js cookbook
goes deeper: choosing between a Server Action and a Route Handler, fire-and-forget
versus awaited sends, the Pages Router equivalent, and edge-versus-Node tradeoffs.
The nextjs-transactional example
is a runnable project with both send paths wired up.