Folge-Punkte aus dem Code-Review von #3598. Wurden bewusst nicht im PR mitgemacht, um den initialen Mail-Stand für RealUnit zügig live zu bringen. In einem separaten PR nachzuziehen.
F1: disabledMailContexts-Schema einführen
Aktuell ad-hoc-Filter in jedem Service:
if (entity.wallet?.name === REALUNIT_WALLET_NAME) {
await this.repo.update(...entity.somethingMail());
continue;
}
Sieben Mal verteilt (3× buy-crypto, 3× buy-fiat, 1× limit-request).
Sauber: Eine deklarative Wallet-Spalte mit MailContext[] (granularer als bestehender disabledMailTypes auf MailContextType). Ähnliches Pattern wie Wallet.disabledMailTypes.
Vorteil: jeder neue Mail-Trigger braucht keine Service-Anpassung mehr; pro Wallet ein DB-Record.
F2: confirmSentMail() für nicht-versendete Mails ist semantisch falsch
Skip-Logic markiert mit confirmSentMail() / pendingMail() / chargebackMail() — mailSendDate wird gesetzt, obwohl keine Mail rausging.
Folgen:
- Audit/Reporting wird falsch (aussagt "Mail wurde gesendet")
- Etwaige spätere Resend-Logik weiß nicht, dass es ein Skip war
- Nicht klar erkennbar in Logs, ob es ein Skip oder ein echter Send war
Sauber: Ein separates mailSkipped-Feld auf den Entities (BuyCrypto / BuyFiat / LimitRequest) oder ein Skip-Log.
F3: Fehlende Test-Coverage
Null neue Tests für die DFX-API-weiten Änderungen:
MailFactory.createUserV2Mail — wallet-aware translation, welcome-Insert, forced-lang, body-injection-Reihenfolge
MailFactory.createPersonalMail — analog
- RealUnit-Opt-out-Filter (10 amlReasons in pendingBuyCrypto/pendingBuyFiat, plus chargeback / chargebackUnconfirmed / limit-request)
Sauber: Mindestens einen Integration-Test pro Mail-Pfad mit / ohne RealUnit-Wallet.
F4: Hardcoded WordPress-CDN-URL für RealUnit-Logo
In realunit.hbs ist das Logo als Image-Src hartcodiert:
https://realunit.ch/wp-content/uploads/2023/06/realunit-logo-tagline-full-color-rgb.svg
Risiko: WordPress-Media-Library kann den Pfad ändern (z. B. bei Migration), Logo bricht.
Sauber: Logo-URL per Wallet-Config setzen (Config.mail.wallet.RealUnit.logoUrl), oder das SVG inline ins Template einbetten.
F5: Preview-Script-Status klären
scripts/generate-realunit-previews.js (~600 Zeilen JS) ist im PR mitcommitted. Dient als Review-Tool für DAS und das Compliance-Team.
Frage: Soll's bleiben (für künftige Anpassungen jederzeit ein neuer Export möglich) oder als lokales Tool unter .gitignore?
Falls bleiben: Folge-Aufgabe — README in scripts/ mit Beschreibung wie es ausgeführt wird.
F6: Wallet-Body-Text-Mechanismus auch für PersonalMail
MailFactory.getWalletBodyTexts(...) wird aktuell nur in createUserV2Mail aufgerufen, nicht in createPersonalMail. Konsistenz-Lücke. Falls eine RealUnit-Mail je per MailType.PERSONAL gehen würde (aktuell limit-request für RealUnit deaktiviert, also kein konkretes Problem), würde der Body-Override nicht funktionieren.
Sauber: Den getWalletBodyTexts-Aufruf auch in createPersonalMail einbauen.
F7: Mail-Text-Polish
Aus dem Review von DAS / RealUnit-Compliance noch ausstehend (kann auch direkt von DAS kommen):
- sell-processing: Salutation und Body doppeln "wird verarbeitet"
- buy-crypto-completed Body: "Sie sollten die Anzahl Token sowie den aktuellen Wert sehen" — "sollten" konjunktiv-unsicher
- buy-crypto-completed Title: "Transfer Ihrer RealUnit-Token" generisch, klare Variante: "Kauf RealUnit-Token erfolgreich"
- kyc-success: Salutation und Body doppeln "abgeschlossen"
- kyc-missing-data: "fehlen noch Daten, die Sie noch ergänzen müssen" — "noch" zweimal
- pending-merge-incomplete line2: "im vorangegangenen E-Mail" — Genus-Inkonsistenz (sollte "in der vorangegangenen Mail")
- fiat-input-currency-exchange: Reihenfolge umkehren — der Hinweis "In der RealUnit App stehen Ihnen sowohl ein CHF- als auch ein EUR-Bankkonto zur Verfügung" ist die handlungsrelevanteste Information und sollte zuerst kommen
- pending-video-ident: Stornier-Option in line5 fragwürdig (DFX betreut RealUnit eh telefonisch); plus "DFX AG" zweimal erwähnt
- added-address Body: sehr knapp, fehlt Kontext zur Account-Zusammenlegung — Kunde könnte erschrecken ("Welche Adresse?")
- recommendation-mail: doppelter Button-Konflikt (registration_button "Klick hier" + hardcoded "App öffnen") — App-öffnen passt nicht für Empfänger ohne App
- ref-reward: Body besteht nur aus "Vielen Dank für Ihre Weiterempfehlung der RealUnit App!" — keine Info zur Prämie
F8: Klärung mit DAS — Mail-Setup-Schluss
Im DAS-Doc v1.0 endet eine Liste mit "Prüfen ob vorhanden:" (unausgefüllt). Klärung, ob die jetzigen Mails das Mail-Setup vollständig abdecken oder zusätzliche Trigger erforderlich sind. Comment dazu wartet auf Antwort: #3598 (comment)
F9: walletName-Parameter-Threading durch die Translation-Chain
In der MailFactory wurde walletName?: string an translate(), translateParams(), getMailAffix() und mapMailAffix() als optionaler Parameter angehängt und durch alle vier Ebenen durchgereicht. Funktioniert, ist aber unsauberes Plumbing.
Sauber: Ein Translation-Context-Objekt ({ lang, walletName }) einmal oben in createUserV2Mail/createPersonalMail aufbauen und durch den Stack reichen — oder direkt einen Translator-Helper-Klasse pro Mail-Build, die Lang + Wallet-Override kapselt.
Aufgefallen in @davidleomay's PR-Review: #3598 (review)
F10: createPersonalMail ignoriert forcedLang und walletName bei der Translation
MailFactory.createPersonalMail nutzt:
lang = userData.language.symbol — kein Fallback auf walletMailConfig.forcedLang
this.translate(title, lang) — kein walletName-Argument; das Subject wird also nie aus mail-<wallet>.json gelesen
createUserV2Mail macht beides korrekt. Inkonsistenz.
Aktuell kein Live-Bug: Die einzige RealUnit-relevante PersonalMail (LIMIT_REQUEST) ist für RealUnit deaktiviert. Aber sobald je eine RealUnit-Mail über MailType.PERSONAL läuft, würde sie nicht in DE rendern und den Subject aus dem DFX-Default ziehen.
Fix: in createPersonalMail analog zu createUserV2Mail:
walletName + walletMailConfig ableiten
lang = walletMailConfig?.forcedLang ? ... : userData.language.symbol.toLowerCase()
this.translate(title, lang, undefined, walletName) aufrufen
Folge-Punkte aus dem Code-Review von #3598. Wurden bewusst nicht im PR mitgemacht, um den initialen Mail-Stand für RealUnit zügig live zu bringen. In einem separaten PR nachzuziehen.
F1:
disabledMailContexts-Schema einführenAktuell ad-hoc-Filter in jedem Service:
Sieben Mal verteilt (3× buy-crypto, 3× buy-fiat, 1× limit-request).
Sauber: Eine deklarative Wallet-Spalte mit
MailContext[](granularer als bestehenderdisabledMailTypesaufMailContextType). Ähnliches Pattern wieWallet.disabledMailTypes.Vorteil: jeder neue Mail-Trigger braucht keine Service-Anpassung mehr; pro Wallet ein DB-Record.
F2:
confirmSentMail()für nicht-versendete Mails ist semantisch falschSkip-Logic markiert mit
confirmSentMail()/pendingMail()/chargebackMail()—mailSendDatewird gesetzt, obwohl keine Mail rausging.Folgen:
Sauber: Ein separates
mailSkipped-Feld auf den Entities (BuyCrypto / BuyFiat / LimitRequest) oder ein Skip-Log.F3: Fehlende Test-Coverage
Null neue Tests für die DFX-API-weiten Änderungen:
MailFactory.createUserV2Mail— wallet-aware translation, welcome-Insert, forced-lang, body-injection-ReihenfolgeMailFactory.createPersonalMail— analogSauber: Mindestens einen Integration-Test pro Mail-Pfad mit / ohne RealUnit-Wallet.
F4: Hardcoded WordPress-CDN-URL für RealUnit-Logo
In
realunit.hbsist das Logo als Image-Src hartcodiert:Risiko: WordPress-Media-Library kann den Pfad ändern (z. B. bei Migration), Logo bricht.
Sauber: Logo-URL per Wallet-Config setzen (
Config.mail.wallet.RealUnit.logoUrl), oder das SVG inline ins Template einbetten.F5: Preview-Script-Status klären
scripts/generate-realunit-previews.js(~600 Zeilen JS) ist im PR mitcommitted. Dient als Review-Tool für DAS und das Compliance-Team.Frage: Soll's bleiben (für künftige Anpassungen jederzeit ein neuer Export möglich) oder als lokales Tool unter
.gitignore?Falls bleiben: Folge-Aufgabe — README in
scripts/mit Beschreibung wie es ausgeführt wird.F6: Wallet-Body-Text-Mechanismus auch für
PersonalMailMailFactory.getWalletBodyTexts(...)wird aktuell nur increateUserV2Mailaufgerufen, nicht increatePersonalMail. Konsistenz-Lücke. Falls eine RealUnit-Mail je perMailType.PERSONALgehen würde (aktuell limit-request für RealUnit deaktiviert, also kein konkretes Problem), würde der Body-Override nicht funktionieren.Sauber: Den
getWalletBodyTexts-Aufruf auch increatePersonalMaileinbauen.F7: Mail-Text-Polish
Aus dem Review von DAS / RealUnit-Compliance noch ausstehend (kann auch direkt von DAS kommen):
F8: Klärung mit DAS — Mail-Setup-Schluss
Im DAS-Doc v1.0 endet eine Liste mit "Prüfen ob vorhanden:" (unausgefüllt). Klärung, ob die jetzigen Mails das Mail-Setup vollständig abdecken oder zusätzliche Trigger erforderlich sind. Comment dazu wartet auf Antwort: #3598 (comment)
F9:
walletName-Parameter-Threading durch die Translation-ChainIn der
MailFactorywurdewalletName?: stringantranslate(),translateParams(),getMailAffix()undmapMailAffix()als optionaler Parameter angehängt und durch alle vier Ebenen durchgereicht. Funktioniert, ist aber unsauberes Plumbing.Sauber: Ein Translation-Context-Objekt (
{ lang, walletName }) einmal oben increateUserV2Mail/createPersonalMailaufbauen und durch den Stack reichen — oder direkt einenTranslator-Helper-Klasse pro Mail-Build, die Lang + Wallet-Override kapselt.Aufgefallen in @davidleomay's PR-Review: #3598 (review)
F10:
createPersonalMailignoriertforcedLangundwalletNamebei der TranslationMailFactory.createPersonalMailnutzt:lang = userData.language.symbol— kein Fallback aufwalletMailConfig.forcedLangthis.translate(title, lang)— keinwalletName-Argument; das Subject wird also nie ausmail-<wallet>.jsongelesencreateUserV2Mailmacht beides korrekt. Inkonsistenz.Aktuell kein Live-Bug: Die einzige RealUnit-relevante PersonalMail (
LIMIT_REQUEST) ist für RealUnit deaktiviert. Aber sobald je eine RealUnit-Mail überMailType.PERSONALläuft, würde sie nicht in DE rendern und den Subject aus dem DFX-Default ziehen.Fix: in
createPersonalMailanalog zucreateUserV2Mail:walletName + walletMailConfigableitenlang = walletMailConfig?.forcedLang ? ... : userData.language.symbol.toLowerCase()this.translate(title, lang, undefined, walletName)aufrufen