Files
teamwallet/docs/superpowers/specs/2026-07-31-mail-versand-design.md
Bastian Wagner 539d073de4 docs: add design spec for mail sending fix and template redesign
Captures the brainstorming outcome for fixing the broken mail dispatch,
removing the mail-only nestjs-i18n setup in favor of hardcoded German
copy, and redesigning the registration/forgot-password email templates.
2026-07-31 22:04:16 +02:00

7.5 KiB

Mailversand reparieren + Templates neu gestalten

Status: approved Datum: 2026-07-31

Kontext

Backend: NestJS (myteamwallet_backend), basierend auf nestjs-boilerplate. Mailversand über @nestjs-modules/mailer (Nodemailer) mit Handlebars-Templates. E-Mails werden für zwei Flows verschickt: Registrierung (Bestätigungsmail) und Passwort vergessen.

Der Mailversand funktioniert aktuell nicht, aus zwei unabhängigen Gründen:

  1. src/mail/mail.service.ts — sowohl userSignUp() als auch forgotPassword() haben ein totes return; als erste Anweisung, noch vor dem eigentlichen mailerService.sendMail(...) Aufruf. Der Versand-Code wird nie erreicht.
  2. .env existiert lokal nicht mehr im Arbeitsverzeichnis (im Commit 1d4546d "styling" wurde die Datei korrekt aus dem Git-Tracking entfernt und zu .gitignore hinzugefügt — das war bereits vor diesem Spec erledigt). Ohne .env sind alle MAIL_*/DATABASE_*/etc. Umgebungsvariablen undefined, die App kann so nicht sinnvoll laufen.

Zusätzlich bestehende Baustellen, die in diesem Zug mit erledigt werden:

  • E-Mail-Templates (activation.hbs, reset-password.hbs) sind unverändertes, unstyled Boilerplate aus dem nestjs-boilerplate Scaffold (graue Kopfzeile, generischer Button, kein Branding).
  • Die E-Mail-Texte laufen aktuell über nestjs-i18n, das ausschließlich für diese zwei Mails genutzt wird und nur eine en-Locale hat, obwohl das Produkt (myteamwallet.de) deutschsprachig ist. Entscheidung (siehe unten): nestjs-i18n komplett entfernen, Texte direkt auf Deutsch im Code/Template.

Das Frontend (myteamwallet_frontend_modern, das aktuell aktiv gebaute/deployte Frontend laut Dockerfile) hat kein fertiges Bild-Logo — nur generische Angular-Default-Icons. Als Markenfarbe dient das dort verwendete Grün #2e7d32 (Material theme-color), Schriftart Roboto, Wordmark-Schreibweise „TeamWallet".

