Évaluons votre projet

MJML, un framework simple pour des mails simples

Publié le 22 avril 2021 par Victor Darras | framework - emails

Cet article est publié sous licence CC BY-NC-SA

Bonjour à tous, aujourd’hui un article où je vais vous parler d’email, pourquoi je n’aime pas devoir en intégrer, et comment on va se simplifier ce fastidieux processus avec un outil bien pratique, MJML.

hide-the-pain

"Victor, on a besoin d'un nouveau template mail pour xxx, t'as un truc rapidos ?"

Je ne vous cache pas mon appréhension quant à l’intégration d’un email. Très à l’aise avec les navigateurs web modernes et parfois même leurs plus vicieuses subtilités je repars souvent de rien lorsqu’il s’agit de développer pour ces « moteurs de rendu » dont le manque de fonctionnalités n’a d’égal que leur capacité à interpréter les règles (CSS) ou balises (HTML) de manière si singulière.

Et, tout à coup… je découvre MJML.

Une présentation s’impose ?

MJML, est un langage type XML, proche de HTML qui permet d’abstraire la complexité inhérente à l’intégration pour les lecteurs de mail. Produit par Mailjet (des petits gars qui ont de la bouteille pour ce qui est des emailings), cet outil semble réduire énormément les frictions quant à l’adaptation responsive des emails, mais aussi à harmoniser leur visuel selon le lecteur de l’utilisateur. Ils s’engagent aussi à maintenir l’outil en fonction des évolutions des différents mailers existant, chose quasi impossible sans une équipe dédiée et chevronnée. MJML n’étant plus tout neuf à l’heure où j’écris ces lignes, je suis sûr que vous trouverez de très bons articles (en plus de la doc) plus exhaustifs que celui-ci, mais nous allons prendre un exemple concret.

Un cas pratique : une liste d’offres, responsive

Pour l’exercice, je vais prendre en exemple un template d’email tout à fait commun dans lequel on va présenter un ensemble d’offres pour une boutique qui vend des bouteilles de vin sur base hebdomadaire.

On aura donc dans ce mail, un header avec le logo de la marque, la baseline, quelques mots d’introduction puis, sur 2 colonnes on affichera des « cartes » de présentation d’offres qui se rangeront sur une colonne pour les devices les moins larges. Enfin le footer de mail, qui sera lui aussi composé de plusieurs éléments en colonne.

Si vous désirez suivre cet article en faisant le même exercice, je vous invite à utiliser l’éditeur web qui sera le plus rapide à mettre en place, mais il existe des plugins pour quelques éditeurs connus et même une app native bien pratique.

screenshot1

Structure de base, HTML, head, body, ça vous parle ?

Commençons par les balises de premier niveau. On va ouvrir une balise <mjml> à la racine, là où nous aurions ouvert une balise <html> d’habitude. Juste en dessous, nous ouvrons une balise <mj-head> qui comme <head> nous permettra d’ajouter des méta-données, des attributs pour d’autres balises… Maintenant vient le contenu, et comme sur le web nous l’appellerons body, et il aura sa balise <mj-body>.

En enfant de ce mj-body nous trouverons un ensemble de sections horizontales que nous déclarerons avec <mj-section> qui elles-mêmes contiendrons des <mj-column> dont le nom vous évoquera subtilement l’usage.

Si je devais diviser mon email d’exemple en section, nous en aurions 3 grandes ; un header avec logo/baseline/intro, le contenu principal avec la liste des offres et le footer. En pratique nous allons en avoir une autre dédiée à l’en-tête de liste avec le lien sur la droite.

screenshot2

Pour chacune de ces sections nous allons donc ajouter une ou plusieurs colonnes en fonction du nombre d’informations que nous voulons présenter horizontalement. Pour le header, nous n’en aurons besoin que d’une, puisque le logo, sa baseline puis l’intro s’affiche l’un en dessous de l’autre « naturellement ».

Passons aux choses sérieuses, un peu de contenu

Notre première image, baseline et intro

<mj-image>

srcset semble ne pas fonctionner avec des URL locales. J’ai fait tourner un petit serveur pendant mes développements.

Gestion du srcset facile sinon, comme en web.

<mj-image width="180px" alt="logo" src="logo.png" srcset="http://192.168.43.170:8080/logo%402x.png 2x, http://192.168.43.170:8080/logo%403x.png 3x" />

<mj-text>

Cette balise très simple nous permet d’afficher du texte. Avec différents attributs inspirés des règles CSS associées, on peut styliser rapidement la baseline. L’intro — quant à elle — utilise les styles par défaut.

<mj-text font-size="21px" align="center" color="#A32468">Here’s the baseline</mj-text>
<mj-text>Lorem ipsum dolor sit amet… </mj-text>

MJML gère les colonnes imbriquées ?

non

Au-dessus de la liste des offres, nous voulons afficher une section simple, avec un titre, un « sur-titre » et sur la droite un lien pour afficher l’ensemble des offres sur le site.

