プログラミング

Pythonで簡単なSMTPクライアントをTCP通信を使って作成してみる!

こんにちは、MSKです。
今回はPythonで簡単なSMTPクライアントをTCP通信を使って組んでみます。
最後におまけとしてPython標準のsmtpモジュールを使って、実際の認証が必要なサーバーを使ってメール送信までを実装してみたいと思います。

SMTP

SMTP(Simple Mail Transfer Protrocol)は電子メールを送信する時に使うプロトコルでTCP/IPの上に組まれます。
TCPのセッションを確立して文字列によるコマンドでやり取りを行います。
コマンドや応答の最後はCRLFをつけることになっています。
TCPのポートは通常25番が利用されます。

SMTPの主なコマンドを紹介します。

コマンド説明
HELO <domain>通信開始
MAIL FROM:<送信者>送信者
RCPT TO:<送信先アドレス>受信者の指定
DATA本文
VRFY <string>ユーザー名の確認
NOOP応答の要求
QUIT終了

コマンドに対する主なレスポンスを紹介します。

レスポンス説明
220 サービスを開始
221サービスを終了
250要求されたメールの処理が完了
354メールの本文の入力を開始。本文は.だけの行で入力終了
421サービスを提供できないため、接続を終了する
451問題が発生したため、処理が中断された
500コマンドが不正
501引数やパラメーターが不正
502リクエストされたコマンドが存在しない
503コマンドの順番が不正
504リクエストされたコマンドのパラメータが存在しない
551ユーザーがこのホストにいないため、要求を受けられない
554その他のエラー

SMTPのシーケンスの例を紹介します。

このシーケンスを次で実際に構築してみます。

TCPでSMTPクライアントを作ってみる

実際のSMTPサーバーでやると認証など少しややこしいので、今回はSMTPの通信にフォーカスしたいので、ローカルのSMTPサーバーと通信するプログラムを作ります。
ソースコードは以下になります。

import socket
import time

# 送信するアドレス
SENDER_ADDRESS = "from@example.com"
# SMTPサーバーの名前
SERVER_NAME = "localhost"
# SMTPサーバーのポート
SERVER_PORT = 8025
# 受信の最大サイズ
RECEIVE_SIZE = 1024
# メールの本文の最後
TERMINAL_MESSAGE = ".\r\n"
# 送信先アドレス
TO_EMAIL_ADDRESS = "to@example.com"
# メールのタイトル
SUBJECT = "Test!!"
# メールの本文
MESSAGE = "Hello!! My Name is MSk!"


# TCPクライアントとしてメッセージを送信
def send(client_sock, msg: str):
    client_sock.send(msg.encode())


# TCPクライアントとしてデータを受け取る
# 指定したステータスコード以外が受け取るとFalseを返す
def receive(client_sock, status_code) -> bool:
    receive_msg = client_sock.recv(RECEIVE_SIZE).decode()
    print(receive_msg)
    if receive_msg[:3] != str(status_code):
        return False
    else:
        return True


# SMTPクライアントの一連の動きを行う
# 問題なくメッセージまで送信できたらTrue
# 途中で失敗した場合、False
def create_smtp_client(to, subject, body) -> bool:
    # ソケットを作成
    with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
        # サーバーに接続
        sock.connect((SERVER_NAME, SERVER_PORT))
        if not receive(sock, 220):
            return False

        # HELOコマンドを送信
        send(sock, "HELO LocalHost\r\n")
        if not receive(sock, 250):
            return False

        # MAIL FROM:コマンドを送信
        from_command = "MAIL FROM: <" + SENDER_ADDRESS + ">\r\n"
        send(sock, from_command)
        if not receive(sock, 250):
            return False

        # RCPT TO:コマンドを送信
        rcpt_command = "RCPT TO: <" + to + ">\r\n"
        send(sock, rcpt_command)
        if not receive(sock, 250):
            return False

        # 本文を送信する
        send(sock, "DATA\r\n")
        if not receive(sock, 354):
            return False

        # ここから本文
        send(sock, "From user1 <" + SENDER_ADDRESS + ">\r\n")
        send(sock, "To: user2 <" + to + ">\r\n")
        send(sock, "Date: " + time.asctime(time.localtime(time.time())) + "\r\n")
        send(sock, "Subject: " + subject + "\r\n")
        send(sock, "\r\n")
        send(sock, body+"\r\n")
        # ここまで本文

        # DATAの最後を送信
        send(sock, TERMINAL_MESSAGE)
        if not receive(sock, 250):
            return False

        # 終了を通知
        send(sock, "QUIT\r\n")
        if not receive(sock, 221):
            return False

        return True


if __name__ == '__main__':

    if create_smtp_client(TO_EMAIL_ADDRESS, SUBJECT, MESSAGE):
        print("Success to Send EMail")
    else:
        print("Failed to Send EMail")

基本的にはサーバーとTCP通信を開始して、SMTPの手順に沿って通信を行っています。

動作確認

動作を見てみたいので、動作確認のためにローカルで試せるSMTPサーバーをインストールします。

pip install aiosmtpd

aiosmtpdをインストールして、次のコマンドを打つとローカルで簡易的なSMTPサーバーが立ち上がります。

python -m aiosmtpd -n

ポートは8025がデフォルトでは使用されます。

このSMTPサーバーを立ち上げて、上のプログラムを実行します。
すると、SMTPサーバーを立ち上げているターミナルに次のように表示がされると思います。

---------- MESSAGE FOLLOWS ----------
From user1 <from@example.com>
To: user2 <to@example.com>
Date: Sun Aug  7 22:39:50 2022
Subject: Test!!
X-Peer: ('127.0.0.1', 49990)

Hello!! My Name is MSk!
------------ END MESSAGE ------------

実際のサーバーで使ってみる

ついでに実際のメールサーバーを使って、プログラムからメールを送信することをやってみます。
ここではsmtplibモジュールを使います。

import smtplib
from email.mime.text import MIMEText

mail_acc = "xxxx@yyyyyyyy.com"
mail_pass = "aabbccdd"

from_email = "xxxxxx@ssssss.com"
from_email_port = 21


if __name__ == '__main__':
    to_email = "test@gmail.com"
    title = "test"
    message = "Hello!!"
    # メッセージを作成
    msg = MIMEText(message, "plain")
    msg["Subject"] = title
    msg["To"] = to_email
    msg["From"] = from_email

    server = smtplib.SMTP("tttttttttt.com", from_email_port)
    # 認証
    server.starttls()
    server.login(mail_acc, mail_pass)
    # 実際にメール送信
    server.send_message(msg)
    server.quit()

最後に

今回はSMTPクライアントを実際にTCP通信の上で構築してみました。
SMTPはメールを送信するTCP/IP上のプロトコルで、コマンドとレスポンスによりやり取りを行っています。

最後までご覧いただき、ありがとうございます。
「Pythonで簡単なSMTPクライアントをTCP通信を使って作成してみる!」でした。

ABOUT ME
MSK
九州在住の組み込み系エンジニアです。 2児の父親でもあります。 数学やプログラミングが趣味です。 最近RustとReact、結び目理論と曲面結び目理論にはまっています。