Komponenten statt Copy-Paste-Tabellen
Bestellbestätigungen, Versandbenachrichtigungen und Passwort-Reset-Mails teilen sich fast immer dieselbe Grundstruktur. MJML übernimmt dafür die zuverlässige, tabellenbasierte Layout-Ebene, während Tailwind-Utility-Klassen das visuelle Design liefern, sodass Transaktions-E-Mails wartbar bleiben statt aus verstreuten Kopien zu bestehen.
Inhaltsverzeichnis
- 1. Warum Transaktions-E-Mails eine eigene Vorlagen-Strategie brauchen
- 2. MJML als Layout-Ebene für zuverlässige Tabellenstrukturen
- 3. Tailwind-Klassen in MJML-Komponenten kombinieren
- 4. Der Build-Prozess: MJML kompilieren, Tailwind einbetten
- 5. Wiederverwendbare Komponenten für alle Transaktions-E-Mails
- 6. Praxisbeispiel: Bestellbestätigung als MJML-Vorlage
- 7. Integration in Symfony Mailer und Magento-Transaktionen
- 8. Dynamische Daten und Platzhalter sicher einsetzen
- 9. MJML im Vergleich zu reinem Tabellen-HTML
- 10. Zusammenfassung
- 11. FAQ
1. Warum Transaktions-E-Mails eine eigene Vorlagen-Strategie brauchen
Transaktions-E-Mails wie Bestellbestätigung, Versandbenachrichtigung, Rechnung und Passwort-Reset unterscheiden sich von Marketing-Newslettern dadurch, dass sie in Echtzeit aus einem Backend-Ereignis ausgelöst werden und meist geschäftskritische Informationen enthalten. Eine fehlerhaft dargestellte Bestellbestätigung kostet Vertrauen und erzeugt Support-Aufwand, während ein verzögerter Newsletter meist folgenlos bleibt. Diese höhere Verlässlichkeitsanforderung macht eine strukturierte Vorlagen-Strategie für Transaktions-E-Mails wichtiger als für andere E-Mail-Typen.
Gleichzeitig teilen sich fast alle Transaktions-E-Mails eines Shops oder einer Anwendung dieselben Bausteine: Kopfzeile mit Logo, Hauptbotschaft, eine Tabelle mit Positionen oder Details, ein Call-to-Action-Button und eine Fußzeile mit rechtlichen Informationen. Werden diese Bausteine für jede Transaktions-E-Mail separat als Tabellen-HTML kopiert, entsteht schnell Wildwuchs, bei dem eine Designänderung an mehreren Stellen manuell nachgezogen werden muss. Genau hier setzen MJML und Tailwind gemeinsam an.
2. MJML als Layout-Ebene für zuverlässige Tabellenstrukturen
MJML ist eine von Mailjet entwickelte Markup-Sprache, die eigene, deklarative Komponenten wie <mj-section>, <mj-column> und <mj-button> bereitstellt und diese beim Kompilieren automatisch in robustes, tabellenbasiertes HTML mit allen nötigen Outlook-Fallbacks übersetzt. Der entscheidende Vorteil für Transaktions-E-Mails: Statt jede verschachtelte Tabellenstruktur und jeden MSO-Conditional-Comment von Hand zu schreiben, beschreibt man das Layout auf einer höheren Abstraktionsebene, während MJML die fehleranfällige Detailarbeit übernimmt.
Für Teams, die bereits mit Tailwind CSS arbeiten, fühlt sich MJML zunächst wie ein Bruch mit der gewohnten Utility-Klassen-Arbeitsweise an, weil MJML eigene Attribute wie background-color oder padding direkt auf seinen Komponenten anbietet. Der pragmatische Mittelweg besteht darin, MJML ausschließlich für die Layout-Struktur zu verwenden, während visuelles Feintuning wie Textfarben, Schriftgrößen und Abstände innerhalb der Komponenten über eingebettetes Tailwind-CSS gesteuert wird, das MJML unangetastet durchreicht.
# Install MJML alongside the existing Tailwind toolchain
npm install --save-dev mjml juice tailwindcss
3. Tailwind-Klassen in MJML-Komponenten kombinieren
MJML unterstützt einen <mj-raw>-Block sowie <mj-html-attributes>, mit denen sich normales HTML mit Tailwind-Klassen in eine MJML-Vorlage einbetten lässt, ohne die MJML-eigene Komponentenlogik zu verlassen. In der Praxis bedeutet das: Die äußere Struktur, Sections und Columns, wird mit MJML-Tags beschrieben, während der Inhalt innerhalb einer Spalte, etwa eine Produkttabelle oder ein Textblock, ganz normal mit Tailwind-Klassen im eingebetteten HTML gestylt wird.
Damit die Tailwind-Klassen tatsächlich zu Inline Styles werden, kompiliert man das Ergebnis von MJML in einem zweiten Schritt zusätzlich durch die aus einem anderen Artikel bekannte Inline-Pipeline mit juice. Der Workflow läuft dann in drei Stufen: MJML kompiliert die robuste Layout-Struktur, Tailwind liefert das Stylesheet für die im eingebetteten HTML verwendeten Klassen, und juice schreibt am Ende alle Regeln als Inline Style in das fertige Dokument.
<!-- order-confirmation.mjml — MJML structure with embedded Tailwind classes -->
<mjml>
<mj-body background-color="#f8fafc">
<mj-section padding="0">
<mj-column>
<mj-raw>
<!-- Content inside this raw block uses ordinary Tailwind utility classes -->
<div class="px-6 py-8 bg-sky-600 text-white">
<p class="text-xl font-bold m-0">Bestellung #12345 bestätigt</p>
</div>
</mj-raw>
</mj-column>
</mj-section>
</mj-body>
</mjml>
4. Der Build-Prozess: MJML kompilieren, Tailwind einbetten
Der vollständige Build-Prozess für eine Transaktions-E-Mail besteht aus einer festen Abfolge von Schritten, die sich gut in ein npm-Skript oder eine PHP-Build-Pipeline integrieren lassen. Zuerst kompiliert der MJML-Compiler die .mjml-Quelldatei in valides, tabellenbasiertes HTML mit allen Outlook-Fallbacks. Danach kompiliert Tailwind das CSS für die im MJML-Dokument verwendeten Utility-Klassen. Abschließend übernimmt juice das Inlinen, sodass am Ende eine fertige, verschickfertige HTML-Datei entsteht.
Für Projekte, in denen die Backend-Logik in PHP statt in Node.js läuft, etwa in Symfony oder Magento, lässt sich dieser Build-Schritt einmalig als separater Kompilierungsprozess ausführen, dessen Ergebnis anschließend als statische Twig- oder PHTML-Vorlage im PHP-Projekt abgelegt wird. Die dynamischen Platzhalter für Bestellnummer, Kundenname oder Artikelliste bleiben dabei als normale Template-Variablen erhalten und werden erst zur Laufzeit vom PHP-Mailer befüllt, nicht schon während des MJML-Builds.
{
"scripts": {
"build:transactional": "npm run build:mjml && npm run build:tailwind && npm run build:inline",
"build:mjml": "mjml src/*.mjml --output dist/",
"build:tailwind": "tailwindcss -i src/email.css -o dist/email.css --minify",
"build:inline": "node inline.js"
}
}
# Full build chain: MJML -> Tailwind CSS -> inline styles
npx mjml src/order-confirmation.mjml -o dist/order-confirmation.raw.html
npx tailwindcss -i src/email.css -o dist/email.css --minify
node inline.js dist/order-confirmation.raw.html dist/email.css dist/order-confirmation.html
# Repeat for every transactional email template sharing the same build script
for tpl in order-confirmation shipping-notification invoice; do
npx mjml "src/$tpl.mjml" -o "dist/$tpl.raw.html"
node inline.js "dist/$tpl.raw.html" dist/email.css "dist/$tpl.html"
done
5. Wiederverwendbare Komponenten für alle Transaktions-E-Mails
MJML unterstützt native Includes über <mj-include>, mit denen sich wiederkehrende Bausteine wie Kopfzeile, Fußzeile und Call-to-Action-Button in eigenen Dateien pflegen lassen, die von allen Transaktions-E-Mails gemeinsam genutzt werden. Eine Bestellbestätigung, eine Versandbenachrichtigung und eine Rechnung teilen sich damit exakt dieselbe Kopf- und Fußzeilen-Datei, während nur der individuelle Hauptinhalt pro E-Mail-Typ variiert. Ändert sich das Logo oder der rechtliche Footer-Text, genügt eine einzige Anpassung in der gemeinsamen Include-Datei.
Für den Call-to-Action-Button empfiehlt sich eine eigene MJML-Komponente mit parametrisierbarem Text und Link, die konsistent dieselbe Tailwind-Klassenkombination für Hintergrundfarbe, Innenabstand und Schriftgewicht verwendet. So sieht der Button in der Bestellbestätigung optisch identisch zu dem in der Versandbenachrichtigung aus, ohne dass die Klassen an jeder Stelle erneut ausgeschrieben werden müssten.
<!-- shared-header.mjml — included by every transactional email template -->
<mj-section background-color="#0f172a" padding="24px 0">
<mj-column>
<mj-image width="140px" src="https://mironsoft.de/logo.png" />
</mj-column>
</mj-section>
<!-- order-confirmation.mjml — reuses the header via mj-include -->
<mjml>
<mj-body>
<mj-include path="./shared-header.mjml" />
<mj-section><mj-column><mj-text>Order-specific content here</mj-text></mj-column></mj-section>
</mj-body>
</mjml>
6. Praxisbeispiel: Bestellbestätigung als MJML-Vorlage
Eine vollständige Bestellbestätigung besteht aus der gemeinsamen Kopfzeile, einem individuellen Begrüßungstext mit Bestellnummer, einer Produkttabelle mit Positionen, Preisen und Gesamtsumme, sowie einem Call-to-Action-Button zur Bestellverfolgung. Die Produkttabelle wird als natives HTML-<table>-Element innerhalb eines <mj-raw>-Blocks eingebettet, mit Tailwind-Klassen für Zellenpolsterung und alternierende Zeilenfarben, während MJML die äußere Sektion und Spaltenaufteilung übernimmt.
Für die dynamischen Positionen nutzt die zugrunde liegende Template-Engine, etwa Twig in Symfony, eine Schleife über die Bestellpositionen, wobei jede Zeile dieselbe Tailwind-Klassenkombination erhält. Das Ergebnis ist eine Bestellbestätigung, die in Outlook dank MJMLs Tabellen-Fallbacks korrekt aussieht, in Gmail dank der anschließenden Inline-Pipeline zuverlässig gestylt ist, und deren Quellcode dank Tailwind-Klassen für Entwickler weiterhin gut lesbar bleibt.
<!-- Twig loop inside the mj-raw block, filled at runtime by Symfony Mailer -->
<table class="w-full">
{% for item in order.items %}
<tr class="border-b border-slate-200">
<td class="p-3">{{ item.name }}</td>
<td class="p-3 text-right">{{ item.quantity }}</td>
<td class="p-3 text-right">{{ item.total }}</td>
</tr>
{% endfor %}
</table>
7. Integration in Symfony Mailer und Magento-Transaktionen
In Symfony-Projekten lässt sich die kompilierte, inline-gestylte HTML-Datei direkt als Twig-Template im Symfony Mailer registrieren, wobei die MJML- und Tailwind-Kompilierung als separater Build-Schritt vor dem Deployment läuft, nicht zur Laufzeit. Der Mailer selbst muss dadurch weder MJML noch Tailwind kennen, sondern liefert lediglich fertiges HTML mit eingesetzten Twig-Variablen an den jeweiligen E-Mail-Provider aus.
In Magento-Kontexten lässt sich derselbe Ansatz für benutzerdefinierte Transaktions-E-Mail-Vorlagen nutzen, die über Magento\Framework\Mail\Template\TransportBuilder versendet werden. Die kompilierte HTML-Datei wird dabei als E-Mail-Template im Admin-Bereich hinterlegt, wobei Magentos eigene Variablen-Syntax mit doppelten geschweiften Klammern für dynamische Werte weiterhin funktioniert, solange der MJML- und Tailwind-Build-Schritt diese Platzhalter unverändert durchreicht, statt sie zu interpretieren.
8. Dynamische Daten und Platzhalter sicher einsetzen
Ein wichtiger Aspekt bei der Kombination aus MJML, Tailwind und einer serverseitigen Template-Engine ist die klare Trennung der Kompilierungsphasen. MJML und Tailwind laufen einmalig während des Builds und erzeugen eine statische HTML-Datei mit Platzhaltern für dynamische Werte. Diese Platzhalter, egal ob als Twig-Syntax, Magento-Direktiven oder einfache Platzhalter-Strings, dürfen vom MJML-Compiler und vom Inline-Werkzeug nicht verändert werden, da sonst die spätere Werteinsetzung fehlschlägt.
In der Praxis bedeutet das, Platzhalter so zu wählen, dass sie nicht mit MJML- oder HTML-Syntax kollidieren, und den Build-Schritt einmal testweise mit Beispieldaten durchlaufen zu lassen, um zu prüfen, dass alle Platzhalter nach dem Build noch unverändert vorhanden sind. Ein automatisierter Test, der nach dem Build gezielt nach den erwarteten Platzhalter-Strings sucht, fängt Regressionen ab, bevor eine fehlerhafte Vorlage produktiv eingesetzt wird.
# Automated regression test: verify placeholders survive the MJML + Tailwind build
grep -q "{{ order.number }}" dist/order-confirmation.html \
&& echo "OK: placeholder present" \
|| echo "FAIL: placeholder missing, template is broken"
# Run this check as part of CI right after the build step completes
9. MJML im Vergleich zu reinem Tabellen-HTML
Ob sich der zusätzliche MJML-Kompilierungsschritt lohnt, hängt stark von der Anzahl und Komplexität der Transaktions-E-Mail-Vorlagen ab. Die folgende Tabelle vergleicht beide Ansätze direkt.
| Kriterium | Reines Tabellen-HTML | MJML plus Tailwind |
|---|---|---|
| Wartbarkeit bei mehreren Vorlagen | Kopien pro E-Mail-Typ | Gemeinsame Includes für Header, Footer, Buttons |
| Outlook-Fallbacks | Manuell schreiben | Automatisch durch MJML-Compiler |
| Lernkurve | Nur HTML und CSS nötig | Zusätzliche MJML-Syntax lernen |
| Eignung | Eine einzelne, seltene E-Mail | Mehrere Transaktions-E-Mail-Typen mit gemeinsamen Bausteinen |
Für ein einzelnes, selten geändertes E-Mail-Template kann reines Tabellen-HTML mit Tailwind-Klassen ausreichend sein. Sobald ein Projekt mehrere Transaktions-E-Mail-Typen mit gemeinsamer Kopf- und Fußzeile pflegt, zahlt sich die Investition in MJML durch geringeren Wartungsaufwand und automatische Outlook-Fallbacks schnell aus.
Ein guter Anhaltspunkt für die Entscheidung ist die Anzahl der geplanten Transaktions-E-Mail-Typen zum Projektstart: Ab drei oder mehr Typen mit gemeinsamem Header und Footer überwiegt der Nutzen von MJML fast immer die zusätzliche Lernkurve.
Mironsoft
Tailwind CSS, MJML und Transaktions-E-Mails für Symfony- und Magento-Projekte
Wartbare Vorlagen für alle Transaktions-E-Mails?
Wir bauen MJML-Komponentenbibliotheken mit Tailwind-Design, gemeinsamen Bausteinen und einer Build-Pipeline, die direkt in Symfony Mailer oder Magento-Transaktions-E-Mails integriert wird.
Komponenten-Bibliothek
Gemeinsame MJML-Bausteine für Header, Footer und Buttons
Build-Pipeline
MJML, Tailwind und Inline-Styling in einem reproduzierbaren Prozess
PHP-Integration
Anbindung an Symfony Mailer und Magento TransportBuilder
10. Zusammenfassung
Die Kombination aus MJML und Tailwind CSS löst das strukturelle Problem von Transaktions-E-Mails: MJML übernimmt die zuverlässige, tabellenbasierte Layout-Ebene inklusive automatischer Outlook-Fallbacks, während Tailwind-Utility-Klassen das visuelle Design innerhalb der Komponenten liefern. Über <mj-include> lassen sich Kopfzeile, Fußzeile und Call-to-Action-Button als gemeinsame Bausteine für alle Transaktions-E-Mail-Typen pflegen, statt sie für jede Vorlage neu zu kopieren.
Der Build-Prozess aus MJML-Kompilierung, Tailwind-CSS-Generierung und abschließendem Inlinen mit juice lässt sich sauber von der eigentlichen Backend-Logik trennen, egal ob diese in Symfony Mailer oder in Magentos TransportBuilder läuft. Wer mehrere Transaktions-E-Mail-Typen mit gemeinsamem Design pflegt, spart mit diesem Ansatz erheblichen Wartungsaufwand gegenüber isolierten, händisch gepflegten Tabellen-HTML-Dateien.
<!-- shared-cta-button.mjml — one component reused by every transactional email -->
<mj-section>
<mj-column>
<mj-raw>
<a class="inline-block bg-sky-600 text-white font-bold py-3 px-6 rounded-lg"
href="{{ cta_url }}">{{ cta_label }}</a>
</mj-raw>
</mj-column>
</mj-section>
<!-- Included from order-confirmation.mjml, shipping-notification.mjml, and invoice.mjml -->
<!-- with cta_url and cta_label filled in individually by each calling template -->
Diese Wiederverwendbarkeit reduziert nicht nur den Pflegeaufwand, sondern verhindert auch visuelle Inkonsistenzen zwischen den einzelnen Transaktions-E-Mail-Typen, die sonst leicht entstehen, wenn jeder Entwickler den Button-Code für eine neue Vorlage erneut von Hand schreibt.
Transaktions-E-Mails mit Tailwind und MJML — Das Wichtigste auf einen Blick
Rollenverteilung
MJML für zuverlässige Tabellenstruktur und Outlook-Fallbacks, Tailwind für visuelles Design innerhalb der Komponenten.
Wiederverwendung
mj-include für gemeinsame Header, Footer und Buttons über alle Transaktions-E-Mail-Typen hinweg.
Build-Prozess
MJML kompilieren, Tailwind-CSS generieren, mit juice inlinen, als statische Vorlage ausliefern.
PHP-Integration
Kompiliertes HTML als Twig-Template in Symfony Mailer oder als Vorlage im Magento TransportBuilder.