symfony/mailer でTwigを使ってテキストメールをbase64で送る。

Symfony Advent Calendar 2019 15日目の記事です。

# はじめに

メールを業務で利用する場合は、定型文を用意して、例えば、お客様の名前とか日付とかを、送信する相手毎に置換をしてから送信をすることが多いです。

メールの本文を、<code>str_replace</code>で置換をしてもよいのですが、テンプレートエンジンの**twig**を使ってみたくなります。

# [Twig: HTML & CSS](https://symfony.com/doc/4.4/mailer.html#twig-html-css)を試す

Twigを使ってメールを送る方法について見ていきます。

[Mailerドキュメント](https://symfony.com/doc/4.4/mailer.html)に、[Twig: HTML & CSS](https://symfony.com/doc/4.4/mailer.html#twig-html-css)の項目がありますので、そのまま実行してみます。

```php:MailerController.php
class MailerController extends AbstractController
{
    /**
     * @Route("/mailer", name="mailer")
     */
    public function index(MailerInterface $mailer)
    {

        $email = (new TemplatedEmail())
            ->from('fabien@example.com')
            ->to(new Address('ryan@example.com'))
            ->subject('Thanks for signing up!')

            // path of the Twig template to render
            ->htmlTemplate('emails/signup.html.twig')

            // pass variables (name => value) to the template
            ->context([
                'expiration_date' => new \DateTime('+7 days'),
                'username' => 'foo',
            ])
        ;

        $mailer->send($email);

        return $this->render('mailer/index.html.twig', [
            'controller_name' => 'MailerController',
        ]);
    }
}
```

このソースは、https://github.com/idani/symfony_mailer_tutorial3 で保管しています。
今回のコミットは以下で見れます。
https://github.com/idani/symfony_mailer_tutorial3/commit/39d7caa797f0d1e663ba517553d8b23b8b319410

http://localhost:8000/mailer を開くと、以下の画面が表示されました。メール送信ができたようです。一番下のデバッグ用のツールバーのメールアイコンにも、メールが１通送信されたことが表示されていますね。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/133291/2989fd54-7bca-de62-2c21-bb31e1d99cc9.png)

http://127.0.0.1:8025/ で動作しているMailDevを確認すると、１通メールが届いていました。
デフォルトで表示されるのは、TwigでレンダリングされたHTMLメールになります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/133291/b51be0a1-fec5-b71a-1729-8168e71dc096.png)

MailDevで設定を切り替えて、テキストメールにしてみます。
その場合は、HTMLからテキストを抽出したテキストメールが表示されます。

誤解されないように説明すると、MailDevはメールソースに記載されたテキストメールの部分を表示しています。テキストのメールは、symfony/mailerによって、配信前にHtmlからテキストを抽出して、メールソースに組み込まれています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/133291/adfe19be-7176-6a03-cac4-73760c4643f2.png)

メールソースは以下です。

<code>Content-Type</code>が<code>text/plain; charset=utf-8</code>と指定されたテキストと、<code>text/html; charset=utf-8</code>と指定されたHTMLが<code>quoted-printable</code>でエンコードされて送られています。

```php:メールソース
From: fabien@example.com
To: ryan@example.com
Subject: Thanks for signing up!
Message-ID: <a7169f3be53c5470e3765e9fc649f5b1@example.com>
MIME-Version: 1.0
Date: Thu, 05 Dec 2019 15:53:55 +0900
Content-Type: multipart/alternative;
 boundary="_=_symfony_1575528835_8df53d757797744c9475726f052de156_=_"

--_=_symfony_1575528835_8df53d757797744c9475726f052de156_=_
Content-Type: text/plain; charset=utf-8
Content-Transfer-Encoding: quoted-printable

Welcome !


    You signed up as foo the following email:

ryan@e=
xample.com


    Click here to activate your account
    (this link=
 is valid until December 12th)


--_=_symfony_1575528835_8df53d757797744c9475726f052de156_=_
Content-Type: text/html; charset=utf-8
Content-Transfer-Encoding: quoted-printable

<h1>Welcome !</h1>

<p>
    You signed up as foo the following email:=

</p>
<p><code>ryan@example.com</code></p>

<p>
    <a href=3D"#"=
>Click here to activate your account</a>
    (this link is valid until De=
cember 12th)
</p>

--_=_symfony_1575528835_8df53d757797744c9475726f052de156_=_--

```

