CluPilotCloud/app/Services
nexxo f40b762413 Zwei Empfaengerkreise, und eine Wegwahl, die auch das Konto verschiebt
Fix-Welle nach dem Gesamt-Review, Punkte 4 und 5. Beide sitzen in denselben
Dateien, deshalb ein Commit.

DIE HOST-MELDUNG WAR WORTWOERTLICH DIE KUNDENMAIL (Punkt 4).
notifyHostManagers() verschickte dieselbe SecurityBlockMail. Deren Text ist fuer
den Kunden geschrieben ("bei Ihrer Cloud", "…koennen Sie die Sperre auf der
Sicherheitsuebersicht aufheben"), der Knopf zeigte auf portal.security — wo ein
Betreiber kein Konto hat und Host-Sperren nie erscheinen —, welcher Host
betroffen ist stand nirgends, und die Anrede blieb leer, weil $block->instance
null ist.

Die Mail kann jetzt beide Faelle. Unterscheidungsmerkmal in der Ansicht ist
$hostName: gesetzt heisst Host-Sperre, null heisst Instanz-Sperre. Ein zweites
Flag daneben waere eine zweite Wahrheit.

- Betreff nennt den Host.
- Eigener Textblock (mail_heading_host, mail_preheader_host, mail_intro_host mit
  "fehlgeschlagene SSH-Anmeldungen am Host :host"), eigene Tabellenzeile
  field_host, eigener Hinweis mail_note_host.
- Der Knopf zeigt auf admin.hosts.show, also auf die Host-Detailseite der
  Konsole, wo die Host-Sperren mit ihrem Aufheben-Knopf stehen.
- Keine Anrede im Host-Fall — sie war vorher leer, und der Empfaengerkreis ist
  ohnehin mehrere Personen.
- Der Fehlalarm-Satz ist ein anderer und sagt, was ein Betreiber wissen will:
  bestehende Verbindungen bleiben unberuehrt, die Regel steht unter
  "established,related accept".

R19: die zwei Kommentare, die fuer Host-Sperren Unwahres behaupteten, sind
richtiggestellt — der Kopfkommentar von SecurityBlockMail und der
MailCatalogue-Eintrag. Beide beschreiben jetzt zwei Empfaengerkreise. Ein
Kommentar mit falscher Begruendung haelt den Naechsten vom Nachsehen ab.

DIE WEGWAHL VERSCHOB DEN ABSENDER, NICHT DAS KONTO (Punkt 5).
SecurityBlockMail nagelte mailer('cp_system') fest und holte das Postfach ueber
die Wegwahl. MailboxTransport loeste weiter ueber den ZWECK auf und
authentifizierte mit dessen Zugangsdaten. Legte der Betreiber security-block auf
info@ — der ausdrueckliche Anlass fuer Abschnitt 3a der Spec —, ging die Mail
mit From: info@ durch die SMTP-Sitzung von no-reply@. Viele Server weisen das ab
("553 5.7.1 Sender address rejected: not owned by user"), und dann verschwindet
ausgerechnet die Mail, die er gerade umgelegt hat. Betraf ALLE Mailarten.

Die Wegwahl bestimmt jetzt auch den Mailer. Die zweitbeste Loesung — die Auswahl
in der Konsole auf Postfaecher zu begrenzen, deren Konto benutzt wird — war
nicht noetig.

- config/mail.php erzeugt AUS MailCatalogue::all() je Mailart einen Mailer
  cp_mail_<mailart> mit transport, purpose und mail_key. Erzeugt statt
  aufgelistet, weil der Katalog ausdruecklich die EINE Liste ist; eine zweite
  hier waere die, die beim siebzehnten Mail vergessen wird. Genauso statisch wie
  die fuenf Zweck-Mailer: ein reines Array, keine Datenbank, auch unter
  config:cache unveraendert.
- MailboxTransport nimmt den mail_key entgegen und loest damit ueber DIESELBE
  MailRoute::purposeOrMailbox() auf, die auch der Umschlag fragt. Ohne
  Schluessel entscheidet wie bisher allein der Zweck — jeder Mailer, der keinen
  hat, verhaelt sich unveraendert.
- Jede Mailart mit Schluessel benennt ihren Mailer ueber
  MailCatalogue::mailer(). Dessen Rueckfall auf cp_<zweck> fuer einen
  unbekannten Schluessel ist nicht Zierde: DunningNoticeMail baut seinen
  Schluessel aus der Mahnstufe, und ohne den Rueckfall waere eine Stufe
  ausserhalb 0-3 ein "Mailer [...] is not defined" und damit ein Totalausfall
  statt eines falschen Absenders.
- Betrifft alle Mailarten: 13 Mailables, die CloudReady-Notification und die
  cloud-ready-Vorschau. ContactRequestMail bleibt auf cp_support — es hat keinen
  Schluessel im Katalog, weil es keine eigene Mailart ist.

MailSenderOwnershipTest ist mitgezogen und dabei STRENGER geworden. Die alte
Fassung verglich "From-Zweck == Mailer-Zweck"; jetzt prueft sie, dass der
Katalog dem Zweck zustimmt, den der Umschlag verlangt. Katalog-Drift war bis
hierher nur als Warnung im Docblock von MailCatalogue festgehalten und durch
nichts erzwungen. Ein Literal, das selbst kein Schluessel ist, wird als PRAEFIX
behandelt (der Mahnlauf) — dann muessen alle Schluessel dahinter zustimmen, und
es muss mindestens einen geben.

MailPreviewTest fragte den Zweck bisher, indem es 'cp_' vom Mailer-Namen
abschnitt. Das war eine Zusicherung ueber das Namensschema, nicht ueber die
Zusage; sie liest den Zweck jetzt aus der Konfiguration des Mailers selbst.

tests/Feature/Mail/SenderAddressTest.php gehoert inhaltlich zur parallel
laufenden Sitzung. Vier Zusicherungen darin pruefen genau den Mailer-Namen, den
dieser Commit aendert, also sind sie mitgezogen — die Datei war dabei sauber,
es steckt keine fremde unfertige Arbeit in diesem Commit.

Committet mit ausdruecklicher Dateiangabe am Zeilenende, weil eine parallele
Sitzung an derselben Ablage arbeitet und der Index fremde Arbeit enthalten kann.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-03 17:00:20 +02:00
..
Billing Merge branch 'claude/angry-golick-187172' into feat/neue-pakete 2026-08-01 16:27:13 +02:00
Deployment Der Tunnel bekommt einen eigenen Container 2026-08-03 07:49:29 +02:00
Devices Show where an account is signed in, and let it sign the other places out 2026-07-28 23:38:17 +02:00
Dns Hetzner-Cloud-DNS, Bereitschaftsseite aus sich heraus behebbar 2026-07-31 19:50:01 +02:00
Domains Prove a custom domain before serving it, and keep proving it 2026-07-29 14:47:51 +02:00
Env Add a raw .env editor to the Integrations page, with a net under it 2026-07-29 01:51:27 +02:00
Legal Write the processing agreement and its annex, and let it be downloaded 2026-07-30 17:12:30 +02:00
Mail Zwei Empfaengerkreise, und eine Wegwahl, die auch das Konto verschiebt 2026-08-03 17:00:20 +02:00
Maintenance Keep the purpose mailer through the queue instead of losing it to the default 2026-07-28 03:50:02 +02:00
Monitoring Add the SSH identity to the vault, and give deployment config a console page 2026-07-29 00:52:44 +02:00
Provisioning Fix-Runde: preferredDatacenter() blind fuer Reservierung, Loeschen hob sie auf 2026-08-01 16:21:46 +02:00
Proxmox Fix-Runde 2: ein werfender Gast reisst den Lauf nicht mehr mit 2026-08-03 14:15:36 +02:00
Proxy Keep IP addresses out of a certificate overview 2026-07-31 01:01:47 +02:00
Secrets Testergebnis erscheint am Feld statt am Seitenende; DNS-Token bekommt einen Testknopf 2026-07-31 21:26:14 +02:00
Security Was aus dem Gast kommt, ist keine Adresse, bis es geprueft ist 2026-08-03 17:00:00 +02:00
Ssh Hetzner-Cloud-DNS, Bereitschaftsseite aus sich heraus behebbar 2026-07-31 19:50:01 +02:00
Status Let an incident be deleted, and start measuring whether the hosts answer 2026-07-29 15:16:48 +02:00
Stripe Fix-Runde: Reichweite der Sperre, keine doppelte Gebuehr, verlorene Nachricht 2026-07-31 22:57:56 +02:00
Tax Ask the EU register whether a VAT number is real 2026-07-29 22:42:29 +02:00
Terminal Terminal: keine Fehlerseite mehr, ein Knopf auf jeder Zeile, eine Buehne statt roter Zeilen 2026-08-02 19:27:35 +02:00
Traefik Serve the custom domain, not just announce it 2026-07-29 16:44:52 +02:00
Traffic feat(billing): the plan catalogue becomes three tables, and config stops selling 2026-07-26 12:05:56 +02:00
Vpn Fix round: CGNAT, guarded write/delete, and a silent host is not a missing template 2026-07-30 14:19:44 +02:00
Wireguard Release v1.3.85 — Handshake-Prüfung hing an einem Programm, das es nicht gibt 2026-08-01 03:00:19 +02:00