StatsBeginner: 初学者の統計学習ノート

初学者が統計学、機械学習、R、Pythonの勉強の過程をメモっていくノート。

Pythonの簡単なコードでメールを自動送信してみる

意外と簡単にできた

 メールを300人ぐらいに発信する必要がありまして、Toに全員入れるわけにはいかないし、BCCで送るのもダサいかなと思って、「1人1人を個別にToに指定して、同じ件名・同じ文面のメールを送る」ってのをPythonでやってみました。*1
 標準モジュールのemailってのとsmtplibってのを使って、50行程度のコードで簡単に送れました。
 1点心残りなのは、後述のとおりFromの欄に日本語の差出人名を表示させるやつが、色々調べたものの結局できませんでした。

用意するもの

 アドレスリストをCSVで用意して、本文はテキストファイルに書いておきました。

f:id:midnightseminar:20180212202431p:plain
f:id:midnightseminar:20180212202409p:plain

コード

 ネットでsmtplibを使ったPythonでのメール送信の解説を探すと、sendmail()というメソッドで送っているものと、send_message()というメソッドで送っているものがあります。sendmail()が基本なのですが、send_message()はそれをより簡単に扱えるようにラップしてくれているような感じらしいです。今回はsend_message()を使いました。


 コードは以下の通りですが、全体としては、

  1. モジュールを読み込む
  2. 差出人アドレスやメールサーバの認証情報などの設定項目をまとめて書いておく
  3. メールサーバに接続する
  4. emailモジュールのMIMETextでテキスト形式のメール本体(MIME文書)をつくる
  5. MIME文書に、差出人、件名、宛先等の情報を入れていく
  6. smtplibのsend_messageでメールを送信する
  7. サーバとの接続を終了する

 という流れになっています。

# モジュールの読み込み
import time
import smtplib
from email.mime.text import MIMEText
from email.header import Header
import pandas as pd

# 基本的な設定たち
srv_smtp = 'XXXXXX.XXXXXX.jp'  # SMTPサーバ
srv_port = 587                 # ポート番号
srv_user = 'XXXXXX'            # サーバのユーザ名(ちなみに私が使ってるやつだとメアドがユーザ名)
srv_pw   = 'XXXXXXXX'          # サーバのパスワード
jp_encoding = 'iso-2022-jp'    # 日本語文字エンコーディングの指定
add_sender = 'XXXX@XXXXXX.jp'  # 差出人(自分)アドレスの設定
add_bcc = 'XXXX@XXXXXX.jp'     # BCCの複製を送るアドレス
add_rcp_path = '/XXXXX/XXXXX/address_test.csv'  #アドレス一覧が入ったCSVの置き場
body_path =    '/XXXXX/XXXXX/body_test.txt'     # 本文を書いたテキストファイルの置き場
mail_subject = 'くさめの件につきまして'         # 共通の件名

# 本文ファイルの読み込み
with open(body_path, 'r', encoding='utf-8') as file:
    mail_body = file.read()


# 宛先リスト読み込み
# 元ファイルには名前も入れてるけどとりあえず使わないことにする
add_rcp_df = pd.read_csv(add_rcp_path, encoding='utf-8')  # csvの読み込みはpandasでしかやったことないので…
add_rcp_list = add_rcp_df['Address'].tolist()


# SMTPサーバへの接続
server = smtplib.SMTP(srv_smtp, srv_port)
server.ehlo()
server.starttls()  # TLSでアクセス
server.ehlo()
server.login(srv_user,srv_pw)  # ログイン認証

# 送信を繰り返す
for add in add_rcp_list:
    try:
        msg = MIMEText(mail_body.encode(jp_encoding), 'plain', jp_encoding,)
        msg['From'] = add_sender
        msg['Subject'] = Header(mail_subject, jp_encoding)
        msg['Bcc'] = add_bcc
        msg['To'] = add
        server.send_message(msg)  # 送信する
        time.sleep(3)  # 3秒まつ
    except:
        # なんかあった時用
        print('An error occured when sending a mail to ' + add)
        

# サーバ接続を終了
server.close()


 MIMETextでMIME文書を作って、件名等の情報を入れた後、特定の情報だけ差し替えるというのが上手く行かなかったので、forループでメールを1通1通送る際に、MIME文書の生成自体をまるごとやり直しています。これが適切なのかよく分かってませんがとりあえずメール送信には成功しました。
 あと、メールソフトに送信済みメールが残らないので、記録用にBCCで自分のアドレス宛にメールを飛ばしています。
 1万件とか送るのであれば、受信側のメールサーバにSPAM判定されないように時間を空けて送る必要があるかと思いますが*2、300件ぐらいならべつに全部即時送信してしまっていいような気はします。上記では一応3秒ずつ空けています。

