Skip to content

EvanSchleret/lara-mjml

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

About

Laravel wrapper for MJML that adds a Blade-based MJML email templating engine, seamlessly compiling MJML to responsive HTML for use with Laravel Mailables and Notifications.

Topics

Resources

License

Code of conduct

Contributing

Security policy

Stars

Watchers

Forks

Contributors

Languages