この記事は BASE アドベントカレンダー 13 日目の記事です。
はじめに
こんにちは。 BASE BANK 株式会社 Dev Division にて Software Developer をしている永野(@glassmonekey)です。 普段はバックエンドエンジニアとして、Go/Python/PHP を主に書いてたりします。 最近はチームの分析基盤づくりとかもやってたりします。 そのことについて先日書いたりもしたので、もし良かったらご確認ください。
私達のチームでは、「BASE」でショップを運営しているショップオーナーが簡単に資金調達をできる「YELL BANK」というサービスの開発・運営しています。
あるプロジェクトで、ショップオーナーへのコミュニケーション手段に HTML メール
を新しく作ることになりました。その際に HTML メール
のコーディングも自分たちで行う必要がありました。
今回は その際に行った HTML メール
制作をMJMLを使うと楽にできたのでその紹介になります。
HTMLメールの事情
大前提として、HTML メール
を使うとリッチな内容でコミュニケーションを取ることができます。
昨今のメーラーだと基本的には対応しているので使いたくなります。
弊社でも HTML メール
の事例で4月に購入完了メールをリニューアルしました。
では早速 HTML メール
を作りましょうと言っても、なかなかそうは行きません。
なぜなら一般に、HTML メール
のマークアップと Webページ
のマークアップと毛色が違うからです。
特に、HTML メール
のマークアップは 基本的なCSS はインライン
、どのような環境でも安定しているテーブルレイアウト
が基本となる事情があります。メーラーそれぞれの描画任せが要因だったりするようです。
参考: How to Create HTML Emails Using the Table Element [+ Templates]
では HTML メール
でどのタグが利用できるのでしょうか。
それに関しては大体は以下のサイトで簡単に調べることができます。
例えば、「BASE」ならぬ <base>タグ
だと以下のような対応状況のようです。緑は完全に対応、黄色は部分的に対応、赤は未対応です。
ちなみに対応状況は手動で調べられてるようです。とはいえ最初に見る状況としては大変助かっています。
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> <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>
見た目はこのようになります。
ちなみに、実際の 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;"> </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で確認しつつ編集できるので便利です。
- 基本構成例
<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 Code
の preview
が動作しないことが挙げられます。
おそらく preview
には mj-body
などの要素が必要だが、パーツ側コンポーネントにはそれらが含まれていなからだとは思われます。
なので、最初から分離しようとせずにコーディングがある程度完了してから分離したほうが作業効率は良いです。
余談
余談ですが私自身は MJML のことは知らず、同僚の@sam8helloworldが同僚となる前に教えてくれたという個人的エピソードもあります。大変助かりました。
htmlメールのいい感じのフレームワークとかない?
— エターナル・フィールド (@glassmonekey) 2021年5月6日
sendgridで素直に作れ?はい。
HTMLメールはご存知かもですが仕様が独特かつHTMLのルールも古いので人がコーディングするのはただただコスト高いっすね
— さむ (@sam8helloworld) 2021年5月6日
それでもコーディングベースで細かくやりたいならMJML, SaaSで基本ノーコードでやりたいなら無難にsendgrid, mailchimp ,b-dashってところですかね
おわりに
MJML
を紹介しましたがいかがだったでしょうか?
HTML メール
は令和となった現代においてもなかなか難しいので、ぜひ皆さんもMJML
をご活用ください。
無論実際のメーラー上の確認は最終的には必要ですが、手元でプレビュー見つつローカルで大半の作業を完結できるので、開発体験は最高でしたし何より工数の削減にも繋がり大変重宝しました。
最後に宣伝ですが、まだまだやれてないことがたくさんあるので、一緒にプロダクトを成長させていく仲間を募集中です。
明日は 2021 年のデザインリサーチ振り返りです。楽しみですね〜