Guia do desenvolvedorIntegrações
Node.js com Express
Enviar e-mail pelo Couryo numa API Node.js com Express, com idempotência, tratamento de erros e o webhook assinado lido com o corpo cru.
Um servidor Express que envia o e-mail de boas-vindas no cadastro e recebe os eventos de entrega por webhook, com o SDK Node.
1. Instale#
npm install express @couryo/nodeCOURYO_API_KEY=ck_test_...
COURYO_WEBHOOK_SECRET=whsec_...Rode com node --env-file=.env server.js (Node 20.6 ou mais novo lê o .env sozinho).
2. Enviar no cadastro#
import express from "express";
import { Couryo, CouryoError, verifyWebhook, WebhookVerificationError } from "@couryo/node";
const app = express();
const couryo = new Couryo(); // lê COURYO_API_KEY
app.post("/cadastro", express.json(), async (req, res) => {
const { email, nome } = req.body;
const usuario = await criarUsuario({ email, nome }); // a sua função
try {
const enviado = await couryo.emails.send(
{
from: "Exemplo <oi@exemplo.com.br>",
to: email,
subject: `Boas-vindas, ${nome}!`,
html: `<p>Oi, ${nome}! Sua conta está pronta.</p>`,
text: `Oi, ${nome}! Sua conta está pronta.`,
tags: { tipo: "boas-vindas" },
},
// Se esta rota rodar duas vezes para o mesmo usuário, o e-mail sai uma vez só.
{ idempotencyKey: `boas-vindas-${usuario.id}` },
);
res.status(201).json({ usuario: usuario.id, email_id: enviado.id });
} catch (e) {
if (e instanceof CouryoError) {
// e.code é estável: domain_not_verified, daily_limit_reached, recipient_suppressed...
console.error(e.code, e.message, e.requestId);
return res.status(201).json({ usuario: usuario.id, aviso: "conta criada, e-mail não enviado" });
}
throw e;
}
});O SDK já tenta de novo erros de rede, 5xx e rate_limited, sempre com a mesma Idempotency-Key. O que chega no catch é o que não adianta repetir, com a mensagem em português.
3. Receber os eventos por webhook#
A assinatura é calculada sobre o corpo exato. Use express.raw nesta rota, nunca express.json:
app.post("/webhooks/couryo", express.raw({ type: "application/json" }), (req, res) => {
let evento;
try {
evento = verifyWebhook({ secret: process.env.COURYO_WEBHOOK_SECRET, headers: req.headers, body: req.body });
} catch (e) {
if (e instanceof WebhookVerificationError) return res.status(400).send("assinatura inválida");
throw e;
}
switch (evento.type) {
case "delivered":
break;
case "bounced":
case "complained":
// O Couryo já suprimiu o endereço quando é o caso. Marque no seu banco, se quiser:
// evento.data.recipient, evento.data.explanation
break;
}
res.sendStatus(204);
});
app.listen(3000);Registre o express.json() global, se tiver um, depois desta rota, ou deixe-o só nas rotas que precisam (como acima). Responda rápido: qualquer 2xx confirma; trabalho demorado vai para uma fila sua.
4. Lote, templates e agendamento#
// Até 100 e-mails numa chamada, com resultado por item, na mesma ordem.
const { data } = await couryo.emails.batch(
clientes.map((c) => ({ from: "Loja <oi@exemplo.com.br>", to: c.email, template: "aviso-entrega", variables: { nome: c.nome } })),
);
// Agendar para daqui a 1 hora (até 30 dias).
await couryo.emails.send({ from: "Loja <oi@exemplo.com.br>", to: "ana@exemplo.com.br", subject: "Lembrete", text: "...", scheduled_at: new Date(Date.now() + 3600_000).toISOString() });Por SMTP (Nodemailer)#
Se o projeto já usa Nodemailer, troque só o transporte:
import nodemailer from "nodemailer";
export const transport = nodemailer.createTransport({
host: "smtp.couryo.com",
port: 587, // STARTTLS; ou 465 com secure: true
secure: false,
requireTLS: true,
auth: { user: "couryo", pass: process.env.COURYO_API_KEY },
});Os dois caminhos geram a mesma linha do tempo e os mesmos webhooks. Veja SMTP para os códigos de resposta.