Devlog · Phase 0 · 4/7
E-Mail-Bestätigung für die Warteliste von aubia.dev
aubia.dev erfasst Anmeldungen vor der E-Mail-Bestätigung. Wie das Formular Tippfehler erkennt, Missbrauch begrenzt und was mit unbestätigten Adressen geschieht.
- waitlist
- react
- security
- gdpr

Sie können die E-Mail-Adresse einer anderen Person in ein Formular eingeben. Eine erfolgreiche Validierung dieser Adresse verrät weder, wer sie eingegeben hat, noch, ob der Empfänger eine Einladung erhalten möchte.
Auf aubia.dev bitte ich deshalb um eine Bestätigung per E-Mail. Das Formular legt auf aubia.cloud eine ausstehende Anmeldung an. Über einen per E-Mail zugeschickten Link kann der Empfänger sie anschließend bestätigen. Dieses Double-Opt-in fügt dem Ablauf einen Schritt hinzu, und die Nachricht wird möglicherweise nie geöffnet.
Die Website leitet die Anfrage weiter, ohne Anmeldungen in einer lokalen Datenbank zu speichern. Der Artikel zum Betrieb ohne Datenbank erklärt diese Trennung zwischen Website und Cloud.
Tippfehler vor dem Absenden erkennen
Eine falsch eingegebene Adresse kann gültig sein und trotzdem nie die vorgesehene Nachricht erhalten. Das Formular schlägt eine Korrektur vor, wenn die Domain der eines bekannten E-Mail-Dienstes ähnelt: Aus gmial.com könnte gmail.com werden.
Der Vergleich nutzt die Levenshtein-Distanz. Sie zählt die Änderungen, die nötig sind, um eine Zeichenfolge in eine andere umzuwandeln. Anhand begrenzter Listen wird zunächst die Domain geprüft und dann ihre Endung, falls keine Korrektur für die Domain gefunden wurde. Das Postfach wird nicht abgefragt, der Teil vor dem @ wird nicht korrigiert.
Der Vorschlag erscheint, wenn Sie das Feld verlassen. Er wird auch beim Absenden neu berechnet, weil das Absenden über die Tastatur den Fokus nicht unbedingt aus dem Feld verschiebt. Bei einem wahrscheinlichen Tippfehler wird der erste Sendeversuch angehalten, damit Sie die Adresse überprüfen können.
Sie können die Korrektur übernehmen oder Ihre Eingabe beibehalten. Beide Schaltflächen setzen den Fokus zurück ins Feld, ohne das Formular abzusenden. Sie können dieselbe Adresse auch ein zweites Mal absenden, ohne eine der beiden Optionen gewählt zu haben: Die Ähnlichkeit mit einer bekannten Domain reicht nicht aus, um eine Adresse zu sperren, von der Sie wissen, dass sie richtig ist.
Das Label ist mit dem Feld verknüpft, Fehler verwenden aria-invalid und role="alert", und aria-describedby verbindet den Vorschlag mit dem Eingabefeld. Die Korrektur muss auch lesbar sein, ohne einen Farbwechsel wahrnehmen zu müssen.
Sofortige Rückmeldung, noch ohne Serverantwort
Die React-Komponente übermittelt das Formular mit Inertia, ohne die gesamte Seite neu zu laden. Sobald das Absenden beginnt, löst sie eine globale Benachrichtigung aus, die mit useOptimistic verwaltet wird. Die visuelle Rückmeldung erscheint vor der Antwort des Servers.
Diese Anzeige nimmt einen Erfolg vorweg. Vor der Serverantwort weiß der Browser noch nicht, ob die Cloud die Anfrage erfasst hat oder ob eine E-Mail zugestellt werden kann. Die tatsächliche Antwort ersetzt die Anzeige durch die passende Meldung oder nimmt den vorläufigen Zustand bei einem Fehler zurück.
Die Cloud unterscheidet vier Ergebnisse für eine akzeptierte Anfrage:
| Ergebnis | Verarbeitung der Anfrage |
|---|---|
confirmation_sent |
Eine ausstehende Anmeldung wird angelegt und der Versand der Bestätigungs-E-Mail in die Warteschlange gestellt. |
confirmation_resent |
Nach Ablauf der Wartefrist für den erneuten Versand wird ein neuer Link für eine ausstehende Anmeldung vorbereitet. |
already_pending |
Es liegt bereits eine aktuelle Anfrage vor; der Versand einer weiteren E-Mail wird nicht ausgelöst. |
already_confirmed |
Die Adresse ist bereits bestätigt; die Anmeldung muss nicht wiederholt werden. |
Zu jedem Ergebnis gibt es eine Meldung in den sechs Sprachen der Website. Ich teile einem Empfänger lieber mit, dass seine Adresse bereits bestätigt ist, als ihn auf eine E-Mail warten zu lassen, die nicht verschickt wird.
Der Proxy prüft auch den HTTP-Statuscode. Validierungs-, Ratenbegrenzungs- und Dienstfehler haben Vorrang vor einem möglichen Erfolgsstatus im Antwortkörper. HTTP 409 wird gezielt in Verbindung mit already_confirmed als bereits bestätigte Anmeldung erkannt. Andere Konflikte werden nicht als Erfolge behandelt.
Eine abgelehnte Adresse und ein überschrittenes Anfragelimit haben jeweils eine eigene Meldung. Bei einer abgelehnten Signatur oder einem nicht verfügbaren Cloud-Dienst erscheint eine Meldung zur vorübergehenden Nichtverfügbarkeit; die technischen Details dienen der Diagnose auf dem Server.
Selbst confirmation_sent belegt keine Zustellung. Ein Job in der Warteschlange übernimmt den E-Mail-Versand: Der Server hat seine Ausführung angefordert, aber der Versand kann noch scheitern oder der E-Mail-Dienst des Empfängers kann die Nachricht herausfiltern.
Missbrauch ohne Captcha begrenzen
Ich ergänze das Formular nicht um ein Captcha. Es würde den Besuchern eine weitere Prüfung abverlangen, ohne automatisierte Anfragen unmöglich zu machen. Die Website kombiniert Prüfungen, die keine zusätzliche Interaktion erfordern.
Ein Honeypot, ein unsichtbares und von der Tastaturnavigation ausgeschlossenes Feld, muss leer bleiben. Er fängt Anfragen von Bots ab, die Felder wahllos ausfüllen. Eine Zeitfalle vergleicht einen vom Browser übermittelten Zeitstempel mit dem Absendezeitpunkt und weist zu schnelle Anfragen zurück.
Der Server prüft diese Werte, sie stammen aber vom Client. Ein Programm kann den Honeypot leer lassen und einen akzeptablen Zeitstempel liefern. Diese Fallen filtern einfache Verhaltensmuster; sie beweisen nicht, dass ein Mensch das Formular ausgefüllt hat.
Ein Rate Limiter begrenzt parallel die Anfragen pro IP-Adresse, pro E-Mail-Adresse und insgesamt. Das Limit pro E-Mail-Adresse reduziert wiederholte Anfragen an denselben Empfänger. Die globale Obergrenze begrenzt die Anfragen auch dann, wenn sie von mehreren IPs stammen. Dafür kann sie bei einem starken Besucheranstieg auch legitime Anmeldungen zurückweisen.
Die Adressvalidierung prüft das Format und in der Produktionsumgebung auch das DNS der Domain. DNS kann dabei helfen, eine Domain auszuschließen, die keine E-Mails empfangen kann. Ob das konkrete Postfach existiert, lässt sich daraus nicht ableiten.
Sobald die Website eine Anfrage akzeptiert, signiert sie diese mit HMAC und leitet sie an die Cloud weiter. Beide Server teilen ein Geheimnis, mit dem sich die Signatur des empfangenen Anfragekörpers prüfen lässt. Dieser Schutz gilt für die Anmeldeanfrage zwischen den Servern: Er verhindert, dass ein unsignierter Aufruf dieses Endpoints den Proxy ersetzt. Nicht alle API-Routen verlangen diese Signatur, insbesondere nicht diejenigen, die Bestätigungs- oder Abmeldetokens verwenden.
Der Link in der E-Mail
Die Cloud speichert die Adresse mit dem Status PENDING, bevor sie die E-Mail vorbereitet. Der Link enthält einen zufälligen Token, von dem nur der Hash im Anmeldedatensatz gespeichert wird. Er ist ab dem von der Anwendung erfassten Versandzeitpunkt 48 Stunden gültig. Dieser Zeitpunkt wird vor der Ausführung des E-Mail-Jobs erfasst.
Die Bestätigungsschaltfläche öffnet eine Seite auf aubia.dev in der Sprache der Anmeldung. Nach dem Laden liest die Seite den Token aus der URL und fragt direkt die Cloud-API ab. Sie zeigt das Ergebnis an: bestätigt, bereits bestätigt, ungültiger Link, nicht gefunden oder abgelaufen.
Die Bestätigung aktualisiert den Status und seinen Zeitstempel nur einmal. Zwei gleichzeitige Anfragen dürfen keine zwei Bestätigungen auslösen. Ein weiterer Klick auf denselben erkannten Link liefert "bereits bestätigt", auch nach Ablauf der ursprünglichen Gültigkeitsdauer, weil die Anmeldung bereits abgeschlossen ist.
Bei einer noch ausstehenden Anmeldung ersetzt ein erneuter Versand den Token-Hash und beginnt eine neue Gültigkeitsdauer. Über den vorherigen Link lässt sich die Anmeldung dann nicht mehr finden. Der erneute Versand macht den alten Link bereits bei der Vorbereitung ungültig, selbst wenn die neue E-Mail noch nicht angekommen ist.
Die Bestätigungs- und Abmeldeseiten deklarieren noindex. Diese Anweisung bittet Suchmaschinen, die Seiten nicht zu indexieren; sie beschränkt den Zugriff nicht. Die Links der Sprachauswahl werden ohne Query-String neu erstellt, damit der Token nicht in fünf weitere URLs kopiert wird. Ein Sprachwechsel setzt die Bestätigung mit diesem Token daher nicht fort.
Ein Programm, das dem Link folgen und diesen Ablauf ausführen kann, kann ebenfalls bestätigen. Double-Opt-in prüft die Verwendung eines Links, der an die angegebene Adresse geschickt wurde. Es bescheinigt weder die Identität noch die menschliche Anwesenheit dessen, der ihn verwendet. Der Bestätigungszeitstempel hilft, die Anmeldung nachzuvollziehen; allein belegt er nicht, dass die gesamte Verarbeitung DSGVO-konform ist.
Adressen, die unbestätigt bleiben
Der Ablauf des Tokens löscht den Datensatz nicht. Eine tägliche Aufgabe löscht Anmeldungen, die noch den Status PENDING haben und deren ursprüngliches Anmeldedatum mehr als 30 Tage zurückliegt. Ein erneuter Versand der Bestätigung setzt diese Aufbewahrungsfrist nicht zurück.
Die Abmeldung folgt einem anderen Ablauf. Die dafür vorgesehene Seite übermittelt den Token an die Cloud, die den Datensatz anonymisiert: Die E-Mail-Adresse wird ersetzt, Tokens und Attributionsdaten werden gelöscht, und der Status wechselt zu PURGED. Die Zeile selbst bleibt mit ihren Zeitstempeln erhalten.
Die Bestätigungsschaltfläche im E-Mail-Text ist von den Headern List-Unsubscribe und List-Unsubscribe-Post getrennt. Diese Header geben E-Mail-Programmen Anweisungen, um eine Abmeldeoption anzubieten. Dass diese Header vorhanden sind, garantiert nicht, dass jedes Programm diese Option anzeigt oder gleich verarbeitet.
Was der Zähler misst
Der Zähler auf der Startseite fragt bei der Cloud die Anzahl der Anmeldungen mit dem Status CONFIRMED ab. Ausstehende Adressen sind ausgeschlossen. Administrative Einladungen, die direkt als bestätigt erfasst wurden, werden ebenfalls gezählt. Die Summe entspricht daher nicht ausschließlich den Klicks auf Links aus dem öffentlichen Anmeldeformular.
Der Wert wird nach dem ersten Rendern über eine verzögerte Anfrage geladen und serverseitig gecacht. Er kann einer Bestätigung hinterherhinken und wird erst oberhalb eines Schwellenwerts angezeigt. Der Artikel zu Octane und Inertia SSR erklärt den Ladevorgang.
Um zu messen, wie viele Formularanfragen zu einer Bestätigung führen, muss ich sie von administrativen Einladungen unterscheiden.
Dieses Projekt wird hier erzählt, Artikel für Artikel. Was als Nächstes kommt, hängt davon ab, was Sie dazu sagen.
Auf die Warteliste