No description
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
2026-09-21 08:35:20 +00:00
_config first commit 2026-09-21 10:33:05 +02:00
src first commit 2026-09-21 10:33:05 +02:00
.gitignore first commit 2026-09-21 10:33:05 +02:00
composer.json first commit 2026-09-21 10:33:05 +02:00
LICENSE first commit 2026-09-21 10:33:05 +02:00
PLAN.md first commit 2026-09-21 10:33:05 +02:00
README.md Update README.md 2026-09-21 08:35:20 +00:00

ErrorMailer für SilverStripe

Schickt SilverStripe-Fehlermeldungen per E-Mail — über einen eigenen Mail-Zugang, der vom Mail-Versand der Website vollständig unabhängig ist.

Läuft auf SilverStripe 4, 5 und 6 aus einem Paket.

ACHTUNG: Noch unvollständig funktioniert prinzipiell hat aber noch potentielle Sicherheitslücken noch nicht verwenden

Warum

Einen dedizierten „Error Mailer" gibt es seit SilverStripe 4 nicht mehr (Debug::send_errors_to() ist entfallen). Die offizielle Doku zeigt Monolog\Handler\NativeMailerHandler — der versendet aber über PHP-mail() und kennt keine SMTP-Zugangsdaten.

Dieses Modul hängt stattdessen einen Monolog-Handler mit eigenem Transport an Psr\Log\LoggerInterface. Weil SilverStripe\Logging\MonologErrorHandler diesen Logger als PHP-Error-/Exception-Handler registriert, erfasst er:

  • uncaught Exceptions und Fatal Errors,
  • eigene Aufrufe von Injector::inst()->get(LoggerInterface::class)->error(…).

Es gibt bewusst keinen Fallback auf den Mail-Zugang der Website. Ist nichts konfiguriert, wird nichts versendet (NullHandler).


Installation

  1. Ordner errormailer/ ins Projektstammverzeichnis kopieren.

  2. Zugangsdaten anlegen:

    cd errormailer/_config
    cp errormailer-credentials.yml.dist errormailer-credentials.yml
    

    Datei ausfüllen (Empfänger, Absender, SMTP-Zugang).

  3. ?flush=1.

Ein Ordner mit _config/ wird von SilverStripe als Modul erkannt, die Klassen findet der Class-Manifest — ein Composer-Eintrag ist nicht nötig (composer.json liegt für die spätere Packagist-Veröffentlichung bei). dev/build wird nicht gebraucht: das Modul legt keine Datenbanktabellen an.

Die ausgefüllte errormailer-credentials.yml ist über die .gitignore des Moduls aus dem Repository ausgeschlossen — ausgeliefert wird nur die .dist-Vorlage, die pro Umgebung einmal händisch gepflegt wird.


Konfiguration

Die gesamte Konfiguration kommt aus der YAML — ohne jeden Datenbankzugriff. Das ist Absicht: Fehler-Mails müssen gerade dann rausgehen, wenn die Datenbank nicht erreichbar ist. Der Versandpfad berührt die Datenbank an keiner Stelle.

Zugangsdaten in _config/errormailer-credentials.yml (aus der .dist-Vorlage kopiert):

---
Name: errormailer-credentials
After:
  - 'errormailer'
  - 'errormailer-dev'
---
ErrorMailer\ErrorEmailer:
  enabled: true
  send_to: 'fehler@example.com'
  send_from: 'noreply@example.com'
  send_from_name: 'Website Fehler'
  subject: '[example.com] %level_name%: %message%'
  smtp_server: 'smtp.example.com'
  smtp_port: 587
  smtp_user: 'noreply@example.com'
  smtp_password: 'geheim'
  level: 'error'
  deduplicate_seconds: 300

Statt der smtp_*-Felder geht auch eine vollständige DSN (dsn: 'smtp://user:pw@host:465'); sie hat Vorrang.

Optional: Pflege im CMS

Wer die Zugangsdaten lieber im CMS pflegt, kopiert zusätzlich errormailer-siteconfig.yml.dist → errormailer-siteconfig.yml und führt dev/build aus. Dann erscheinen die Felder unter Einstellungen → Fehler-Mails (bzw. im Tab Email Einstellungen, wenn moritz-sauer-13/silverstripe-mailconfig installiert ist) und überschreiben die YAML-Werte — aber nur, solange die Datenbank erreichbar ist. Für den Ausfall-Fall sollte in der YAML trotzdem ein funktionierender Zugang stehen.

Das Modul hängt an keiner Stelle von silverstripe-mailconfig ab; es bringt seine eigenen Felder mit und funktioniert deshalb auch auf SS4-/SS5-Sites, wo dieses Paket nicht installierbar ist.

Alle Optionen

