kiliokatsu

Troquei o Resend pelo Brevo e foi a melhor decisão do projeto

Comecei o sistema com Resend porque era o que todo tutorial mandava usar. Só que quando o volume transacional subiu, o custo e o limite não fecharam mais. Dito isso, aqui está exatamente o que eu ganhei e o que eu perdi na troca.

O que eu usava

O sistema mandava três tipos de e-mail: confirmação de cadastro, aviso de vencimento de prazo e o resumo semanal. Nada sofisticado. O Resend entrou porque era o que aparecia em todo tutorial de Next.js, a documentação é boa e a integração leva quinze minutos.

Funcionou por uns quatro meses sem eu pensar no assunto - e é isso que eu esperava de uma ferramenta de e-mail. Ferramenta boa é a que eu esqueço que existe.

O que doeu

O aviso de pendência é disparado por cliente, não por usuário. Quando o segundo cliente entrou no portal, o volume triplicou de uma semana para a outra. Aí apareceram duas coisas ao mesmo tempo:

  • o limite do plano começou a bater no meio do dia, e o disparo da tarde entrava na fila;
  • o custo por mil envios, na faixa em que eu estava caindo, ficou difícil de defender.

Acredito eu que o erro não foi escolher o Resend. Foi escolher sem estimar volume - eu simplesmente não tinha pensado em quantos e-mails o sistema mandaria com dez clientes dentro.

O que eu deveria ter feito antes: uma conta de guardanapo. Clientes × equipamentos × avisos por mês. Levava cinco minutos e teria mudado a escolha.

Pra onde eu fui

Fui para o Brevo. O que decidiu não foi preço: foi o plano ter fila própria, então disparo em lote deixou de ser problema meu. Ou seja, eu troquei um problema de código por um problema de configuração - e problema de configuração eu resolvo uma vez.

A troca em si foi pequena, porque o envio já estava atrás de uma função só. Isto é o arquivo inteiro depois da migração:

lib/email.tstypescript
// Uma função só. Trocar de provedor mexe aqui e em lugar nenhum mais.
import { BrevoClient } from '@getbrevo/brevo'

const cliente = new BrevoClient(process.env.BREVO_API_KEY!)

export async function enviar(para: string, assunto: string, html: string) {
  const r = await cliente.transactionalEmails.send({
    to: [{ email: para }],
    sender: { email: 'nao-responda@meudominio.com.br', name: 'Portal' },
    subject: assunto,
    htmlContent: html,
  })

  // Log do id: sem isso, "o cliente diz que não recebeu" é indefensável.
  console.info('[email] enviado', { para, messageId: r.messageId })
  return r.messageId
}

Repare no console.info com o messageId. Isso não estava no código antigo, e foi o que mais me ajudou depois: quando um cliente diz que não recebeu, eu tenho o número do envio para procurar no painel do provedor. Sem isso, a conversa vira "mandei sim" contra "não chegou".

O que eu ganhei

  • Fila no provedor. Disparo em lote parou de ser problema de código.
  • Custo previsível na faixa de volume em que eu de fato estou.
  • Rastreabilidade: agora eu sei o número de cada envio.

O que eu perdi

E essa é a parte que quase ninguém escreve, então vai completa:

  • A documentação do Brevo é pior. Levei uma tarde para achar a forma certa de mandar o remetente.
  • O painel é mais pesado e tem muita coisa de marketing que eu não uso.
  • Perdi um dia de trabalho na troca. Um dia que não virou funcionalidade nenhuma para o cliente.

Troca de ferramenta nunca é de graça. Quando alguém te conta só o lado bom, ou a troca foi recente demais, ou tem link de indicação no meio.

Se eu começasse hoje

Faria a conta de guardanapo antes, e manteria o envio atrás de uma função só desde o primeiro dia - foi isso que fez a migração ser um arquivo em vez de uma refatoração.

Fica uma dúvida honesta minha: em que ponto vale sair de um serviço gerenciado e mandar e-mail por conta própria? Eu não sei responder - e desconfio que a resposta é "quase nunca", só que não tenho número para defender isso.

kiliokatsu, assinado

Escrito por Vinícius · desenvolvedor de sistemas · ver perfil →