MJML oder React Email: dieselbe Bestellbestätigung in beiden Tools
Von Mara Levin · Aktualisiert am 7. Oktober 2026 · Dokumentationen und Preise am selben Tag geprüft
Beide kosten nichts und erzeugen tabellenbasiertes HTML. Entscheidend ist, in welcher Sprache Ihr Backend läuft, wer die Vorlagen pflegt und ob dieselbe Mail auf Deutsch und Englisch erscheinen muss.
Die kurze Antwort
Nehmen Sie React Email, wenn Ihre Anwendung ohnehin in React und TypeScript geschrieben ist: Vorlagen werden zu typisierten Komponenten, und die Texte pro Sprache lassen sich laut Dokumentation mit next-intl auslagern. Nehmen Sie MJML, wenn Ihr Backend PHP, Python, Ruby, .NET oder etwas anderes ist, wenn mehrere Systeme dieselben Vorlagen rendern sollen oder wenn Sie ein kleines, lesbares Vokabular ohne JavaScript-Toolchain wünschen. Eine Lizenzgebühr fällt bei keinem der beiden an; Geld kostet erst der Versand.
Auf einen Blick
| Frage | MJML | React Email |
|---|---|---|
| Was Sie schreiben | Tags wie mj-section, mj-column, mj-text | React-Komponenten in TSX oder JSX |
| Installation | npm install mjml | npm install react-email -E |
| HTML erzeugen | CLI: mjml eingabe.mjml -o ausgabe.html, oder mjml2html in Node | render() liefert HTML, toPlainText() den Textteil |
| Sprache und Richtung | lang und dir am mjml-Tag | lang und dir an Html und Body |
| Mehrsprachigkeit | Nicht eingebaut; Template-Engine nach dem Kompilieren | Eigener Guide mit next-intl |
| Backends | Community-Ports für Laravel, Django, Rails, .NET, Elixir, Rust | Node mit React-Build |
| Lizenz | MIT laut MJML-FAQ | MIT laut GitHub-Seite |
| Hersteller | Mailjet | Resend |
Dieselbe Bestellbestätigung, zweimal geschrieben
Für einen fairen Vergleich braucht es dieselbe Aufgabe. Hier ist eine Bestellbestätigung, wie ein Onlineshop sie verschickt: Vorschautext, Anrede, ein Satz zum Versand, eine kleine Artikeltabelle, ein Button und eine Abmeldezeile. Die MJML-Fassung folgt dem Aufbau aus documentation.mjml.io: mj-title füllt laut Dokumentation den title und zugleich das aria-label des umgebenden div, mj-preview setzt den Vorschautext, mj-attributes legt Standardwerte fest, und mj-table nimmt gewöhnliches Tabellen-HTML auf.
<mjml lang="de">
<mj-head>
<mj-title>Ihre Bestellung 4812</mj-title>
<mj-preview>Wir haben Ihre Bestellung erhalten.</mj-preview>
<mj-attributes>
<mj-all font-family="Arial, Helvetica, sans-serif" />
<mj-button background-color="#0a58ca" color="#ffffff" />
</mj-attributes>
</mj-head>
<mj-body background-color="#f4f4f5">
<mj-section background-color="#ffffff">
<mj-column>
<mj-text font-size="22px" font-weight="bold">Danke für Ihre Bestellung, Frau Berger</mj-text>
<mj-text>Bestellung 4812 ist eingegangen und geht bis Donnerstag in den Versand.</mj-text>
<mj-table>
<tr style="text-align:left;">
<th scope="col">Artikel</th>
<th scope="col">Menge</th>
<th scope="col">Preis</th>
</tr>
<tr>
<td>Wanderrucksack 30 l</td>
<td>1</td>
<td>89,90 €</td>
</tr>
</mj-table>
<mj-button href="https://shop.example.de/bestellungen/4812">Bestellung ansehen</mj-button>
</mj-column>
</mj-section>
<mj-section>
<mj-column>
<mj-text font-size="12px" align="center">
Sie erhalten diese E-Mail, weil Sie bei shop.example.de bestellt haben.
<a href="{{abmelde_url}}">Abmelden</a>
</mj-text>
</mj-column>
</mj-section>
</mj-body>
</mjml> Kompiliert wird per Kommandozeile oder aus Node. Der Aufruf aus der Dokumentation ist asynchron; validationLevel kennt strict, soft und skip und steht standardmäßig auf soft, ein Tippfehler im Tag-Namen bricht den Build also nur ab, wenn Sie strict verlangen.
import mjml2html from 'mjml';
const { html, errors } = await mjml2html(quelltext, { validationLevel: 'strict' });
if (errors.length) throw new Error(errors.map((e) => e.message).join('\n')); Die React-Email-Fassung verwendet die Komponenten aus der React-Email-Dokumentation: Html mit lang und dir, Preview, Container, Section mit Row und Column, Button und Text. Die Daten kommen als typisierte Props herein.
import { Html, Head, Body, Preview, Container, Section, Row, Column, Heading, Text, Button, Link } from "react-email";
type Props = {
anrede: string;
nummer: string;
artikel: { name: string; menge: number; preis: string }[];
abmeldeUrl: string;
};
export default function Bestellung({ anrede, nummer, artikel, abmeldeUrl }: Props) {
return (
<Html lang="de" dir="ltr">
<Head />
<Preview>Wir haben Ihre Bestellung erhalten.</Preview>
<Body lang="de" dir="ltr" style={{ backgroundColor: "#f4f4f5", fontFamily: "Arial, Helvetica, sans-serif" }}>
<Container style={{ backgroundColor: "#ffffff", padding: "24px" }}>
<Heading as="h1" style={{ fontSize: "22px" }}>Danke für Ihre Bestellung, {anrede}</Heading>
<Text>Bestellung {nummer} ist eingegangen und geht bis Donnerstag in den Versand.</Text>
<Section>
{artikel.map((a) => (
<Row key={a.name}>
<Column>{a.name}</Column>
<Column>{a.menge}</Column>
<Column>{a.preis}</Column>
</Row>
))}
</Section>
<Button href={"https://shop.example.de/bestellungen/" + nummer}
style={{ background: "#0a58ca", color: "#ffffff", padding: "12px 20px" }}>
Bestellung ansehen
</Button>
<Text style={{ fontSize: "12px", textAlign: "center" }}>
Sie erhalten diese E-Mail, weil Sie bei shop.example.de bestellt haben.{" "}
<Link href={abmeldeUrl}>Abmelden</Link>
</Text>
</Container>
</Body>
</Html>
);
} Zum Rendern zeigt die render-Dokumentation den Weg zu HTML und Textteil:
import { render, toPlainText } from "react-email";
import Bestellung from "./emails/bestellung";
const html = await render(<Bestellung anrede="Frau Berger" nummer="4812" artikel={artikel} abmeldeUrl={url} />);
const text = toPlainText(html);
Was die beiden Beispiele zeigen
Die MJML-Datei ist kürzer und enthält fast keine Logik: Tags beschreiben das Layout, Schrift und Button-Farbe stehen einmal in mj-attributes. Dafür hat sie keine Schleife. Hätte die Bestellung fünf oder fünfzig Positionen, bräuchten Sie in MJML ein zweites Werkzeug, das die Tabellenzeilen erzeugt, etwa die Template-Engine Ihres Backends. In der React-Fassung ist dieselbe Liste eine map()-Schleife, ein falsch benannter Prop fällt beim Kompilieren auf, und die Artikelzeile lässt sich später als eigene Komponente in anderen Mails wiederverwenden. Dafür ist die Datei länger, und jemand muss Node und React im Build pflegen.
Ein Detail verdient Beachtung: In MJML legt mj-body laut Dokumentation um den Inhalt ein div mit role="article", aria-roledescription="email" und dem aria-label aus mj-title, und die Attribute lang und dir wandern an html und dieses div. React Email rät dagegen, lang und dir an Html und an Body gleich zu setzen, weil manche Mail-Clients die Tags html oder body entfernen. Wie weit das für Barrierefreiheit trägt, steht im Leitfaden zur Barrierefreiheit.
Mehrsprachige Mails: der größte praktische Unterschied
Wer für den deutschen Markt versendet, liefert selten nur eine Sprache aus: Österreich und die Schweiz, englischsprachige Kunden, vielleicht Französisch. Hier trennen sich die Wege. React Email hat in der Dokumentation einen Guide zu next-intl: Pro Sprache gibt es eine JSON-Datei, die Vorlage erhält einen locale-Parameter, und createTranslator liefert die Texte. Der Guide nennt eine Einschränkung, die man vor dem Einstieg kennen sollte: Andere Next.js-Funktionen stehen dem Vorschau-Server nicht zur Verfügung, weil er den next-intl-Kontext Ihrer App nicht kennt.
// messages/de.json (Aufbau wie im Guide der React-Email-Dokumentation)
{
"bestellung": {
"titel": "Danke für Ihre Bestellung",
"eingegangen": "Ihre Bestellung ist eingegangen.",
"button": "Bestellung ansehen"
}
}
// emails/bestellung.jsx
import { createTranslator } from "next-intl";
export default async function Bestellung({ locale }) {
const t = createTranslator({
messages: await import(`../messages/${locale}.json`),
namespace: "bestellung",
locale,
});
// ... im JSX: {t("titel")}, {t("eingegangen")}, {t("button")}
} MJML kennt keine Übersetzungsschicht. Üblich ist, die Texte als Platzhalter zu schreiben, zu kompilieren und danach mit der Template-Engine des Backends zu füllen. Die Dokumentation warnt bei mj-html-attributes, dass HTML-ähnliche Template-Tags beim erneuten Parsen verändert werden können, und empfiehlt, die Template-Engine nach dem Rendering einzusetzen; für Tags, die das Kleiner-Zeichen enthalten, gibt es den Kommentar <!-- htmlmin:ignore --> in Verbindung mit der Minify-Option. Wer bereits Twig, Blade oder Jinja im Backend nutzt, hat damit seine Mehrsprachigkeit. Wer sie neu aufbauen müsste, spart mit React Email einen Schritt.
Backend und Betrieb
MJML läuft über das Node-Kommando in jeder CI, und die Dokumentation listet Ports und Bindings: MRML in Rust, MJML.NET, eine Elixir-Anbindung, Emjay mit einem ActionMailer-Interceptor für Rails, zwei Python-Pakete, django-mjml sowie Laravel-Pakete für PHP. Die Dokumentation vermerkt, dass das MJML-Team diese Beiträge nicht direkt unterstützt, und beim Rust-Port steht, dass mj-style mit inline noch nicht umgesetzt ist. Für einen Shop auf Laravel oder Symfony heißt das: Entweder Sie bleiben beim offiziellen Node-Compiler im Build und liefern HTML aus, oder Sie hängen sich an einen Community-Port und prüfen vorher dessen Pflegestand.
React Email dagegen sitzt in einem Node-Projekt. Die Integrationsseiten nennen Resend, Nodemailer, Mailgun, SendGrid, Postmark, AWS SES, Brevo, MailerSend und weitere Dienste; sie zeigen, wie der gerenderte String an den jeweiligen Versand übergeben wird. Ein Entwicklungsserver mit Live-Vorschau ist Teil des Pakets, bei MJML bleiben der Watch-Modus mjml -w, das VS-Code-Plugin und der kostenlose Online-Editor.
Rendering in Outlook und Co.
Keine der beiden Dokumentationen verspricht identische Darstellung überall. React Email gibt an, die Komponenten seien in Gmail, Apple Mail, Outlook, Yahoo! Mail, HEY und Superhuman getestet worden, und die Render-Ausgabe eines Buttons enthält mso-Bedingungskommentare für Outlook. MJML spricht von responsivem HTML auch in Outlook, warnt aber bei mj-button, dass der Button wegen der Client-Unterstützung nicht vollständig klickbar sei. Prüfen Sie Ihre eigene Vorlage deshalb in den Clients Ihrer Empfänger; was dabei meist über Erfolg entscheidet, ist die Vorlage, nicht das Framework. Die Grundlagen finden Sie im CSS-Leitfaden und im Leitfaden zur Client-Kompatibilität.
Was der Versand kostet
Die Frameworks selbst kosten nichts: MJML steht laut FAQ unter der MIT-Lizenz, React Email laut GitHub-Seite ebenfalls. Bezahlen müssen Sie den Versanddienst, und dessen Rechnung hängt vom Volumen ab. Zwei Beispiele von den Preisseiten (7. Oktober 2026): Bei Resend kostet der Free-Tarif 0 $ für 3.000 E-Mails im Monat bei höchstens 100 pro Tag, Pro 20 $ für 50.000 und auf dem Schieberegler 35 $ bei 100.000 E-Mails; Scale beginnt bei 90 $ für 100.000. Postmark nennt auf dem Schieberegler für den Tarif Basic 15 $ bei 10.000, 55 $ bei 50.000 und 115 $ bei 125.000 E-Mails. Ein Shop mit 4.000 Bestellmails im Monat landet bei Resend also über dem Free-Tarif im Pro-Tarif für 20 $, bei Postmark im 10.000er-Tarif für 15 $. Die Details, auch zu Webhooks und Idempotenz, stehen im Vergleich Resend gegen Postmark.
Wann welches Tool
React Email passt, wenn…
- Ihre Anwendung bereits in React oder Next.js läuft und E-Mail-Daten dieselben Typen verwenden sollen.
- die Vorlagen Logik enthalten: Schleifen über Positionen, bedingte Blöcke, mehrere Sprachfassungen.
- Entwickler die Vorlagen allein pflegen und die schnelle Live-Vorschau mehr zählt als Reichweite.
MJML passt, wenn…
- Ihr Backend PHP, Python, Ruby, .NET oder Go ist und Sie keine React-Toolchain nur für E-Mails aufbauen möchten.
- mehrere Teams oder Dienste dieselben kompilierten Vorlagen verwenden.
- Sie ein kleines, deklaratives Vokabular schätzen und fertige Bausteine wie Hero, Karussell oder Akkordeon nutzen wollen.
Wenn Ihnen beides zu viel Code ist: Für visuelle Editoren lesen Sie den Leitfaden zu HTML-E-Mail-Buildern; zu Tailwind-Workflows siehe Maizzle gegen MJML und React Email gegen Maizzle.
Wechsel zwischen den Tools
Einen Konverter, dem man trauen sollte, gibt es nicht, und die Layouts unterscheiden sich so stark, dass ein Neuaufbau meist schneller geht als eine Übersetzung. Bewährt hat sich folgender Weg: Die alten Vorlagen bleiben aktiv, eine unkritische Mail (Passwort-Reset, Versandbenachrichtigung) wird im neuen Tool nachgebaut, beide Fassungen laufen durch dieselbe Client-Vorschau, und erst nach dem Vergleich der Screenshots folgen die übrigen. Beginnen Sie mit den einfachsten Layouts.
Häufige Fragen
Ist React Email nur ein React-Aufsatz auf MJML?
Nein, die beiden Projekte sind unabhängig. MJML ist eine Auszeichnungssprache mit eigenem Compiler, und die MJML-Dokumentation führt unter den Community-Ports das Paket mjml-react auf, das MJML-Tags als React-Komponenten verpackt. React Email besteht dagegen aus eigenen Komponenten, die per render() zu einem HTML-String werden; ein MJML-Compiler ist daran nicht beteiligt.
Kann ich MJML nutzen, wenn mein Backend PHP oder Python ist?
Ja, das ist die Stärke von MJML. Die Dokumentation nennt Community-Anbindungen für Laravel, Django, Rails (Emjay), .NET, Elixir und Rust (MRML) und weist darauf hin, dass das MJML-Team diese Beiträge nicht direkt unterstützt. Alternativ kompilieren Sie die Vorlage einmal in der CI mit dem Node-Kommando und liefern das fertige HTML an Ihr Backend. React Email setzt dagegen überall, wo gerendert wird, einen Node- und React-Build voraus.
Wie bekomme ich deutsche und englische Fassungen in eine Vorlage?
React Email beschreibt in einem eigenen Guide, wie Sie mit next-intl pro Sprache eine JSON-Datei anlegen und der Vorlage einen locale-Parameter übergeben. MJML bringt keine Übersetzungsfunktion mit: Sie kompilieren die Vorlage und füllen Texte mit der Template-Engine Ihres Backends. Die Dokumentation warnt bei mj-html-attributes, dass HTML-ähnliche Template-Tags verändert werden können, und rät, die Template-Engine erst nach dem MJML-Rendering laufen zu lassen.
Brauche ich Resend, wenn ich React Email nutze?
Nein. React Email stammt von Resend und hat eine dokumentierte Resend-Integration, die Dokumentation beschreibt aber auch Nodemailer, Mailgun, SendGrid, Postmark, AWS SES und weitere Dienste. Das Ergebnis von render() ist ein gewöhnlicher HTML-String, den jeder Versanddienst annimmt.