evanschleret/lara-mjml

Just a service provider for Spatie's MJML wrapper

Maintainers

Package info

github.com/EvanSchleret/lara-mjml

Homepage

pkg:composer/evanschleret/lara-mjml

Fund package maintenance!

EvanSchleret

Statistics

Installs: 23 492

Dependents: 0

Suggesters: 0

Stars: 17

Open Issues: 1

v1.0.0 2026-03-25 12:18 UTC

This package is auto-updated.

Last update: 2026-03-25 12:25:43 UTC


README

LaraMJML banner

LaraMJML

MJML rendering for Laravel Blade emails with a dedicated view engine, configurable MJML options, and predictable output for Mailables and Notifications.

Packagist Version Packagist Downloads License Tests PHP >= 8.2 Laravel 12.x | 13.x

Why LaraMJML

LaraMJML gives you a focused way to render MJML in Laravel without changing your email workflow:

  • keeps Laravel Blade as the template layer
  • compiles .mjml.blade.php layouts through an MJML view engine
  • supports MJML runtime options from Laravel config
  • works with Mailables and Notifications
  • keeps rendering behavior deterministic across environments

Requirements

  • PHP >=8.2
  • Laravel 12.x or 13.x
  • Node.js runtime with the mjml package installed

Installation

Install the package:

composer require evanschleret/lara-mjml

Install MJML in your Laravel app:

npm install mjml

Publish the package config (optional):

php artisan vendor:publish --provider="EvanSchleret\LaraMjml\Providers\LaraMjmlServiceProvider"

Quick start

Create an MJML layout:

<mjml>
  <mj-body>
    <mj-section>
      <mj-column>
        @yield('content')
      </mj-column>
    </mj-section>
  </mj-body>
</mjml>

Save it as:

resources/views/layouts/base.mjml.blade.php

Create your email view with regular Blade inheritance:

@extends('layouts.base')

@section('content')
  <mj-text>Hello {{ $userName }}</mj-text>
@endsection

Save it as:

resources/views/emails/welcome.blade.php

Blade file rules

Use this hierarchy to avoid malformed MJML:

  • the layout contains <mjml> and <mj-body>
  • the layout filename includes .mjml.blade.php
  • child views extend the MJML layout
  • child views do not include .mjml in the filename

Correct:

resources/views/layouts/base.mjml.blade.php
resources/views/emails/welcome.blade.php

Incorrect:

resources/views/layouts/base.mjml.blade.php
resources/views/emails/welcome.mjml.blade.php

Use with Mailable

<?php

namespace App\Mail;

use Illuminate\Bus\Queueable;
use Illuminate\Mail\Mailable;
use Illuminate\Mail\Mailables\Content;
use Illuminate\Mail\Mailables\Envelope;
use Illuminate\Queue\SerializesModels;

class WelcomeMail extends Mailable
{
    use Queueable;
    use SerializesModels;

    public function __construct(
        public string $userName,
    ) {}

    public function envelope(): Envelope
    {
        return new Envelope(
            subject: 'Welcome',
        );
    }

    public function content(): Content
    {
        return new Content(
            view: 'emails.welcome',
            with: [
                'userName' => $this->userName,
            ],
        );
    }
}

Send it:

use App\Mail\WelcomeMail;
use Illuminate\Support\Facades\Mail;

Mail::to($user->email)->send(new WelcomeMail($user->name));

Use with Notification

<?php

namespace App\Notifications;

use App\Models\User;
use Illuminate\Bus\Queueable;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Notification;

class WelcomeNotification extends Notification
{
    use Queueable;

    public function __construct(
        private readonly User $user,
    ) {}

    public function via(object $notifiable): array
    {
        return ['mail'];
    }

    public function toMail(object $notifiable): MailMessage
    {
        return (new MailMessage())
            ->subject('Welcome')
            ->view('emails.welcome', [
                'userName' => $this->user->name,
            ]);
    }
}

Dispatch it:

$user->notify(new WelcomeNotification($user));

Configuration

The config/laramjml.php file controls:

  • binary_path: path to the Node binary for MJML execution
  • beautify: format generated HTML
  • minify: minify generated HTML
  • keep_comments: preserve MJML comments in output
  • options: extra options passed to MJML

Environment variables:

MJML_NODE_PATH=null
LARA_MJML_BEAUTIFY=false
LARA_MJML_MINIFY=true
LARA_MJML_KEEP_COMMENTS=false

Troubleshooting

  • Empty or broken HTML: ensure only the layout contains <mjml> and <mj-body>
  • MJML binary error: install mjml in the project and verify Node.js is available
  • Malformed MJML exceptions: remove .mjml suffix from child views and keep it only on the layout

Testing

Run tests:

composer test

Validate MJML templates

Validate all MJML Blade layouts:

php artisan laramjml:validate

Validate specific paths:

php artisan laramjml:validate --path=resources/views/emails
php artisan laramjml:validate --path=resources/views/layouts/base.mjml.blade.php

Use a different validation level:

php artisan laramjml:validate --validation=soft

The command returns a non-zero exit code when at least one template fails validation, so it is ready for CI workflows.

Roadmap

  • Add an optional Artisan install command (laramjml:install) to publish config in one step
  • Add optional plain HTML fallback rendering when MJML conversion fails
  • Add built-in MJML lint/validate command for CI workflows
  • Add a stub generator for MJML layout + starter email view
  • Add snapshot tests for common MJML output patterns
  • Add first-party examples for dark mode email patterns
  • Add docs for queue-first email pipelines with MJML pre-rendering
  • Add benchmarking docs and performance tips for high-volume mailing

Other packages

If you want to explore more of my packages:

Open source