Přejít na obsah
Dokumentace

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.

Aktualizováno 1. 10. 2026

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

Terminal
npm install @emailit/node server-only

Dí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):

.env.local
EMAILIT_API_KEY=secret_••••••••••••••••••••••••••••••••
EMAILIT_WEBHOOK_SECRET=whsec_••••••••

Vytvořte jednoho klienta v modulu, který běží jen na serveru:

lib/emailit.ts
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í:

types/emailit.d.ts
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:

app/api/contact/route.ts
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:

app/signup/actions.ts
'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 },
  });
}
app/signup/page.tsx
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:

pages/api/contact.ts
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):

lib/mailer.ts
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():

app/api/webhooks/emailit/route.ts
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-only to vynucuje už při sestavení.
  • Nikdy nenechte klienta vybrat odesílatele. Adresu from zadejte napevno a to z 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ý.

Další kroky

Ošetření chyb a Nodemailer podrobněji.
Přílohy, plánování a měření.
Navrhněte e-maily jednou a odesílejte je podle aliasu.
Rozsahy oprávnění, omezení na doménu a výměna.

Byla tato stránka užitečná?

Děkujeme za zpětnou vazbu.

Děkujeme, čteme každou zprávu.