# [Text Content](https://symfony.com/doc/4.4/mailer.html#text-content)を試す。

[Mailerドキュメント](https://symfony.com/doc/4.4/mailer.html)を読み進めると、[Text Content](https://symfony.com/doc/4.4/mailer.html#text-content)の部分で、メールに入っているテキスト部分をカスタマイズできるようです。

さっそく、サンプルソースに<code>->textTemplate('emails/signup.txt.twig')</code>を追加します。

```php:MailerController.php
        $email = (new TemplatedEmail())
            ->from('fabien@example.com')
            ->to(new Address('ryan@example.com'))
            ->subject('Thanks for signing up!')

            // path of the Twig template to render
            ->htmlTemplate('emails/signup.html.twig')

            // pass variables (name => value) to the template
            ->context([
                'expiration_date' => new \DateTime('+7 days'),
                'username' => 'foo',
            ])
            ->textTemplate('emails/signup.txt.twig')
        ;

        $mailer->send($email);
```

```twig:signup.txt.twig
Welcome !

    You signed up as foo the following email:

    {{ email.to[0].address }}


    Click here to activate your account

    http://www.example.com/xxxxxxxxxxx
    (this link is valid until {{ expiration_date|date('F jS') }})
```
https://github.com/idani/symfony_mailer_tutorial3/commit/2931651279278b57ad8c3f2bfe045ac03d59f141

実際に送信をされたメールを見ると、修正通りのテキストメールが送られていました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/133291/7a09a155-eddd-2ed0-d418-f58fc134f773.png)

```php:メールソース
From: fabien@example.com
To: ryan@example.com
Subject: Thanks for signing up!
Message-ID: <f6cc0e2166ff05ab5816d2bb26255080@example.com>
MIME-Version: 1.0
Date: Thu, 05 Dec 2019 16:27:04 +0900
Content-Type: multipart/alternative;
 boundary="_=_symfony_1575530824_57df7e1ab6fbb47d7ffce4184ba1587c_=_"

--_=_symfony_1575530824_57df7e1ab6fbb47d7ffce4184ba1587c_=_
Content-Type: text/plain; charset=utf-8
Content-Transfer-Encoding: quoted-printable

Welcome !

    You signed up as foo the following email:

    ryan@=
example.com


    Click here to activate your account

    http:/=
/www.example.com/xxxxxxxxxxx
    (this link is valid until December 12th)
--_=_symfony_1575530824_57df7e1ab6fbb47d7ffce4184ba1587c_=_
Content-Type: text/html; charset=utf-8
Content-Transfer-Encoding: quoted-printable

<h1>Welcome !</h1>

<p>
    You signed up as foo the following email:=

</p>
<p><code>ryan@example.com</code></p>

<p>
    <a href=3D"#"=
>Click here to activate your account</a>
    (this link is valid until De=
cember 12th)
</p>

--_=_symfony_1575530824_57df7e1ab6fbb47d7ffce4184ba1587c_=_--
```

# テキストメールだけ送る

これまではHTMLメールが主役で、テキストメールがおまけ的な感じでした。
Twigを使って、テキストメールだけ送るにはどうしたらよいでしょうか？

さきほどのサンプルコードから、<code>htmlTemplate</code>を外してみましょう。

```php

        $email = (new TemplatedEmail())
            ->from('fabien@example.com')
            ->to(new Address('ryan@example.com'))
            ->subject('Thanks for signing up!')

            // path of the Twig template to render
            //->htmlTemplate('emails/signup.html.twig') <=コメントアウト

            // pass variables (name => value) to the template
            ->context([
                'expiration_date' => new \DateTime('+7 days'),
                'username' => 'foo',
            ])
            ->textTemplate('emails/signup.txt.twig')
        ;

        $mailer->send($email);
```
https://github.com/idani/symfony_mailer_tutorial3/commit/8313dcc169ee6cf4f4ab0a0356ea15997aa0bada

