.env already exists locally with valid creds (the deleted repo-root .env was an unused duplicate), and no MailDev infra exists for the e2e mail tests, so the plan shifts to a MailService unit test plus one manual send instead of touching .env or standing up e2e infra.
8.6 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. Ursache (nach genauerer Prüfung — korrigiert gegenüber einer ersten Analyse):
src/mail/mail.service.ts— sowohluserSignUp()als auchforgotPassword()haben ein totesreturn;als erste Anweisung, noch vor dem eigentlichenmailerService.sendMail(...)Aufruf. Der Versand-Code wird nie erreicht. Das ist die alleinige Ursache.
Korrektur: die im Repo-Root gelöschte .env (Commit 1d4546d "styling", korrekt aus dem
Git-Tracking entfernt und zu .gitignore hinzugefügt) ist eine andere, von der App nicht
genutzte Datei. Die tatsächlich verwendete myteamwallet_backend/.env (das Arbeitsverzeichnis
der NestJS-App) existiert bereits lokal, ist bereits in myteamwallet_backend/.gitignore
ausgeschlossen und enthält bereits gültige MAIL_*/DATABASE_* Werte (Strato-SMTP,
lokale MySQL-Instanz läuft). Es ist keine Aktion an .env nötig.
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 demnestjs-boilerplateScaffold (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 eineen-Locale hat, obwohl das Produkt (myteamwallet.de) deutschsprachig ist. Entscheidung (siehe unten):nestjs-i18nkomplett 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: bleiben unangetastet —
myteamwallet_backend/.envexistiert bereits lokal mit gültigen Strato-Werten (keine Rotation im Rahmen dieser Änderung). - Testing-Infrastruktur: kein lokaler MailDev-Container/e2e-Aufbau in diesem Zug (siehe Testing-Abschnitt) — Unit-Test + einmalige manuelle Verifikation mit echtem Versand stattdessen.
- Sprachen: nur Deutsch. Keine mehrsprachige i18n-Infrastruktur für Mails.
- i18n-Mechanismus: kein Sprachdatei-System — deutsche Texte direkt im Code/Template.
nestjs-i18nwird komplett entfernt (Modul-Registrierung,src/i18n/, Dependency,I18n-Nutzung inmail.service.ts), da es im Backend ausschließlich für die zwei Mails verwendet wurde. - Branding: Grün
#2e7d32(ausmyteamwallet_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
firstNamean beiden Aufrufstellen imAuthServicebereits verfügbar ist.
Architektur / Komponenten
1. Bugfix mail.service.ts
- Die zwei toten
return;Statements entfernen (vorsendMailinuserSignUp()undforgotPassword()). I18n/I18nRequestScopeServiceConstructor-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 }),Twird pro Aufruf umfirstName?: stringerweitert:MailData<{ hash: string; firstName?: string }>.
2. Aufrufstellen auth.service.ts
register():dto.firstNamezusätzlich inmailData.datadurchreichen.forgotPassword():user.firstNamezusätzlich inmailData.datadurchreichen.- 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-i18nDependency auspackage.jsonentfernen (npm uninstall, damit Lockfile konsistent bleibt).app.config.tsFelderfallbackLanguage/headerLanguage(APP_FALLBACK_LANGUAGE,APP_HEADER_LANGUAGE) werden mit entfernt: geprüft, sie werden ausschließlich inapp.module.tsfürI18nModulegelesen (kein anderer Konsument im Code) — würden sonst toten Config-Code hinterlassen.
4. Templates
- Neues Handlebars-Partial
src/mail/mail-templates/partials/layout-header.hbsundlayout-footer.hbs(oder ein kombinierteslayout.hbs, falls das mit demHandlebarsAdaptersauberer 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.tsan Handlebars übergibt:app_name("TeamWallet"),firstName,url, ggf.yearfürs Footer-Copyright — Rest (Texte, Button-Label, Titel) fest im Template.
5. .env
Keine Aktion nötig — myteamwallet_backend/.env existiert bereits lokal mit gültigen Werten
(siehe Kontext oben).
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
Korrektur gegenüber einer ersten Analyse: der bestehende e2e-Test
test/user/auth.e2e-spec.ts ("Confirm email") pollt eine MailDev-HTTP-Inbox-API
(MAIL_HOST:MAIL_CLIENT_PORT). Dafür existiert im Repo keine Infrastruktur (kein
docker-compose.yml, kein MailDev-Container), und die echte .env zeigt auf produktives
Strato-SMTP statt auf eine lokale Test-Mailbox — dieser Test kann unabhängig vom Bugfix nicht
grün werden, ohne separate Test-Infrastruktur aufzusetzen. Entscheidung: kein MailDev-Aufbau in
diesem Zug (zu viel Zusatzaufwand für den aktuellen Scope).
Stattdessen:
- Neuer Jest Unit-Test für
MailService(gemockterMailerService/ConfigService), der verifiziert, dassuserSignUp()undforgotPassword()sendMail(...)mit den erwarteten Parametern (to,subject,template,context) aufrufen. Dieser Test hätte denreturn;-Bug erkannt und ist die primäre automatisierte Regression-Absicherung. - Manuelle Verifikation: Backend lokal starten (bestehende
.env, laufende lokale MySQL-Instanz vorhanden), einmal Registrierung und einmal Passwort-vergessen über die echten Endpunkte auslösen und den tatsächlichen Mailversand über Strato-SMTP mit einer echten Test-Mailadresse prüfen. - Der bestehende e2e-Test bleibt unverändert im Repo (nicht Teil dieses Scopes, weiterhin ohne lokale MailDev-Instanz nicht lauffähig — vorbestehende Einschränkung, nicht durch diese Änderung verursacht). Kein neuer e2e-Test für forgot-password (gleiche Infrastruktur-Einschränkung).
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/:hashbzw./password-change/:hashlinken. - MJML- oder Build-Pipeline-Einführung für Templates.