From fb3ed10b412765dfc13bfdfa8c6b4297bf181f35 Mon Sep 17 00:00:00 2001 From: Bastian Wagner Date: Fri, 31 Jul 2026 22:22:27 +0200 Subject: [PATCH] docs: add mail-versand implementation plan --- .../plans/2026-07-31-mail-versand.md | 664 ++++++++++++++++++ 1 file changed, 664 insertions(+) create mode 100644 docs/superpowers/plans/2026-07-31-mail-versand.md diff --git a/docs/superpowers/plans/2026-07-31-mail-versand.md b/docs/superpowers/plans/2026-07-31-mail-versand.md new file mode 100644 index 0000000..92d75e8 --- /dev/null +++ b/docs/superpowers/plans/2026-07-31-mail-versand.md @@ -0,0 +1,664 @@ +# Mailversand reparieren + Templates neu gestalten — Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Mailversand für Registrierung und Passwort-vergessen im NestJS-Backend +(`myteamwallet_backend`) wieder funktionsfähig machen, das mail-only `nestjs-i18n` Setup +entfernen (Texte direkt auf Deutsch), und die zwei Handlebars-Templates in einem zu +"TeamWallet" (Grün `#2e7d32`) passenden, hübschen Design neu bauen. + +**Architecture:** `MailService` (`@nestjs-modules/mailer` + Nodemailer + Handlebars) bleibt die +zentrale Versandstelle. Der Bug ist ein totes `return;` vor `sendMail(...)` in beiden Methoden — +Fix ist ein reiner Code-Fix, keine Config-Änderung nötig (`.env` ist bereits lokal korrekt +befüllt). `nestjs-i18n` wird komplett entfernt, deutsche Texte wandern direkt in die +`.hbs`-Templates bzw. als String-Literale in `mail.service.ts`. Die zwei Templates teilen sich +ein gemeinsames Handlebars-Block-Partial (`partials/layout.hbs`) für Header/Footer, um +Duplikation zu vermeiden. + +**Tech Stack:** NestJS 9, `@nestjs-modules/mailer` 1.8.1 (Nodemailer 6.8.0), Handlebars 4.7.7, +Jest 29 (Unit-Tests), TypeScript 4.8. + +## Global Constraints + +- Nur Deutsch — keine mehrsprachige i18n-Infrastruktur für Mails, keine Sprachdateien. +- `nestjs-i18n` wird vollständig aus dem Backend entfernt (Modul, Dependency, `src/i18n/`). +- Branding: Akzentfarbe `#2e7d32` (Grün), Textwordmark „TeamWallet" (kein Bild-Logo), Roboto mit + Fallback-Stack `Roboto, Helvetica, Arial, sans-serif`, abgerundete Card-Optik (~12px Radius), + `max-width: 600px`, Inline-CSS-safe (die `HandlebarsAdapter` inlined ` + + +
+
+
+ TeamWallet +
+
+ {{> @partial-block }} +
+
+ +
+ + +``` + +- [ ] **Step 4: `activation.hbs` neu gestalten** + +Datei `myteamwallet_backend/src/mail/mail-templates/activation.hbs` komplett ersetzen: + +```handlebars +{{#> layout}} +

Hallo{{#if firstName}} {{firstName}}{{/if}},

+

schön, dass du bei TeamWallet dabei bist! Bestätige deine E-Mail-Adresse, um dein Konto zu aktivieren.

+
+ {{actionTitle}} +
+

Falls der Button nicht funktioniert, kopiere diesen Link in deinen Browser:
{{url}}

+{{/layout}} +``` + +- [ ] **Step 5: `reset-password.hbs` neu gestalten** + +Datei `myteamwallet_backend/src/mail/mail-templates/reset-password.hbs` komplett ersetzen: + +```handlebars +{{#> layout}} +

Hallo{{#if firstName}} {{firstName}}{{/if}},

+

du hast angefragt, dein TeamWallet-Passwort zurückzusetzen. Klicke auf den Button, um ein neues Passwort zu vergeben.

+
+ {{actionTitle}} +
+

Falls du diese Anfrage nicht gestellt hast, kannst du diese E-Mail einfach ignorieren — es wird nichts verändert.

+

Falls der Button nicht funktioniert, kopiere diesen Link in deinen Browser:
{{url}}

+{{/layout}} +``` + +- [ ] **Step 6: Partials-Verzeichnis in `mail-config.service.ts` registrieren** + +In `myteamwallet_backend/src/mail/mail-config.service.ts` den `template.options` Block +erweitern (`partials.dir` zeigt auf den neuen Ordner, damit `HandlebarsAdapter` die `.hbs` +Dateien darin beim Versand automatisch als Partials lädt): + +```typescript + createMailerOptions(): MailerOptions { + return { + transport: { + host: this.configService.get('mail.host'), + port: this.configService.get('mail.port'), + ignoreTLS: this.configService.get('mail.ignoreTLS'), + secure: this.configService.get('mail.secure'), + requireTLS: this.configService.get('mail.requireTLS'), + auth: { + user: this.configService.get('mail.user'), + pass: this.configService.get('mail.password'), + }, + }, + defaults: { + from: `"${this.configService.get( + 'mail.defaultName', + )}" <${this.configService.get('mail.defaultEmail')}>`, + }, + template: { + dir: path.join( + this.configService.get('app.workingDirectory'), + 'src', + 'mail', + 'mail-templates', + ), + adapter: new HandlebarsAdapter(), + options: { + strict: true, + partials: { + dir: path.join( + this.configService.get('app.workingDirectory'), + 'src', + 'mail', + 'mail-templates', + 'partials', + ), + }, + }, + }, + } as MailerOptions; + } +``` + +- [ ] **Step 7: Test laufen lassen, Erfolg bestätigen** + +Run: `cd myteamwallet_backend && npx jest mail-templates.spec.ts` +Expected: PASS — alle 3 Tests grün. + +- [ ] **Step 8: Alle Unit-Tests + Build** + +Run: `cd myteamwallet_backend && npm test && npm run build` +Expected: alle Tests grün, Build erfolgreich. + +- [ ] **Step 9: Commit** + +```bash +git add myteamwallet_backend/src/mail/mail-config.service.ts myteamwallet_backend/src/mail/mail-templates +git commit -m "feat(mail): redesign email templates with TeamWallet branding and shared layout partial" +``` + +--- + +### Task 4: Manuelle Verifikation mit echtem Versand + +Kein Code-Task — Nachweis, dass Registrierung und Passwort-vergessen tatsächlich E-Mails +verschicken (Strato-SMTP, kein lokaler MailDev vorhanden, siehe Spec). + +**Files:** keine. + +- [ ] **Step 1: Backend lokal starten** + +Voraussetzung: lokale MySQL-Instanz läuft (Container `brave_einstein`, Port 3306, bereits aktiv +laut `docker ps`), `myteamwallet_backend/.env` unverändert vorhanden. + +Run: `cd myteamwallet_backend && npm run start:dev` +Expected: Server startet ohne Fehler auf Port `3999` (kein Absturz durch die entfernten +`nestjs-i18n`-Imports, keine `MailerModule`-Config-Fehler). + +- [ ] **Step 2: Registrierung auslösen (Aktivierungsmail)** + +In einem zweiten Terminal, mit einer echten, von dir kontrollierten Test-Adresse: + +```bash +curl -X POST http://localhost:3999/api/v1/auth/email/register \ + -H "Content-Type: application/json" \ + -d '{"email":"DEINE-TEST-ADRESSE@example.com","password":"Test1234!","firstName":"Max","lastName":"Mustermann"}' +``` + +Expected: HTTP 201, und innerhalb kurzer Zeit trifft eine E-Mail „Bestätige deine +E-Mail-Adresse" mit grünem TeamWallet-Header, Begrüßung „Hallo Max," und funktionierendem +Bestätigungs-Button in der Test-Mailbox ein. + +- [ ] **Step 3: Passwort-vergessen auslösen** + +```bash +curl -X POST http://localhost:3999/api/v1/auth/forgot/password \ + -H "Content-Type: application/json" \ + -d '{"email":"DEINE-TEST-ADRESSE@example.com"}' +``` + +Expected: HTTP 204/200 (je nach Response des Endpoints), und eine E-Mail „Passwort +zurücksetzen" mit gleichem Layout trifft ein. + +- [ ] **Step 4: Server stoppen** + +`npm run start:dev` Prozess beenden (Ctrl+C). + +- [ ] **Step 5: Ergebnis festhalten** + +Kein Commit nötig — dies ist ein manueller Verifikationsschritt. Falls eine der beiden Mails +nicht ankommt, zurück zu systematic-debugging (SMTP-Verbindung, Firewall, Spam-Ordner prüfen) +bevor der Task als abgeschlossen gilt. + +--- + +## Self-Review + +- **Spec coverage:** Bugfix (Task 1), i18n-Entfernung (Task 2), Template-Redesign inkl. + Branding/Partial (Task 3), Personalisierung (Task 1), Testing-Strategie laut korrigiertem Spec + — Unit-Tests statt e2e/MailDev (Task 1 + 3), manueller Realversand (Task 4) — alles abgedeckt. + `.env` explizit als "keine Aktion" markiert (Global Constraints), passend zum korrigierten + Spec. +- **Placeholder-Scan:** keine TBD/TODO, jeder Step enthält vollständigen Code. +- **Typ-Konsistenz:** `MailData<{ hash: string; firstName?: string | null }>` konsistent in + `mail.service.ts` (Task 1) und den Aufrufstellen in `auth.service.ts` (Task 1) verwendet; + Context-Keys `title`/`year`/`firstName`/`url`/`actionTitle` konsistent zwischen `mail.service.ts` + (Task 1) und den Templates (Task 3).