Laravel Tech インフラ 効率化 開発ツール

Laravel Sail9から導入されたMailhogの後継Fake SMTP/mailpitを使ってみた

TL;DR

  • Fake SMTPについて
  • MailHogとmailpitの機能差について
  • maiplitの機能について(メール送信,API,SMTPS,SMTP_AUTHの検証)

今回の検証用ソースはこちらです。

Fake SMTPについて

Fake SMTPはメール送信テストの際に有用です。

わざわざテスト用のSMTPサーバーをレンタルしたりする必要がなくなります。

また実際にメール送信をするわけではないので、テスト時に誤って本番メールアドレスへ送信するという事態を防ぐことができます。

Fake SMTPの仕組みとしてはざっくり以下のような感じです。

  • メール送信
  • 実際にメール送信が行われるのではなく、Fake SMTPで設定したストレージにメールが保存される
  • そのストレージに保存したメールをブラウザもしくはAPI等から確認できる

製品としては以下のようなものが有名どころのようです。

MailHog

先に紹介したFake SMTPのうち近年最も使われているのはMailHogの印象を受けます。

自分なりに考察した結果、MailHogがここまで流行った要因というのはLaravel Sail(8系まで)に採用されていたことだと考えます。

mailpit

しかしLaravel 9系からはMailHogではなくmailpitが採用されています。

なぜmailpitに変えたのだろう?と思うのですが、おそらくこれが原因ではないだろうかというものを見つけました。

それは、mailpitのREADMEに記載されています。

I had been using MailHog for a few years to intercept and test emails generated from several projects. MailHog has a number of severe performance issues, many of the modules are horribly out of date, and other than a few accepted MRs, it is not actively developed.

Initially I started trying to upgrade a fork of MailHog (both the UI as well as the HTTP server & API), but soon discovered that it is (with all due respect) very poorly designed. It is over-engineered (split over 9 separate projects) and has too many unnecessary features for my purpose. It performs exceptionally poorly when dealing with large amounts of emails or processing any email with an attachment (a single email with a 3MB attachment can take over a minute to ingest). The API also transmits a lot of duplicate and unnecessary data on every message request for all web calls, and there is no HTTP compression.

In order to improve it I felt it needed to be completely rewritten, and so Mailpit was born.

https://github.com/axllent/mailpit#why-rewrite-mailhog

簡単に要約すると、

  • MailHogは性能面で深刻な問題を抱えている
  • モジュールが恐ろしく古く、最近は活発に開発されていない
  • 大量メールや添付ファイルがあるメールを処理するとパフォーマンスが悪い
  • APIは余計なデータを送信している
  • 以上を解消するにはソースを一から書き直さないと無理なほど設計がお粗末

ということを言っています。(ボロカスやないか…)

MailHogは本当に性能が悪いのか?

ということで検証してみました。

手順としては、

  • メール送信の直前直後にログを仕込む
  • MailHog,mailpitそれぞれに対して1MBの添付ファイルを付けてメール送信を行う
  • ログの時間から処理時間を計測

という単純なものです。

2に関して、大量件数で送信しないの?と思われた方がいらっしゃると思います。

当初そのつもりで準備していたのですが、テストで1件送信(1MB画像添付あり)した時点でその性能差が浮き彫りになってしまいました。

結論として、性能差は圧倒的。

2023-02-11 09:08:42,348 - INFO:root - mailhog begin
2023-02-11 09:09:06,724 - INFO:root - mailhog end
2023-02-11 09:13:36,661 - INFO:root - mailpit begin
2023-02-11 09:13:37,104 - INFO:root - mailpit end

これは1MBの画像を1件添付し、1通のメールをMailHogとmailpitで送った結果です。

MailHog -> 約24秒

mailpit -> 約500ミリ

ということで圧倒的にmailpitの勝利でした。

Laravel Sailがmailpitに乗り換えるのも納得。

メール送信のプログラムの全体は最初に記載したgithubに載せています。

一応ここにも全体を載せておきます。

以降の検証でも同じソースを使いまわします。

import logging
logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(levelname)s:%(name)s - %(message)s",filename="logs/test.log")

