Journal de bord · Phase 0 · 4/7
La confirmation par email sur la liste d'attente d'aubia.dev
Le formulaire d'aubia.dev enregistre une demande avant la confirmation par email. Corrections de saisie, protection contre les abus et devenir des adresses restées en attente.
- waitlist
- react
- security
- gdpr

Vous pouvez saisir l'adresse email de quelqu'un d'autre dans un formulaire. Une validation correcte de cette adresse ne permet pas de savoir qui l'a envoyée, ni si son destinataire souhaite recevoir une invitation.
Sur aubia.dev, je demande donc une confirmation par email. Le formulaire crée une inscription en attente sur aubia.cloud, puis un lien reçu par email permet de la confirmer. Ce double opt-in ajoute une étape au parcours, avec la possibilité que le message ne soit jamais ouvert.
La vitrine transmet la demande sans conserver les inscriptions dans une base locale. L'article sur l'absence de base de données détaille cette séparation entre le site et le cloud.
Rattraper une faute avant l'envoi
Une adresse mal saisie peut être valide et ne jamais recevoir le message attendu. Le formulaire propose une correction quand son domaine ressemble à celui d'un service de messagerie connu : gmial.com peut devenir gmail.com.
Cette comparaison utilise la distance de Levenshtein, qui compte les modifications nécessaires pour passer d'une chaîne de caractères à une autre. Elle examine le domaine, puis son suffixe si aucune correction de domaine n'a été trouvée, à partir de listes limitées. Elle ne consulte pas la boîte aux lettres et ne corrige pas la partie qui précède le @.
La suggestion apparaît à la sortie du champ. Elle est aussi recalculée à la soumission, car un envoi au clavier peut se faire sans quitter le champ. Si une faute probable est détectée, le premier envoi est retenu pour laisser le temps de relire l'adresse.
Vous pouvez appliquer la correction ou conserver votre saisie. Ces boutons rendent le focus au champ sans envoyer le formulaire. Une seconde soumission de la même adresse est possible même sans avoir choisi l'un des deux : une ressemblance avec un domaine connu ne suffit pas à interdire une adresse que vous savez correcte.
Le label est associé au champ, les erreurs utilisent aria-invalid et role="alert", et la suggestion est reliée à la saisie par aria-describedby. Le message de correction doit aussi pouvoir être lu sans repérer un changement de couleur.
Un retour immédiat, encore provisoire
Le composant React transmet le formulaire avec Inertia, sans recharger toute la page. Dès que l'envoi commence, il déclenche une notification globale gérée avec useOptimistic. Le retour visuel apparaît avant la réponse du serveur.
Cet affichage anticipe un succès. Avant la réponse du serveur, le navigateur ne sait encore ni si le cloud a enregistré la demande, ni si un email pourra être livré. La réponse réelle le remplace par le message adapté, ou annule cet état provisoire en cas d'erreur.
Le cloud distingue quatre résultats pour une demande acceptée :
| Résultat | Traitement de la demande |
|---|---|
confirmation_sent |
Une inscription en attente est créée et l'envoi de la confirmation est placé en queue. |
confirmation_resent |
Un nouveau lien est préparé pour une inscription en attente, après le délai autorisant un renvoi. |
already_pending |
Une demande récente existe déjà ; aucun nouvel email n'est déclenché. |
already_confirmed |
L'adresse est déjà confirmée ; il n'y a pas d'inscription à recommencer. |
Chaque résultat correspond à un message traduit dans les six langues du site. Je préfère indiquer qu'une adresse est déjà confirmée plutôt que laisser son destinataire attendre un email qui ne partira pas.
Le proxy examine aussi le code HTTP. Les erreurs de validation, de limitation et de service restent prioritaires sur un éventuel statut de succès dans le corps de la réponse. Le cas HTTP 409 accompagné précisément de already_confirmed est reconnu comme une inscription déjà confirmée. Un autre conflit n'est pas transformé en succès.
Une adresse refusée et une limite de requêtes dépassée ont chacune leur message. Une signature rejetée ou un service cloud indisponible donnent un message d'indisponibilité temporaire ; les détails techniques servent au diagnostic côté serveur.
Même le résultat confirmation_sent ne prouve pas la livraison. L'envoi passe par un job en queue : le serveur a demandé son exécution, mais le message peut encore échouer à l'envoi ou être filtré par la messagerie du destinataire.
Limiter les abus sans captcha
Je n'ajoute pas de captcha au formulaire. Il imposerait une vérification supplémentaire au visiteur, sans rendre les soumissions automatisées impossibles. Le site combine des contrôles qui n'exigent aucune interaction de plus.
Un honeypot, champ caché à l'affichage et exclu de la navigation au clavier, doit rester vide. Il permet de rejeter les soumissions d'automates qui remplissent les champs sans distinction. Un piège temporel compare l'horodatage transmis par le navigateur au moment de l'envoi et refuse les soumissions trop rapides.
Le serveur vérifie ces valeurs, mais elles proviennent du client. Un programme peut laisser le honeypot vide et fournir un horodatage acceptable. Ces pièges filtrent des comportements simples ; ils ne prouvent pas qu'un humain a rempli le formulaire.
Un rate limiter agit en parallèle par adresse IP, par adresse email et sur le volume global. La limite par email réduit les sollicitations répétées d'un même destinataire. Le plafond global borne les demandes même lorsqu'elles proviennent de plusieurs IP, au risque de refuser aussi des inscriptions légitimes pendant une forte affluence.
La validation de l'adresse vérifie son format et, en production, le DNS du domaine. Le DNS peut aider à écarter un domaine impropre à recevoir du courrier, mais il ne renseigne pas sur l'existence de la boîte précise.
La demande acceptée par la vitrine est ensuite signée avec HMAC avant sa transmission au cloud. Les deux serveurs partagent un secret qui permet de vérifier la signature du corps reçu. Cette protection concerne le segment interserveurs de l'inscription : elle empêche de remplacer le proxy par un appel non signé à cet endpoint. Les autres routes de l'API ne sont pas toutes soumises à cette signature, en particulier celles qui utilisent les tokens de confirmation ou de désinscription.
Le lien reçu par email
Le cloud enregistre l'adresse avec le statut PENDING avant de préparer l'email. Le lien contient un token aléatoire dont seule l'empreinte est conservée dans la ligne d'inscription. Sa validité est de 48 heures à partir de la date d'envoi enregistrée par l'application, avant l'exécution du job de messagerie.
Le bouton de confirmation ouvre une page d'aubia.dev dans la langue de l'inscription. Après chargement, cette page lit le token dans l'URL et interroge directement l'API cloud. Elle affiche le résultat : confirmé, déjà confirmé, lien invalide, introuvable ou expiré.
La confirmation met à jour le statut et sa date une seule fois. Deux requêtes concurrentes ne doivent pas déclencher deux confirmations. Un nouveau clic sur le même lien reconnu renvoie « déjà confirmé », y compris après le délai initial, puisque l'inscription a déjà abouti.
Pour une adresse encore en attente, un renvoi remplace l'empreinte du token et redémarre sa durée de validité. Le lien précédent ne retrouve donc plus l'inscription. Le renvoi invalide le lien précédent dès sa préparation, même si le nouvel email n'est pas encore arrivé.
Les pages de confirmation et de désinscription déclarent noindex. Cette directive demande aux moteurs de ne pas les indexer ; elle n'en protège pas l'accès. Les liens du sélecteur de langue sont reconstruits sans la chaîne de requête, pour ne pas recopier le token dans les cinq autres URL. Changer de langue ne poursuit donc pas la confirmation avec ce token.
Un programme capable de suivre le lien et d'exécuter ce parcours peut aussi confirmer. Le double opt-in vérifie l'utilisation d'un lien envoyé à l'adresse fournie, sans certifier l'identité ni la présence humaine de la personne qui l'utilise. L'horodatage de confirmation contribue au suivi de l'inscription ; il ne suffit pas, à lui seul, à prouver toute la conformité RGPD du traitement.
Les adresses qui restent en attente
L'expiration du token ne supprime pas la ligne. Une tâche quotidienne supprime les inscriptions encore PENDING dont la date d'inscription initiale remonte à plus de 30 jours. Un renvoi de confirmation ne remet pas ce délai de conservation à zéro.
La désinscription suit un autre traitement. La page dédiée transmet le token au cloud, qui anonymise la ligne : l'adresse est remplacée, les tokens et les données d'attribution sont effacés, le statut devient PURGED. La ligne elle-même subsiste, avec ses dates.
Le bouton de confirmation dans le corps de l'email est distinct des en-têtes List-Unsubscribe et List-Unsubscribe-Post. Ces en-têtes fournissent des instructions aux logiciels de messagerie pour proposer la désinscription. Leur présence ne garantit pas que chaque logiciel affichera cette commande ou la traitera de la même façon.
Ce que le compteur mesure
Le compteur de la page d'accueil demande au cloud le nombre d'inscriptions au statut CONFIRMED. Les adresses en attente en sont exclues. Les invitations administratives enregistrées directement comme confirmées sont aussi comptées : le total ne correspond donc pas exclusivement à des clics sur le lien du formulaire public.
Sa valeur arrive après le rendu initial, par une requête différée, avec un cache côté serveur. Elle peut avoir du retard sur une confirmation et n'est affichée qu'au-delà d'un seuil. Le mécanisme de chargement est détaillé dans l'article sur Octane et Inertia SSR.
Pour mesurer combien de demandes du formulaire aboutissent à une confirmation, il faut les distinguer des invitations administratives.
Ce chantier se raconte ici, article après article. Ce qui vient ensuite dépend de ce que vous en direz.
Rejoindre la liste d'attente