gmailでメールを送る

gmailでメールを送るスクリプトを作り直しました。
プロトタイプの時に作ったスクリプトがあったのですが、googleのセキュリティアップデート(?)でSSL接続以外では基本的に接続できないようになったため、以前のシンプルな認証手順のスクリプトは動作しなくなりました。

実際には、gmailアカウントからセキュリティの設定をしなおすと一応接続はしてくれるようになります。しかし、送付先がgmail内に制限されているようです。

再び検索

全く持ってどうすればいいのかわからなかったので、またしても検索しました。

結果、こちらのページを見つけましたので、これを基に自分の作業用のメール送信スクリプトを作ることにします。ありがとうございます。
おかげさまで6時間ぐらいで完成させることが出来ました。

目的・目標

このスクリプトの目的は、基本的なメール送信の手順を確認することが第一の目的です。
デバイスのデータをまとめているサーバ側からユーザへの通知方法の1つとして、メールは必須と考えられるので基本的なところを勉強しておこうということです。 また今回作ったスクリプトは、データをまとめたグラフをユーザへプッシュすることを目的としています。

スクリプトの目標は以下のとおりです。

  • 送信者リスト(recipients list)から送信先を読み込んで、それをメールの送信先として設定する。
  • 添付ファイル(PDF)を付けられるようにする。`
  • 送信者リストのファイル名からIDを抽出して、それをメールのタイトルに入れる
    • 具体的には、送信者リストのファイル名の中の数値(整数)をとりだします

結果

ソースコードはgithubの方に載せておきます。(相変わらず使い方がよくわからない。。。)
sendgmail.py

動作の検証を何回かやって、エラーハンドリング用のコードを少し入れて通常に使うにはokぐらいになりました。

ポイント

今回の作業でポイントとなったところをいくつか記録しておきます。

ヘッダの中の宛先とメール送信コマンドの引数としての宛先

普段メールを書いているときにはほとんど意識しない宛先の取り扱いですが、意識していないからこそひっかかったという感じでしょうか。

送信先を読み込んで、その宛先を msg['To'] = to_addrというように設定してあげます。to_addrは文字列で複数のメールアドレスがコンマで区切られて入っています。
最初はてっきりこの設定で実際のメール送信の宛先になるものだ、と思い込んでいました。
しかしながら、複数の宛先が指定されていても、送られてくるのは最初の宛先だけ、という状況でした。 アドレス指定が文字列じゃいけなくて、リストなのかなあ、などと思いつつ色々調べていて、はたと気がつきました。「もしかしたら送信用のアドレス指定とメールヘッダ用の宛先指定は違うのじゃないか?」と。スクリプトを眺めると0.1秒でそれが正しいことがわかりました。

結局、msg['To'] = to_addrはヘッダ用で、実際の送信にこの内容は関与しない、という事です。さらに送信用の実際のアドレス指定はsend_via_gmail(from_addr, to_addr, passwd, msg)to_addrであって、これは複数の宛先の場合「リスト」で有る必要があります。 具体的には、

1
2
ヘッダ用アドレス指定:'aaa@gmial.com, bbb@yahoo.com'
メール送信用アドレス指定:['aaa@gmail.com', 'bbb@yahoo.com']

これら2つの変数を混同して使っていたため、send_vi_gmailに送られる引数のアドレスは「複数のアドレスを記載した文字列」となっていました。これが、send_via_gmailに渡されてリストとして扱われたため、「要素数1」で(頭に書かれている)1ヶ所だけにメールが送られていたようです。

パーサが使える!

pythonでこんなに簡単に引数をパースできるとは!参考にしたページを書いていただいた人に感謝します。ありがとうございます。

早速使わせて頂きました。

「内包表現」?

なんだかよくわからないけど、表現としてはすっきりするので使って見ました。
具体的には、recipient listから読み込んだ内容をリストにして、そのリスト内から不要なもの(空、#で始まるコメントの要素)を取り除くために使っています。

1
2
3
4
5
6
7
8
#  正規表現定義(コメント行と空行) 
re_comment = re.compile("^\s*#")
re_null_line = re.compile("^\s*$")

#  内包表現を使って正規表現に”マッチしない”要素を取り出す
#  address_listには、コメントや空行も含めファイルから読み込んだ宛先が入っている
address_list= [x for x in address_list if re_comment.match(x) == None ]
address_list= [x for x in address_list if re_null_line.match(x) == None ]

添付ファイルの用意

添付ファイルはMIMEエンコードする必要があるので、なんだかちょっと面倒です。
説明できないですが、webの情報をかき集めてこのようなコードにしています。

多分こんなかんじかと。。。

  • MIMEBaseで添付ファイルのオブジェクトをPDFとして作る。
  • 添付ファイルの実体をファイルから読み込んでオブジェクトに入れる。
  • base64でエンコード
  • ヘッダをつける

ここには書かれていませんが、このあとメッセージにattachします。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#   prepare PDF file as a attachment
    attach_file_name = args.attach_file

    if attach_file_name != None :
        if os.path.exists(attach_file_name) :
            attachment = MIMEBase("application","pdf")

            with open(args.attach_file, 'rb') as fp:
                attachment.set_payload(fp.read())

            Encoders.encode_base64(attachment)
            base_file_name = os.path.basename(attach_file_name)
            attachment.add_header("Content-Disposition", "attachment", filename=base_file_name)
        else:
            print ('No attachment file is found.')
    else:
       print ('No attachment file is specified.')

展望

動作は問題ありませんが、メールアカウントのパスワードが生で入っているのがちょっと気になりますね。 スクリプト自体をrootしか見れないようにすれば少しは良いかもしれません。