def sendmail(host, port):
  import smtplib
  from email.mime.text import MIMEText
  from email.mime.multipart import MIMEMultipart
  from email.mime.application import MIMEApplication
  smtp_obj = smtplib.SMTP(host, port)
  # mailpitのみSMTPS, SMTP_AUTHのどちらも設定している
  # smtp_obj.starttls()
  # smtp_obj.login('user', 'pass')

  body = "メールの本文"
  msg = MIMEMultipart()
  msg['Subject'] = "メールの件名"
  msg['To'] = 'to@office54.net'
  msg['From'] = 'from@office54.net '
  msg.attach(MIMEText(body))

  with open(r"/src/img/1MB.jpeg", "rb") as f:
      attachment = MIMEApplication(f.read())

  attachment.add_header("Content-Disposition", "attachment", filename="1MB.jpeg")
  msg.attach(attachment)
  smtp_obj.send_message(msg)
  smtp_obj.quit()

# logging.info("mailhog begin")
# sendmail("mailhog", 1026)
# logging.info("mailhog end")
logging.info("mailpit begin")
sendmail("mailpit", 1025)
logging.info("mailpit end")

mailpit でいろいろ試してみる

性能面でMailHogを圧倒したmailpitですが、そのほかの機能についても使い勝手を確認していこうと思います。

今回試したのは以下の通りです。

  • API
  • SMTPSでのメール送信
  • SMTP_AUTHの利用

以降で詳しく書きます。

API

APIを利用してメールの一覧取得、内容取得、削除が可能です。

今回は試さなかったのですが、Basic認証HTTPSの利用もできそうです。

(ドキュメントへのリンクを貼っておきます)

今回はVSCodeの拡張機能の「REST Client」から試しています。

メールの一覧取得

### GET MESSAGE LIST
GET http://127.0.0.1:8025/api/v1/messages

メールの内容取得

### GET MESSAGE
GET http://127.0.0.1:8025/api/v1/message/4067ddc0-a6e9-4905-bec6-52ffc8aa8307

メールの削除

### DELETE MESSAGE
DELETE http://127.0.0.1:8025/api/v1/messages

{
  "ids": ["4067ddc0-a6e9-4905-bec6-52ffc8aa8307"]
}

SMTPSの有効化

テストなので自己署名の証明書を使います。

まずaxllent/mailpitのコンテナに入ってopensslをインストールします。

鍵と証明書の置き場所をつ口、鍵と証明書を生成。

apk add openssl
mkdir keys
openssl req -x509 -newkey rsa:4096 -nodes -keyout /keys/privkey.pem -out /keys/cert.pem -sha256 -days 3650

生成したらDockerから抜けて、鍵の置き場所をdocker-compose.ymlから指定します。

(ここまでの手順はDockerfileベースで作るか、あるいはあらかじめ別途用意しておいた鍵と証明書を使うほうがいいかもです)

  mailpit:
    image: axllent/mailpit
    tty: true
    ports:
      - "8025:8025"
      - "1025:1025"
    environment:
      MP_DATA_FILE: /home/mailpit/mails
      MP_SMTP_SSL_CERT: /keys/cert.pem #これを追加
      MP_SMTP_SSL_KEY: /keys/privkey.pem #これを追加
    volumes:
      - ./mails/mailpit:/home/mailpit/mails

docker-compose.ymlを修正したらコンテナを起動しなおして、Pythonのコードを修正します。

def sendmail(host, port):
  import smtplib
  from email.mime.text import MIMEText
  from email.mime.multipart import MIMEMultipart
  from email.mime.application import MIMEApplication
  smtp_obj = smtplib.SMTP(host, port)
  # mailpitであればSMTPS, SMTP_AUTHのどちらも対応できる
  smtp_obj.starttls() #このコメントを外す
  # smtp_obj.login('from@office54.net', 'Password')

これでSMTPSを使ったメール送信ができます。

プログラムの実行は、appコンテナに入ってpythonを実行します。

docker-compose exec app bash
python sendmail.py

SMTP_AUTHの有効化

ドキュメントによると、SMTPSを有効にしないとSMTP_AUTHは利用できないようです。

なので、先述のSMTPSの手順を行った上で以降を実施する必要があります。

そこでまず、SMTP_AUTHで利用するユーザー認証情報を用意します。

認証の形式はbasic認証と同じであることがドキュメントに記載されているので、今回はhtpasswdを利用して認証ファイルを作成します。

あらかじめファイルを用意する形でも問題ないと思います。

ではhtpasswdのインストール

# htpasswdが入ってる
apk add apache2-utils

# ヘルプが見れればインストールできている
htpasswd --help

インストールしたら認証ファイルを作成します。

htpasswd -c ./keys/.htpasswd mailpit