MailDevでは、意図した通り、テキストメールが見れました。
画像では変化がないので、メールソースを見ていただくと、テキストメールになっていることがわかります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/133291/ae812e9b-7579-edea-c042-10dad18d42bc.png)

```php:メールソース
From: fabien@example.com
To: ryan@example.com
Subject: Thanks for signing up!
Message-ID: <3d16b774412841ddbdef19077a4e8fc2@example.com>
MIME-Version: 1.0
Date: Thu, 05 Dec 2019 16:33:07 +0900
Content-Type: text/plain; charset=utf-8
Content-Transfer-Encoding: quoted-printable

Welcome !

    You signed up as foo the following email:

    ryan@=
example.com


    Click here to activate your account

    http:/=
/www.example.com/xxxxxxxxxxx
    (this link is valid until December 12th)
```

# 日本語テンプレートで送信をしてみる

問題ないと思いますが、テンプレートを日本語にしてみます。

```php:signup.txt.twig
Welcome !
ようこそ ！

    You signed up as {{ username }} the following email:
    次のメールアドレスで{{ username }}としてサインアップしました。

    {{ email.to[0].address }}


    Click here to activate your account
    アカウントを有効にするにはここをクリックしてください

    http://www.example.com/xxxxxxxxxxx
    (this link is valid until {{ expiration_date|date('F jS') }})
    （このリンクは、{{ expiration_date|date('F jS') }}まで有効です。
```
https://github.com/idani/symfony_mailer_tutorial3/commit/14a7ee816dbb4acb8e263060e97e8230d785cc99

送信すると、以下のように問題なく日本語も表示されました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/133291/273ca272-c1e9-772c-b20f-28b9a5a912ac.png)

```php:メールソース
From: fabien@example.com
To: ryan@example.com
Subject: Thanks for signing up!
Message-ID: <0072478296801e97a3472e1ce06a9f38@example.com>
MIME-Version: 1.0
Date: Thu, 05 Dec 2019 16:41:55 +0900
Content-Type: text/plain; charset=utf-8
Content-Transfer-Encoding: quoted-printable

Welcome !
=E3=82=88=E3=81=86=E3=81=93=E3=81=9D =EF=BC=81

    You sig=
ned up as foo the following email:
    =E6=AC=A1=E3=81=AE=E3=83=A1=
=E3=83=BC=E3=83=AB=E3=82=A2=E3=83=89=E3=83=AC=E3=82=B9=E3=81=A7foo=E3=81=
=A8=E3=81=97=E3=81=A6=E3=82=B5=E3=82=A4=E3=83=B3=E3=82=A2=E3=83=83=E3=83=
=97=E3=81=97=E3=81=BE=E3=81=97=E3=81=9F=E3=80=82

    ryan@example.com=



    Click here to activate your account
    =E3=82=A2=E3=82=
=AB=E3=82=A6=E3=83=B3=E3=83=88=E3=82=92=E6=9C=89=E5=8A=B9=E3=81=AB=E3=81=
=99=E3=82=8B=E3=81=AB=E3=81=AF=E3=81=93=E3=81=93=E3=82=92=E3=82=AF=E3=83=
=AA=E3=83=83=E3=82=AF=E3=81=97=E3=81=A6=E3=81=8F=E3=81=A0=E3=81=95=E3=81=
=84

    http://www.example.com/xxxxxxxxxxx
    (this link is valid u=
ntil December 12th)
    =EF=BC=88=E3=81=93=E3=81=AE=E3=83=AA=E3=83=B3=
=E3=82=AF=E3=81=AF=E3=80=81December 12th=E3=81=BE=E3=81=A7=E6=9C=89=
=E5=8A=B9=E3=81=A7=E3=81=99=E3=80=82
```

# Base64で送信する

ここまでできても合格なのですが、可能であれば、<code>quoted-printable</code>ではなく、<code>base64</code>で送信をしたいです。

どうしたらよいのでしょうか？

