PythonでSMTP通信を実装する機会があったので実装方法を書いておきます。
いろいろと実装方法を調べていると標準ライブラリのレガシーAPIで実装している情報はたくさん出てきたのですが、現在推奨されているAPIでの方法があまり見つかりませんでした。WebフレームワークのDjangoもレガシーAPIを使っていたりして、主要ライブラリやフレームワークのコードも参考にできませんでした。
なので、Pythonのsmtplib
とemail
の標準ライブラリを中心にコードリーディングしながら実装しました。
環境
- OS: MacOS Catalina
- Python: 3.7
- Library: smtplib, email (Python standard libraries)
実装内容
- SMTPサーバーとのコネクション確立
- SMTP Auth
- メール内容送信
- 複数宛先への送信時に失敗した宛先をログに出力
ソースコード
import smtplib
from email.message import EmailMessage
class MailClient:
def __init__(self, mail_data: Dict[str, Any] = {}) -> None:
"""
Args:
mail_data(Dict[]): Defaults to {}.
"""
self._mail_data = mail_data
def request(self) -> bool:
"""
Returns:
bool
"""
with smtplib.SMTP(host=SMTP_HOST, port=int(SMTP_PORT)) as smtp:
smtp.login(SMTP_USER, SMTP_PASSWORD)
errors = smtp.send_message(self.message())
if isinstance(errors, dict) and len(errors) > 0:
logger.warn(
f'''Failed to send to all recipients.
Details: {errors}'''
)
return True
def message(self) -> EmailMessage:
msg = EmailMessage()
msg['From'] = self._mail_data['from_address']
msg['To'] = self._mail_data['to_address']
msg['Subject'] = self._mail_data['subject']
msg.set_default_type('text/plain')
msg.set_content(self._mail_data['message'])
bcc = self._mail_data.get('bcc')
if bcc:
msg['Bcc'] = convert_to_str(bcc)
cc = self._mail_data.get('cc')
if cc:
msg['Cc'] = convert_to_str(cc)
return msg
解説
requestメソッド
smtplib.SMTP
でSMTPサーバーと接続を開始します。TLSを使用している場合はsmtplib.SMTP_SSL
を使うか、一旦平文でコネクションを張っておいてからstarttls()
でTLSで接続します。
コネクションが正常に確立できたら、 login()
で認証を行います。その後send_message
でデータを送信します。実際にデータ送信を担うのはsendmailです。この関数の中でSMTPコマンドを実行してメールを送信します。
エラーハンドリングですが、今回は単純に例外が発生したらraiseするだけでいいのでtry-exceptは書いていません。しかし、複数の宛先に送信した時に一つでも失敗したらログとして残すようにしています。その情報がsend_message
の返り値でありerrors
変数に格納されます。errorsの型はDictでキーが宛先アドレス、値はSTMP応答コードと対応するメッセージを含むタプルです。どのような場合にsend_message / sendmailが値を返すのか詳細を知りたい方はこちらにソースコードがありますので一読してみてください。
messageメソッド
今回はemailモジュールのEmailMessageを使ってメールデータを作成していますが、send_message
やsendmail
に直接文字列を代入することはできます。しかし、プロトコルの仕様を理解していないと難しいのでお勧めはしません。できる限りEmailMessageを使用しましょう。
おわりに
この記事はもっと早くに公開する予定でしたが、途中まで書いて忘れていたのでブログの書き納め?(笑)みたいになってしまいました。
それではよいお年を