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.
Códigos ou links?
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.