Email OTP e di verifica: velocità, sicurezza e best practice

Come generare, conservare e inviare via email codici OTP e link di verifica in modo sicuro, con recapito rapido, scadenze e limiti sensati e un template affidabile.

L’email con un codice monouso è il messaggio più impaziente che il tuo prodotto invierà mai. L’utente fissa una schermata che dice “Inserisci il codice di 6 cifre che ti abbiamo inviato”, e ogni secondo di ritardo dell’email è un secondo in cui si chiede se lasciar perdere.

Le email di verifica si trovano anche esattamente sul tuo confine di sicurezza. Se le sbagli, o chiudi fuori utenti reali o lasci entrare gli attaccanti. Questa guida copre entrambi gli aspetti: far arrivare rapidamente il codice in inbox e assicurarsi che solo la persona giusta possa usarlo.

Le forme più comuni sono due:

  • Codici monouso (OTP): un numero breve come 482913, da digitare nella tua app. Ideali per l’accesso e la verifica in due passaggi, e funzionano anche quando l’utente legge l’email su un altro dispositivo.
  • Link di verifica: un token casuale lungo dentro un URL. Ideali per confermare un indirizzo email alla registrazione o per reimpostare una password, perché un clic completa il flusso.

Tutto ciò che segue vale per entrambi. La differenza è soprattutto la lunghezza: i link possono contenere un token lungo, i codici devono essere abbastanza brevi da digitare.

Genera i codici in modo sicuro

Usa il generatore casuale crittograficamente sicuro del tuo linguaggio. Math.random() e il modulo random di Python sono prevedibili e non vanno mai usati per i codici di sicurezza.

// 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()

Conserva solo un hash con chiave (HMAC), insieme all’ID utente, a una scadenza e a un contatore dei tentativi. Un semplice SHA-256 non basta: con solo un milione di codici possibili, chiunque abbia il database potrebbe provarli tutti in meno di un secondo. L’HMAC richiede un segreto conservato fuori dal database, quindi una fuga del solo database non rivela i codici validi.

Scadenza breve, tentativi limitati

Un codice di sei cifre ha un milione di combinazioni. Sono più che sufficienti solo se gli attaccanti non possono continuare a tentare:

Controllo Valore consigliato
Durata del codice 10 minuti per i codici di accesso; fino a 24 ore per i link di conferma dell’email
Tentativi errati per codice 5, poi il codice non è più valido
Codici inviati per indirizzo Circa 3 ogni 15 minuti, con un’attesa visibile su “Invia di nuovo”
Codici inviati per indirizzo IP Un limite più alto, per impedire a un attaccante di passare in rassegna molti indirizzi
Utilizzo Monouso: elimina il codice o segnalo come usato non appena va a buon fine

Confronta sempre i codici in tempo costante (crypto.timingSafeEqual in Node.js, hmac.compare_digest in Python), e l’emissione di un nuovo codice deve invalidare il precedente.

Invialo subito

Le email OTP devono partire nella stessa richiesta che le ha generate, non in un job batch eseguito ogni minuto. Con Inbixio basta una chiamata:

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),
});

Inbixio accetta la richiesta e mette subito in coda il recapito, così il tuo endpoint può rispondere rapidamente. Se la chiamata non riesce, dillo subito all’utente (“Non siamo riusciti a inviare il codice. Riprova tra un momento”) invece di lasciarlo in attesa di un’email che non arriverà mai. Gestire gli errori dell’API email elenca quali errori vale la pena ritentare automaticamente.

Due consigli di recapito specifici per i codici:

  • Usa un mittente dedicato, come security@yourco.com, collegato come server a sé, con una chiave API associata a quel server (riassocia la chiave se un giorno rimuovi quel server). Gli utenti imparano a fidarsi di quell’indirizzo e le campagne di marketing non possono intaccarne la reputazione.
  • Autentica il tuo dominio. I codici che finiscono nello spam sono codici che non arrivano mai. Segui la checklist SPF, DKIM e DMARC.

Un’email che non sembri phishing

Il tuo template compete con email di phishing che copiano esattamente questo formato. Rendi quella autentica facile da riconoscere:

  • Metti il codice nell’oggetto, ad esempio “482913 è il tuo codice di accesso a YourApp”. Gli utenti possono leggerlo dalla notifica senza aprire l’email, e le tastiere degli smartphone possono suggerirlo.
  • Spiega a cosa serve e quanto dura: “Usa questo codice per accedere a YourApp. Scade tra 10 minuti.”
  • Spiega cosa fare se non sono stati loro: “Se non hai provato ad accedere, puoi ignorare questa email. Il tuo account è al sicuro.”
  • Non chiedere mai che il codice ti venga comunicato. Dillo chiaramente: “YourApp non ti chiederà mai questo codice per telefono o via email.”
  • Mantienila breve, senza marketing, e riduci i link al minimo indispensabile.

Un template HTML semplice e accessibile:

<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>

Gli stili inline sono una scelta voluta. Molti client email eliminano i blocchi <style>, quindi il CSS inline è il modo più affidabile per formattare un’email.

Non rivelare quali email hanno un account

In una schermata di accesso o di “inviami un codice”, mostra la stessa risposta che l’indirizzo sia registrato o meno: “Se esiste un account per questo indirizzo, ti abbiamo inviato un codice.” Altrimenti il tuo modulo diventa uno strumento gratuito per scoprire quali indirizzi email usano il tuo prodotto.

Checklist

  • Codici da un generatore crittograficamente sicuro; conservati solo gli HMAC, con il segreto fuori dal database
  • Scadenza di 10 minuti, monouso, 5 tentativi, confronto in tempo costante
  • Limiti per indirizzo e per IP, con un’attesa prima del nuovo invio
  • Invio immediato, con un errore chiaro se l’invio non riesce
  • Indirizzo mittente dedicato e autenticato
  • Codice nell’oggetto, scopo e scadenza nel corpo, indicazioni su cosa fare “se non sei stato tu”
  • Stessa risposta che l’account esista o meno

Invialo con Inbixio

Collega il tuo server SMTP, crea una chiave API con permessi mirati e invia da qualsiasi linguaggio con una sola richiesta HTTPS.

Crea un account Vedi i prezzi