Next.js
Send transactional emails from your Next.js application using Server Actions, Route Handlers, and the Veil Mail SDK.
Prerequisites
- Next.js 14 or later (App Router)
- Node.js 18+
- A Veil Mail API key
- A verified domain
1. Install the SDK
npm install @resonia/veilmail-sdk2. Configure Environment
Add your API key to .env.local:
VEILMAIL_API_KEY=veil_live_xxxxxCreate a shared client instance:
import { VeilMail } from '@resonia/veilmail-sdk';
export const veilmail = new VeilMail({
apiKey: process.env.VEILMAIL_API_KEY!,
});3. Send Email from a Server Action
Server Actions run on the server, so your API key stays secure. This is the recommended approach for form submissions and user-triggered emails.
'use server';
import { veilmail } from '@/lib/veilmail';
export async function sendWelcomeEmail(formData: FormData) {
const email = formData.get('email') as string;
const name = formData.get('name') as string;
await veilmail.emails.send({
from: 'hello@yourdomain.com',
to: email,
subject: `Welcome, ${name}!`,
html: `<h1>Welcome to our platform</h1><p>Hi ${name}, thanks for signing up.</p>`,
});
return { success: true };
}'use client';
import { sendWelcomeEmail } from '@/app/actions/email';
export default function SignupForm() {
return (
<form action={sendWelcomeEmail}>
<input name="name" placeholder="Name" required />
<input name="email" type="email" placeholder="Email" required />
<button type="submit">Sign Up</button>
</form>
);
}4. Send Email from a Route Handler
Use Route Handlers when you need an API endpoint, for example to trigger emails from external services or cron jobs.
import { NextResponse } from 'next/server';
import { veilmail } from '@/lib/veilmail';
export async function POST(request: Request) {
const { to, orderId } = await request.json();
const email = await veilmail.emails.send({
from: 'orders@yourdomain.com',
to,
subject: `Order #${orderId} confirmed`,
html: `<p>Your order #${orderId} has been confirmed.</p>`,
tags: [{ name: 'type', value: 'order-confirmation' }],
});
return NextResponse.json({ emailId: email.id });
}5. Handle Webhooks
Create a Route Handler to receive webhook events. Always verify the signature using the X-VeilMail-Signature header.
VEILMAIL_WEBHOOK_SECRET=whsec_xxxxximport crypto from 'crypto';
import { NextResponse } from 'next/server';
function verifySignature(payload: string, signature: string, secret: string) {
const expected = crypto
.createHmac('sha256', secret)
.update(payload)
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expected)
);
}
export async function POST(request: Request) {
const signature = request.headers.get('x-veilmail-signature');
const body = await request.text();
if (!signature || !verifySignature(body, signature, process.env.VEILMAIL_WEBHOOK_SECRET!)) {
return NextResponse.json({ error: 'Invalid signature' }, { status: 401 });
}
const event = JSON.parse(body);
switch (event.type) {
case 'email.delivered':
// Update delivery status in your database
console.log('Delivered:', event.data.emailId);
break;
case 'email.bounced':
// Handle bounce — remove from mailing list
console.log('Bounced:', event.data.emailId);
break;
case 'email.complained':
// Handle spam complaint
console.log('Complaint:', event.data.emailId);
break;
}
return NextResponse.json({ received: true });
}Important: Use request.text() instead of request.json() to get the raw body for signature verification. Parsing JSON first changes the string representation.
6. Use Templates
Use Veil Mail templates to keep your email content separate from your code.
import { veilmail } from '@/lib/veilmail';
await veilmail.emails.send({
from: 'hello@yourdomain.com',
to: 'user@example.com',
subject: 'Your monthly report',
templateId: 'template_xxxxx',
templateData: {
userName: 'Alice',
reportMonth: 'January 2025',
totalEmails: '1,234',
},
});