E-mails de OTP e verificação: velocidade, segurança e boas práticas

Como gerar, armazenar e enviar por e-mail códigos únicos e links de verificação com segurança, entrega rápida, expiração e rate limits, e um modelo confiável.

Um e-mail com código de uso único é a mensagem mais impaciente que o seu produto vai enviar. O usuário está olhando para uma tela que diz “Digite o código de 6 dígitos que enviamos para você”, e cada segundo que o e-mail demora é um segundo em que ele pensa em desistir.

E-mails de verificação também ficam bem na fronteira da sua segurança. Se você errar, ou bloqueia usuários reais, ou deixa invasores entrarem. Este guia cobre os dois lados: fazer o código chegar rápido à caixa de entrada e garantir que só a pessoa certa consiga usá-lo.

Há dois formatos comuns:

  • Códigos de uso único (OTP): um número curto, como 482913, digitado de volta no seu app. Bons para login e verificação em duas etapas, e funcionam quando o usuário lê o e-mail em outro dispositivo.
  • Links de verificação: um token aleatório longo em uma URL. Bons para confirmar um endereço de e-mail no cadastro ou para redefinir uma senha, porque um clique conclui o fluxo.

Tudo o que vem a seguir vale para os dois. A diferença está principalmente no tamanho: links podem carregar um token longo, enquanto códigos precisam ser curtos o bastante para serem digitados.

Gere códigos com segurança

Use o gerador aleatório criptograficamente seguro da sua linguagem. Math.random() e o módulo random do Python são previsíveis e nunca devem ser usados para códigos de segurança.

// Node.js
import { randomInt, createHmac } from 'node:crypto';

const code = String(randomInt(0, 1_000_000)).padStart(6, '0'); // "048213"
// Keyed hash: OTP_SECRET lives in your secrets manager, never in the database.
const codeHash = createHmac('sha256', process.env.OTP_SECRET).update(`${user.id}:${code}`).digest('hex');
# Python
import hashlib
import hmac
import os
import secrets

code = f"{secrets.randbelow(1_000_000):06d}"
# Keyed hash: OTP_SECRET lives in your secrets manager, never in the database.
code_hash = hmac.new(os.environ["OTP_SECRET"].encode(), f"{user_id}:{code}".encode(), hashlib.sha256).hexdigest()

Armazene apenas um hash com chave (HMAC), junto com o ID do usuário, um horário de expiração e um contador de tentativas. Um SHA-256 simples não basta: com apenas um milhão de códigos possíveis, quem tiver acesso ao banco de dados poderia testar todos em menos de um segundo. O HMAC exige um segredo mantido fora do banco de dados, então um vazamento do banco, sozinho, não revela códigos válidos.

Expire rápido e limite as tentativas

Um código de seis dígitos tem um milhão de possibilidades. Isso é bastante apenas se os invasores não puderem continuar chutando:

Controle Valor recomendado
Validade do código 10 minutos para códigos de login; até 24 horas para links de confirmação de e-mail
Tentativas erradas por código 5; depois disso, o código é invalidado
Códigos enviados por endereço Cerca de 3 a cada 15 minutos, com um tempo de espera visível no “Reenviar”
Códigos enviados por endereço IP Um limite mais alto, para impedir que um invasor percorra muitos endereços
Uso Uso único: exclua o código ou marque-o como usado assim que ele for aceito

Sempre compare códigos em tempo constante (crypto.timingSafeEqual no Node.js, hmac.compare_digest no Python), e emitir um novo código deve invalidar o anterior.

Envie imediatamente

E-mails de OTP devem sair na mesma requisição que os solicitou, e não em um job em lote que roda a cada minuto. Com a Inbixio, é uma única chamada:

await fetch('https://backend.inbixio.com/api/send-email/', {
  method: 'POST',
  headers: { 'X-API-Key': process.env.INBIXIO_API_KEY, 'Content-Type': 'application/json' },
  body: JSON.stringify({
    to_email: user.email,
    subject: `${code} is your YourApp sign-in code`,
    message: otpTemplate({ code, minutes: 10 }),
  }),
  signal: AbortSignal.timeout(10_000),
});

A Inbixio aceita a requisição e coloca a entrega na fila na hora, então seu endpoint pode responder rapidamente. Se a chamada falhar, avise o usuário imediatamente (“Não conseguimos enviar seu código. Tente novamente em instantes”) em vez de deixá-lo esperando por um e-mail que nunca vai chegar. O artigo como lidar com erros da API de e-mail lista quais erros valem uma retentativa automática.

Duas dicas de entrega específicas para códigos:

  • Use um remetente dedicado, como security@yourco.com, conectado como um servidor próprio, com uma chave de API vinculada a ele (vincule a chave de novo se algum dia remover esse servidor). Os usuários aprendem a confiar nesse endereço, e campanhas de marketing não afetam a reputação dele.
  • Autentique seu domínio. Códigos que caem no spam são códigos que nunca chegam. Siga o checklist de SPF, DKIM e DMARC.

Escreva um e-mail que não pareça phishing

Seu modelo compete com e-mails de phishing que copiam exatamente esse formato. Faça o verdadeiro ser fácil de reconhecer:

  • Coloque o código no assunto, por exemplo “482913 é o seu código de login do YourApp”. Os usuários conseguem lê-lo na notificação sem abrir o e-mail, e o teclado do celular pode sugeri-lo.
  • Diga para que serve e quanto tempo vale: “Use este código para entrar no YourApp. Ele expira em 10 minutos.”
  • Diga o que fazer se não tiver sido a pessoa: “Se você não tentou entrar, pode ignorar este e-mail. Sua conta está segura.”
  • Nunca peça o código de volta. Deixe isso claro: “O YourApp nunca vai pedir este código por telefone ou e-mail.”
  • Seja breve, sem marketing, e reduza os links ao mínimo absoluto.

Um modelo HTML simples e acessível:

<div style="font-family:system-ui,sans-serif;max-width:480px;margin:auto;color:#111">
  <p>Use this code to sign in to YourApp:</p>
  <p style="font-size:32px;font-weight:700;letter-spacing:6px;margin:24px 0">482913</p>
  <p>It expires in 10 minutes. If you didn't try to sign in, you can ignore this email.</p>
  <p style="color:#666;font-size:13px">YourApp will never ask you for this code.</p>
</div>

Os estilos inline são intencionais. Muitos clientes de e-mail removem blocos <style>, então CSS inline é a forma mais confiável de formatar e-mails.

Não revele quais e-mails têm conta

Em uma tela de login ou de “envie-me um código”, mostre a mesma resposta esteja o endereço cadastrado ou não: “Se existir uma conta para esse endereço, enviamos um código.” Caso contrário, seu formulário vira uma ferramenta gratuita para descobrir quais endereços de e-mail usam o seu produto.

Checklist

  • Códigos de um gerador criptograficamente seguro; apenas HMACs armazenados, com o segredo fora do banco de dados
  • Expiração em 10 minutos, uso único, 5 tentativas, comparação em tempo constante
  • Rate limits por endereço e por IP, com tempo de espera para reenvio
  • Envio imediato, com um erro claro se o envio falhar
  • Endereço remetente dedicado e autenticado
  • Código no assunto, finalidade e validade no corpo, orientação de “não foi você?”
  • A mesma resposta exista a conta ou não

Envie com a Inbixio

Conecte seu próprio servidor SMTP, crie uma chave de API com permissões restritas e envie de qualquer linguagem com uma única requisição HTTPS.

Criar conta Ver preços