BASEプロダクトチームブログ

ネットショップ作成サービス「BASE ( https://thebase.in )」、ショッピングアプリ「BASE ( https://thebase.in/sp )」のプロダクトチームによるブログです。

MJMLでhtmlメールを楽にいい感じに書くことができた話

この記事は BASE アドベントカレンダー 13 日目の記事です。

f:id:glassmonekey:20211213011911p:plain

はじめに

こんにちは。 BASE BANK 株式会社 Dev Division にて Software Developer をしている永野(@glassmonekey)です。 普段はバックエンドエンジニアとして、Go/Python/PHP を主に書いてたりします。 最近はチームの分析基盤づくりとかもやってたりします。 そのことについて先日書いたりもしたので、もし良かったらご確認ください。

devblog.thebase.in

私達のチームでは、「BASE」でショップを運営しているショップオーナーが簡単に資金調達をできる「YELL BANK」というサービスの開発・運営しています。

thebase.in

あるプロジェクトで、ショップオーナーへのコミュニケーション手段に HTML メール を新しく作ることになりました。その際に HTML メール のコーディングも自分たちで行う必要がありました。

今回は その際に行った HTML メール 制作をMJMLを使うと楽にできたのでその紹介になります。

HTMLメールの事情

大前提として、HTML メール を使うとリッチな内容でコミュニケーションを取ることができます。 昨今のメーラーだと基本的には対応しているので使いたくなります。 弊社でも HTML メール の事例で4月に購入完了メールをリニューアルしました。

では早速 HTML メール を作りましょうと言っても、なかなかそうは行きません。 なぜなら一般に、HTML メール のマークアップと Webページ のマークアップと毛色が違うからです。

特に、HTML メール のマークアップは 基本的なCSS はインラインどのような環境でも安定しているテーブルレイアウト が基本となる事情があります。メーラーそれぞれの描画任せが要因だったりするようです。

参考: How to Create HTML Emails Using the Table Element [+ Templates]

では HTML メール でどのタグが利用できるのでしょうか。 それに関しては大体は以下のサイトで簡単に調べることができます。

www.caniemail.com

例えば、「BASE」ならぬ <base>タグ だと以下のような対応状況のようです。緑は完全に対応、黄色は部分的に対応、赤は未対応です。

f:id:glassmonekey:20211213013657p:plain

ちなみに対応状況は手動で調べられてるようです。とはいえ最初に見る状況としては大変助かっています。

Because every test is done manually, some features might not have been tested on every email client. Can I email… Email Client Support Scoreboard

また製作者の方である@HTeuMeuLeuさんの記事の1つのOutlook is not an Email Clientからは各社メーラへの対応状況の複雑さの苦労が垣間見えます。

以上のことから基本的には HTML メール のコーディングは避けたほうが無難です。無理してコーディングする必要がなければ、Send Gridなどの SasS を使うことが良いでしょう。

しかし、コーディングしないといけないときもあるでしょう。そのときに便利なものが MJML となります。

MJMLとは

MJML とはレスポンシブな HTML メール を作ることのできるフレームワークです。 mailjet社が開発しています。

だいたいのことはドキュメントに載っているので参照ください。

最初はオンラインエディタで試しながら始めてみるのがおすすめです。

これを使うと複雑なテーブルタグを書かずとも、簡単に HTML メール を書くことができます。 具体例を見てみましょう。

mjml.io

例)

<mjml>
  <mj-body>
    <mj-section>
      <mj-column>
        <mj-image width="100px" src="https://mjml.io/assets/img/logo-small.png"></mj-image>
        <mj-divider border-color="#F45E43"></mj-divider>
        <mj-text font-size="20px" color="#F45E43" font-family="helvetica">Hello World</mj-text>
      </mj-column>
    </mj-section>
  </mj-body>
</mjml>

見た目はこのようになります。

f:id:glassmonekey:20211213014818p:plain

ちなみに、実際の HTML はこのような形で出力されています。 MJML で書くと大分簡略化されていることがわかります。

<!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-lspace: 0pt;
      mso-table-rspace: 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]-->
  <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">
    @media only screen and (max-width:480px) {
      table.mj-full-width-mobile {
        width: 100% !important;
      }

      td.mj-full-width-mobile {
        width: auto !important;
      }
    }
  </style>
</head>

<body style="word-spacing:normal;">
  <div style="">
    <!--[if mso | IE]><table align="center" border="0" cellpadding="0" cellspacing="0" class="" 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="center" style="font-size:0px;padding:10px 25px;word-break:break-word;">
                        <table border="0" cellpadding="0" cellspacing="0" role="presentation" style="border-collapse:collapse;border-spacing:0px;">
                          <tbody>
                            <tr>
                              <td style="width:100px;">
                                <img height="auto" src="/assets/img/logo-small.png" style="border:0;display:block;outline:none;text-decoration:none;height:auto;width:100%;font-size:13px;" width="100" />
                              </td>
                            </tr>
                          </tbody>
                        </table>
                      </td>
                    </tr>
                    <tr>
                      <td align="center" style="font-size:0px;padding:10px 25px;word-break:break-word;">
                        <p style="border-top:solid 4px #F45E43;font-size:1px;margin:0px auto;width:100%;">
                        </p>
                        <!--[if mso | IE]><table align="center" border="0" cellpadding="0" cellspacing="0" style="border-top:solid 4px #F45E43;font-size:1px;margin:0px auto;width:550px;" role="presentation" width="550px" ><tr><td style="height:0;line-height:0;"> &nbsp;
