Laravel の開発時、メールのデバッグってどうやってますか?
はじめに
メールを送る処理の実装時うっかり誰かにメールを送ってしまうことはありませんか?
開発時のメールの誤送信事故を防止するために、ダミーのメールサーバに送るということが多いかと思います。
MailCatcher や MailHog、Mailpit などのツールで解決されている方も多いのではないでしょうか。
弊プロダクトでは、ツールは利用せずにログにメールの送信内容を出力しています。
この記事では、Laravel を利用した開発時にログにメールの送信内容を出力するアプローチでのメリットとデメリット、ログの可読性を上げるための工夫など私たちが行っていることを紹介します。
前提
- 大量のメールを一括送信するようなケースは想定していない
- HTML形式のメールのデバッグは想定していない
ツールを使うことの利点
代表的なツールとして、MailCatcher や MailHog、Mailpit などがよく挙げられます。
ツールを利用するメリットとしては下記が考えられます。
- Web UI から、メールのヘッダーや本文、HTMLレンダリングされた結果などを簡単に確認することができる
- Dockerイメージが提供されていることもあり CI/CD に容易に組み込める
- API が提供されているものであれば、API を通じて正しく送信されたこと、内容が正しいことを検証することができる
一方で、ツールを導入するには工数がかかります。
ツールの選定や初期設定、導入後のアップグレードなどの保守コストが発生するため、導入を決断するには少し慎重になると思います。
弊社では、導入時の保守コストが少なく、よりシンプルな手法を模索しておりました。
ログに出力するメリットとデメリット
メリット
- 実際のメールサーバを使用しないので、うっかり実際のユーザーにメールを送信してしまうリスクがない
- メール内容が直接ログに出力されるため、リアルタイムにメールの送信内容を確認できる
- 外部サービスやSMTPサーバに依存せずに開発が可能になる
- 追加のサービスやツールを設定する必要がなく、開発環境がシンプルになる
ツールを利用した際のメリットとかぶるところもあるかもしれませんが、ログ出力の方がシンプルで保守コストの少ない案になったと考えております。
デメリット
- メールの視覚的検証が難しい
- 実際のメール送信プロセスをエミュレートすることができない
ログ出力にしたことで見やすさは確かに劣りますが、テキストメールであれば我慢できるレベルです(HTMLメールは厳しいです)。
また、ログ出力では実際のメール送信プロセスを確認することはできませんが、テスト観点を切り分けて、別のフェーズ(結合テストなど)でメール送信プロセスを確認すれば問題ないと考えています。
ログにメールの内容を出力する
ログドライバーを設定
メールをログに出力するには、メールのログドライバーを利用します。
ログドライバーを利用するには、環境変数 MAIL_MAILER
に log
を設定します。
標準では SMTPドライバーが選択されますが、MAIL_MAILER
に log
を設定することでログドライバーを選択することが可能です。
MAIL_MAILER=log
出力結果
実際にメールの送信処理を実行してデバッグしてみます。
[2023-12-19 05:11:19] local.DEBUG: From: XXXXXXX@example.com
To: XXXXXXX@example.com
Subject: =?utf-8?Q?=E3=80=90=E3=81=88=E3=82=93=E3=81=95?=
=?utf-8?Q?=E3=81=8C=E3=81=9D=E3=81=A3=E2=99=AA?=
=?utf-8?Q?=E3=80=91=E4=BB=AE=E7=99=BB=E9=8C=B2?=
=?utf-8?Q?=E3=81=AE=E5=8F=97=E4=BB=98?=
MIME-Version: 1.0
Date: Tue, 19 Dec 2023 05:11:19 +0900
Message-ID: <d9e579f978972f2ec3123617649c9eec@example.com>
Content-Type: text/plain; charset=utf-8
Content-Transfer-Encoding: quoted-printable
=E3=81=93=E3=81=AE=E5=BA=A6=E3=81=AF=E3=80=8C=E3=81=88=E3=82=93=E3=81=95=
=E3=81=8C=E3=81=9D=E3=81=A3=E2=99=AA=E3=80=8D=E3=82=92=E3=81=94=E5=88=A9=
=E7=94=A8=E3=81=84=E3=81=9F=E3=81=A0=E3=81=8D=E3=81=82=E3=82=8A=E3=81=8C=
=E3=81=A8=E3=81=86=E3=81=94=E3=81=96=E3=81=84=E3=81=BE=E3=81=99=E3=80=82
=
=E7=8F=BE=E5=9C=A8=E4=BB=AE=E7=99=BB=E9=8C=B2=E3=81=AE=E7=8A=B6=E6=85=8B=
=E3=81=A7=E3=81=99=E3=81=AE=E3=81=A7=E3=80=81=E4=B8=8B=E8=A8=98URL=E3=81=
=8B=E3=82=89=E6=9C=AC=E7=99=BB=E9=8C=B2=E3=82=92=E8=A1=8C=E3=81=A3=E3=81=
=A6=E3=81=8F=E3=81=A0=E3=81=95=E3=81=84=E3=80=82
http://localhost:8000/register/?t=xxxxxx
=E6=9C=AC=E3=83=A1=E3=83=BC=E3=83=AB=E3=81=
=AB=E8=A6=9A=E3=81=88=E3=81=8C=E3=81=AA=E3=81=84=E5=A0=B4=E5=90=88=E3=80=
=81=E4=BD=95=E3=81=8B=E3=81=94=E4=B8=8D=E6=98=8E=E3=81=AA=E7=82=B9=E3=81=
=8C=E3=81=82=E3=82=8B=E5=A0=B4=E5=90=88=E3=81=AF=E4=B8=8B=E8=A8=98=E3=82=
=88=E3=82=8A=E3=81=8A=E5=95=8F=E3=81=84=E5=90=88=E3=82=8F=E3=81=9B=E3=81=
=8F=E3=81=A0=E3=81=95=E3=81=84=E3=80=82
http://localhost:8000/contact/
=
=E2=80=BB=E6=9C=AC=E3=83=A1=E3=83=BC=E3=83=AB=E3=81=AF=E3=80=81=E3=82=
=B7=E3=82=B9=E3=83=86=E3=83=A0=E3=82=88=E3=82=8A=E8=87=AA=E5=8B=95=E9=80=
=81=E4=BF=A1=E3=81=95=E3=82=8C=E3=81=A6=E3=81=84=E3=81=BE=E3=81=99=E3=80=
=82=E3=81=93=E3=81=AE=E3=83=A1=E3=83=BC=E3=83=AB=E3=81=AB=E7=9B=B4=E6=8E=
=A5=E3=81=94=E8=BF=94=E4=BF=A1=E3=81=84=E3=81=9F=E3=81=A0=E3=81=8F=E3=81=
=93=E3=81=A8=E3=81=AF=E3=81=A7=E3=81=8D=E3=81=BE=E3=81=9B=E3=82=93=E3=80=
=82
--------------------------------------------------------
=E3=81=
=88=E3=82=93=E3=81=95=E3=81=8C=E3=81=9D=E3=81=A3=E2=99=AA=EF=BC=9Ahttp://lo=
calhost:8000
=E9=9B=BB=E8=A9=B1=E7=95=AA=E5=8F=B7=EF=BC=9A06-4862-7707=
=E3=81=8A=E5=95=8F=E3=81=84=E5=90=88=E3=82=8F=E3=81=9B=EF=BC=9Ahttp://lo=
calhost:8000/contact/
=E9=81=8B=E5=96=B6=E4=BC=9A=E7=A4=BE=EF=BC=9ABABYJO=
B=E6=A0=AA=E5=BC=8F=E4=BC=9A=E7=A4=BE
-----------------------------------=
---------------------
このように、ログドライバーを設定するだけでは quoted-printable エンコードされていてそもそも読めない。且つ、URLをクリックできない状態になってしまいます。
quoted-printable をデコードする処理を追加する
可読性を上げる工夫として quoted-printable をデコードする処理 を追加します。
サービスプロバイダーの仕組みを利用して環境変数 MAIL_MAILER=log
の場合、Laravelのメールのログ出力の実装クラス \Illuminate\Mail\Transport\LogTransport
を自前の実装クラス(ExtendedLogTransport
)に差し替えるアプローチをします。
後述する ExtendedLogTransport
クラスでログの出力チャンネルの設定のために必要ですので、環境変数 MAIL_LOG_CHANNEL
を追加します。
ここでは、 stderr
としています。
MAIL_MAILER=log
MAIL_LOG_CHANNEL=stderr
ExtendedLogTransport クラスを作成します。以下のようなイメージです。
class ExtendedLogTransport extends LogTransport implements TransportInterface
{
public function send(RawMessage $message, Envelope $envelope = null): ?SentMessage
{
$this->logger->debug(
sprintf(
"Logging instead of sending mail...\nFrom: %s\nTo: %s\nBcc: %s\nSubject: %s\n\n%s\n",
$this->getAddress($message, "From"),
$this->getAddress($message, "To"),
$this->getAddress($message, "Bcc"),
$this->getSubject($message),
$this->getBody($message)
)
);
return new SentMessage($message, $envelope ?? Envelope::create($message));
}
private function getAddress(Message $message, string $name): ?string
{
$header = $message->getHeaders()->get($name);
if ($header === null) {
return null;
}
return $header->getBodyAsString();
}
private function getSubject(Message $message): string
{
$header = $message->getHeaders()->get("Subject");
return $header->getValue();
}
private function getBody(Message $message): string
{
$body = $message->getBody();
return $body->getBody();
}
}
afterResolving()
メソッドを利用して MailManager
の依存解決後に ExtendedLogTransport
を依存注入します。
class LogTransportServiceProvider extends ServiceProvider
{
public function register()
{
$this->app->afterResolving(MailManager::class, function (MailManager $mailManager) {
$mailManager->extend("log", function ($config) {
$logger = $this->app->make(LoggerInterface::class);
$logger->channel($config['channel']);
return new ExtendedLogTransport($logger);
});
});
}
}
調整後の出力結果
[2023-12-19 05:16:55] local.DEBUG: Logging instead of sending mail...
From: XXXXXXX@example.com
To: XXXXXXX@example.com
Bcc:
Subject: 【えんさがそっ♪】仮登録の受付
この度は「えんさがそっ♪」をご利用いただきありがとうございます。
現在仮登録の状態ですので、下記URLから本登録を行ってください。
http://localhost:8000/register/?t=xxxxxx
本メールに覚えがない場合、何かご不明な点がある場合は下記よりお問い合わせください。
http://localhost:8000/contact/
※本メールは、システムより自動送信されています。このメールに直接ご返信いただくことはできません。
--------------------------------------------------------
えんさがそっ♪:http://localhost:8000
電話番号:06-4862-7707
お問い合わせ:http://localhost:8000/contact/
運営会社:BABYJOB株式会社
--------------------------------------------------------
これで実際に送信したメールとほぼおなじ状態で表示することができるようになりました!
おわりに
簡単ではありますがログにメールの送信内容を出力するアプローチでの実装方法やメリットとデメリット、出力結果などを紹介させていただきました。
最新の Laravel10 であれば、記事中で実施していた「quoted-printable をデコードする処理」も不要で、より簡単にログでのメールのデバッグが可能です。
もし他にも「もっと良いやり方」や「こうした方がいいよ」などがありましたらコメント等で教えて頂けますと嬉しいです!
最後までお読みいただきありがとうございました!
参考リンク
私たち BABY JOB は、子育てを取り巻く社会のあり方を変え、「すべての人が子育てを楽しいと思える社会」の実現を目指すスタートアップ企業です。圧倒的なぬくもりと当事者意識をもって、こどもと向き合う時間、そして心のゆとりが生まれるサービスを創出します。baby-job.co.jp/
Discussion