こんにちは、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通信を使って作成してみる!」でした。