なんか止まってた

 300件の送信中、2回止まりました。
 たぶん回線が不安な環境だったので、ネット接続が切れたんだと思いますw
 エラーが出たアドレスのところを確認して、あと一応BCCでコピーを飛ばしていたメールも確認して、未送信の人だけのアドレスリストを作ってやり直しました。
 上記のコードでは、何らかのエラーが出た時の対処はまったく記述していないので、簡単なコードでむやみに大量のメール送信をすると何が起きるかわからんという点には注意が必要かと思います。

日本語文字のエンコード

 エンコーディングのところに関して、参考にしたブログ記事などが一様に、日本語のメールで伝統的に使われているという'iso-2022-jp'を指定していたので、上記コードではその通りにしてますが、↓のページに書かれているように、今はべつにUTF-8でも問題ないようです。
 実際、UTF-8でも自分の持っている幾つかのメールボックスに送ってみましたが、ちゃんと見れました(ただ、iPhoneとMacでしか確認してないので見れて当然なのかもしれません)。
 
Pythonで日本語メールを送る方法をいろいろ試した
 
 受信側の環境にも拠るのかもしれないので、私は念のため伝統的なほうを使いましたが、実際どっちのほうが安全なのかはよく知りません。
 
 

差出人表示を日本語でする方法が分からない

 上記コードで、

add_sender = 'XXXX@XXXXXX.jp'

のところを

add_sender = 'K.Yoshida <XXXX@XXXXXX.jp>'

にすると、受信側のメールソフトで差出人名を「K.Yoshida」として表示してくれたりします。


f:id:midnightseminar:20180212202526p:plain:w300


 それは今回もできたんですが、ここに日本語の名前を入れる方法というのが色々難しく、結局ちゃんとはできませんでした。
 下記のようなページで紹介されているように、

  • send_message()ではなくsendmail()で送る
  • アドレス部分はそのままに、日本語の差出人名の部分だけエンコードする。その際単に文字列としてではなくHeaderインスタンスとして生成する

 という方法でできるらしいのですが、自分でやってみたところ、受信側サーバによっては受信拒否、一応送れたサーバでも、差出人名のあとに「@」と受信サーバの情報を表す文字列がくっついた状態で表示されてしまい、要するに不正なメールとして判定されたんだと思います。この辺、正式にできるやり方を調べる必要があります。
 Stackoverflow等をみると海外でもウムラウト付きの文字などを表示させようとして苦労している人がいました。


Python3 日本語でメール送信 - textbook
Pythonでメール送信時に送信者に日本語を使用する : fujishinko 雑記帳


[追記]
下記のとおり、成功しました。
Python3でのメール送信時に日本語の差出人名を使う - StatsBeginner: 初学者の統計学習ノート
[/追記]

 

拡張

 上記のようにとりあえず単純なメールを送れる状態にしておけば、あとは

  • アドレスリストから名前を取得して、本文の一行目に「◯◯様」と可変で入れる。
  • そもそも本文自体を人に合わせて変える。
  • 途中でネット回線が切れたりするのが怖いのでAWS等の仮想マシンから発射する。
  • multipartして、base64した添付ファイルをつける


 など、今後いろいろ工夫できるかと思いました。
 ただ、私のような素人が自前のコードでメール送信をすると、なんか事故が起きそう(宛先と添付ファイルが1個ずつズレるとか)なのでなるべくやりたくはないですね。
 
 

関連リンク

mimeTEXTの説明書
https://docs.python.jp/3.3/library/email.mime.html

smtplibの説明書
https://docs.python.jp/3/library/smtplib.html

ここにemailモジュールとsmtplibモジュールを使ってメールを送る際のコード例が載っています。
https://docs.python.jp/3.3/library/email-examples.html

ここに書かれているように、From欄とかnon-ascii文字を使いたければ、Headerモジュールを使ってHeaderインスタンスとして投入する必要があると書いてあります。
https://docs.python.jp/3.3/library/email.header.html

*1:MacのAutomatorでも、group mailっていうアクションを使うとアドレス帳で指定したグループあてに「1人1人をToに指定した別々のメール」として一斉送信ができるけど、冒頭に'Dear Bob,'みたいなgreetingを入れる必要があり、しかもこれが英語仕様しかないので強制的に最後にカンマが入るという、日本人にとっては中途半端な仕様です。

*2:1万件あれば、docomo.ne.jp等に同時に数百から数千通発射することになる