
Contao 5 Notification Center: Zentrale Kommunikation & E-Mail-Workflows

In jedem professionellen Web-Projekt kommt irgendwann der Moment, in dem das System mit dem Benutzer kommunizieren muss. Sei es die Bestätigung einer Strandkorb-Buchung, der Double-Opt-In für einen Newsletter oder eine simple Kontaktanfrage.
Viele Entwickler neigen dazu, diese E-Mails fest im Code (z.B. in Contao Hooks oder Symfony Controllern) zu verdrahten. Das führt jedoch zu einem massiven Problem: Sobald der Kunde ein Komma im E-Mail-Text ändern möchte, muss der Entwickler ran. Die Lösung für dieses Problem ist das Contao 5 Notification Center (kurz: NC). Entwickelt von terminal42, ist es die absolute Standard-Erweiterung, wenn es um transaktionale Kommunikation in Contao geht. Es trennt die Auslösung einer Nachricht (Code) von ihrem Inhalt (Backend-Konfiguration).
Warum das Notification Center in Contao 5 ein Gamechanger ist
Mit dem Release von Contao 5 hat das Notification Center einen gewaltigen Versionssprung auf die Version 2.x gemacht. Diese Version ist kein einfaches Update, sondern ein komplettes Re-Engineering, basierend auf modernsten Symfony-Architekturen.
Wenn du das Contao 5 Notification Center professionell einsetzen willst, musst du die neuen Konzepte verstehen:
Gateways statt E-Mails: Das NC verschickt nicht einfach nur "E-Mails". Es nutzt Gateways. Ein Gateway kann der klassische E-Mail-Versand (via Symfony Mailer) sein, aber auch eine SMS-Schnittstelle, eine Slack-Nachricht oder ein API-Aufruf an ein CRM-System.
Parcels & Receipts (Pakete & Quittungen): Die Architektur orientiert sich jetzt an einem echten Postamt. Die Daten, die du versenden willst, werden in ein "Paket" (
Parcel) gepackt. Sobald das Paket verarbeitet wurde, generiert das System eine "Quittung" (Receipt). Das ermöglicht dir als Entwickler ein extrem detailliertes Logging und Fehler-Tracking.Asynchroner Versand: In Kombination mit dem Symfony Messenger kann das Contao 5 Notification Center Nachrichten asynchron im Hintergrund versenden. Das bedeutet: Wenn ein Nutzer das Buchungsformular absendet, muss er nicht 3 Sekunden warten, bis die PDF-Rechnung generiert und der SMTP-Server geantwortet hat. Das Formular lädt sofort, der Versand passiert im Hintergrund.
Der Plan für dieses Modul
Wir werden das harte Twig-Template aus unserem Buchungsformular (Teil 8) entfernen und stattdessen das Notification Center anbinden. Unser Fahrplan:
Installation und Konfiguration der Gateways im Backend.
Anlegen von Benachrichtigungen und Arbeiten mit der Symfony Expression Language in Simple Tokens.
Programmatische Auslösung der Benachrichtigung aus unserem Formular-Controller.
Eigene "Notification Types" und "Token Definitions" per Event Subscriber registrieren, damit der Redakteur unsere Strandkorb-Daten (
##chair_number##) als Platzhalter nutzen kann.
Lass uns das Notification Center installieren und die Kontrolle an den Backend-Nutzer übergeben!

1. Installation und das Konzept der Gateways
Um das Contao 5 Notification Center zu nutzen, müssen wir es zunächst installieren. Da wir uns in einer modernen Contao 5 Umgebung befinden, erledigen wir das über die Konsole (oder alternativ über den Contao Manager).
Führe in deinem Projekt-Root folgenden Befehl aus:
composer require terminal42/contao-notification_centerDa das Notification Center eigene Datenbanktabellen für die Konfiguration (Gateways, Notifications, Messages) sowie für das Logging (Parcels, Receipts) anlegt, müssen wir danach die Datenbank aktualisieren:
vendor/bin/contao-console contao:migrateNach einem Reload des Contao-Backends siehst du links in der Navigation den neuen Menüpunkt "Notification Center".
2. Dein erstes Gateway konfigurieren
In der alten Version 1.x hast du direkt eine "Benachrichtigung" angelegt. In Version 2.x des Contao 5 Notification Center musst du zuerst definieren, wie Nachrichten das System überhaupt verlassen dürfen. Dafür gibt es die Gateways.
Ein Gateway ist die Brücke zwischen dem Notification Center und der Außenwelt. Das Standard-Gateway, das wir für unsere Strandkorb-Buchungen benötigen, ist das E-Mail-Gateway.
Schritt-für-Schritt Einrichtung:
Klicke im Backend unter Notification Center auf Gateways.
Klicke auf "Neu" und wähle als Typ E-Mail (Symfony Mailer).
Vergib einen internen Namen, z.B.
Standard E-Mail Versand.Mailer-Transport: Hier zeigt sich die tiefe Integration in Contao 5. Du kannst hier auswählen, welchen Symfony Mailer-Transport du nutzen willst. Wenn du in deiner
.env.localDatei die VariableMAILER_DSNkonfiguriert hast (z.B. für einen SMTP-Server), wählst du hier einfach den Standard-Transport aus. Du könntest aber auch über dieconfig.yamlmehrere Transports (z.B. einen für Rechnungen, einen für Marketing) definieren und hier explizit zuweisen.
3. Der Geheimtipp für Entwickler: Das Void-Gateway
Ein massives Problem bei der Entwicklung von Formularen und E-Mails ist oft: Wie teste ich den Versand, ohne versehentlich echte E-Mails an Dummy-Adressen zu schicken oder in Spam-Filtern zu landen?
Das Contao 5 Notification Center liefert hierfür out-of-the-box eine geniale Lösung: Das Void Gateway. Lege dir in deiner lokalen Entwicklungsumgebung einfach ein zweites Gateway vom Typ "Void (Nichts tun)" an.
Wenn du dieses Gateway in deinen Benachrichtigungen nutzt, durchläuft das Notification Center den kompletten Prozess: Es generiert die Tokens, rendert das Twig-Template, packt das Parcel und erzeugt ein Receipt (das du im Backend-Log ansehen kannst). Aber anstatt die Nachricht an einen Server zu senden, wird sie am Ende einfach verworfen. So kannst du zu 100% sicher sein, dass der E-Mail-Text, die Variablen und die Bedingungen stimmen, ohne je eine echte E-Mail versendet zu haben.
Jetzt, da unsere Gateways stehen, können wir im nächsten Schritt die eigentlichen Benachrichtigungen (die "Inhalte") erstellen und mit Tokens arbeiten.

3. Die Benachrichtigung anlegen (Das Herzstück)
Nachdem unsere Gateways (E-Mail und Void) konfiguriert sind, können wir die eigentliche E-Mail im Backend aufbauen. Das Contao 5 Notification Center arbeitet hier mit einer strikten, aber genialen Hierarchie:
Benachrichtigung (Notification): Der logische Container (z.B. "Buchungsbestätigung Strandkorb").
Nachricht (Message): Hier wird die Benachrichtigung mit einem Gateway verknüpft (z.B. "E-Mail an den Kunden" oder "E-Mail an den Admin"). Eine Benachrichtigung kann mehrere Nachrichten haben!
Sprache (Language): Innerhalb der Nachricht definierst du den eigentlichen Text für verschiedene Sprachen (Deutsch, Englisch, etc.).
Schritt-für-Schritt in der Praxis:
Navigiere zu Notification Center -> Benachrichtigungen und klicke auf "Neu".
Titel:
Strandkorb Buchung - Admin InfoTyp: Hier musst du einen "Notification Type" auswählen. Standardmäßig bringt Contao hier Typen für den Formulargenerator oder die Mitgliederregistrierung mit. Da wir später unseren eigenen Typ programmieren werden (Iteration 5), wähle für den Moment einfach testweise "Core-Formular" aus, um die Maske speichern zu können.
Klicke danach auf das Briefumschlag-Icon ("Nachrichten der Benachrichtigung verwalten") und lege eine neue Nachricht an:
Titel:
E-Mail an VermieterGateway: Wähle dein in Iteration 2 erstelltes E-Mail-Gateway.
Im nächsten Level (den Sprachen) wird es spannend. Hier definieren wir Absender, Empfänger, Betreff und den HTML- oder Text-Inhalt der E-Mail. Doch wie bekommen wir die Daten des Kunden (Name, Datum) in diesen Text? Die Antwort lautet: Simple Tokens.
(H2) 4. Magie der Simple Tokens & Expression Language
Ein Simple Token ist ein Platzhalter, der zur Laufzeit vom Contao 5 Notification Center durch echte Daten ersetzt wird. Die Syntax besteht immer aus zwei Rauten: ##token_name##.
Wenn wir unser Buchungsformular später anbinden, werden wir die Formularfelder als Tokens übergeben. Dein Backend-Redakteur kann dann eine E-Mail wie folgt zusammenklicken:
Betreff: Neue Buchung für Strandkorb Nr. ##chair_number## Text:
Hallo Admin,
es gab eine neue Buchung von ##customer_name##.
Zeitraum: ##date_start## bis ##date_end##Der Gamechanger: Symfony Expression Language Ein reiner Platzhalter-Austausch reicht oft nicht. Was ist, wenn der Kunde optional einen "Premium-Service" gebucht hat und wir diesen Textabsatz nur dann anzeigen wollen? Seit Contao 4.10 und vollumfänglich im Contao 5 Notification Center unterstützen Simple Tokens die Symfony Expression Language. Das bedeutet, du kannst echte Logik im Backend-Textfeld abbilden, ohne PHP programmieren zu müssen!
Beispiel im E-Mail-Text:
1{if premium_service == true}
2 Der Kunde hat das Premium-Paket (inkl. Handtücher) gebucht!
3{endif}
4
5{if chair_number in ['1', '2', '3']}
6 Achtung: Dies ist ein Strandkorb in der ersten Reihe!
7{endif}Du kannst && (UND), || (ODER), mathematische Berechnungen oder Array-Prüfungen (in, not in) direkt im Backend verwenden. Das gibt deinen Redakteuren eine unglaubliche Flexibilität, E-Mails dynamisch zu gestalten, ohne jemals in den Code eingreifen zu müssen.
In der neuen Version 2 des Notification Centers kannst du im Backend sogar "Custom Tokens" anlegen, mit denen Redakteure eigene Token basteln können, indem sie z.B. Strings abschneiden oder Berechnungen durchführen – fast so mächtig wie Twig selbst!
Im nächsten Schritt verlassen wir das Backend wieder und gehen in unseren Controller, um genau diese Benachrichtigung per PHP auszulösen.

5. Benachrichtigungen per Code auslösen (Der Controller)
In Teil 8 haben wir den E-Mail-Versand hart im BookingFormElementController über den Symfony Mailer verdrahtet. Das war eine gute Übung, aber nicht flexibel. Jetzt ersetzen wir diesen Code durch das Contao 5 Notification Center.
Das Ziel: Sobald das Formular valide ist und die Buchung in der Datenbank liegt, übergeben wir die Formulardaten als Array an das Notification Center. Dieses übernimmt dann das Rendern der Simple Tokens, die Prüfung der Expression Language Bedingungen und den eigentlichen Versand über die konfigurierten Gateways.
Schritt 1: Dependency Injection anpassen
Wir öffnen unseren BookingFormElementController und injizieren den zentralen Service des Notification Centers anstelle des nackten Mailers.
1<?php
2
3namespace Acme\BeachsideBundle\Controller\ContentElement;
4
5// ... andere Imports ...
6use Terminal42\NotificationCenterBundle\NotificationCenter; // <-- WICHTIG!
7
8#[AsContentElement(category: 'beachside', template: 'content_element/booking_form')]
9class BookingFormElementController extends AbstractContentElementController
10{
11 public function __construct(
12 private readonly FormFactoryInterface $formFactory,
13 private readonly EntityManagerInterface $entityManager,
14 private readonly NotificationCenter $notificationCenter // <-- NC injizieren
15 ) {
16 }
17 // ...Schritt 2: Die Logik im getResponse anpassen
Wir springen in den Bereich, in dem das Formular erfolgreich verarbeitet wurde (if ($form->isSubmitted() && $form->isValid())).
1if ($form->isSubmitted() && $form->isValid()) {
2
3 /** @var Booking $bookingData */
4 $bookingData = $form->getData();
5 $bookingData->setTstamp(time());
6
7 $this->entityManager->persist($bookingData);
8 $this->entityManager->flush();
9
10 // --------------------------------------------------
11 // NEU: Das Contao 5 Notification Center auslösen
12 // --------------------------------------------------
13
14 // 1. Das Token-Array aufbauen (Die Daten für unsere ##...## Platzhalter)
15 $tokens = [
16 'customer_name' => $bookingData->getCustomerName(),
17 'chair_number' => $bookingData->getBeachChair()->getChairNumber(),
18 // Wichtig: Datums-Objekte als formatierte Strings übergeben,
19 // da Simple Tokens klassischerweise Text erwarten.
20 'date_start' => $bookingData->getDateStart()->format('d.m.Y'),
21 'date_end' => $bookingData->getDateEnd()->format('d.m.Y'),
22 // Ein spezielles Token für administrative Empfänger
23 'admin_email' => 'admin@beachside.de',
24 ];
25
26 // 2. Die ID der Benachrichtigung definieren
27 // In der Praxis fügst du deinem Content-Element über die DCA-Konfiguration
28 // ein Select-Feld hinzu, damit der Redakteur die Benachrichtigung im Backend
29 // auswählen kann (z.B. $model->nc_notification).
30 // Für dieses Beispiel gehen wir davon aus, die ID ist 1.
31 $notificationId = 1;
32
33 // 3. Den Versand triggern
34 if ($notificationId) {
35 // Die Methode gibt eine ReceiptCollection (Quittungen) zurück.
36 // Der dritte Parameter ist die Sprache (Locale), damit das NC weiß,
37 // ob es die deutsche oder englische E-Mail laden soll!
38 $receiptCollection = $this->notificationCenter->sendNotification(
39 $notificationId,
40 $tokens,
41 $request->getLocale()
42 );
43
44 // Optional für Profis: Du kannst die $receiptCollection prüfen,
45 // ob ein Gateway einen Fehler geworfen hat, und entsprechend loggen.
46 }
47 // --------------------------------------------------
48
49 $template->set('success', true);
50 return $template->getResponse();
51 }6. Warum dieser Code so mächtig ist
Die Magie dieses Codes liegt in der Trennung der Verantwortlichkeiten. Dein Controller weiß nichts darüber, wie die Nachricht versendet wird. Er weiß nicht, ob es eine E-Mail, eine SMS oder ein API-Call ist. Er weiß auch nicht, wer der Empfänger ist oder welcher Text gesendet wird.
Die Zeile $this->notificationCenter->sendNotification(...) schnürt lediglich ein Parcel (Paket) mit den $tokens und übergibt es an das Contao 5 Notification Center. Dieses schaut in der Datenbank nach der ID 1, prüft das verknüpfte Gateway, übersetzt die Tokens und schickt die Daten auf die Reise. Das dritte Argument ($request->getLocale()) ist essenziell für mehrsprachige Webseiten: Das Notification Center wählt vollautomatisch die passende E-Mail-Sprache basierend auf der aktuellen Sprache der Webseite!
Doch ein kleines Problem haben wir noch: Wenn wir das so machen, funktioniert der Versand zwar technisch, aber der Redakteur im Backend muss "raten", dass es die Tokens ##customer_name## oder ##chair_number## gibt. Er hat keine Autovervollständigung oder Hilfetexte im Backend. Das beheben wir im nächsten Schritt, indem wir einen eigenen Notification Type programmieren.

7. Eigene Notification Types (Das Backend optimieren)
In Iteration 4 haben wir die Daten per PHP an das Contao 5 Notification Center übergeben. Wir haben ein Array mit $tokens (wie customer_name und chair_number) geschnürt. Wenn der Redakteur nun im Backend die E-Mail zusammenbaut, weiß das System aber noch nicht, dass es diese Variablen gibt. Der Redakteur muss "blind" raten und ##customer_name## eintippen. Bei Tippfehlern knallt es.
Das ist nicht der Standard, den wir in einer Masterclass anstreben. Wir wollen, dass der Redakteur unter dem Textfeld eine saubere Liste aller verfügbaren Platzhalter (inklusive Erklärung) sieht und diese per Klick einfügen kann.
Dafür müssen wir dem System unseren eigenen Notification Type (Benachrichtigungs-Typ) bekannt machen und die Token Definitions hinterlegen.
(H2) 8. Den Notification Type via Event Listener registrieren
In der alten Contao-Welt (NC Version 1) haben wir dafür ein gigantisches, verschachteltes $GLOBALS['NOTIFICATION_CENTER'] Array in der config.php manipuliert. Das Contao 5 Notification Center (Version 2.x) ist komplett objektorientiert und nutzt Symfony Events.
Wir erstellen einen Event Subscriber, der sich in den Prozess einklinkt, wenn das Notification Center seine Typen und Tokens sammelt.
Erstelle die Datei /src/BeachsideBundle/src/EventListener/NotificationCenterSubscriber.php:
1<?php
2
3namespace Acme\BeachsideBundle\EventListener;
4
5use Symfony\Component\EventDispatcher\EventSubscriberInterface;
6use Terminal42\NotificationCenterBundle\Event\GetNotificationTypeEvent;
7use Terminal42\NotificationCenterBundle\Event\GetTokenDefinitionsForNotificationTypeEvent;
8use Terminal42\NotificationCenterBundle\NotificationType\NotificationType;
9use Terminal42\NotificationCenterBundle\Token\Definition\TextTokenDefinition;
10use Terminal42\NotificationCenterBundle\Token\Definition\EmailTokenDefinition;
11
12class NotificationCenterSubscriber implements EventSubscriberInterface
13{
14 public static function getSubscribedEvents(): array
15 {
16 return [
17 // Event 1: Unseren eigenen Typ registrieren
18 GetNotificationTypeEvent::class => 'onGetNotificationType',
19 // Event 2: Die Tokens für unseren Typ definieren
20 GetTokenDefinitionsForNotificationTypeEvent::class => 'onGetTokenDefinitions',
21 ];
22 }
23
24 public function onGetNotificationType(GetNotificationTypeEvent $event): void
25 {
26 // Wenn das Backend nach unserem spezifischen Typ fragt, liefern wir ihn aus.
27 // Der Name 'beachchair_booking' ist die interne ID unseres Typs.
28 if ('beachchair_booking' === $event->getName()) {
29 $type = new NotificationType(
30 'beachchair_booking',
31 // Die Tokens, die in der Benachrichtigung zwingend benötigt werden (z.B. für den Versand)
32 ['admin_email'],
33 // Die Überschrift im Backend (kann auch ein Translation-Key sein)
34 'Strandkorb Buchungen'
35 );
36 $event->setNotificationType($type);
37 }
38 }
39
40 public function onGetTokenDefinitions(GetTokenDefinitionsForNotificationTypeEvent $event): void
41 {
42 // Wir fügen unsere Tokens NUR zu unserem eigenen Typ hinzu
43 if ('beachchair_booking' !== $event->getNotificationType()->getName()) {
44 return;
45 }
46
47 // Token: Kundenname (Einfacher Text)
48 $event->addTokenDefinition(
49 new TextTokenDefinition('customer_name', 'Name des buchenden Kunden')
50 );
51
52 // Token: Strandkorb Nummer
53 $event->addTokenDefinition(
54 new TextTokenDefinition('chair_number', 'Die Nummer des gebuchten Strandkorbs')
55 );
56
57 // Token: Start- und Enddatum
58 $event->addTokenDefinition(
59 new TextTokenDefinition('date_start', 'Anreisedatum (Format: TT.MM.JJJJ)')
60 );
61 $event->addTokenDefinition(
62 new TextTokenDefinition('date_end', 'Abreisedatum (Format: TT.MM.JJJJ)')
63 );
64
65 // Token: E-Mail des Admins (Spezieller Typ für Validierung)
66 $event->addTokenDefinition(
67 new EmailTokenDefinition('admin_email', 'Die E-Mail-Adresse des Vermieters')
68 );
69 }
70}Was dieser Code bewirkt
Sobald du den Cache geleert hast (composer install oder contao-console cache:clear), passiert im Contao-Backend Folgendes:
Wenn du unter Notification Center -> Benachrichtigungen eine neue Benachrichtigung anlegst, erscheint im Dropdown "Typ" nun der Eintrag Strandkorb Buchungen.
Wenn der Redakteur nun die E-Mail-Nachricht konfiguriert (Titel, Text, HTML), listet das Contao 5 Notification Center unterhalb der Eingabefelder alle unsere Tokens (z.B.
##customer_name##) fein säuberlich mit unserer hinterlegten Beschreibung auf.Die
EmailTokenDefinitionsorgt sogar dafür, dass das System meckert, wenn der Redakteur dieses Token in ein Feld einfügt, das gar keine E-Mail-Adresse sein darf (z.B. als Absender-Name). Das ist maximale Typsicherheit!
Dein Code aus Iteration 4 (Controller) und das Backend sind nun perfekt miteinander synchronisiert.

9. Dateianhänge: Statisch vs. Dynamisch
Eine professionelle Buchungsbestätigung braucht oft Anhänge. Das können die Allgemeinen Geschäftsbedingungen (AGB) als PDF sein oder – und hier wird es technisch anspruchsvoll – eine dynamisch generierte Rechnung mit der individuellen Buchungsnummer des Kunden.
Das Contao 5 Notification Center unterscheidet hier konzeptionell zwischen zwei Wegen:
Statische Anhänge (Backend): Wenn du immer dieselbe Datei (z.B. AGB.pdf) versenden willst, musst du keinen Code schreiben. Der Redakteur kann in den Einstellungen der Nachricht (unter Notification Center -> Benachrichtigungen -> Nachrichten) einfach den Contao Dateiwähler öffnen und die PDF aus dem Dateisystem (Dateiverwaltung) anhängen. Das Notification Center kümmert sich um den Rest.
Dynamische Anhänge (Code): Was aber, wenn die PDF-Datei in der Sekunde des Submits erst generiert wird (z.B. mit Dompdf) und physisch vielleicht gar nicht auf dem Server liegt, sondern nur im Arbeitsspeicher (RAM) existiert? Hierfür nutzen wir die neue Event-Architektur des Contao 5 Notification Centers – genauer gesagt das
CreateParcelEvent.
10. Das CreateParcelEvent: Den Postboten abfangen
Erinnern wir uns an die Metapher aus Iteration 1: Das System packt die Daten in ein Paket (Parcel) und klebt Metadaten als Briefmarken (Stamps) darauf. Bevor dieses Paket nun an das Gateway (den E-Mail-Server) übergeben wird, wirft das Notification Center das CreateParcelEvent (Symfony EventDispatcher). Hier können wir uns einklinken, das Paket kurz öffnen, unsere generierte PDF hineinlegen und es wieder verschließen.
Wir erweitern unseren Event Subscriber aus Iteration 5:
1<?php
2
3namespace Acme\BeachsideBundle\EventListener;
4
5use Symfony\Component\EventDispatcher\EventSubscriberInterface;
6use Terminal42\NotificationCenterBundle\Event\CreateParcelEvent;
7use Terminal42\NotificationCenterBundle\Parcel\Stamp\TokenCollectionStamp;
8use Terminal42\NotificationCenterBundle\Parcel\Message\EmailMessage;
9// ... (andere Imports aus Iteration 5)
10
11class NotificationCenterSubscriber implements EventSubscriberInterface
12{
13 public static function getSubscribedEvents(): array
14 {
15 return [
16 // ... (Typen und Tokens aus Iteration 5)
17
18 // NEU: Wir fangen das Paket vor dem Versand ab
19 CreateParcelEvent::class => 'onCreateParcel',
20 ];
21 }
22
23 public function onCreateParcel(CreateParcelEvent $event): void
24 {
25 $parcel = $event->getParcel();
26 $message = $parcel->getMessage();
27
28 // 1. Wir greifen nur ein, wenn es sich um eine E-Mail handelt
29 if (!$message instanceof EmailMessage) {
30 return;
31 }
32
33 // 2. Wir prüfen anhand der "Briefmarken" (Stamps), ob es unsere Buchung ist.
34 // Wir suchen nach dem Token 'chair_number', das wir im Controller übergeben haben.
35 $tokenStamp = $parcel->getStamp(TokenCollectionStamp::class);
36 if (!$tokenStamp || !$tokenStamp->getTokenCollection()->has('chair_number')) {
37 return; // Nicht unsere Benachrichtigung, wir machen nichts!
38 }
39
40 // 3. Daten für die PDF aus den Tokens extrahieren
41 $customerName = $tokenStamp->getTokenCollection()->getByName('customer_name')->getValue();
42
43 // 4. PDF generieren (Hier stark vereinfacht dargestellt)
44 // In der Praxis würdest du hier z.B. einen PdfGeneratorService injizieren.
45 $pdfContent = $this->generatePdfForCustomer($customerName);
46
47 // 5. Die PDF an die E-Mail anhängen
48 // Wir holen das zugrundeliegende Symfony Mailer Email-Objekt
49 $email = $message->getEmail();
50
51 // Wir hängen den binären Content an (Name, Content-Type)
52 $email->attach($pdfContent, 'buchungsbestaetigung.pdf', 'application/pdf');
53 }
54
55 private function generatePdfForCustomer(string $name): string
56 {
57 // Dummy-Logik: Gibt einen PDF-String zurück
58 return '%PDF-1.4 ... Rechnung für ' . $name;
59 }
60}Die Eleganz dieser Lösung
Mit diesem Ansatz bleibt unser Controller (der das Formular verarbeitet) extrem schlank. Er muss nichts über PDFs oder E-Mail-Anhänge wissen. Er ruft nur $this->notificationCenter->sendNotification(...) auf.
Der Event Subscriber arbeitet völlig autark im Hintergrund. Sobald das Contao 5 Notification Center versucht, eine Benachrichtigung zu verschicken, die das Token chair_number enthält, fängt unser Code das Paket ab, generiert on-the-fly die PDF-Rechnung im Arbeitsspeicher und fügt sie nahtlos als Attachment in die E-Mail ein. Das ist saubere, entkoppelte Software-Architektur (Separation of Concerns).

11. Das Nadelöhr beim Checkout: Synchrone Verarbeitung
Stell dir vor, dein Kunde klickt in deinem Strandkorb-Formular auf "Jetzt zahlungspflichtig buchen". Was passiert im Hintergrund?
Die Daten werden in der Datenbank gespeichert (schnell).
Das
CreateParcelEventwird gefeuert und unser Server generiert eine PDF-Rechnung im Arbeitsspeicher (dauert ca. 500ms).Das Contao 5 Notification Center baut die E-Mail zusammen und baut eine Verbindung zu deinem SMTP-Server (z.B. Office365 oder SendGrid) auf.
Der Server wartet auf den "OK"-Handshake vom E-Mail-Provider (dauert 1 bis 3 Sekunden, je nach Netzwerk).
Während dieser ganzen Zeit blockiert PHP. Der Nutzer starrt auf einen drehenden Ladekreis in seinem Browser. Wenn der SMTP-Server gerade einen Ausfall hat, rennt dein Script in einen Timeout und der Kunde sieht im schlimmsten Fall eine weiße Fehlerseite, obwohl seine Buchung eigentlich in der Datenbank liegt. Das ist extrem unprofessionell.
12. Die Lösung: Message Queueing mit dem Symfony Messenger
Die Lösung für dieses Problem heißt "Asynchrone Verarbeitung". Anstatt die E-Mail sofort zu versenden, packen wir unser Paket (Parcel) einfach in ein Postausgangs-Körbchen (eine Datenbank-Tabelle) und sagen dem Browser des Nutzers sofort: "Vielen Dank für deine Buchung!".
Ein Hintergrund-Prozess (ein sogenannter Worker) schaut dann jede Sekunde in dieses Körbchen, nimmt die Pakete heraus und verschickt sie in aller Ruhe an den E-Mail-Server. Wenn der Mail-Server ausfällt, legt der Worker das Paket einfach zurück und probiert es 5 Minuten später noch einmal (Retry-Logik).
Das Contao 5 Notification Center (ab Version 2.x) ist tief in den Symfony Messenger integriert, der genau diese Funktionalität out-of-the-box liefert.
13. Den asynchronen Versand in Contao 5 aktivieren
Contao 5 liefert bereits fertige "Transports" (Warteschlangen) für den Symfony Messenger mit (z.B. contao_prio_high, contao_prio_normal, contao_prio_low). Sie speichern die Aufgaben standardmäßig in der Datenbank ab.
Um dem System zu sagen, dass das Notification Center (und der Symfony Mailer) diese Warteschlangen nutzen soll, müssen wir eine YAML-Konfiguration anpassen.
Erstelle oder ergänze die Datei config/packages/messenger.yaml in deinem Contao-Root:
1framework:
2 messenger:
3 routing:
4 # Wir leiten alle fertigen E-Mails in die Warteschlange mit niedriger Priorität
5 'Symfony\Component\Mailer\Messenger\SendEmailMessage': contao_prio_low
6
7 # Profi-Tipp: Wenn du große Anhänge generierst, kannst du auch
8 # schon das Parcel des Notification Centers in die Queue schieben!
9 'Terminal42\NotificationCenterBundle\Messenger\SendParcelMessage': contao_prio_normalLeere danach den Cache (vendor/bin/contao-console cache:clear).
14. Den Worker starten
Wenn du jetzt eine Buchung absendest, ist das Formular in Millisekunden fertig. Aber: Die E-Mail kommt nicht an! Warum? Weil sie jetzt in der Datenbank-Warteschlange liegt und wartet.
Du musst den "Postboten" losschicken. In deiner lokalen Entwicklungsumgebung machst du das über die Konsole:
php vendor/bin/contao-console messenger:consume contao_prio_normal contao_prio_low -vvDieser Befehl läuft endlos (er beendet sich nicht) und verarbeitet alle eingehenden Nachrichten in Echtzeit.
Auf dem Live-Server (Produktion): Auf einem echten Webserver darfst du diesen Befehl nicht einfach manuell ausführen, da er stoppt, sobald du das Terminal schließt. Hier nutzt du ein Tool wie Supervisor oder systemd (Linux-Dienste), das sicherstellt, dass der messenger:consume Befehl immer im Hintergrund läuft und bei einem Absturz sofort neu gestartet wird. (Hinweis: Wenn du keinen eigenen Server hast, bieten gute Contao-Hoster wie Hosting.de oder Hetzner oft Möglichkeiten, Background-Worker in ihrem Interface zu konfigurieren).
Mit dieser Integration katapultierst du die wahrgenommene Performance (UX) deiner Contao-Applikation auf das absolute Maximum.

15. Best Practices für das Contao 5 Notification Center
Du beherrschst nun die Architektur des Notification Centers. Um deine Applikation langfristig stabil und wartbar zu halten, solltest du im Live-Betrieb folgende Best Practices anwenden:
Gateways nach Zweck trennen: Nutze nicht ein einziges SMTP-Gateway für alles. Lege ein Gateway für "Kunden-E-Mails" (Transaktionsmails wie Buchungsbestätigungen) und ein separates für "Admin-Benachrichtigungen" an. Warum? Wenn ein Admin-Postfach voll ist und Bounces (Rückläufer) erzeugt, verschlechtert das die Sender-Reputation deines Gateways. Kunden-E-Mails könnten dann im Spam landen. Durch die Trennung isolierst du dieses Risiko.
DSGVO & Log-Rotation (Das Postamt aufräumen): Das Contao 5 Notification Center loggt standardmäßig alle Pakete (
Parcels) und Quittungen (Receipts) detailliert in der Datenbank. Das ist grandios fürs Debugging. Aber: Diese Logs enthalten oft sensible Kundendaten (Namen, E-Mails) und die Datenbanktabelle (tl_nc_message_logetc.) kann bei stark frequentierten Seiten gigantisch werden. Richte einen Cronjob ein, der regelmäßig alte Logs bereinigt. Contao bringt hierfür oft eigene Automatismen mit, oder du schreibst einen simplen Symfony Command (AsCommand), der Logs löscht, die älter als 30 Tage sind.Fallback-Sprachen definieren: Wenn du das
sendNotification-Kommando im Controller mit einer Locale aufrufst (z.B.$request->getLocale()), sucht das System nach der exakt passenden Sprache. Was, wenn dein Redakteur nur eine englische (en) und deutsche (de) Nachricht angelegt hat, der User aber auf der französischen (fr) Seite bucht? Markiere im Backend immer eine Sprache als "Fallback", damit das System nicht ins Leere läuft.

Teil der Serie
Contao 5 Masterclass: The Beachside Project
Contao 5 Bundle Entwicklung: Die Masterclass für echte Entwickler Pillar
Contao 5 Bundle Setup: Das Fundament für professionelle Erweiterungen
Contao 5 Doctrine Entities: Moderne Datenmodellierung statt SQL-Chaos
Contao 5 DCA: Perfekte Backend-Masken für Entities erstellen
Contao 5 Doctrine Relations: Wir bauen die Buchungs-Logik
Contao 5 Service Layer: Business-Logik sauber kapseln
Contao 5 Unit Testing: Code-Absicherung mit PHPUnit & Test-Case
Contao 5 Twig Templates: Frontend-Ausgabe mit Fragment Controllern
Contao 5 Symfony Forms: Professionelle Formularverarbeitung
Contao 5 Notification Center: Zentrale Kommunikation & E-Mail-Workflows
Contao 5 Backend Dashboards & Custom Routing
Contao 5 Console Commands: Automatisierung & Hintergrund-Jobs
Contao 5 Headless CMS API: REST-Schnittstellen bauen
Häufig gestellte Fragen (FAQ)
Zusammenfassung
Kommunikation ist der Schlüssel jeder modernen Web-Applikation. Mit dem Contao 5 Notification Center hast du ein Werkzeug an die Hand bekommen, das Enterprise-Ansprüchen gerecht wird:
Absolute Trennung: Code und Inhalt sind getrennt. Redakteure können Texte ändern, ohne den Entwickler zu stören.
Flexibilität: Simple Tokens und die Symfony Expression Language machen E-Mails extrem dynamisch.
Erweiterbarkeit: Mit Events (
CreateParcelEvent) klinken wir uns in den Versand ein und generieren on-the-fly PDF-Dokumente.Performance: Dank der nahtlosen Integration in den Symfony Messenger blockiert der E-Mail-Versand nie wieder den Browser des Nutzers.
Unsere Strandkorb-Applikation im Frontend ist nun absolut wasserdicht. Der Kunde kann buchen, Daten werden validiert, sicher gespeichert und eine professionelle E-Mail mit PDF-Rechnung geht asynchron im Hintergrund raus.
Doch was macht der Vermieter der Strandkörbe? Aktuell müsste er sich durch unübersichtliche Standard-DCA-Tabellen klicken. In Teil 10 ändern wir das. Wir betreten die Welt der Backend-Dashboards und Custom Routes. Wir bauen eine maßgeschneiderte, moderne Kommandozentrale für unsere Applikation direkt in das Contao-Backend ein.

Dietrich Bojko
Senior Webentwickler
Webinteger arbeitet seit vielen Jahren produktiv mit
Linux-basierten Entwicklungsumgebungen unter Windows.
Der Fokus liegt auf
performanten Setups mit WSL 2, Docker, PHP, Node.js und modernen
Build-Tools in realen Projekten –
nicht auf theoretischen Beispielkonfigurationen.
Die Artikel dieser Serie entstehen direkt aus dem täglichen Einsatz in Kunden- und Eigenprojekten und dokumentieren bewusst auch typische Fehler, Engpässe und bewährte Workarounds.


