Next.js
Send transactional email from a Next.js app through eusend — from a Server Action or a Route Handler, with React Email templates and without blocking the response.
Next.js runs your sending code on the server, so the eusend SDK works as it does anywhere else in Node.js. This page covers the two places a send usually lives — a Server Action and a Route Handler — and the two things that go wrong most often: a key that leaks into the client bundle, and a serverless function frozen before the request to eusend finishes.
Setup
Install the SDK
npm install @eusend_dev/sdkAdd the API key
Create a key under API keys. Keep it out of any variable
prefixed NEXT_PUBLIC_ — those are inlined into the browser bundle.
EUSEND_API_KEY=eu_test_...An eu_test_ key runs validation, quota and suppression checks and shows the email in your
log without delivering it. Switch to an eu_live_ key when you deploy.
Create one client
import 'server-only'
import { Eusend } from '@eusend_dev/sdk'
export const eusend = new Eusend(process.env.EUSEND_API_KEY)server-only turns an accidental import from a Client Component into a build error.
Send from a Server Action
'use server'
import { eusend } from '@/lib/eusend'
export async function sendContactMessage(formData: FormData) {
const email = String(formData.get('email') ?? '')
const message = String(formData.get('message') ?? '')
const { error } = await eusend.emails.send({
from: 'Acme <hello@yourdomain.com>',
to: 'support@yourdomain.com',
replyTo: email,
subject: 'New contact form message',
text: message,
})
if (error) return { ok: false, code: error.name }
return { ok: true }
}The SDK returns { data, error } instead of throwing, so a failed send is a value you can
return to the form. Branch on error.name — the error code —
not on the message.
A public form that sends email is an open relay unless you bound it. Send to your own address and
put the visitor in replyTo, as above, and rate-limit the action. Never let a form field choose
the recipient.
Send from a Route Handler
import { eusend } from '@/lib/eusend'
export async function POST(request: Request) {
const { orderId, email } = await request.json()
const { data, error } = await eusend.emails.send(
{
from: 'Acme <billing@yourdomain.com>',
to: email,
subject: `Receipt for order ${orderId}`,
html: `<p>Thanks — order ${orderId} is confirmed.</p>`,
},
{ idempotencyKey: `receipt-${orderId}` },
)
if (error) return Response.json({ code: error.name }, { status: error.statusCode ?? 502 })
return Response.json({ id: data.id })
}The idempotencyKey makes a retried request — a double click, a webhook redelivery — return
the original email instead of sending a second one.
React Email templates
Pass a component as react and the SDK renders it to HTML before sending:
import { eusend } from '@/lib/eusend'
import { WelcomeEmail } from '@/emails/welcome'
export async function POST(request: Request) {
const { name, email } = await request.json()
const { error } = await eusend.emails.send({
from: 'Acme <hello@yourdomain.com>',
to: email,
subject: 'Welcome to Acme',
react: <WelcomeEmail name={name} />,
})
return Response.json({ ok: !error })
}This needs react and @react-email/render installed. The file must be .tsx.
Don't block the response
Sending adds a network round trip to the request. When the user doesn't need the result,
schedule it with after so the response goes out first and the platform keeps the function
alive until the send finishes:
import { after } from 'next/server'
after(async () => {
const { error } = await eusend.emails.send({ ... })
if (error) console.error('welcome email failed', error.name)
})Without after, a send you don't await can be cut off when a serverless function is
frozen after responding.
Next steps
- Verify a domain to send to real recipients from your own address.
- Auth.js or Better Auth for sign-in and verification email.
- Webhooks to learn when a message is delivered, bounced or complained about.