Python smtplibとは?メールを送信する方法
smtplibは、組み立てたメールをSMTPサーバーへ送る標準ライブラリです。
メールの中身を作るのはemail モジュールの役割です。作る係と送る係が分かれていると覚えると混乱しません。
送信の最短コード
with文で開き、send_message()に渡すだけです。
import smtplib
from email.message import EmailMessage
m = EmailMessage()
m["Subject"] = "通知"
m["From"] = "me@example.com"
m["To"] = "you@example.com"
m.set_content("本文")
with smtplib.SMTP("127.0.0.1", 25, timeout=10) as s:
result = s.send_message(m)
print(result)
実行結果:
{}
空の辞書が返ったら全員に送れたという意味です。ここが直感に反するところで、Trueが返るわけではありません。
一部の宛先だけ拒否された場合は、拒否された宛先が辞書に入って返ります。つまりif result:が真なら失敗した宛先があるということです。
Gmailから送る
GmailのSMTPはポート587のSTARTTLSか、ポート465のSSLを使います。
import smtplib
from email.message import EmailMessage
m = EmailMessage()
m["Subject"] = "テスト"
m["From"] = "you@gmail.com"
m["To"] = "dest@example.com"
m.set_content("本文")
with smtplib.SMTP("smtp.gmail.com", 587, timeout=30) as s:
s.starttls() # ここから暗号化
s.login("you@gmail.com", "アプリパスワード")
s.send_message(m)
通常のGoogleアカウントのパスワードでは接続できません。2段階認証を有効にしてから発行するアプリパスワード(16文字)を使います。
SMTP_SSLなら最初から暗号化された接続になり、starttls()は不要です。
with smtplib.SMTP_SSL("smtp.gmail.com", 465, timeout=30) as s:
s.login("you@gmail.com", "アプリパスワード")
s.send_message(m)
パスワードをコードに書かない
import os, smtplib
user = os.environ["GMAIL_USER"]
app_pass = os.environ["GMAIL_APP_PASSWORD"]
with smtplib.SMTP("smtp.gmail.com", 587, timeout=30) as s:
s.starttls()
s.login(user, app_pass)
環境変数に置けば、ソースコードを共有しても鍵は漏れません。設定ファイルで管理するならPython configparserとは?設定ファイルを読み書きする方法が使えます。
宛先が拒否されたとき
存在しない宛先だけを指定すると例外になります。検証用のSMTPサーバーを立てて、550を返させた結果です。
try:
with smtplib.SMTP("127.0.0.1", 2526, timeout=10) as s:
s.send_message(m)
except smtplib.SMTPRecipientsRefused as e:
print(type(e).__name__)
print("recipients:", e.recipients)
実行結果:
SMTPRecipientsRefused
recipients: {'ng@example.com': (550, b'No such user here')}
recipientsに宛先ごとの応答コードと理由が入ります。ログに残すならここを見ます。
接続できないとき
try:
smtplib.SMTP("127.0.0.1", 2, timeout=3)
except Exception as e:
print(type(e).__name__, "->", e)
実行結果:
ConnectionRefusedError -> [Errno 61] Connection refused
接続の失敗は smtplib の例外ではなく OSError 系で飛んできます。smtplib.SMTPExceptionだけを捕まえていると取り逃がします。
よく出る例外の見分け方
SMTPAuthenticationError… ユーザー名かパスワードが違う。Gmailならアプリパスワードを確認SMTPRecipientsRefused… 宛先がすべて拒否されたSMTPSenderRefused… 差出人が拒否された。Fromと認証アカウントの不一致が多いSMTPServerDisconnected… 暗号化前に認証した、タイムアウトなどConnectionRefusedError・socket.timeout… そもそも繋がっていない
暗号化の確認
starttls()は既定でSSLとTLSの違いとは?の検証を行います。中身は次の設定です。
import ssl
ctx = ssl.create_default_context()
print("verify_mode:", ctx.verify_mode)
print("check_hostname:", ctx.check_hostname)
実行結果:
verify_mode: VerifyMode.CERT_REQUIRED
check_hostname: True
証明書の検証を切る設定をネットで見かけても真似しないでください。暗号化している意味がなくなります。
まとめ
send_message()が空の辞書を返したら成功- Gmailはアプリパスワード。通常のパスワードでは繋がらない
- 587なら
starttls()、465ならSMTP_SSL - 接続失敗は
OSError系。SMTPExceptionだけでは足りない
関連記事
あわせて読むと理解が深まります。