</td></tr></table><![endif]-->
                      </td>
                    </tr>
                    <tr>
                      <td align="left" style="font-size:0px;padding:10px 25px;word-break:break-word;">
                        <div style="font-family:helvetica;font-size:20px;line-height:1;text-align:left;color:#F45E43;">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>

MJML入門

では実査に、MJML を触ってみましょう。

プロジェクト構成

最初にメールを作成するためのプロジェクトを準備しましょう。 今回説明するディレクトリ構成は以下としました。

├── dist
├── mjml
│   ├── section
│   └── template
├── package.json
└── yarn.lock

各ディレクトリの責務は以下の形です。 今回、メールのコンポーネントは再利用 する/しない ぐらいの区別しかしていません。 MJML によるメール作成の知見が社内で成熟してくれば、変わる可能性はあります。

  • dist …生成される html を出力します。
  • mjml … 生成するための MJML ファイル群を置きます。
    • section… Header や Footer など再利用するコンポーネント用 mjml ファイル群を置く所
    • template ... 1 メールのバリエーションと対応した mjml ファイル群を置く所

インストール

基本的には npm で mjml-cli を入れたら終わりです。

$ npm install mjml

package.json には npm run build などで build できるように、以下を追記しておきます。

"scripts": {
    "build": "mjml ./mjml/template/*.mjml -o ./dist"
 }

基本構成

基本的にはドキュメントを見つつ、 テンプレートを見て、参考にさせてもらうのが近道です。

基本的には HTML と同じ用なセマンティクスを持っているのでそれに従って記載するといいでしょう。

ローカルで開発時はVS codeの拡張を入れると previewで確認しつつ編集できるので便利です。

f:id:glassmonekey:20211213015343p:plain

  • 基本構成例
<mjml>
  <mj-head>
  <mj-attributes>
    <mj-text color="red" />
    <mj-class name="content" color="black" />
  </mj-attributes>
  </mj-head>
  <mj-body>
    <mj-section>
      <mj-column>
        <mj-text>Hello, World(Red) </mj-text>
        <mj-text mj-class="content">Hello, World(Black)</mj-text>
      </mj-column>
    </mj-section>
  </mj-body>
</mjml>

基本的なマークアップの規則としては、いわゆるグリッドレイアウトになります。

メールは複数のセクションから構成されますが、基本的な 1 つのセクションの構成は mj-section > mj-column > コンテンツ要素 から成り立ちます。 主に利用するタグは以下の流れになります。

  • mj-section … 基本的なコンテンツの区切りを定義します。
  • mj-column … 複数使うことでモバイルでは 1 列、PC では 2 列といったレスポンシブを実現します。
  • コンテンツ要素

共通の装飾について

文字色などの装飾は個別の要素に指定すれば変更できます。

<mj-text color="red">赤色</mj-text>

しかし、余白や文字サイズなどは統一感出したいでしょう。その場合は mj-class を使って予め用意しておくことで同時に複数の要素に共通的な装飾を施すことも可能です。 準備は以下のようにmj-attributeに class 名を用意しておきます。

  <mj-head>
    <mj-attributes>
        <mj-class name="content" font-size="14px" font-family="Arial" line-height="24px" padding="0" />
        <mj-class name="annoation" font-size="12px" font-family="Arial" line-height="18px" padding="0" />
    </mj-attributes>
  </mj-head>

利用自体は簡単で mj-class を指定するだけです。

<mj-text mj-class="content">

半角スペースで複数のクラスの適用もできます。

<mj-text mj-class="A B">

ファイル分割について

ある程度メールの種類が増えてくると、ヘッダー部分などの共通部分を分離したくなりますよね。 それには、mj-includeを使うことで実現します。

たとえば共通の分割線パーツコンポーネントを section/common/divider.mjml で用意してたとします。

<mj-section>
  <mj-column>
    <mj-divider border-width="4px" border-color="#F0F1F4" padding="0"/>
  </mj-column>
</mj-section>

呼び出し側としては以下のように相対パスで記述するだけで完了です。

略…
<mj-include path="../section/common/divider.mjml" />

基本的にはセクション単位で扱うことが多いはずなので、共通のパーツも section 単位で作っておくと再利用がしやすいです。

パーツ分割における注意事項としては、パーツ側のコンポーネント編集時には VS Codepreview が動作しないことが挙げられます。
おそらく preview には mj-body などの要素が必要だが、パーツ側コンポーネントにはそれらが含まれていなからだとは思われます。
なので、最初から分離しようとせずにコーディングがある程度完了してから分離したほうが作業効率は良いです。

余談

余談ですが私自身は MJML のことは知らず、同僚の@sam8helloworldが同僚となる前に教えてくれたという個人的エピソードもあります。大変助かりました。

おわりに

MJML を紹介しましたがいかがだったでしょうか?
HTML メール は令和となった現代においてもなかなか難しいので、ぜひ皆さんもMJMLをご活用ください。

無論実際のメーラー上の確認は最終的には必要ですが、手元でプレビュー見つつローカルで大半の作業を完結できるので、開発体験は最高でしたし何より工数の削減にも繋がり大変重宝しました。

最後に宣伝ですが、まだまだやれてないことがたくさんあるので、一緒にプロダクトを成長させていく仲間を募集中です。

herp.careers

herp.careers

明日は 2021 年のデザインリサーチ振り返りです。楽しみですね〜