Tutoriál
Odesílání e-mailů z Next.js
Odesílejte e-maily z route handlerů a server actions v Next.js přes Node.js SDK Emailitu, držte API klíč na serveru a ověřujte webhooky.
Tento návod ukazuje, jak odesílat e-maily z aplikace v Next.js přes SDK @emailit/node, a to z route handleru i ze server action, a jak ověřovat webhooky Emailitu. Příklady používají App Router; Pages Router popisuje konec části o odesílání.
Předpoklady
- Next.js 14 nebo novější. SDK potřebuje Node.js 18 nebo novější.
- Ověřená odesílací doména, například
acme.com. - API klíč. Stačí klíč jen pro odesílání omezený na vaši doménu.
- Dokud váš workspace nemá produkční přístup, můžete odesílat jen na e-mailové adresy účtů členů workspace.
Nainstalujte SDK
npm install @emailit/node server-onlyDíky server-only sestavení selže, pokud modul s vaším klíčem importuje klientská komponenta.
Nastavte API klíč
Pro vývoj přidejte klíč do .env.local a pro produkci do proměnných prostředí u svého hostingu (například v nastavení projektu ve Vercelu):
EMAILIT_API_KEY=secret_••••••••••••••••••••••••••••••••
EMAILIT_WEBHOOK_SECRET=whsec_••••••••Vytvořte jednoho klienta v modulu, který běží jen na serveru:
import 'server-only';
import { Emailit } from '@emailit/node';
export const emailit = new Emailit(process.env.EMAILIT_API_KEY!);SDK zatím nemá deklarace typů pro TypeScript. Pokud si kompilátor stěžuje, přidejte soubor s deklarací:
declare module '@emailit/node';Odesílejte z route handleru
Route handler je správné místo pro odesílání, které spouští váš vlastní frontend nebo jiné služby. Tento handler kontaktního formuláře odesílá na pevnou interní adresu, takže ho návštěvníci nemohou zneužít k posílání e-mailů komukoli:
import { emailit } from '@/lib/emailit';
export async function POST(request: Request) {
const { email, message } = await request.json();
if (typeof email !== 'string' || typeof message !== 'string' || !email.includes('@')) {
return Response.json({ error: 'Invalid input' }, { status: 400 });
}
try {
const sent = await emailit.emails.send({
from: 'Acme website <website@acme.com>',
to: 'support@acme.com',
reply_to: email,
subject: 'New contact form message',
text: message,
});
return Response.json({ id: sent.id });
} catch (error) {
console.error(error);
return Response.json({ error: 'Could not send the message' }, { status: 502 });
}
}Z prohlížeče ho volejte přes fetch('/api/contact', { method: 'POST', body: JSON.stringify(data) }). Klíč nikdy neopustí server.
Odesílejte ze server action
Server actions běží na serveru, takže mohou klienta používat přímo:
'use server';
import { emailit } from '@/lib/emailit';
export async function signUp(formData: FormData) {
const email = formData.get('email');
if (typeof email !== 'string' || !email.includes('@')) return;
// Create the account here, then send the welcome email.
await emailit.emails.send({
from: 'Acme <hello@acme.com>',
to: email,
template: 'welcome',
variables: { email },
});
}import { signUp } from './actions';
export default function SignupPage() {
return (
<form action={signUp}>
<input type="email" name="email" required />
<button type="submit">Create account</button>
</form>
);
}template přijímá alias šablony nebo ID tem_; viz Šablony. Server actions jsou veřejné endpointy, proto před odesláním validujte vstup a přidejte svou obvyklou ochranu proti botům a zneužití.
Pages Router
S Pages Routerem odesílejte místo toho z API route:
import type { NextApiRequest, NextApiResponse } from 'next';
import { emailit } from '@/lib/emailit';
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
if (req.method !== 'POST') return res.status(405).end();
const sent = await emailit.emails.send({
from: 'Acme website <website@acme.com>',
to: 'support@acme.com',
subject: 'New contact form message',
text: String(req.body.message ?? ''),
});
res.status(200).json({ id: sent.id });
}Odesílání přes SMTP
Pokud už používáte Nodemailer, nastavte ho na SMTP relay Emailitu uvnitř route handleru nebo server action (SMTP potřebuje běhové prostředí Node.js, ne Edge):
import 'server-only';
import nodemailer from 'nodemailer';
export const mailer = nodemailer.createTransport({
host: 'smtp.emailit.com',
port: 587,
secure: false,
requireTLS: true,
auth: { user: 'emailit', pass: process.env.EMAILIT_API_KEY },
});Pak zavolejte await mailer.sendMail({ from, to, subject, html }). Serverless funkce při většině volání otevírají nové SMTP spojení, takže na Vercelu a podobných platformách je API obvykle rychlejší. Viz Nastavení SMTP.
Přijímejte webhooky
Vytvořte webhook, který míří na https://your-app.com/api/webhooks/emailit. Handler musí ověřit podpis vůči surovému tělu požadavku, proto ho před parsováním načtěte pomocí request.text():
import { createHmac, timingSafeEqual } from 'node:crypto';
export const runtime = 'nodejs';
type EmailitEvent = {
event_id: string;
type: string;
data: { object: Record<string, unknown> };
};
function isValid(rawBody: string, signature: string | null, timestamp: string | null) {
if (!signature || !timestamp) return false;
const age = Math.abs(Math.floor(Date.now() / 1000) - Number(timestamp));
if (!Number.isFinite(age) || age > 300) return false;
const expected = createHmac('sha256', process.env.EMAILIT_WEBHOOK_SECRET!)
.update(`${timestamp}.${rawBody}`)
.digest('hex');
const a = Buffer.from(expected);
const b = Buffer.from(signature);
return a.length === b.length && timingSafeEqual(a, b);
}
export async function POST(request: Request) {
const rawBody = await request.text();
const valid = isValid(
rawBody,
request.headers.get('x-emailit-signature'),
request.headers.get('x-emailit-timestamp'),
);
if (!valid) return new Response('Invalid signature', { status: 401 });
// Each request carries an array of up to 100 events.
const events = JSON.parse(rawBody) as EmailitEvent[];
for (const event of events) {
if (event.type === 'email.bounced') {
// Flag event.data.object.to in your database.
}
}
return new Response(null, { status: 200 });
}Do 30 sekund vraťte 2xx; při jiných odpovědích se požadavek opakuje. Viz Ověření podpisu webhooků a Typy událostí.
Tipy pro produkční provoz
- Odesílejte jen ze serveru. S klíčem by měly pracovat jen
lib/emailit.ts, route handlery a server actions.server-onlyto vynucuje už při sestavení. - Nikdy nenechte klienta vybrat odesílatele. Adresu
fromzadejte napevno atoz prohlížeče přijímejte, jen když jde o vlastní adresu přihlášeného uživatele. - Chraňte veřejné endpointy. U kontaktních formulářů a registračních akcí omezte rychlost, a pokud je najdou boti, přidejte CAPTCHA. Každý e-mail stojí kredity a počítá se do vašich limitů odesílání.
- Nastavte proměnné pro každé prostředí zvlášť. Pro náhledová a produkční nasazení používejte samostatné klíče, abyste mohli jeden odvolat, aniž by to ovlivnilo druhý.