Como enviar e-mail com Node (sem cair no spam)
Por que o processo de envio de e-mail mudou tanto nos últimos anos, o que serviços como Gmail e Hotmail passaram a exigir, e como enviar e-mails de verdade com Node usando o Mailtrap.
Muito programador, inclusive gente com bastante experiência, nunca se aprofundou em como e-mail funciona de verdade. O motivo é simples: no dia a dia só é preciso enviar um e-mail aqui e ali, então a solução é pegar a primeira ferramenta que resolve e seguir em frente. O problema é que sem entender o mínimo de como esse processo funciona hoje, é bem provável que o e-mail caia direto no spam, na aba de promoções, ou nem chegue.
Como e-mail funcionava antes
Anos atrás, qualquer hospedagem compartilhada já vinha com um serviço de envio de e-mail embutido. Quem programava em PHP, por exemplo, tinha uma função pronta para enviar e-mail onde bastava informar o destinatário e o remetente, sendo que o remetente podia ser literalmente qualquer string. Dava para se passar por qualquer domínio, inclusive um e-mail fingindo ser do governo ou de um banco. Esse buraco gigante é uma das raízes do phishing como ele existe até hoje.
Como funciona hoje
O processo técnico de enviar e-mail não mudou tanto assim. O que mudou de forma drástica foi o processo de recebimento por parte dos grandes serviços, Gmail, Hotmail, Yahoo e companhia. Com o volume de spam crescendo todo dia, esses serviços passaram a ser muito mais criteriosos sobre o que deixam passar, e até sobre o que sequer entregam no spam.
Quando é uma pessoa mandando e-mail já logada na própria conta do Gmail, não tem problema, o próprio Gmail sabe que aquele envio é legítimo. O complicado é quando é um sistema enviando e-mail em nome de alguém, seja um e-mail de recuperação de senha, seja um envio em massa para milhares de pessoas. Nesse cenário existe toda uma ciência por trás, com conceitos como DKIM e configurações específicas que precisam existir no DNS do domínio remetente, para provar que aquele e-mail realmente saiu de onde diz que saiu.
Também existe o conceito de aquecimento de domínio: um domínio que já tem histórico de enviar e-mails que são abertos e não marcados como spam ganha credibilidade aos poucos com Gmail, Yahoo e Hotmail. Um domínio novo, sem histórico nenhum, começa em desvantagem.
Por isso, aquele serviço de e-mail que já vem pronto na hospedagem compartilhada praticamente parou de funcionar bem. Ele usa um IP qualquer, às vezes até um IP que já está em alguma lista negra, e isso prejudica a entrega sem que você nem saiba o motivo.
Usando um serviço externo de envio
A alternativa mais confiável hoje é usar um serviço externo especializado em envio de e-mail, como SendGrid ou Mailtrap. Geralmente não são caros, principalmente para volume baixo, e vários deles têm um free tier generoso. O Mailtrap, por exemplo, libera 1000 e-mails por mês de graça, e é o que vou usar aqui.
Ele também tem um ambiente de teste separado do envio oficial: toda mensagem enviada nesse modo cai numa caixa de entrada de teste dentro do próprio painel, sem precisar ficar checando uma conta de Gmail ou Hotmail de verdade para saber se o envio funcionou.
Instalando e configurando
Com o projeto Node rodando, o primeiro passo é instalar a biblioteca oficial:
npm install mailtrapEla já vem com suporte a TypeScript, então funciona direto num projeto que já usa TS.
Por organização, vale centralizar tudo relacionado ao Mailtrap num arquivo só, por exemplo src/libs/mailtrap.ts. Assim, qualquer ajuste futuro fica concentrado num único lugar:
import { MailtrapClient } from "mailtrap";
const client = new MailtrapClient({
token: process.env.MAILTRAP_TOKEN as string,
testInboxId: Number(process.env.MAILTRAP_TEST_INBOX_ID),
});O token é gerado no painel do Mailtrap, em API Tokens, criando um token novo com permissão de email testing. O testInboxId aparece na própria URL quando você abre uma caixa de entrada de teste no painel. Os dois valores ficam em variáveis de ambiente, nunca direto no código.
Criando a função de envio
Com o cliente configurado, o próximo passo é uma função genérica que recebe para quem enviar, o assunto e o corpo do e-mail:
export async function sendEmail(to: string, subject: string, body: string) {
try {
await client.testing.send({
from: { name: "Sistema", email: "teste@sistema.com" },
to: [{ email: to }],
subject,
text: body,
html: `<p>${body}</p>`,
});
return true;
} catch (error) {
return false;
}
}Alguns detalhes importantes aqui:
fromé o remetente, com nome e e-mail.toé um array, dá para mandar o mesmo e-mail para várias pessoas de uma vez.- Enviar
textehtmljuntos ajuda na entrega. Cada cliente de e-mail escolhe qual versão exibir, e ter as duas cobre mais casos do que mandar só uma. - Enquanto estiver em ambiente de teste, o envio passa por
client.testing.send. Em produção, o mesmo código funciona trocando paraclient.send, sem otestInboxIdconfigurado.
Usando a função numa rota
Um cenário comum é um fluxo de esqueci minha senha. A rota recebe o e-mail da pessoa, faz as validações necessárias e dispara o envio:
export const forgot: RequestHandler = async (req, res) => {
const { email } = req.body;
await sendEmail(
email,
"Esqueci a senha",
"Corpo do e-mail com o código de recuperação de acesso ao sistema."
);
res.status(200).json({ message: "E-mail enviado" });
};Testando essa rota com um e-mail qualquer no corpo da requisição, a mensagem aparece na caixa de entrada de teste do Mailtrap em segundos, já com uma análise de spam incluída. Vale reparar que um domínio genérico como sistema.com tende a puxar a nota de spam para baixo, porque provavelmente já foi usado por várias pessoas só para teste. Em produção, com um domínio próprio e configurado corretamente, esse problema não existe.
Resumindo
Enviar e-mail com Node é simples na prática, mas entender o motivo por trás de cada configuração é o que evita a dor de cabeça de descobrir, semanas depois, que os e-mails do sistema estão indo direto para o spam. Configurar um serviço externo, separar ambiente de teste e produção, e mandar texto e HTML juntos já resolve a maior parte dos problemas de entrega que costumam pegar quem está começando nesse assunto.
Leia também