Table of Contents
1. テストSMTPサーバーを立てる
emailのテストをするには、何はともあれSMTPサーバーが必要ですが、Pythonではとても簡単にテスト用のSMTPサーバーを立てることができます。
まずはpipでモジュールをインストールします。
pip install aiosmtpd
そして実行します。
python -m aiosmtpd -n
なんと、これだけ。aiosmtpd公式ドキュメントによるとlocalhostのポート8025をリッスンするようです。変えるには -l <[HOST][:PORT]> オプションを与えます。
pipするのが面倒な方は、標準ライブラリにsmtpdがありますが、公式ドキュメントによるとdeprecatedなのでaiosmtpdを使うように書いてありました。以下で使えます。
python -m smtpd -n -c DebuggingServer <IP address>:<port>
2. テストメールを送る
SMTPサーバーを立てるほどではありませんが、テストメールの送信も簡単にできます。
import smtplib
from email import utils
from email.mime.text import MIMEText
msg = MIMEText('This is the body') # メール本文
msg['To'] = utils.formataddr((
'Recipient', 'test_recv@example.com'))
msg['From'] = utils.formataddr((
'Author', 'test_send@example.com'))
msg['Subject'] = 'Test email subject'
server = smtplib.SMTP('localhost', 8025)
# server.set_debuglevel(True) # デバッグ用詳細メッセージ出力
try:
server.sendmail('test_send@example.com',
['test_recv@example.com'],
msg.as_string())
finally:
server.quit()
3. テストしてみる
ターミナル上でテスト用のSMTPサーバーを走らせている状態で、上記のテストメール送信スクリプトを別ターミナルから実行してみます。
python emtest.py
あ、来ました。
$ python -m aiosmtpd -n
^[---------- MESSAGE FOLLOWS ----------
Content-Type: text/plain; charset="us-ascii"
MIME-Version: 1.0
Content-Transfer-Encoding: 7bit
To: Recipient <test_recv@example.com>
From: Author <test_send@example.com>
Subject: Simple test message
X-Peer: ('::1', 52132, 0, 0)
This is the body.
------------ END MESSAGE ------------
Pythonを使うとemailのテストがとても簡単にできますね。
上記スクリプトのデバッグ用詳細メッセージ出力の行のコメントを外すと、SMTPサーバーとの詳細なやりとりが見られます。(一部情報を伏せています)
$ python emtest.py send: 'ehlo localhost.localdomain\r\n' reply: b'250-localhost.localdomain\r\n' reply: b'250-8BITMIME\r\n' reply: b'250 HELP\r\n' reply: retcode (250); Msg: b'localhost.localdomain\n8BITMIME\nHELP' send: 'mail FROM:<test_send@example.com>\r\n' reply: b'250 OK\r\n' reply: retcode (250); Msg: b'OK' send: 'rcpt TO:<test_recv@example.com>\r\n' reply: b'250 OK\r\n' reply: retcode (250); Msg: b'OK' send: 'data\r\n' reply: b'354 End data with <CR><LF>.<CR><LF>\r\n' reply: retcode (354); Msg: b'End data with <CR><LF>.<CR><LF>' data: (354, b'End data with <CR><LF>.<CR><LF>') send: b'Content-Type: text/plain; charset="us-ascii"\r\nMIME-Version: 1.0\r\nContent-Transfer-Encoding: 7bit\r\nTo: Recipient <test_recv@example.com>\r\nFrom: Author <test_send@example.com>\r\nSubject: Simple test message\r\n\r\nThis is the body.\r\n.\r\n' reply: b'250 OK\r\n' reply: retcode (250); Msg: b'OK' data: (250, b'OK') send: 'quit\r\n' reply: b'221 Bye\r\n' reply: retcode (221); Msg: b'Bye'
4. 宛先が表示されない?
あることに気がつきました。上で紹介した簡易smtpサーバーで表示される情報に、宛先(To/Cc/Bcc)が表示されないことがあるのです。調べてみたところ、宛先の情報はSMTP envelopeに含まれるRCPT TOが使われることがわかりました。実際の宛先が知りたい場合には、上記smtpサーバーで出力される情報では不足です。
そこで、aiosmtpdのドキュメントを参考(ほぼ丸写しとも言う)に、SMTPサーバー側も簡単なスクリプトを作成してRCPT TO情報を出力するようにしました。
import asyncio
class MyHandler:
async def handle_DATA(self, server, session, envelope):
print(f'Msg from {envelope.mail_from}')
# envelopeのRCPT TO情報を表示する
print(f'Msg for {envelope.rcpt_tos}')
print(f'Msg data:\n')
for ln in envelope.content.decode('utf8', errors='replace').splitlines():
print(f'> {ln}'.strip())
print()
print('End of msg')
return '250 Message accepted for delivery'
from aiosmtpd.controller import Controller
ctrlr = Controller(MyHandler(), hostname='<IPアドレス>')
ctrlr.start()
さて、どうでしょうか。To/Cc/Bccを含めない送信元からメールを出してみます。
Msg from <送り主emailアドレス> Msg for ['<宛先emailアドレス>'] # RCPT TOsアドレスのリスト Msg data: > Date: Fri, 5 Feb 2021 18:12:38 -0500 (EST) > From: <送り主emailアドレス> > Message-ID: <XXXXX2588.7.XXXXXXXX.JavaMail.root@XXXXX> > Subject: <タイトル文字列> > MIME-Version: 1.0 > Content-Type: text/plain; charset=us-ascii > Content-Transfer-Encoding: 7bit > > This is email contents. > End of msg
無事、宛先アドレスがわかるようになりました。(引用では伏せてあります)
なお、送信側を以下のように変えて、RCPT TOにToとは異なるアドレス(複数可能)を指定することができます。
try:
server.sendmail('send@example.com', # mail fromアドレス
['recv1@example.com', 'recv2@example.com'], # RCPT TOsアドレスのリスト
msg.as_string())