On commence par définir quelques attributs sur la section concernée. Une couleur de fond légère pour marquer la distinction avec l’intro, et un padding, global à ce header. Il ne faudra pas oublier d’annuler quelques paddings des enfants directs pour s’assurer de rester aligner avec le début du contenu. Juste en dessous, on ajoute 2 mj-column de 80 et 20%. La première contient 2 simples mj-text, et la seconde me pose problème. Comment afficher 2 éléments côte-à-côte avec les balises apportées par MJML mais sans pouvoir rajouter des colonnes (puisque nous sommes déjà dans l’une d’elle) ?

Eh bien en utilisant un peu de HTML « à l’ancienne » dans un tag mj-raw. On peut ajouter un paragraphe pour aligner l’ensemble à droite, dans lequel on ajoute un span pour le texte et un img pour le picto de flèche.

<mj-section background-color="#f5f5f5"  padding="10px 20px" >
  <mj-column width="80%">
    <mj-text padding="0" font-size="14px" color="#666">Semaine du 02/11 au 07/11</mj-text>
    <mj-text padding="0" font-size="24px" text-transform="Uppercase">Offres de la semaine</mj-text>
  </mj-column>
  <mj-column width="20%">
    <mj-raw>
      <p style="text-align: right">
        <span style="font-size:14px;font-weight:700;color:#666;text-align:right;font-family:sans-serif">VOIR TOUT &nbsp;</span>
        <img
          width="24px"
          src="http://localhost:8080/images/arrow-right.png"
          srcset="http://localhost:8080/images/arrow-right%402x.png 2x, http://localhost:8080/images/arrow-right%403x.png 3x"
        >
      </p>
    </mj-raw>
  </mj-column>
</mj-section>

Puisqu’on utilise ici du HTML, les attributs de style ne sont plus disponibles, mais l’attribut style lui, l’est de nouveau.

La liste en grille

Vient ce moment tant attendu où la fonctionnalité responsive d’MJML fait vraiment son office. Afficher 2 colonnes de contenu, qui passeront ensuite sur 2 lignes. Dans la maquette d’exemple, on a 3 lignes de 2 colonnes.

Pour contenir nos 3 lignes, et nous prémunir des styles par défaut qui ajouteraient des padding un peu partout, nous commençons par un mj-wrapper qui aura la responsabilité d’afficher un fond blanc à l’ensemble des offres. En enfants directs nous aurons donc 3 fois la même structure : 2 colonnes dans une section.

Enfin pour chaque section, nous utiliserons un même partial qui prendra des paramètres dans l’implémentation finale afin d’afficher différentes offres. Pour le moment nous nous contenterons d’afficher le même à chaque fois. La bordure visible sur la maquette autour de chaque offre nous force à retirer les paddings de cette structure, que nous reporterons sur le partial d’offre.

<mj-wrapper background-color="#fff" padding="0">

  <!-- Duplicate this section, for each line you need -->
  <mj-section padding="0">
    <mj-column padding="0">
      <mj-include path="./offer.mjml">
    </mj-column>
    <mj-column padding="0">
      <mj-include path="./offer.mjml">
    </mj-column>
  </mj-section>

</mj-wrapper>

La carte offre

Ici m’est venu la première appréhension quant à la pertinence du framework pour mon template d’email. J’aurais encore besoin d’afficher 2 colonnes pour mettre l’image sur la gauche, et le texte à droite, voire pour afficher le tag à côté du prix. On va donc repasser en HTML, mais en profitant un petit peu de MJML avec son tag mj-table. Il est d’abord pensé pour afficher un tableau de données, et ne prend pas de tag du framework en enfant, seulement du HTML. Ça tombe bien puisque l’on comptait s’en servir autrement ! On commence par créer une ligne avec un tr, puis nos 2 colonnes en td. Dans la première on vient ajouter nos images de bouteille. Et dans la seconde l’ensemble des textes. Attention, par défaut ils n’hériteront pas des styles du « document » il faudra surcharger les styles du tableau, mais nous verrons ça plus tard.

Pour la seconde colonne, même technique que pour le lien « VOIR TOUT », un paragraphe contenant 2 éléments inline. Pour l’exemple, vous voyez que j’ai utilisé une classe tag sur l’élément « offre spéciale ». Nous pourrons aussi ajouter un ensemble de styles qui seront intégrés inline à la compilation.

<mj-table padding="0" border="0.5px solid #eee;" >
  <tr>
    <td style="padding: 10px 10px 10px 20px;">
      <img width="42px"
        alt="bottle"
        src="http://localhost:8080/images/bottle.png"
        srcset="http://localhost:8080/images/bottle%402x.png 2x, http://localhost:8080/images/bottle%403x.png 3x"
      />
    </td>
    <td style="padding: 10px 20px 10px 10px;">
      <p style="font-size:18px;font-weight:bold;margin:0">Nom de la cuvée<br/>2012</p>
      <p style="font-size:16px;margin:0">Producteur<br/>75cl</p>
      <p>
        <span class="tag">
          Offre spéciale
        </span>
        <span style="display:inline-block;font-size: 24px;font-weight:700;float:right;">15€</span>
      </p>
    </td>
  </tr>
</mj-table>

Même méthode que pour le reste du contenu, si ce n’est qu’on doit ici ajouter une couleur de fond et changer celle du texte. On aura aussi 3 colonnes différentes en tailles :

