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.
This commit is contained in:
137
docs/superpowers/specs/2026-07-31-mail-versand-design.md
Normal file
137
docs/superpowers/specs/2026-07-31-mail-versand-design.md
Normal file
@@ -0,0 +1,137 @@
|
|||||||
|
# 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.
|
||||||
Reference in New Issue
Block a user