Python3で添付ファイル付きgmailを送信する

完成ソース

import smtplib
from email import encoders
from email.mime.base import MIMEBase
from email.mime.text import MIMEText
from email.mime.image import MIMEImage
from email.mime.message import MIMEMessage
from email.mime.multipart import MIMEMultipart
from email.utils import formatdate
import ssl
import magic
import os

import info

def create_message(from_addr, to_addr, cc_addrs, bcc_addrs, subject):
    msg = MIMEMultipart()
    msg['Subject'] = subject
    msg['From'] = from_addr
    msg['To'] = to_addr
    msg['Cc'] = cc_addrs
    msg['Bcc'] = bcc_addrs
    msg['Date'] = formatdate()
    return msg

def attach_file(msg, attach_dir):
    for file_name in os.listdir(attach_dir):
        path = os.path.join(attach_dir, file_name)
        with open(path, 'rb') as fp:
            types = get_mimetypes(path)
            attachment = MIMEBase(types['maintype'], types['subtype'])
            attachment.set_payload(fp.read())
            encoders.encode_base64(attachment)
            attachment.add_header(
                'Content-Disposition', 'attachment',
                filename = file_name
            )
            msg.attach(attachment)

def add_text(msg, body):
    msg.attach(MIMEText(body))

def send_mail(from_addr, to_addrs, password, msg):
    sender = smtplib.SMTP_SSL('smtp.gmail.com', 465)
    sender.login(from_addr, password)
    sender.sendmail(from_addr, to_addrs, msg.as_string())
    sender.quit()

def get_mimetypes(path):
    m = magic.from_file(path, mime=True).split('/')
    types = dict(maintype = m[0], subtype = m[1])
    return types

if __name__ == '__main__':
    from_addr = info.FROM_ADDRESS
    to_addr = info.TO_ADDRESS
    cc_addrs = info.CC
    bcc_addrs = info.BCC
    subject = info.SUBJECT
    body = info.BODY
    password = info.PASSWORD
    attach_dir = info.ATTACH_DIR

    msg = create_message(from_addr, to_addr, cc_addrs, bcc_addrs, subject)
    add_text(msg, body)
    attach_file(msg, attach_dir)
    send_mail(from_addr, to_addr, password, msg)

githubは以下になります。

github.com

前準備

今回gmailのアカウントとSMTPサーバを利用してメールを送信するのですが、その際googleアカウントの設定で「安全性の低いアプリのアクセス」というのを有効にする必要があります。

ログイン - Google アカウント

こちらのページで「安全性の低いアプリ」と検索し、有効にしてください。

実装

関数を一つずつ説明していきます。

MIMEオブジェクトの作成

def create_message(from_addr, to_addr, cc_addrs, bcc_addrs, subject):
    msg = MIMEMultipart()
    msg['Subject'] = subject
    msg['From'] = from_addr
    msg['To'] = to_addr
    msg['Cc'] = cc_addrs
    msg['Bcc'] = bcc_addrs
    msg['Date'] = formatdate()
    return msg

この関数はMIMEオブジェクトを作成し、メールの基本情報を設定します。
上から、件名・送信元・送信先・CC・BCC・日付 となっています。
その後MIMEオブジェクトを返します。
このオブジェクトに対して本文を追加したり、画像を添付したりしていきます。

メール送信

def send_mail(from_addr, to_addrs, password, msg):
    sender = smtplib.SMTP_SSL('smtp.gmail.com', 465)
    sender.login(from_addr, password)
    sender.sendmail(from_addr, to_addrs, msg.as_string())
    sender.quit()

この関数ではgamilのSMTPサーバへ接続し、メールを送信しています。
from_addrでは送信元、自分のgmailのアドレスを指定します。
passwordにはそのアカウントのパスワードを指定します。

添付ファイル

def get_mimetypes(path):
    m = magic.from_file(path, mime=True).split('/')
    types = dict(maintype = m[0], subtype = m[1])
    return types

def attach_file(msg, attach_dir):
    for file_name in os.listdir(attach_dir):
        path = os.path.join(attach_dir, file_name)
        with open(path, 'rb') as fp:
            types = get_mimetypes(path)
            attachment = MIMEBase(types['maintype'], types['subtype'])
            attachment.set_payload(fp.read())
            encoders.encode_base64(attachment)
            attachment.add_header(
                'Content-Disposition', 'attachment',
                filename = file_name
            )
            msg.attach(attachment)

def add_text(msg, body):
    msg.attach(MIMEText(body))

get_mimetypes関数では指定されたファイルのMIMETypeを返す関数です。

{'maintype':maintype, 'subtype':subtype}

という形式で値を返却します。
MIMETypeについては以下のサイトを参考にしてください。

メディアタイプ - Wikipedia

添付ファイルを送信する際にはそのファイルのMIMETypeを指定する必要があるので後々使います。

def add_text(msg, body):
    msg.attach(MIMEText(body))

もう一つの関数で簡単な方から説明します。
こちらは引数で受け取った文字列を本文としてMIMEオブジェクトに追加します。

def attach_file(msg, attach_dir):
    for file_name in os.listdir(attach_dir):
        path = os.path.join(attach_dir, file_name)
        with open(path, 'rb') as fp:
            types = get_mimetypes(path)
            attachment = MIMEBase(types['maintype'], types['subtype'])
            attachment.set_payload(fp.read())
            encoders.encode_base64(attachment)
            attachment.add_header(
                'Content-Disposition', 'attachment',
                filename = file_name
            )
            msg.attach(attachment)

この関数では指定したディレクトリのファイルを列挙し、それを添付ファイルとしてMIMEオブジェクトに追加します。

types = get_mimetypes(path)
attachment = MIMEBase(types['maintype'], types['subtype'])

ここで先ほど説明したget_mimetypes関数を使います。
そこで得た値を使ってMIMETypeを明示します。
通常のテキストファイルならtext/plainになります。

attachment.set_payload(fp.read())
encoders.encode_base64(attachment)

ここでは添付ファイルの本体をセットし、それをbase64エンコーディングしています。
電子メールではasciiコードでしか文字を送れないためbase64エンコーディングをする必要があります。
これはMIMEの規格で定められた変換方法で、データを(a~z,A~Z,0~9,+,-)の64文字に変換します。
変換したデータを受信側でデコードすることでascii以外の文字もやり取りすることができます。

attachment.add_header(
'Content-Disposition', 'attachment',
filename = file_name
)
msg.attach(attachment)

ここでは添付ファイルでコンテンツを追加する処理をしています。
'attachment'を指定しないと本文として追加されます。
msg.attach(attachment)ではMIMEオブジェクトに添付ファイルを追加しています。

if __name__ == '__main__':
    from_addr = info.FROM_ADDRESS
    to_addr = info.TO_ADDRESS
    cc_addrs = info.CC
    bcc_addrs = info.BCC
    subject = info.SUBJECT
    body = info.BODY
    password = info.PASSWORD
    attach_dir = info.ATTACH_DIR

    msg = create_message(from_addr, to_addr, cc_addrs, bcc_addrs, subject)
    add_text(msg, body)
    attach_file(msg, attach_dir)
    send_mail(from_addr, to_addr, password, msg)

最後にメイン処理ですが、これはそのまま各関数を呼び出しているだけです。
info.hogeというのは私が別ファイルでそれらの情報を管理しているだけで、普通にfrom_addr = 'from@example.com'としていただいて大丈夫です。
これでpythonでメールを送ることができます。

まとめ

プログラムからメールが送信できるようになったので、それを利用してアラート的に使ってみようかなと思います。