LoginSignup
6
4

More than 3 years have passed since last update.

PythonでSMTP通信してみた

Posted at

PythonでSMTP通信を実装する機会があったので実装方法を書いておきます。

いろいろと実装方法を調べていると標準ライブラリのレガシーAPIで実装している情報はたくさん出てきたのですが、現在推奨されているAPIでの方法があまり見つかりませんでした。WebフレームワークのDjangoもレガシーAPIを使っていたりして、主要ライブラリやフレームワークのコードも参考にできませんでした。
なので、Pythonのsmtplibemailの標準ライブラリを中心にコードリーディングしながら実装しました。

環境

  • OS: MacOS Catalina
  • Python: 3.7
  • Library: smtplib, email (Python standard libraries)

実装内容

  1. SMTPサーバーとのコネクション確立
  2. SMTP Auth
  3. メール内容送信
  4. 複数宛先への送信時に失敗した宛先をログに出力

ソースコード

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_messagesendmailに直接文字列を代入することはできます。しかし、プロトコルの仕様を理解していないと難しいのでお勧めはしません。できる限りEmailMessageを使用しましょう。

おわりに

この記事はもっと早くに公開する予定でしたが、途中まで書いて忘れていたのでブログの書き納め?(笑)みたいになってしまいました。
それではよいお年を:snowman2:

参考資料

6
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
4