読者です 読者をやめる 読者になる 読者になる

Python3.5 + smtplib.SMTP_SSL.send_message()で、Gmailからメールを送信する

Python Google

Python3.5 + SMTPで、Gmailからメールを送信しようと考え調べてみたところ、Web上には色々な書き方がありました。

自分なりにいろいろと手を動かしてみて、Pythonの標準ライブラリであるsmtplib.SMTP_SSL.send_message()に行き着いた時のメモを残します。

ただ、自分なりの調査のため、もし誤りなどがあればご指摘いただけるとありがたいです。

 
なお、MIMETextの設定など、どのような書き方でも変わらない部分は、

import sys
import smtplib
from email.mime.text import MIMEText
from email.utils import formatdate

ACCOUNT = "example@gmail.com"
ALIAS = "example+alias@gmail.com"
PASSWORD = "passw0rd"

def create_message(subject):
    msg = MIMEText("Body: Hello world!")
    msg["Subject"] = subject
    msg["From"] = ACCOUNT
    msg["To"] = ALIAS
    msg["Date"] = formatdate(localtime=True)
    return msg

のような定数・関数を用意しています。

実際にテストするときは、定数の値を自分のアカウント情報へと変更します。

 

環境

  • Windows10 x64
  • Python 3.5.1 32bit

 
今回、

  • emailパッケージのMIMETextを使って、emailメッセージを構築
  • smtplibモジュールを使って、Gmailからメールを送信

を行います。

 
また、どのコードから送信されたのかを分かりやすくするため、送信処理を行った関数名をメールのSubjectに入れました。
実行中の関数・メソッド名を取得したい - Qiita

ほかに、GitHub上のソースコードを行数付で参照していますが、masterだと行数が変わってしまうおそれがあるため、適当な最近のコミットを指定しています。

 

MIMETextのimportについて

MIMETextのimportでは2つの方法を見かけたため、両方を試してみたところ、

from email.MIMEText import MIMEText
# => ImportError: No module named 'email.MIMEText'

from email.mime.text import MIMEText
# => OK

前者ではImportErrorになりました。

ソースコードを見たところ、Python2.7系ではモジュールがあったものの、Python3.5系では削除されていました。

ちなみに、from email.mime.text import MIMETextは、Python2.7系でも使えそうでした。

 

SMTPオブジェクトを使う実装

starttls()の前後で、ehlo()を呼ぶ実装

公式ドキュメントを読むと、helo()connect()は不要そうだったので、それらのメソッドは使いませんでした。

 
また、終了するときに、close()quit()、どちらを使えば良いのか分かりませんでした。ただ、

  • 公式ドキュメントにはclose()の記載なし
  • ソースコードを読んだところ、quit()の中でclose()を呼んでいた

ということから、quit()を使うのが良さそうでした。

msg = create_message(sys._getframe().f_code.co_name)

s = smtplib.SMTP("smtp.gmail.com", 587)
s.ehlo()
s.starttls()
s.ehlo()
s.login(ACCOUNT, PASSWORD)

s.sendmail(ACCOUNT, ALIAS, msg.as_string())
s.quit()

 

ehlo()を使わない実装

starttls()の前後でehlo()を呼ぶ必要があるのかを調べたところ、

ということから、必要なさそうでした。

msg = create_message(sys._getframe().f_code.co_name)

s = smtplib.SMTP("smtp.gmail.com", 587)
s.starttls()
s.login(ACCOUNT, PASSWORD)

s.sendmail(ACCOUNT, ALIAS, msg.as_string())
s.quit()

print("finished: {}".format(sys._getframe().f_code.co_name))

 

SMTP_SSLオブジェクトを使う実装

starttls()メソッドでSTARTTLSコマンドを使って暗号化をしていました。ただ、Web上のいくつかのサンプルでは、SMTP_SSLオブジェクトを使っていました。
class smtplib.SMTP_SSL - 21.17. smtplib — SMTP プロトコルクライアント — Python 3.5.1 ドキュメント

SMTP_SSLクラスのソースコードを見たところ、

  • SMTPクラスを継承
  • ポートを指定しない場合、SMTP-over-SSL port (465)を使用
  • _get_socket()メソッドをオーバーライドして、SSLを使って通信

でした。

SMTP_SSLを使った場合、以下のような実装となりました。

msg = create_message(sys._getframe().f_code.co_name)

s = smtplib.SMTP_SSL("smtp.gmail.com")
s.login(ACCOUNT, PASSWORD)

s.sendmail(ACCOUNT, ALIAS, msg.as_string())
s.quit()

print("finished: {}".format(sys._getframe().f_code.co_name))

 

sendmail()の代わりにsend_message()を使う実装

MIMETextでFromやToを設定しているのにもかかわらず、sendmail()の引数にもFromやToを渡す必要があります。

良い方法がないかを調べたところ、Python3.2から追加されたsend_message()がありました。
SMTP.send_message - 21.17. smtplib — SMTP プロトコルクライアント — Python 3.5.1 ドキュメント

ソースコードを見たところ、内部でsendmail()を呼んでいたため、これを使うのが良さそうでした。

msg = create_message(sys._getframe().f_code.co_name)

s = smtplib.SMTP_SSL("smtp.gmail.com")
s.login(ACCOUNT, PASSWORD)

s.send_message(msg)
s.quit()

print("finished: {}".format(sys._getframe().f_code.co_name))

最初のものに比べるとかなり短くなりました。

 
なお、SMTPオブジェクトを使う場合でもsend_message()は使えます。

 

ソースコード

GitHubに上げました。
thinkAmi-sandbox/smtplib-sample

 

参考

SMTPまわりのPythonでの経緯とかが分かりやすく書かれていました。
プログラミングと慶應通信 : Pythonでgmailの送受信

受信側はこちらのコードが参考になりそうです。
Python でメール送受信(Gmail で SMTP と IMAP を使う場合) - akiyoko blog

なお、GmailにはAPIもあり、Pythonライブラリも用意されています。
Gmail API Client Library for Python  |  API Client Library for Python  |  Google Developers

公式ドキュメントで役立ちそうなのも載せておきます。