spatie/mjml-php
Convert MJML email markup to responsive HTML from PHP. Spatie’s mjml-php wraps the Node mjml compiler (Node 16+ required) and provides a simple API like Mjml::new()->toHtml($mjml) to render production-ready email HTML.
Installation:
composer require spatie/mjml-php
No additional configuration is required—just autoload the package.
First Use Case: Convert a simple MJML template to HTML:
use Spatie\Mjml\Mjml;
$mjml = <<<'MJML'
<mjml>
<mj-body>
<mj-section>
<mj-column>
<mj-text>Hello, World!</mj-text>
</mj-column>
</mj-section>
</mj-body>
</mjml>
MJML;
$html = Mjml::new()->toHtml($mjml);
Output: Renders a responsive email-ready HTML block.
Where to Look First:
Template Storage:
resources/views/emails/welcome.mjml) or as strings.<mjml>
<mj-body>
<mj-section>
<mj-column>
<mj-text>{{ $greeting }}</mj-text>
</mj-column>
</mj-section>
</mj-body>
</mjml>
Conversion in Controllers/Jobs:
use Spatie\Mjml\Mjml;
use Illuminate\Support\Facades\View;
$mjml = View::file('emails.welcome', ['greeting' => 'Hi there!']);
$html = Mjml::new()->toHtml($mjml);
Integration with Mailables:
Extend Laravel’s Mailable class to use MJML:
use Spatie\Mjml\Mjml;
use Illuminate\Mail\Mailable;
class WelcomeMailable extends Mailable
{
public function build()
{
$mjml = $this->view('emails.welcome');
$html = Mjml::new()->toHtml($mjml);
return $this->subject('Welcome!')
->html($html);
}
}
Dynamic Content:
Use MJML’s built-in components (e.g., <mj-button>, <mj-image>) for interactive elements:
<mj-button href="{{ $url }}" background-color="#000000">
Click Me
</mj-button>
Caching: Cache converted HTML to avoid reprocessing:
$html = Cache::remember("mjml_{$template}", now()->addHours(1), function () {
return Mjml::new()->toHtml($mjml);
});
Invalid MJML:
Email Client Quirks:
use Spatie\Mjml\Mjml;
use Premailer\Premailer;
$html = Mjml::new()->toHtml($mjml);
$premailer = new Premailer();
$html = $premailer->transform($html);
Performance:
Attribute Handling:
padding, background-color) must match MJML spec. Invalid attributes are ignored silently.Enable Verbose Output:
Use Mjml::new()->debug(true) to log conversion steps (helpful for troubleshooting).
Compare Snapshots: For complex templates, compare output against MJML’s live preview or the package’s snapshot tests.
Check for Deprecated Tags:
The package supports MJML v4+. If using older MJML, update tags (e.g., <mj-section> instead of <mjml-section>).
Custom Components: Extend the package by creating a wrapper for reusable MJML snippets:
class EmailComponent
{
public static function header(string $title): string
{
return <<<MJML
<mj-section>
<mj-column>
<mj-text font-size="24px" color="#333">{$title}</mj-text>
</mj-column>
</mj-section>
MJML;
}
}
Post-Processing: Chain methods to modify HTML after conversion:
$html = Mjml::new()
->toHtml($mjml)
->replace('{{url}}', $this->url)
->prepend('<div>Footer</div>');
Configuration: Override default options (e.g., minify output):
Mjml::new()->minify(true)->toHtml($mjml);
mjml --watch to preview changes in real-time.// mix.js
const mjml = require('mjml');
mix.process('resources/mjml/email.mjml', (content) => {
return mjml(content).html;
});
expect($html)->toMatchSnapshot();
How can I help you explore Laravel packages today?