O pedido foi confirmado e o cliente precisa saber. Parece uma linha de código, enviarEmail(cliente, "Pedido confirmado"), e é. Até a rede cair no meio, o provedor demorar 30 segundos ou a fila reprocessar o evento e o cliente receber a confirmação três vezes.
A integração que presta é a que, no dia ruim, ainda deixa você responder: esse aviso saiu? Quando? Chegou?
Comece pelo evento
Escolha um fluxo só para começar. Confirmação de pedido é um bom candidato. Escreva quando ele dispara, quem recebe, o que vai no texto e a parte que todo mundo esquece: quando não deve sair, como no pedido cancelado antes do envio.
Dê a cada notificação um identificador interno amarrado ao evento de origem, algo como pedido-4821-confirmacao. Ele serve para evitar duplicidade, achar a mensagem nos logs e explicar ao atendimento o que aconteceu. Corpo inteiro e token de acesso não vão para log comum.
API ou SMTP
Se o sistema já fala SMTP (WordPress, um ERP antigo, aquele software que ninguém quer abrir), use SMTP. Para código novo, API costuma ganhar: resposta estruturada, erro com nome e o identificador da mensagem na volta.
Não escolha pelo tamanho do exemplo de código. Escolha testando o fluxo real, com uma falha provocada de propósito.
A chave fica no servidor
Crie uma credencial só para a aplicação, com o mínimo de permissão que o provedor deixar: só enviar, só daquele domínio e, se der, só daquela faixa de IP. Guarde em variável de ambiente ou num cofre de segredos. Nunca no JavaScript do navegador, nunca no repositório, nunca no print que alguém mandou para o suporte.
Use chaves separadas para teste e produção, e combine antes como se troca uma chave sem derrubar nada: publica a nova, confirma que tudo usa a nova e só então revoga a antiga.
Timeout não é falha
Quando a chamada ao provedor estoura o tempo, você não sabe o que houve. Pode ser que o pedido nunca chegou. Pode ser que chegou, a mensagem saiu e só a resposta se perdeu. Tentar de novo às cegas manda duas.
A saída é idempotência: a mesma chave em todas as tentativas da mesma operação, para o provedor reconhecer a repetição. O conceito vem do HTTP (RFC 9110). Use exatamente como a documentação do seu provedor descreve, inclusive o prazo em que ele guarda a chave.
Coloque o envio numa fila, separado da ação principal, porque o cliente não precisa esperar o e-mail sair para ver "pedido confirmado" na tela. Defina quantas tentativas, com que intervalo e até quando o aviso faz sentido. Um código de acesso válido por 10 minutos que chega depois de 40 é pior do que nenhum.
Os eventos que voltam
Provedores avisam por webhook quando a mensagem foi aceita pelo destino, devolvida ou marcada como spam. Esse endpoint é uma porta aberta na sua aplicação. Verifique a assinatura do jeito que a documentação manda, recuse eventos antigos demais e não trate URL secreta como autenticação.
Eventos chegam repetidos e fora de ordem, e "entregue" pode aparecer antes de "aceito". Guarde o estado de um jeito que uma repetição não dispare outro e-mail nem apague um resultado mais novo.
Um exemplo com a API da Oveyon
A Oveyon mantém este site, então o exemplo é dela. O envio é uma requisição com a credencial no Authorization e a chave de idempotência amarrada ao pedido:
curl https://api.oveyon.com/v1/send \
-H "Authorization: Bearer $OVEYON_API_KEY" \
-H "Idempotency-Key: pedido-4821-confirmacao" \
-H "X-Oveyon-Sandbox: 1" \
-H "Content-Type: application/json" \
-d '{"from": "Loja <pedidos@seudominio.com.br>",
"to": "cliente@example.com",
"subject": "Pedido 4821 confirmado",
"text": "Recebemos o seu pedido."}'
Com X-Oveyon-Sandbox: 1 a mensagem é aceita e não sai, o que permite testar a integração inteira sem incomodar ninguém. Repetir a chamada com a mesma Idempotency-Key devolve o identificador original em vez de mandar de novo. E a documentação avisa que domínio sem DKIM verificado não envia.
Os webhooks chegam com x-oveyon-timestamp e x-oveyon-signature, que é um HMAC-SHA256 do timestamp, de um ponto e do corpo bruto. Em Node.js:
import { createHmac, timingSafeEqual } from 'node:crypto';
export function eventoAutentico(segredo, timestamp, corpoBruto, assinatura) {
const idade = Math.abs(Date.now() / 1000 - Number(timestamp));
if (!timestamp || !(idade <= 300)) return false; // janela contra repetição
const esperado = createHmac('sha256', segredo)
.update(timestamp + '.').update(corpoBruto).digest('hex');
const recebido = String(assinatura).replace(/^sha256=/, '');
const a = Buffer.from(esperado, 'hex');
const b = Buffer.from(recebido, 'hex');
return a.length === b.length && timingSafeEqual(a, b);
}
Use o corpo exatamente como chegou, antes de converter para JSON. Os nomes de eventos e campos estão na documentação. Com outro provedor, o desenho é o mesmo.
Antes de ligar em produção
Teste o caminho ruim, não só o bom:
- derrube a rede no meio de um envio e veja se sai duplicado;
- use uma credencial inválida e confira se o erro aparece num lugar que alguém olha;
- mande para um endereço que não existe e acompanhe a devolução chegar;
- reenvie o mesmo webhook duas vezes;
- peça para alguém do atendimento achar uma mensagem específica sem a sua ajuda.
Para começar com a Oveyon, a documentação descreve o fluxo de criar o domínio e a credencial. Não copie segredos nem registros DNS de exemplo nenhum, inclusive os desta página.
Fontes e referências
- Oveyon: produto e canais de integração
- Oveyon: documentação técnica: envio, idempotência e webhooks
- RFC 9110: métodos HTTP e idempotência
Documentação consultada na revisão. Regras e interfaces de terceiros podem mudar.