Tutorial
Enviar e-mails com Next.js
Envie e-mails de route handlers e server actions do Next.js com o SDK de Node.js do Emailit, mantenha a chave de API no servidor e verifique webhooks.
Este guia mostra como enviar e-mails de uma aplicação Next.js com o SDK @emailit/node, tanto a partir de um route handler quanto de uma server action, e como verificar os webhooks do Emailit. Os exemplos usam o App Router; o Pages Router é abordado no fim da seção de envio.
Pré-requisitos
- Next.js 14 ou mais recente. O SDK exige Node.js 18 ou mais recente.
- Um domínio de envio verificado, por exemplo,
acme.com. - Uma chave de API. Uma chave Sending Only restrita ao seu domínio é suficiente.
- Até o seu workspace ter acesso de produção, você só pode enviar para os e-mails das contas dos membros do workspace.
Instalar o SDK
npm install @emailit/node server-onlyserver-only faz o build falhar se um client component importar o módulo que guarda a sua chave.
Configurar a chave de API
Adicione a chave ao .env.local para desenvolvimento e às variáveis de ambiente da sua hospedagem (por exemplo, as configurações do projeto na Vercel) para produção:
EMAILIT_API_KEY=secret_••••••••••••••••••••••••••••••••
EMAILIT_WEBHOOK_SECRET=whsec_••••••••Crie um único cliente em um módulo exclusivo do servidor:
import 'server-only';
import { Emailit } from '@emailit/node';
export const emailit = new Emailit(process.env.EMAILIT_API_KEY!);O SDK ainda não tem declarações de TypeScript. Se o compilador reclamar, adicione um arquivo de declaração:
declare module '@emailit/node';Enviar a partir de um route handler
Um route handler é o lugar certo para envios disparados pelo seu próprio frontend ou por outros serviços. Este handler de formulário de contato envia para um endereço interno fixo, então os visitantes não podem usá-lo para enviar e-mails a qualquer pessoa:
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 });
}
}Chame-o a partir do navegador com fetch('/api/contact', { method: 'POST', body: JSON.stringify(data) }). A chave nunca sai do servidor.
Enviar a partir de uma server action
As server actions são executadas no servidor, então podem usar o cliente diretamente:
'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 recebe o alias ou o ID tem_ de um template; consulte Templates. As server actions são endpoints públicos, então valide a entrada e adicione a sua proteção habitual contra bots e abusos antes de enviar.
Pages Router
Com o Pages Router, envie a partir de uma 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 });
}Enviar por SMTP como alternativa
Se você já usa o Nodemailer, configure-o com o relay do Emailit dentro de um route handler ou de uma server action (o SMTP exige o runtime Node.js, não o runtime 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 },
});Depois, chame await mailer.sendMail({ from, to, subject, html }). As funções serverless abrem uma nova conexão SMTP na maioria das invocações, então a API costuma ser mais rápida na Vercel e em plataformas semelhantes. Consulte Configurações de SMTP.
Receber webhooks
Crie um webhook que aponte para https://your-app.com/api/webhooks/emailit. O handler precisa verificar a assinatura com base no corpo bruto, então leia-o com request.text() antes de interpretá-lo:
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 });
}Retorne um 2xx em até 30 segundos; as outras respostas recebem novas tentativas. Consulte Assinatura das requisições e Tipos de evento.
Dicas para produção
- Mantenha os envios no servidor. Apenas
lib/emailit.ts, os route handlers e as server actions devem ter acesso à chave.server-onlygarante isso no momento do build. - Nunca deixe o cliente escolher o remetente. Fixe
fromno código e só aceitetodo navegador quando for o próprio endereço do usuário conectado. - Proteja os endpoints públicos. Limite a taxa de requisições dos formulários de contato e das ações de cadastro e adicione um CAPTCHA se os bots os encontrarem. Cada e-mail custa créditos e conta para os seus limites de envio.
- Defina variáveis por ambiente. Use chaves separadas para os deploys de preview e de produção para poder revogar uma sem afetar a outra.