Closed10

HTML メールと向き合う

Kenta WatashimaKenta Watashima

まともにゼロから書くのは人間の所業ではないので別の手段を探す。
フレームワークがいくつかあるっぽい。

React で書いたコンポーネントを HTML メールの仕様に沿ったマークアップに変換。
https://react.email/

こちらも同様だが、独自のマークアップ言語。歴史は長い。
https://mjml.io/

Kenta WatashimaKenta Watashima

とりあえず React Email 触ってみる

https://react.email/docs/getting-started/manual-setup

import { Container } from '@react-email/container';
import { Head } from '@react-email/head';
import { Html } from '@react-email/html';
import { Section } from '@react-email/section';
import { Text } from '@react-email/text';
import * as React from 'react';

export default function Email() {
  return (
    <Html>
      <Head />
      <Section style={main}>
        <Container style={container}>
          <Text>hello</Text>
        </Container>
      </Section>
    </Html>
  );
}

const main = {
  backgroundColor: '#ffffff',
};

const container = {
  paddingLeft: '12px',
  paddingRight: '12px',
  margin: '0 auto',
};

これが

<!DOCTYPE htmlPUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html lang="en">

  <head>
    <meta http-equiv="Content-Type" content="text/html charset=UTF-8" />
  </head>
  <table style="width:100%;background-color:#ffffff" align="center" border="0" cellPadding="0" cellSpacing="0" role="presentation">
    <tbody>
      <tr>
        <td>
          <div><!--[if mso | IE]>
            <table role="presentation" width="100%" align="center" style="max-width:37.5em;padding-left:12px;padding-right:12px;margin:0 auto;"><tr><td></td><td style="width:37.5em;background:#ffffff">
          <![endif]--></div>
          <div style="max-width:37.5em;padding-left:12px;padding-right:12px;margin:0 auto">
            <p style="font-size:14px;line-height:24px;margin:16px 0">hello</p>
          </div>
          <div><!--[if mso | IE]>
          </td><td></td></tr></table>
          <![endif]--></div>
        </td>
      </tr>
    </tbody>
  </table>

</html>

こうなる。黒魔術感がすごい。

Kenta WatashimaKenta Watashima

React Email をしばらく触っていたものの、まだ Beta ということもあり、挙動が若干 buggy かもしれない。ということで mjml も試す。

https://mjml.io/try-it-live

テンプレートはこんな感じ。

<mjml>
  <mj-body>
    <mj-section>
      <mj-column>
        <mj-image width="100px" src="/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。

<!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 で進めてみることにする。

Kenta WatashimaKenta Watashima

たとえばこういう感じの、1 カラム + 3 カラムの組み合わせからなるレイアウトを mjml で組む時、

<mj-section>
  <mj-column width="100%">
    <mj-text>A</mj-text>
  </mj-column>
  <mj-column width="33.3333333333%">
    <mj-text>B</mj-text>
  </mj-column>
  <mj-column width="33.3333333333%">
    <mj-text>C</mj-text>
  </mj-column>
  <mj-column width="33.3333333333%">
    <mj-text>D</mj-text>
  </mj-column>
</mj-section>

とすると、プレビューでは一見問題なさそうに見える。が、Windows の Outlook で確認したときに盛大にレイアウトが崩れてしまう。

こういう場合はふつうに section を分ける。2 段以上のカラムになる場合は素直に section を分けたほうがよさそう。

<mj-section>
  <mj-column>
    <mj-text>A</mj-text>
  </mj-column>
</mj-section>
<mj-section>
  <mj-column>
    <mj-text>B</mj-text>
  </mj-column>
  <mj-column>
    <mj-text>C</mj-text>
  </mj-column>
  <mj-column>
    <mj-text>D</mj-text>
  </mj-column>
</mj-section>
Kenta WatashimaKenta Watashima

検証には litmus が便利。主要なメーラーとデバイス(macOS / Win / iOS / Android)の組み合わせでレンダリングされた結果をスクショしてくれる。ただし有料で、ベーシックプランでも $99/month という強気の価格設定。

https://litmus.com/

Outlook で確認したときに想定外の罠に陥る可能性があるので、実機による検証環境が用意できない場合はなるべく早い段階で確認しておくべき。Outlook はとにかく曲者。

Kenta WatashimaKenta Watashima

Outlook で line-height がおかしい

テキストの行間がやたらと広くなる。

CSS で mso-line-height-rule: exactly を指定すればよいらしいのだが、mjml はデフォルトで mso-line-height-rule: exactly が付与される。どうも line-height が % 指定だとダメっぽいので、px 指定にすることで解決。

Yahoo! メールでインライン CSS の height が勝手に min-height に書き換えられる

仕様らしい。いやどんな仕様だよ。

mjml だと(確認した限りでは)mj-button の height が無視されて潰れたような見た目になってしまう。
https://github.com/mjmlio/mjml/blob/master/packages/mjml-button/src/index.js#L147
この下あたりに height: this.getAttribute('height') を追加。

参考

https://qiita.com/baraninngilyou/items/5ccbf0a43469304e29dc

Kenta WatashimaKenta Watashima

mjml 使う上でのコツとしては、

  • なるべく mj- ではじまるビルトインのコンポーネントを使う。
  • スタイルの調整も、用意されているプロパティの範囲内で行う。CSS を直接書くのは避けたほうがいい。
  • mj-textmj-table で HTML を直接書くこともできるが、Outlook で崩れたり、うまくいかないことのほうが多いので避けたほうがいい。
  • 画像化できる部分は画像化する。
このスクラップは2023/01/19にクローズされました