<mj-section background-color="#A32468">

  <mj-column padding="0 15px" width="25%">
    <mj-image width="110px" alt="footer-logo" src="images/footer-logo.png" srcset="http://localhost:8080/images/footer-logo%402x.png 2x, http://localhost:8080/images/footer-logo%403x.png 3x" />
  </mj-column>

  <mj-column padding="0 15px" width="50%">
    <mj-text color="#fff" font-weight="300" font-size="12px">
      Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exerc
    </mj-text>
  </mj-column>

  <mj-column padding="0 15px" width="25%">
    <mj-text color="#fff" font-weight="700" font-size="12px" text-transform="uppercase">
      Contact<br />
      0642060606
    </mj-text>
  </mj-column>

</mj-section>

Prenons-nous un peu la tête

Maintenant que l’ensemble du contenu est mis en place, prenons un peu de temps pour remonter en haut de notre document et compléter cette balise <mj-head> tristement vide.

Pour l’exemple j’ai gardé quelque chose de très simple, mais vous comprendrez vite comment étendre l’usage de ces quelques outils. Prenons une balise après l’autre :

<mj-font name="Poppin" href="https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;700&display=swap" />

Comme ce href le laisse penser, mj-font permet de télécharger une ressource de type @font-face que vous pouvez déclarer dans votre codebase, ou — comme ici — récupérer depuis les CDNs Google (une pratique que je ne conseille pas dans l’absolu mais néanmoins très confortable pour ce genre de test).

<mj-attributes>
  <mj-text color="#111" font-family="Poppins" font-size="16px" line-height="1.22" padding="0" />
  <mj-table color="#111" font-family="Poppins" font-size="16px" line-height="1.22" padding="0" />
</mj-attributes>

Pour chacun des tags MJML que nous allons utiliser, nous pouvons définir en amont un ensemble de valeurs par défaut pour leurs attributs. Ici par exemple, je m’assure que les contenus de tableau et de texte simple soient bien de la même couleur, de la même police, sans padding, etc. Vous pouvez voir cela comme des styles de bases que nous aurions pu écrire en CSS à peu près de cette manière :

p, table {
  padding: 0;
  color: #111;
  font-family: Poppins;
  font-size: 16px;
  line-height: 1.22;
}

L’idée est ici de définir des styles de base pour les tags MJML, mais il est possible de définir des classes, que ce soit sur un élément MJML ou sur un tag HTML standard comme nous avons dû utiliser plus haut. Si vous vous souvenez bien, nous avions défini des éléments tag dans le contexte d’une offre, on va donc leur assigner quelques styles, en s’assurant qu’MJML transforme ce bloc de CSS en valeurs d’attribut style (ou inlinés pour les plus franglophile). Il est évidemment possible d’ajouter autant de classes que vous le voulez, pour éviter les répétitions dans le template.

<mj-style inline="inline">
  .tag {
    display:inline-block;
    font-size: 12px;
    text-transform:uppercase;
    padding:4px 8px;
    background:#A32468;
    color:#fff;
    border-radius: 3px;
  }
</mj-style>

Vous aurez aussi la possibilité d’importer une feuille de style externe avec le tag mj-include vu plus haut, mais il faudra vous assurer que le nom du fichier se termine en .mjml (pour l’inclusion), qu’il entoure tous ces styles d’une balise mj-style comportant l’attribut inline, lui aussi vu précédemment.

<mj-head>
  <mj-include src="style.mjml">
</mj-head>

Et dans le fichier style.mjml :

<mj-style inline="inline">
  body {}
</mj-style>

Je m’en suis peu servi pour cet exemple, mais je vous incite plutôt à utiliser cette technique, qui permet de bien séparer le contenu des règles d’affichage comme on le fait en web plus standard.

Vous devriez maintenant avoir dans la partie rendue par votre éditeur (ou après une commande pour lancer le rendu) quelque chose qui ressemble à notre mockup de départ.

screenshot1

Pour quel usage ?

Si je devais résumer, MJML c’est une surcouche pratique à HTML pour gérer des cas simples de layout responsive et gérer facilement les styles inlines. Rien de révolutionnaire donc, mais très pratique, surtout dans le cas où vous voudriez modifier régulièrement le contenu, ou rapidement la structure du mail. Nous avons rapidement été confrontés aux limites de l’aspect responsive du framework, mais nous avons pu voir facilement pourquoi il est plus abordable d’utiliser ce genre d’outils si on n’est pas un habitué de l’intégration d’email. Pendant mes recherches je suis tombé sur son concurrent le plus évident et pourtant très différent, Foundation for email (anciennement Ink) qui semble lui, bien plus complexe et complet. Nous aurons probablement l’occasion d’en faire un petit tour dans un prochain article.

J’espère avoir réussi à vous présenter les avantages de ce genre d’outils, pour ma part j’ai bien l’intention de m’en servir pour certains besoins relativement basiques et versatiles, car après 2-3 mails, la syntaxe devient limpide et l’écriture beaucoup plus aisée qu’en HTML standard.


L’équipe Synbioz.
Libres d’être ensemble.