はじめに
あけましておめでとうございます(あるいは、こんにちは)。
皆さんは年末年始、いかがお過ごしでしたか?
私はというと、暇を持て余していました。 いや、それはともかく!
普段、Django や Flask で開発をしていると、メール送信のテストをしたくなること、ありますよね?
そんな時、デファクトスタンダードとしてよく使われるのが MailHog だと思います。Go言語製で、バイナリ一つで動くし、Dockerコンテナもあって非常に便利なツールだと思います。
しかし、使っていると「ここ、もうちょっとこうしたいな……」という細かい不満や、「この挙動、どうなってるんだ?」とソースを追いたくなる瞬間があります。でも、Go言語の環境をセットアップしてビルドし直すのは、Python使いとしてはちょっと敷居が高い。
「……せや、年末年始で暇やし Python で自分好みの MailHog 作ったろ」
というわけで、年末年始の勢いに任せて、Python エコシステムだけで完結する SMTP サーバー兼 Web UI ツール 「MailOrca(メールオルカ)」 を作ってみました。
今回はその紹介も兼ねて、技術的な構成についての話(全4回)です。
MailOrca とは?
今回作成した MailOrca は、開発環境向けの軽量 SMTP サーバーです。
アプリケーション(Djangoなど)から送信されたメールを SMTP プロトコルで受け取り、メモリ上に保存して、Web ブラウザから即座に確認できます。ようするに 「Python 版 MailHog」 です。
ここがポイント
-
Python Native:
- Go や Docker は不要です。
- Python 3.9 以上があれば動きます。
で直接導入したり、リポジトリを% pip install git+https://github.com/yynet2022/mailorca.git % mailorcacloneしてするだけですぐに使えます。(前者はコマンド% git clone https://github.com/yynet2022/mailorca.git % cd mailorca % pip install -r requirements.txt % python runserver.pymailorcaで、後者はrunserver.pyが用意してあります)- 既存の Python プロジェクトの
dev-dependenciesに GitHub の URL を指定して追加することも可能です。
-
設定が楽:
- 「ポート番号を変えたい」「表示するヘッダー項目を変えたい」といった要望に、JSONファイル一つで応えられるようにしました。
-
モダンな非同期構成:
- 最近の Python といえば
asyncioかなと。SMTP サーバーも Web サーバーも非同期でサクサク動きます。
- 最近の Python といえば
全体アーキテクチャ
「Python だけで作る」といっても、ゼロからソケットプログラミングをするのは車輪の再発明すぎます。
今回は、Python 界隈の強力なライブラリを組み合わせて実現しました。
構成図はこんな感じです。
技術スタック
これらを runserver.py という一つのスクリプト(cloneした場合)から起動するようにしています。
-
Web Framework: FastAPI
- Web UI の提供と、API エンドポイントの実装に使用。
- テンプレートエンジンには Jinja2 を採用。
-
ASGI Server: Uvicorn
- FastAPI を動かすためのサーバー。
-
SMTP Server: aiosmtpd
- Python 標準ライブラリ(だった)
smtpdが非推奨になった今、モダンな代替として使われる非同期 SMTP サーバーライブラリ。
- Python 標準ライブラリ(だった)
-
CLI: Click
- コマンドライン引数の解析に使用。「ポート番号を引数で上書き」みたいな処理が爆速で実装できる。
なぜ作ったのか(もう少し詳しく)
既存ツール(MailHogなど)は素晴らしいですが、Python プロジェクトの開発中に「ちょっと挙動を変えたい」と思った時、Python で書かれていれば 「モンキーパッチで強引に挙動を変える」 とか 「直接ソースをいじって pip install -e . で読み込ませる」 といったハックが容易になります。
「自分が一番慣れ親しんだ道具で、自分好みの道具を作る」
これが最大のモチベーション。(あと、aiosmtpd と FastAPI を組み合わせて何か作りたかった、という技術的興味も)
次回予告
今回は「作った動機」と「全体像」についてお話ししました。
次回からは、実際にどうやって実装したのか、コードレベルでの解説をしていきたいと思います。
-
ClickとUvicornをどうやって連携させたのか? -
FastAPIのlifespanでバックグラウンドタスク(SMTP)を走らせる方法 -
aiosmtpdでメールを受け取って解析する泥臭い部分
などなど、技術的な詳細を掘り下げていきます。
Python でちょっとしたサーバーツールを作ってみたい方の参考になれば幸いです。
それでは、また次回!