Option Default Bedeutung
enabled true In der dev-Umgebung standardmäßig auf false
level 'error' Monolog-Level, z. B. warning, error, critical
send_to '' Empfänger
send_from / send_from_name '' Absender; leer = Empfänger
subject '[SilverStripe] %level_name%: %message%' Monolog ersetzt die Platzhalter
dsn '' Vollständige DSN, hat Vorrang vor den smtp_*-Feldern
smtp_server, smtp_port, smtp_user, smtp_password '' / 587 SMTP-Zugang
smtp_encryption '' tls / ssl; nur für SS4 (SwiftMailer) relevant
deduplicate_seconds 0 >0 bündelt gleichartige Fehler und schützt vor Mailfluten
use_siteconfig false SiteConfig-Felder auswerten (braucht errormailer-siteconfig.yml)
formatter SilverStripe\Logging\DetailedErrorFormatter Beliebiger Monolog-Formatter
handler_class 'auto' symfony, swift oder native erzwingen

In der dev-Umgebung ist das Modul per Default abgeschaltet. Zum Testen:

---
Name: errormailer-dev-on
After: 'errormailer-dev'
Only:
  environment: dev
---
ErrorMailer\ErrorEmailer:
  enabled: true

Testen

/_errormailer zeigt die effektive Konfiguration (Passwörter maskiert) und das gewählte Backend, /_errormailer/send schickt eine Testmeldung durch den Logger. Zugriff nur als Admin oder in der dev-Umgebung. Auf der Konsole:

vendor/bin/sake navigate _errormailer/send      # SS6

Fehler beim Versand landen ausschließlich im PHP-error_log ([errormailer] …) — niemals im SilverStripe-Logger, sonst gäbe es eine Endlosschleife im Error-Handler.


Versions-Kompatibilität

SS 4.13 SS 5 SS 6
PHP ^7.4 || ^8.0 ^8.1 ^8.3
Monolog ^1.16 ^3.2 ^3.8
Mail-Backend swiftmailer symfony/mailer symfony/mailer
gewählter Adapter SwiftMailerAdapter SymfonyMailerAdapter SymfonyMailerAdapter

Das Modul entscheidet zur Laufzeit anhand der tatsächlich vorhandenen Klassen, nicht anhand der SilverStripe-Version: SymfonyMailerAdapter → SwiftMailerAdapter → NativeMailerAdapter.

Damit ein Paket für alle drei Versionen reicht:

  • Keine Monolog-Interfaces werden selbst implementiert — die Signaturen unterscheiden sich zwischen Monolog 1 (array $record) und 3 (LogRecord). Das Modul baut nur vorhandene Handler.
  • Backend-spezifische Klassen werden als anonyme Klassen in Methodenrümpfen deklariert. Sie entstehen erst, wenn das jeweilige Backend tatsächlich verwendet wird — die Dateien sind auf jeder Version gefahrlos ladbar.
  • Factory::create() wird als create($service, array $params = []): ?object deklariert: untypisierter Parameter (erlaubt gegenüber SS6s string $service), gesetzter Rückgabetyp (erlaubt gegenüber den untypisierten Interfaces in SS4/5).
  • Die SiteConfig-Extension erbt von SilverStripe\Core\Extension (DataExtension ist in SS6 entfernt); $db aus Extensions wird vom Config-Layer unabhängig von der Basisklasse gemergt.
  • Sprachniveau durchgängig PHP 7.4.

Aufbau

Datei Aufgabe
src/ErrorEmailer.php Config-Klasse; ermittelt Empfänger, Absender und Zugangsdaten
src/ErrorEmailHandlerFactory.php baut den Monolog-Handler, wählt das Backend
src/ErrorMailTransportFactory.php optionaler benannter Symfony-Transport (SS5/6)
src/Mail/*Adapter.php die drei Mail-Backends
src/Extension/SiteConfigErrorMailExtension.php CMS-Felder (optional, nicht standardmäßig aktiv)
src/Control/ErrorMailTestController.php Diagnose-Route /_errormailer

Warum alles „lazy" ist

CoreKernel::boot() startet das Error-Handling vor dem Datenbank-Bootstrap (CoreKernel.php:48) — der Handler entsteht also, bevor überhaupt eine Datenbankverbindung existiert. Deshalb lösen die Adapter Empfänger, Absender und Transport erst beim Versand auf: über ein Closure als Nachrichten-Template und einen Mailer, der seinen Transport erst in send() erzeugt.

Für den YAML-Standardweg ist das reine Robustheit — dort wird ohnehin nie die Datenbank berührt. Gebraucht wird die Lazy-Auflösung erst bei aktivierter CMS-Pflege (use_siteconfig: true), weil die SiteConfig zum Boot-Zeitpunkt noch nicht lesbar ist.

Einzige Ausnahme: handler_class: 'native'. NativeMailerHandler baut Empfänger, Absender und Betreff schon im Konstruktor ein — dort greifen nur die YAML-Werte.


Bekannte Einschränkungen

  • Bei aktivierter CMS-Pflege (use_siteconfig: true) liegt das SMTP-Passwort im Klartext in der Datenbank.
  • Kein Rate-Limit über deduplicate_seconds hinaus.
  • Auf SS4 ist die $db-Übernahme aus einer reinen Extension (statt DataExtension) noch nicht praktisch verifiziert — betrifft nur die optionale CMS-Pflege, nicht den YAML-Standardweg.