[TemplatedEmail.php](https://github.com/symfony/symfony/blob/bfe697bb86475af9442c7caf2272e6e25b902bf5/src/Symfony/Bridge/Twig/Mime/TemplatedEmail.php#L19)を見てみると、[Email.php](https://github.com/symfony/symfony/blob/master/src/Symfony/Component/Mime/Email.php)の派生クラスであることがわかります。

[Email.php](https://github.com/symfony/symfony/blob/master/src/Symfony/Component/Mime/Email.php)の中身は、[symfony/mailerでbase64やiso-2022-jpを試してみる](https://qiita.com/idani/private/f92be93a16b5ef77d4ce)で見ていきましたが、<code>quoted-printable</code>固定となってしまいます。

そうすると、Twigでテキストをレンダリングして、それをメール本文として指定するのが良さそうです。

[Creating and Using Templates](https://symfony.com/doc/current/templates.html)の[Rendering a Template in Services](https://symfony.com/doc/current/templates.html#rendering-a-template-in-services)に、サービス内でのレンダリング方法が記載されています。

こちらを参考にして、サンプルコードを変更します。
Base64のメールの作成方法は、[symfony/mailerでbase64やiso-2022-jpを試してみる](https://qiita.com/idani/private/f92be93a16b5ef77d4ce)から流用しますので、ガラッと変わります。

```php:MailerController.php

        mb_language("uni");
        mb_internal_encoding("UTF-8");

        $subject = mb_encode_mimeheader('Thanks for signing up! 登録してくれてありがとうございます！！');
        $subject = str_replace("\r\n", '', $subject);

        $headers = (new Headers())
            ->addMailboxListHeader('From', [new Address('hello@example.com', mb_encode_mimeheader('送信者名'))])
            ->addMailboxListHeader('To', [new Address('you@example.com', mb_encode_mimeheader('受信者名'))])
            ->addTextHeader('Subject', $subject)
        ;

        $body = $twig->render('emails/signup.txt.twig', [
            'expiration_date' => new \DateTime('+7 days'),
            'username' => 'foo',
            'email' => 'you@example.com',
        ]);
        $textContent = new TextPart($body, 'utf-8', 'plain', 'base64');
        $email = new Message($headers, $textContent);

        $mailer->send($email);
```

テンプレートの方も、メールアドレスなどの情報も、自動で設定されていたようです。
このためメールアドレスも個別に指定するように変更します。

```php:signup.txt.twig
Welcome !
ようこそ ！

    You signed up as {{ username }} the following email:
    次のメールアドレスで{{ username }}としてサインアップしました。

    {{ email }}


    Click here to activate your account
    アカウントを有効にするにはここをクリックしてください

    http://www.example.com/xxxxxxxxxxx
    (this link is valid until {{ expiration_date|date('F jS') }})
    （このリンクは、{{ expiration_date|date('F jS') }}まで有効です。

```
https://github.com/idani/symfony_mailer_tutorial3/commit/06fd543a301c8bb61f0a362c3fb66e0636d65fd5

想定通り、Twigを使ってテキストメールをBase64で送信することができました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/133291/f1f0161a-1fb5-b20a-9bc2-27b38cbdf9ad.png)

```php:メールソース
From: =?UTF-8?B?6YCB5L+h6ICF5ZCN?= <hello@example.com>
To: =?UTF-8?B?5Y+X5L+h6ICF5ZCN?= <you@example.com>
Subject: Thanks for signing up!
 =?UTF-8?B?55m76Yyy44GX44Gm44GP44KM44Gm44GC44KK44GM?=
 =?UTF-8?B?44Go44GG44GU44GW44GE44G+44GZ77yB77yB?=
Message-ID: <a98efb8654df5bc230c364e0d1e476f4@example.com>
MIME-Version: 1.0
Date: Thu, 05 Dec 2019 17:15:07 +0900
Content-Type: text/plain; charset=utf-8
Content-Transfer-Encoding: base64

V2VsY29tZSAhCuOCiOOBhuOBk+OBnSDvvIEKCiAgICBZb3Ugc2lnbmVkIHVwIGFzIGZvbyB0aGUg
Zm9sbG93aW5nIGVtYWlsOgogICAg5qyh44Gu44Oh44O844Or44Ki44OJ44Os44K544GnZm9v44Go
44GX44Gm44K144Kk44Oz44Ki44OD44OX44GX44G+44GX44Gf44CCCgogICAgeW91QGV4YW1wbGUu
Y29tCgoKICAgIENsaWNrIGhlcmUgdG8gYWN0aXZhdGUgeW91ciBhY2NvdW50CiAgICDjgqLjgqvj
gqbjg7Pjg4jjgpLmnInlirnjgavjgZnjgovjgavjga/jgZPjgZPjgpLjgq/jg6rjg4Pjgq/jgZfj
gabjgY/jgaDjgZXjgYQKCiAgICBodHRwOi8vd3d3LmV4YW1wbGUuY29tL3h4eHh4eHh4eHh4CiAg
ICAodGhpcyBsaW5rIGlzIHZhbGlkIHVudGlsIERlY2VtYmVyIDEydGgpCiAgICDvvIjjgZPjga7j
g6rjg7Pjgq/jga/jgIFEZWNlbWJlciAxMnRo44G+44Gn5pyJ5Yq544Gn44GZ44CCCg==
```

# TwigのテンプレートをDBに保存する

メールの本文をテンプレートという形で保存をして、レンダリングすることもできました。

ところが、メールは本文以外にサブジェクトというデータも必要です。
サブジェクトはDBに保存し、メールの本文のテンプレートはファイルシステムというのは微妙ですよね。

TwigのテンプレートもDBに保存できるとよいですね。

調べてみると、Twigのドキュメントで、[Using a Database to store Templates](https://twig.symfony.com/doc/3.x/recipes.html#using-a-database-to-store-templates)がありました。

ただ、こちらは純粋にTwigのテンプレートの読み出し先をDBにするということみたいです。

サイトとしてHTMLを表示するためでしたらよいのですが、メールとしては使いづらいですね。

それよりは、その次に記載のTwigのテンプレートを変数として受け取って処理をするための、[Loading a Template from a String](https://twig.symfony.com/doc/3.x/recipes.html#loading-a-template-from-a-string)が良さそうです。

サンプルでテンプレートを読み込む部分を書き換えます。

```php:MailerController.php
   public function index(MailerInterface $mailer, Environment $twig)
    {

        mb_language("uni");
        mb_internal_encoding("UTF-8");

        $subject = mb_encode_mimeheader('Thanks for signing up! 登録してくれてありがとうございます！！');
        $subject = str_replace("\r\n", '', $subject);

        $headers = (new Headers())
            ->addMailboxListHeader('From', [new Address('hello@example.com', mb_encode_mimeheader('送信者名'))])
            ->addMailboxListHeader('To', [new Address('you@example.com', mb_encode_mimeheader('受信者名'))])
            ->addTextHeader('Subject', $subject)
        ;

        $twigEmailTemplate =<<<EOL
Welcome !
ようこそ ！

    これはTwigテンプレートを変数から読み込んでレンダリングしたメールです。

    You signed up as {{ username }} the following email:
~~~中略~~~
    （このリンクは、{{ expiration_date|date('F jS') }}まで有効です。

EOL;

        $template = $twig->createTemplate($twigEmailTemplate);
        $body = $template->render([
            'expiration_date' => new \DateTime('+7 days'),
            'username' => 'foo',
            'email' => 'you@example.com',
        ]);
        $textContent = new TextPart($body, 'utf-8', 'plain', 'base64');
        $email = new Message($headers, $textContent);

        $mailer->send($email);

        return $this->render('mailer/index.html.twig', [
            'controller_name' => 'MailerController',
        ]);
    }
```
https://github.com/idani/symfony_mailer_tutorial3/commit/c97d5ac4a4e02c6c74111aa3b249f7abb2a0fc5c

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/133291/79eabb5f-0710-ed2c-1ff6-701173451457.png)


# まとめ

Twigを使って簡単にメールが送れることがわかりました。
Base64にするには、面倒なので、良さそうな修正案を考える必要てプルリクした方がよいですね。