パスワードを聞かれるので、今回はmailpitとします。

ここまでできたら先ほどと同じくdocker-compose.ymlにユーザー認証ファイルの場所を設定します。

  mailpit:
    image: axllent/mailpit
    tty: true
    ports:
      - "8025:8025"
      - "1025:1025"
    environment:
      MP_DATA_FILE: /home/mailpit/mails
      MP_SMTP_SSL_CERT: /keys/cert.pem
      MP_SMTP_SSL_KEY: /keys/privkey.pem
      MP_SMTP_AUTH_FILE: /keys/.htpasswd #これを追加
    volumes:
      - ./mails/mailpit:/home/mailpit/mails
      - ./keys:/keys

ではPythonのコードを修正します。

今回はまず出鱈目なユーザーを指定してみます。

  # mailpitであればSMTPS, SMTP_AUTHのどちらも対応できる
  smtp_obj.starttls()
  smtp_obj.login('from@office54.net', 'Password') #ここを修正

この状態で実行すると認証に失敗して怒られることが確認できます。

root@4e12d0b511af:/src# python sendmail.py 
Traceback (most recent call last):
  File "/src/sendmail.py", line 33, in <module>
    sendmail("mailpit", 1025)
  File "/src/sendmail.py", line 12, in sendmail
    smtp_obj.login('from@office54.net', 'Password')
  File "/usr/local/lib/python3.11/smtplib.py", line 750, in login
    raise last_exception
  File "/usr/local/lib/python3.11/smtplib.py", line 739, in login
    (code, resp) = self.auth(
                   ^^^^^^^^^^
  File "/usr/local/lib/python3.11/smtplib.py", line 662, in auth
    raise SMTPAuthenticationError(code, resp)
smtplib.SMTPAuthenticationError: (535, b'5.7.8 Authentication credentials invalid')

次は正しいユーザーを指定してみます。

  smtp_obj.starttls()
  smtp_obj.login('mailpit', 'mailpit') #ここを修正

今度は正常にメール送信することができました。

MailHogの優位性は失われたのか?

ここまでみてきた感じだと性能面、機能面ともにmailpitの方が上位互換な感じがします。

MailHogは完全に過去の遺物になってしまったのだろうか…ということを考えてみたのですが、MailHogの優位性としてカオスモンキー=Jimがあるのではないかと思います。

この記事を書いた2023/02/11時点ではmailpitのwikiの中にはカオスモンキーの記載を見つけることはできませんでした。

なので、障害テストを一つのソフトの中で完結できるという点がMailHogの優位性であると考えます。

まとめ

今回いろいろと検証してみた結果、私個人としては今後Fake SMTPの導入する際にはmailpitを使おうと思いました。

MailHogに若干の優位性は残るものの、基本的にはmailpitがMailHogの上位互換として君臨することになりそうです。

ただ先日OSSにMailHogを導入してプルリクを出してしまったんですよね…

再度修正することを検討しようかと思います。

直近の推し5選!

1

みなさんこんばんは。 こんにちは、Kanon です。今回は… しめさば先生の『君は僕の後悔』の感想記事です。 しめさば先生はこれまでにも『ひげを剃る。そして女子高生を拾う』や『きみは本当に僕の天使なの ...

2

リンク あらすじ 人生オール80点。 そんな俺が託されたのは、元トップアイドル・香澄ミルの世話だった。 ファン対応がしみつきなかなかクラスに馴染めないミル。 そんな彼女に頼られるうち、俺たちは図らずも ...

3

リンク 二丸先生の他の作品はこちら あらすじ 記憶喪失の湖西廻の前に現れたのは、清純で素朴な美少女、丹沢白雪。 「──私、廻くんと恋人だったの」  白雪はそう言って顔を赤らめ、廻の頬にキスをする。   ...

4

リンク あらすじ その夜、僕の青春は〈炎〉とともに産声をあげた――  スマホを忘れて夜の学校に忍び込んだ在原有葉(ありはらあるは)は、屋上を照らす奇妙な光に気づく。そこで出会ったのは、闇夜の中で燃え上 ...

5

こんにちは、Kanon です。今回は… しめさば先生の『きみは本当に僕の天使なのか』の感想記事です。 表紙とタイトルを見るに幻想的な恋の話のように思えるのですが、実はタイトルはそんな幻のようなものでは ...

-Laravel, Tech, インフラ, 効率化, 開発ツール
-, , , , , , , , , , , , ,