Entscheidungen aus dem Brainstorming

  • SMTP-Zugangsdaten: das alte, aus der Git-Historie wiederhergestellte Strato-Passwort wird vorerst weiterverwendet (keine sofortige Rotation im Rahmen dieser Änderung).
  • Sprachen: nur Deutsch. Keine mehrsprachige i18n-Infrastruktur für Mails.
  • i18n-Mechanismus: kein Sprachdatei-System — deutsche Texte direkt im Code/Template. nestjs-i18n wird komplett entfernt (Modul-Registrierung, src/i18n/, Dependency, I18n-Nutzung in mail.service.ts), da es im Backend ausschließlich für die zwei Mails verwendet wurde.
  • Branding: Grün #2e7d32 (aus myteamwallet_frontend_modern), Textwordmark „TeamWallet" (kein Bild-Logo), Roboto mit System-Font-Fallback, abgerundete Card-Optik passend zum „fintech-lite" Look der App.
  • Personalisierung: Anrede mit Vornamen („Hallo Max,"), da firstName an beiden Aufrufstellen im AuthService bereits verfügbar ist.

Architektur / Komponenten

1. Bugfix mail.service.ts

  • Die zwei toten return; Statements entfernen (vor sendMail in userSignUp() und forgotPassword()).
  • I18n/I18nRequestScopeService Constructor-Injection entfernen.
  • Betreffzeilen als deutsche String-Literale direkt im Service ('E-Mail bestätigen', 'Passwort zurücksetzen').
  • MailData<T> Interface (src/mail/interfaces/mail-data.interface.ts) bleibt strukturell gleich ({ to: string; data: T }), T wird pro Aufruf um firstName?: string erweitert: MailData<{ hash: string; firstName?: string }>.

2. Aufrufstellen auth.service.ts

  • register(): dto.firstName zusätzlich in mailData.data durchreichen.
  • forgotPassword(): user.firstName zusätzlich in mailData.data durchreichen.
  • Keine Änderung an Kontrollfluss, Fehlerbehandlung oder DB-Zugriffen — nur die zusätzliche Datenübergabe.

3. i18n-Entfernung

  • app.module.ts: I18nModule.forRootAsync(...) Import und Registrierung entfernen (HeaderResolver-Import ebenfalls, falls sonst ungenutzt).
  • src/i18n/ Ordner komplett löschen (nur von Mails genutzt, siehe Analyse).
  • nestjs-i18n Dependency aus package.json entfernen (npm uninstall, damit Lockfile konsistent bleibt).
  • app.config.ts Felder fallbackLanguage/headerLanguage (APP_FALLBACK_LANGUAGE, APP_HEADER_LANGUAGE) werden mit entfernt: geprüft, sie werden ausschließlich in app.module.ts für I18nModule gelesen (kein anderer Konsument im Code) — würden sonst toten Config-Code hinterlassen.

4. Templates

  • Neues Handlebars-Partial src/mail/mail-templates/partials/layout-header.hbs und layout-footer.hbs (oder ein kombiniertes layout.hbs, falls das mit dem HandlebarsAdapter sauberer registrierbar ist) — gemeinsamer Rahmen: Header mit „TeamWallet"-Wordmark auf grünem/hellem Grund, Footer mit Kontakt-/Legal-Hinweis (z. B. „Diese E-Mail wurde automatisch von TeamWallet verschickt.").
  • activation.hbs: nutzt das Layout, Inhalt: Begrüßung mit {{firstName}} (Fallback ohne Namen falls nicht vorhanden), kurzer Erklärtext, grüner CTA-Button „E-Mail bestätigen" → {{url}}.
  • reset-password.hbs: nutzt das Layout, Inhalt: Begrüßung, Erklärtext, grüner CTA-Button „Passwort zurücksetzen" → {{url}}, Hinweis auf zeitliche Begrenzung des Links und dass die Mail ignoriert werden kann, falls nicht selbst angefragt.
  • Styling-Konstanten: Akzent #2e7d32, Radius ~12px auf Card-Container, max-width: 600px, Inline-CSS (kein externes Stylesheet — E-Mail-Client-Kompatibilität), tabellenbasiertes Layout wie bisher (kein MJML — unnötige Build-Komplexität für zwei Templates), Roboto mit Fallback-Stack (Roboto, Helvetica, Arial, sans-serif, da Web-Fonts in vielen Mail-Clients nicht geladen werden).
  • Kontext-Variablen, die mail.service.ts an Handlebars übergibt: app_name ("TeamWallet"), firstName, url, ggf. year fürs Footer-Copyright — Rest (Texte, Button-Label, Titel) fest im Template.

5. .env wiederherstellen

  • Lokale, nicht getrackte .env Datei neu anlegen (Datei existiert nicht mehr im Working Tree, ist aber in .gitignore) mit den aus git show 7df5611:.env wiederhergestellten Werten (DB-, Mail-, JWT- und App-Konfiguration wie zuvor).
  • Kein Commit dieser Datei — bleibt lokal/untracked, wie vom User entschieden.

Fehlerbehandlung

  • Kein Verhaltensunterschied zu heute beabsichtigt: mailerService.sendMail(...) wirft bei SMTP-Fehlern eine Exception, die aktuell nicht speziell abgefangen wird (weder vorher noch nachher) — das bleibt so, ist außerhalb des Scopes dieser Änderung. Falls beim Testen ein SMTP- Fehler auftritt (z. B. Strato blockiert alte/rotierte Zugangsdaten), wird das separat besprochen statt stillschweigend Fehlerbehandlung hinzuzufügen.

Testing

  • Bestehenden e2e-Test test/user/auth.e2e-spec.ts (Confirm-Email-Flow über MailDev-artige HTTP-Inbox-API) laufen lassen und verifizieren, dass er jetzt grün ist (war zuvor durch den Bug automatisch rot).
  • Neuer e2e-Test für den Forgot-Password-Mail-Flow, analog zum bestehenden Confirm-Email-Test: Passwort-vergessen anfragen, Mail über MailDev-Inbox abrufen, Hash extrahieren, gegen /api/v1/auth/reset/password verifizieren.
  • Kein Unit-Test-Aufbau für MailService vorgesehen (kein bestehendes Muster im Repo für Mail-Unit-Tests, e2e deckt das Verhalten ausreichend ab) — YAGNI.

Out of Scope

  • Rotation der SMTP-Zugangsdaten (User macht das ggf. später selbst).
  • Mehrsprachigkeit / weitere Locales.
  • Redesign der Frontend-Flows, die auf /confirm-email/:hash bzw. /password-change/:hash linken.
  • MJML- oder Build-Pipeline-Einführung für Templates.