Folge-Bug zu 194c864: User löscht Adresse im Modal → DB-Liste
wird kürzer → Plesk-Sync läuft → Auto-Import sieht "c ist in
Plesk aber nicht in DB" → schreibt c zurück in
additionalForwardingEmails → Diff sagt nichts zu entfernen.
Ursache: Auto-Import (Pentest 83.x) lief für alle Sync-Pfade.
Beim Sync-Button ist Plesk→DB-Übernahme gewollt (Bestands-
Migration). Beim User-Add/Remove ist die DB-Liste die explizite
Intent – Auto-Import macht das User-Delete kaputt.
syncForwardingForEmail(id, opts?: { autoImportPleskMembers? })
mit Default true (Sync-Button-Verhalten). setAdditionalForwards
ruft mit false – entfernte Adressen verschwinden jetzt sauber
auch beim Provider.
83.1 MEDIUM: Auto-Import in syncForwardingForEmail rief
assertValidForwardingEmail nicht auf. Plesk-Member wie
attacker@plesk.internal wären ohne TLD-Block-Check (71.1) in
die DB importiert worden. Fix: jeder importierte Member läuft
durch assertValidForwardingEmail, ungültige werden silent gedroppt
+ auf debug-Level geloggt.
83.2 LOW: Self-Forward-Schutz (81.1) griff nur im Add-Pfad. Wenn
Plesk die eigene Adresse als Mailgroup-Member führte, wäre sie
beim Auto-Import in die DB-Liste gerutscht → nach dem Umschalten
auf Forwarding Mail-Loop. Fix: seenKeys mit der eigenen Adresse
initialisieren bevor die Import-Schleife läuft.
83.3 INFO: PII-Log auf console.debug umgestellt (statt console.log).
Smoke-Test mit gemischter Plesk-Liste: legitimer Member importiert,
reservierte TLDs + Self-Mail (exakt + Plus-Tag) abgelehnt,
Customer-Stamm + Default deduped.
Prod-Bug: zusätzliche Weiterleitung eintragen → Toast meldet
Erfolg, Plesk übernimmt nichts. Plesk hat zwei unabhängige
Verteil-Mechanismen, Mailgroup (alte CLI-Anlagen) und Forwarding
(neue). Unser Sync schrieb nur in Forwarding, die alte Adresse
lief aber via Mailgroup → set:-Befehle landeten in ungenutzter
Tabelle. Stage funktionierte, weil dort frisch im Forwarding-
Modus angelegt.
- EmailExistsResult um mailgroupActive/Members + forwardingActive/
Targets erweitert.
- pleskProvider.emailExists parst alle vier Felder aus --info-
stdout (Mailgroup: true|false, Group member(s): ..., Forward
request: ...).
- pleskProvider.updateForwardTargets setzt -mailgroup false dazu –
deaktiviert den Legacy-Mechanismus.
- syncForwardingForEmail holt vorm Plesk-Update die bestehenden
Mailgroup-Members und Forwarding-Targets ab und importiert sie
in unsere additionalForwardingEmails-Liste (canonical-Key-Dedup).
Verlustfrei – kein Empfänger fällt beim Umschalten raus.
Smoke-Test mit echtem Plesk-stdout (User-Log): 3 Group-Members
sauber geparst, leeres "Forward request" als [] erkannt.
Bug: Die Stressfrei-Adresse selbst (max@stressfrei-wechseln.net)
konnte als zusätzliches Weiterleitungsziel eingetragen werden,
auch Plus-Varianten. Plesk leitet auf sich selbst um → Mail-Loop.
Backend setAdditionalForwards: lädt zusätzlich meta.email, vergleicht
canonicalEmailKey gegen canonicalEmailKey(meta.email). Bei Treffer
hartes ApiError(400) mit klarer "zeigt auf die Adresse selbst –
Mail-Loop"-Meldung statt silent dedup – der User soll merken, dass
sein Eintrag bewusst abgelehnt wurde.
Frontend AdditionalForwardsModal: zusätzliche proaktive Validierung
im Sub-Modal mit identischem canonicalize-Helper. Neuer selfEmail-
Prop, damit auch der Create-Modus (vor Persist) den Check fahren
kann. Spart Roundtrip + sofort sprechende Meldung.
Bug: dieselbe E-Mail-Adresse konnte beim selben Kunden mehrfach
angelegt werden – im Screenshot zwei identische Einträge nach
einem Doppel-Submit.
- createEmail: findFirst auf (customerId, email) case-insensitive,
bei Treffer ApiError(409). Eigene Meldung für inaktive
Duplikate (Hinweis: alten Eintrag reaktivieren statt neu anlegen).
- updateEmail: gleicher Check beim Umbenennen, NOT id-Exclude.
- Controller: catch-Blöcke honorieren ApiError.statusCode (vorher
pauschal 400) → 409 kommt sauber an die UI durch.
- Frontend: updateMutation bekam onError, damit der Fehler nicht
schlucken bleibt.
71.1 MEDIUM: BLOCKED_TLDS-Set in assertValidForwardingEmail –
reservierte/private TLDs (local, internal, corp, lan, home,
private, invalid, test, localhost, example, intranet, localdomain,
arpa) werden abgelehnt. Schließt Plesk-DNS-Probing ins interne Netz.
71.2 LOW: canonicalEmailKey-Helper normalisiert Mail-Adressen für
den Dedup (Plus-Tag wegstrippen, lowercase). billing+x@y und
billing@y haben jetzt denselben Schlüssel – auch gegen Kunden-
Stamm-Mail und gegen config.defaultForwardEmail im sync-Pfad.
71.3 INFO: Neuer requireIdParam-Helper im Controller liefert 400
statt 500 bei nicht-numerischen Route-IDs. Alle acht parseInt-
Stellen umgestellt (auch über die gemeldete eine hinaus).
71.4 INFO: setAdditionalForwards rollt den DB-Stand zurück, wenn
syncForwardingForEmail mit dem Provider scheitert. Vorheriger Wert
wird vorm Update gemerkt und im Fehlerfall wieder eingespielt –
DB und Plesk laufen nicht mehr auseinander.
Smoke-Tests: 11 reservierte TLDs abgelehnt, 4 echte TLDs (de, com,
co.uk, museum) durchgewinkt, Plus-Tag-Strip mit Multi-Plus+Casing.
Pro StressfreiEmail können jetzt weitere Weiterleitungs-Adressen
gepflegt werden, die zusätzlich zur Stamm-E-Mail des Kunden und
zur globalen Default-Forward-Adresse an den Provider gepusht werden.
- Schema: StressfreiEmail.additionalForwardingEmails (TEXT/JSON-
Array), Migration mit IF NOT EXISTS.
- syncForwardingForEmail liest die Zusatzliste mit und filtert
Duplikate gegen customer.email + config.defaultForwardEmail
(case-insensitive) raus.
- Neuer Endpoint PUT /api/stressfrei-emails/:id/additional-forwards
mit Body { emails: string[] } – ersetzt die Liste komplett und
syncht den Provider direkt nach. Hard-Cap 20 Adressen, Format-
Validation per Regex, Audit-Log.
- Frontend: Button "Weitere Weiterleitungen" im Edit-Modus des
StressfreiEmailModals (erscheint sobald die Adresse beim Provider
vorhanden ist). Sub-Modal mit Liste + Add/Remove, Änderungen
gehen sofort live.
User-Feedback: der Refresh-Button war auch bei nicht-provisionierten
Adressen sichtbar (die nur als DB-Eintrag ohne Plesk-Pendant existieren).
Klick darauf gab korrekt einen Fehler, war aber unschön.
Bedingung wieder auf `emailItem.isProvisioned` einschränken. Für
historische Einträge, bei denen das Flag wegen des alten Bugs nie
gesetzt wurde, gibt es jetzt einen automatischen Reconcile-Pfad:
`syncMailboxStatus` (wird beim Öffnen jedes Edit-Modals aufgerufen)
prüft nicht mehr nur `hasMailbox`, sondern auch `isProvisioned`:
- Provider antwortet "existiert" + DB sagt isProvisioned=false
→ DB-Flag auf true ziehen + provisionedAt setzen
- Provider antwortet "nicht da" + DB sagt isProvisioned=true
→ DB-Flag auf false (Adresse wurde im Plesk-UI manuell gelöscht)
- hasMailbox wird zusätzlich konsistent gehalten
Damit heilen sich falsch markierte Adressen automatisch, sobald der
User sie einmal aufmacht zum Bearbeiten – der Refresh-Button erscheint
dann beim Re-Open.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Drei Verbesserungen am gestrigen Sync-Feature:
1. Bug-Fix: isProvisioned wurde nie auf true gesetzt
`createEmail` mit `provisionAtProvider: true` hat das Flag
`isProvisioned` nie gesetzt → blieb auf @default(false). Damit
blieb der Refresh-Button in der UI unsichtbar (Bedingung
`emailItem.isProvisioned`). Jetzt:
- createEmail setzt isProvisioned + provisionedAt korrekt
- Self-Healing: syncForwardingForEmail setzt das Flag nachträglich
auf true sobald der Provider-Aufruf erfolgreich war (Backfill
für historisch falsch markierte Einträge)
- UI-Sichtbarkeit: Bedingung entfernt – der Button erscheint jetzt
immer; ein Klick auf eine nicht-provisionierte Adresse liefert
eine sprechende Fehlermeldung statt stiller Verstecken
2. Passwort-Push bei hasMailbox: true
Bisher wurden nur die Forwards aktualisiert. Jetzt entschlüsselt
syncForwardingForEmail bei Mailbox-Adressen zusätzlich das im CRM
gespeicherte Passwort und setzt es am Provider neu – Self-Healing
für IMAP/SMTP-Logins falls jemand im Plesk-UI manuell ein anderes
Passwort gesetzt hat. Response enthält `passwordReset: true` als
Marker.
3. react-hot-toast statt alert()
Erfolgs-Toast listet die neu gesetzten Forward-Targets + Hinweis
ob Passwort-Reset durchgeführt wurde. Fehler-Toast zeigt die
Backend-Fehlermeldung (z.B. „E-Mail-Adresse beim Provider nicht
gefunden – wurde sie dort gelöscht?").
Audit-Log-Label enthält jetzt sowohl Forwards als auch Passwort-Reset-
Marker, damit der Vorgang im AuditLog nachvollziehbar bleibt.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Nach Änderung der Kunden-Stamm-E-Mail (oder der defaultForwardEmail in
den Provider-Settings) müssen die Plesk-Forwards der Stressfrei-Adressen
des Kunden auf den neuen Wert umgestellt werden. Bisher ging das nur
manuell pro Adresse im Plesk-UI – jetzt mit einem Klick pro Adresse im
CRM.
Backend:
- emailProviderService.setEmailForwardTargets(localPart, targets[]):
dünner Wrapper um die schon vorhandene IEmailProvider-Methode
updateForwardTargets (`set:email1,email2` ersetzt komplett, idempotent)
- stressfreiEmail.service.syncForwardingForEmail(id): lädt Kunde +
Provider-Config, baut [customer.email, defaultForwardEmail] und ruft
den Provider auf
- POST /api/stressfrei-emails/:id/sync-forwarding, customers:update,
Audit-Log mit den neuen Forward-Targets im Label
Frontend:
- Refresh-Icon-Button in der Action-Reihe jeder Stressfrei-Adresse,
sichtbar nur wenn isProvisioned (sonst sinnlos). Confirm-Dialog
zeigt die Ziele, Tooltip erklärt den Vorgang.
- ExternalLink-Icon neben der E-Mail in der Kundenakte (Stammdaten →
Kontakt) öffnet den Stressfrei-Tab des Kunden in neuem Tab.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>