The modern Next.js 15 App Router email architecture
In Next.js 15, sending email from client-rendered components is a critical security anti-pattern that leaks credentials. The modern paradigm renders React Email components into HTML strings entirely on the server inside Server Actions or Route Handlers, dispatching payloads via HTTP fetch.
| Execution Context | Invocation Mechanism | Best Use Case | Key Benefit |
|---|---|---|---|
| Server Actions | 'use server' function call | Form submissions, signup mutations | Co-located progressive enhancement with zero client API route boilerplate |
| Route Handlers | export async function POST | Webhooks, external API triggers, background jobs | Direct control over HTTP status codes, CORS headers, and streaming responses |
1. Designing the React Email Component (WelcomeEmail.tsx)
Using @react-email/components, define a responsive email template with clean Tailwind styling and dark mode typography:
import { Html, Head, Body, Container, Text, Link, Preview } from '@react-email/components';
import * as React from 'react';
interface WelcomeEmailProps {
name: string;
loginUrl: string;
}
export function WelcomeEmail({ name, loginUrl }: WelcomeEmailProps) {
return (
<Html>
<Head />
<Preview>Welcome to SadaSend, {name}</Preview>
<Body style={{ backgroundColor: '#09090b', fontFamily: 'monospace', color: '#fafafa', padding: '40px 20px' }}>
<Container style={{ maxWidth: '560px', margin: '0 auto', border: '1px solid #27272a', padding: '32px', borderRadius: '4px' }}>
<Text style={{ fontSize: '20px', fontWeight: 'bold' }}>Welcome aboard, {name}</Text>
<Text style={{ fontSize: '14px', lineHeight: '1.6', color: '#a1a1aa' }}>
Your account is provisioned. You can now mint scoped agent keys and configure sending domains.
</Text>
<Link href={loginUrl} style={{ display: 'inline-block', backgroundColor: '#10b981', color: '#000', padding: '10px 20px', borderRadius: '3px', textDecoration: 'none', fontWeight: 'bold' }}>
Open Dashboard →
</Link>
</Container>
</Body>
</Html>
);
}2. Server Action Dispatch (actions/send-welcome.ts)
Render the component to HTML using render() and dispatch to SadaSend API with an idempotency key:
'use server';
import { render } from '@react-email/render';
import { WelcomeEmail } from '@/emails/WelcomeEmail';
export async function sendWelcomeAction(userEmail: string, userName: string) {
const html = await render(WelcomeEmail({ name: userName, loginUrl: 'https://app.sadasend.com' }));
const res = await fetch('https://api.sadasend.com/emails', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.SADASEND_API_KEY}`,
'Content-Type': 'application/json',
'Idempotency-Key': `welcome_${userEmail}`,
},
body: JSON.stringify({
to: userEmail,
subject: 'Welcome to SadaSend',
html,
text: `Welcome to SadaSend, ${userName}! Open your dashboard: https://app.sadasend.com`,
}),
});
const data = await res.json();
if (!res.ok) throw new Error(data.message || 'Failed to send');
return { success: true, messageId: data.id };
}Why native fetch beats legacy SMTP in serverless Next.js
- Zero connection pooling overhead during Vercel or AWS Lambda scale-up.
- fast HTTP dispatch latency across global edge PoPs.
- Built-in edge compatibility without requiring Node.js net sockets.
Building AI agents that send email?
Scoped API keys, per-key recipient allowlists, approval mode and a hosted MCP server with ten tools — on the free plan, without a card.