smtplib – 簡易メール転送プロトコルクライアント¶
目的: | メール送信を含めて SMTP サーバと通信する |
---|---|
利用できるバージョン: | 1.5.2 以上 |
smtplib はメールの送信や SMTP サーバとの通信に便利な SMTP クラスを提供します。
Note
本稿で紹介するサンプルコードのメールアドレス、ホスト名、IP アドレスは隠蔽されていますが、そのログはコマンドの流れと正確なレスポンスを説明します。
メールを送信する¶
SMTP の一般的な用途はメールサーバへ接続してメールを送信することです。メールサーバのホスト名とポート番号は引数としてコンストラクタに渡すか、明示的に connect() メソッドで指定します。接続した時点でエンベロープパラメータとメッセージ本文と共に sendmail() を呼び出してください。smtplib はコンテンツやヘッダを全く変更しないので、メッセージのテキストは RFC 2882 完全準拠のメッセージであるべきです。つまり自分で From や To をヘッダに追加するということを意味します。
import smtplib
import email.utils
from email.mime.text import MIMEText
# メッセージを作成する
msg = MIMEText('This is the body of the message.')
msg['To'] = email.utils.formataddr(('Recipient', 'recipient@example.com'))
msg['From'] = email.utils.formataddr(('Author', 'author@example.com'))
msg['Subject'] = 'Simple test message'
server = smtplib.SMTP('mail')
server.set_debuglevel(True) # サーバとの通信内容を表示する
try:
server.sendmail('author@example.com', ['recipient@example.com'], msg.as_string())
finally:
server.quit()
このサンプルではクライアントとサーバ間の通信を表示するためにデバッグも有効にしています。そうしないと、このサンプルは何も出力しません。
$ python smtplib_sendmail.py
send: 'ehlo localhost.local\r\n'
reply: '250-mail.example.com Hello [192.168.1.17], pleased to meet you\r\n'
reply: '250-ENHANCEDSTATUSCODES\r\n'
reply: '250-PIPELINING\r\n'
reply: '250-8BITMIME\r\n'
reply: '250-SIZE\r\n'
reply: '250-DSN\r\n'
reply: '250-ETRN\r\n'
reply: '250-AUTH GSSAPI DIGEST-MD5 CRAM-MD5\r\n'
reply: '250-DELIVERBY\r\n'
reply: '250 HELP\r\n'
reply: retcode (250); Msg: mail.example.com Hello [192.168.1.17], pleased to meet you
ENHANCEDSTATUSCODES
PIPELINING
8BITMIME
SIZE
DSN
ETRN
AUTH GSSAPI DIGEST-MD5 CRAM-MD5
DELIVERBY
HELP
send: 'mail FROM:<author@example.com> size=266\r\n'
reply: '250 2.1.0 <author@example.com>... Sender ok\r\n'
reply: retcode (250); Msg: 2.1.0 <author@example.com>... Sender ok
send: 'rcpt TO:<recipient@example.com>\r\n'
reply: '250 2.1.5 <recipient@example.com>... Recipient ok\r\n'
reply: retcode (250); Msg: 2.1.5 <recipient@example.com>... Recipient ok
send: 'data\r\n'
reply: '354 Enter mail, end with "." on a line by itself\r\n'
reply: retcode (354); Msg: Enter mail, end with "." on a line by itself
data: (354, 'Enter mail, end with "." on a line by itself')
send: 'From nobody Sun Sep 28 10:02:48 2008\r\nContent-Type: text/plain; charset="us-ascii"\r\nMIME-Version: 1.0\r\nContent-Transfer-Encoding: 7bit\r\nTo: Recipient <recipient@example.com>\r\nFrom: Author <author@example.com>\r\nSubject: Simple test message\r\n\r\nThis is the body of the message.\r\n.\r\n'
reply: '250 2.0.0 m8SE2mpc015614 Message accepted for delivery\r\n'
reply: retcode (250); Msg: 2.0.0 m8SE2mpc015614 Message accepted for delivery
data: (250, '2.0.0 m8SE2mpc015614 Message accepted for delivery')
send: 'quit\r\n'
reply: '221 2.0.0 mail.example.com closing connection\r\n'
reply: retcode (221); Msg: 2.0.0 mail.example.com closing connection
sendmail() の2番目の引数、受取人がリストとして渡されていることに注意してください。次々にメッセージを配送するために受取人のリストへ幾つでもメールアドレスを追加することができます。エンベロープ情報はメッセージヘッダから分離されるので BCC で指定するメールアドレスは sendmail() メソッドの引数に追加しますが、メッセージヘッダには追加しません。
認証と暗号化¶
SMTP クラスはサーバが認証と TLS (transport layer security) 暗号化をサポートするときにそういった機能も扱えます。サーバが TLS をサポートする場合、サーバに対してあなたのコンピュータを識別してどのような機能が有効かを問い合わせるために、直接 ehlo() を呼び出してください。それからその応答を確認するために has_extn() を呼び出してください。TLS が開始されると、認証の前に再度 ehlo() を呼び出さなければなりません。
import smtplib
import email.utils
from email.mime.text import MIMEText
import getpass
# ユーザに接続情報を入力させる
to_email = raw_input('Recipient: ')
servername = raw_input('Mail server name: ')
username = raw_input('Mail user name: ')
password = getpass.getpass("%s's password: " % username)
# メッセージを作成する
msg = MIMEText('Test message from PyMOTW.')
msg.set_unixfrom('author')
msg['To'] = email.utils.formataddr(('Recipient', to_email))
msg['From'] = email.utils.formataddr(('Author', 'author@example.com'))
msg['Subject'] = 'Test from PyMOTW'
server = smtplib.SMTP(servername)
try:
server.set_debuglevel(True)
# 自分自身を識別してサポートしている機能をサーバに入力させる
server.ehlo()
# このセッションを暗号化できるならそうする
if server.has_extn('STARTTLS'):
server.starttls()
server.ehlo() # TLS コネクションで自分自身を再識別する
server.login(username, password)
server.sendmail('author@example.com', [to_email], msg.as_string())
finally:
server.quit()
TLS が有効になった時点で STARTTLS は ( EHLO の応答の) 機能リストに現れないことに注意してください。
$ python smtplib_authenticated.py
Recipient: recipient@example.com
Mail server name: smtpauth.isp.net
Mail user name: user@isp.net
user@isp.net's password:
send: 'ehlo localhost.local\r\n'
reply: '250-elasmtp-isp.net Hello localhost.local [<your IP here>]\r\n'
reply: '250-SIZE 14680064\r\n'
reply: '250-PIPELINING\r\n'
reply: '250-AUTH PLAIN LOGIN CRAM-MD5\r\n'
reply: '250-STARTTLS\r\n'
reply: '250 HELP\r\n'
reply: retcode (250); Msg: elasmtp-isp.net Hello localhost.local [<your IP here>]
SIZE 14680064
PIPELINING
AUTH PLAIN LOGIN CRAM-MD5
STARTTLS
HELP
send: 'STARTTLS\r\n'
reply: '220 TLS go ahead\r\n'
reply: retcode (220); Msg: TLS go ahead
send: 'ehlo localhost.local\r\n'
reply: '250-elasmtp-isp.net Hello localhost.local [<your IP here>]\r\n'
reply: '250-SIZE 14680064\r\n'
reply: '250-PIPELINING\r\n'
reply: '250-AUTH PLAIN LOGIN CRAM-MD5\r\n'
reply: '250 HELP\r\n'
reply: retcode (250); Msg: elasmtp-isp.net Hello farnsworth.local [<your IP here>]
SIZE 14680064
PIPELINING
AUTH PLAIN LOGIN CRAM-MD5
HELP
send: 'AUTH CRAM-MD5\r\n'
reply: '334 PDExNjkyLjEyMjI2MTI1NzlAZWxhc210cC1tZWFseS5hdGwuc2EuZWFydGhsaW5rLm5ldD4=\r\n'
reply: retcode (334); Msg: PDExNjkyLjEyMjI2MTI1NzlAZWxhc210cC1tZWFseS5hdGwuc2EuZWFydGhsaW5rLm5ldD4=
send: 'ZGhlbGxtYW5uQGVhcnRobGluay5uZXQgN2Q1YjAyYTRmMGQ1YzZjM2NjOTNjZDc1MDQxN2ViYjg=\r\n'
reply: '235 Authentication succeeded\r\n'
reply: retcode (235); Msg: Authentication succeeded
send: 'mail FROM:<author@example.com> size=221\r\n'
reply: '250 OK\r\n'
reply: retcode (250); Msg: OK
send: 'rcpt TO:<recipient@example.com>\r\n'
reply: '250 Accepted\r\n'
reply: retcode (250); Msg: Accepted
send: 'data\r\n'
reply: '354 Enter message, ending with "." on a line by itself\r\n'
reply: retcode (354); Msg: Enter message, ending with "." on a line by itself
data: (354, 'Enter message, ending with "." on a line by itself')
send: 'Content-Type: text/plain; charset="us-ascii"\r\nMIME-Version: 1.0\r\nContent-Transfer-Encoding: 7bit\r\nTo: Recipient <recipient@example.com>\r\nFrom: Author <author@example.com>\r\nSubject: Test from PyMOTW\r\n\r\nTest message from PyMOTW.\r\n.\r\n'
reply: '250 OK id=1KjxNj-00032a-Ux\r\n'
reply: retcode (250); Msg: OK id=1KjxNj-00032a-Ux
data: (250, 'OK id=1KjxNj-00032a-Ux')
send: 'quit\r\n'
reply: '221 elasmtp-isp.net closing connection\r\n'
reply: retcode (221); Msg: elasmtp-isp.net closing connection
メールアドレスを検証する¶
SMTP プロトコルにはメールアドレスが有効かどうかをサーバへ問い合わせるコマンドがあります。通常 VRFY は正規のメールアドレスをスパマーが見つけられないように無効にしていますが、もしそのコマンドが有効な場合、サーバへ問い合わせて完全なユーザ名と有効かどうかを表すステータスコードを受信します。
import smtplib
server = smtplib.SMTP('mail')
server.set_debuglevel(True) # サーバとの通信内容を表示する
try:
dhellmann_result = server.verify('dhellmann')
notthere_result = server.verify('notthere')
finally:
server.quit()
print 'dhellmann:', dhellmann_result
print 'notthere :', notthere_result
結果出力の最後の2行を見ると、メールアドレス dhellmann は有効ですが notthere は存在しません。
$ python smtplib_verify.py
send: 'vrfy <dhellmann>\r\n'
reply: '250 2.1.5 Doug Hellmann <dhellmann@mail.example.com>\r\n'
reply: retcode (250); Msg: 2.1.5 Doug Hellmann <dhellmann@mail.example.com>
send: 'vrfy <notthere>\r\n'
reply: '550 5.1.1 <notthere>... User unknown\r\n'
reply: retcode (550); Msg: 5.1.1 <notthere>... User unknown
send: 'quit\r\n'
reply: '221 2.0.0 mail.example.com closing connection\r\n'
reply: retcode (221); Msg: 2.0.0 mail.example.com closing connection
dhellmann: (250, '2.1.5 Doug Hellmann <dhellmann@mail.example.com>')
notthere : (550, '5.1.1 <notthere>... User unknown')