Craft emails that look good in each email client using MJML
In a perfect world, email clients can render HTML as good as major browsers. Unfortunately, this is not the case. Email clients don't support modern HTML and CSS niceties and have a lot of quirks to be mindful of. Making sure an HTML email looks good in the most used email clients takes a lot of work.
To make crafting HTML emails a lot more enjoyable, the folks at Mailjet created a solution called MJML, which stands for "Mailjet Markup Language." It's an easy-to-use abstraction layer over HTML.
We have created a new package called spatie/mjml-php to easily convert MJML to HTML using PHP. If you're using Sidecar, you'll be happy to know that we've also created a package called spatie/mjml-sidecar, to convert MJML to HTML using Sidecar.
In this blog post, I'd like to introduce the package to you.
Taking a look at MJML
MJML looks very much like HTML but with mj
tags. Let's take a look at some valid MJML.
<mjml>
<mj-body>
<mj-section>
<mj-column>
<mj-text invalid-attribute>Hello World</mj-text>
</mj-column>
</mj-section>
</mj-body>
</mjml>
In the code above, you can see that there are many mj
components that you can make use of. I won't explain them all as they are described in the extensive documentation. In those docs, you'll also find a complete example of an entire mail.
When converting the MJML to HTML, this is the result:
<!doctype html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:o="urn:schemas-microsoft-com:office:office">
<head>
<title></title>
<!--[if !mso]><!-->
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<!--<![endif]-->
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<style type="text/css">
#outlook a {
padding: 0;
}
body {
margin: 0;
padding: 0;
-webkit-text-size-adjust: 100%;
-ms-text-size-adjust: 100%;
}
table,
td {
border-collapse: collapse;
mso-table-space: 0pt;
mso-table-space: 0pt;
}
img {
border: 0;
height: auto;
line-height: 100%;
outline: none;
text-decoration: none;
-ms-interpolation-mode: bicubic;
}
p {
display: block;
margin: 13px 0;
}
</style>
<!--[if mso]>
<noscript>
<xml>
<o:OfficeDocumentSettings>
<o:AllowPNG/>
<o:PixelsPerInch>96</o:PixelsPerInch>
</o:OfficeDocumentSettings>
</xml>
</noscript>
<![endif]-->
<!--[if lte mso 11]>
<style type="text/css">
.mj-outlook-group-fix { width:100% !important; }
</style>
<![endif]-->
<!--[if !mso]><!-->
<link href="https://fonts.googleapis.com/css?family=Ubuntu:300,400,500,700" rel="stylesheet" type="text/css">
<style type="text/css">
@import url(https://fonts.googleapis.com/css?family=Ubuntu:300,400,500,700);
</style>
<!--<![endif]-->
<style type="text/css">
@media only screen and (min-width:480px) {
.mj-column-per-100 {
width: 100% !important;
max-width: 100%;
}
}
</style>
<style media="screen and (min-width:480px)">
.moz-text-html .mj-column-per-100 {
width: 100% !important;
max-width: 100%;
}
</style>
<style type="text/css">
</style>
<style type="text/css">
</style>
</head>
<body style="word-spacing:normal;">
<div style="">
<!--[if mso | IE]><table align="center" border="0" cellpadding="0" cellspacing="0" class="" role="presentation" style="width:600px;" width="600" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]-->
<div style="margin:0px auto;max-width:600px;">
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width:100%;">
<tbody>
<tr>
<td style="direction:ltr;font-size:0px;padding:20px 0;text-align:center;">
<!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td class="" style="vertical-align:top;width:600px;" ><![endif]-->
<div class="mj-column-per-100 mj-outlook-group-fix" style="font-size:0px;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;">
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top;" width="100%">
<tbody>
<tr>
<td align="left" style="font-size:0px;padding:10px 25px;word-break:break-word;">
<div style="font-family:Ubuntu, Helvetica, Arial, sans-serif;font-size:13px;line-height:1;text-align:left;color:#000000;">Hello World</div>
</td>
</tr>
</tbody>
</table>
</div>
<!--[if mso | IE]></td></tr></table><![endif]-->
</td>
</tr>
</tbody>
</table>
</div>
<!--[if mso | IE]></td></tr></table><![endif]-->
</div>
</body>
</html>
Wow, that's a lot of HTML you didn't have to come up with yourself. Neat! As you can see above, some code is being added to ensure it renders well in older email clients.
Converting MJML to HTML
The MJML provides many different options to convert MJML to HTML: there is a Node tool, a CLI tool, and also a website where you can convert MJML manually. Using one of the many plugins, you can also have auto-completion for MJML in your favorite editor.
Our newest package, spatie/mjml-php, can convert MJML to HTML using PHP. It's a handcrafted, easy-to-use wrapper around the Node tool. By using the Node tool under the hood, we can just make use of all functionality there without having to implement it all in PHP. But as a user of the PHP package, you don't need to mind all this.
The package can be installed using composer:
composer require spatie/mjml-php
Also, you should make sure the Node MJML package is available in your project.
npm install mjml
With this out of the way, you can start converting MJML.
use Spatie\Mjml\Mjml;
// let's assume $mjml contains the MJML you want to convert
$html = Mjml::new()->toHtml($mjml);
There are a couple of methods, such as beautify
, hideComments()
, minify()
to customize the conversion process.
use Spatie\Mjml\Mjml;
// let's assume $mjml contains the MJML you want to convert
$minifiedHtml = Mjml::new()->minify()->toHtml($mjml);
We also added a method that you can use to ensure the given MJML is valid.
use Spatie\Mjml\Mjml;
Mjml::new()->canConvert($mjml); // returns a boolean
Integration with Sidecar
Sidecar is a fantastic package that easily lets you use other programming languages besides PHP in your Laravel codebase. Under the hood, this work by executing code in another language on AWS Lambda.
We've created a package spatie/mjml-sidecar to convert MJML to HTML using Sidecar. This way, the conversion process will be done on AWS, and you don't have to have Node available on your server.
With the sidecar package installed, you should have to tack on the sidecar()
method to execute the conversion on AWS.
use Spatie\Mjml\Mjml;
// let's assume $mjml contains the MJML you want to convert
$html = Mjml::new()->sidecar()->toHtml($mjml);
Why we built this
At Spatie, we're always building packages not only because we like doing it but because we need them ourselves. The MJML package will be used in our paid product Mailcoach. Mailcoach is an affordable, developer- and privacy-friendly solution to send newsletters, set up email automation, and send transactional emails.
Using Mailcoach, you could already create your templates and emails using Markdown. The spatie/mjml-php package will be used to add MJML support to Mailcoach. We bet that many of our users will be happy that using MJML will make it easier to create emails that will look good to each email client.
MJML support will be added in the upcoming v7 of Mailcoach in both the hosted and self-hosted versions. We expect to ship this release in the next couple of weeks.
In closing
There are a couple of options available on our MJML package that we didn't cover in our blog post. To learn more, head over to the readme of spatie/mjml-php on GitHub.
This is one of many packages that we've made. You'll find an extensive list of Laravel and PHP packages we released previously on our company website. There's probably something there for your next project. If you like our open-source stuff, be sure to also look at our paid products that make working on open-source sustainable for our company.
What are your thoughts on "Craft emails that look good in each email client using MJML"?