Python スクリプトからメールを送るのは smtplib があるおかげで、わりと簡単にできます。ですが、添付ファイルを付けようと思うとちょっと手間でした。
今回は簡潔に書くため、全て localhost の SMTP サーバを利用しています。Gmail の SMTP サーバで送るときは TLS を使う必要があるのでご注意ください。
テキストだけのメール
import smtplib
from email.mime.text import MIMEText
from email.header import Header
from email.utils import formatdate
def mime_message(from_addr, to_addr, subject, body):
encoding = 'utf-8'
m = MIMEText(body, 'plain', encoding)
m['Subject'] = Header(subject, encoding)
m['To'] = to_addr
m['From'] = from_addr
m['Date'] = formatdate()
return m
def send_message(m):
smtp_host = 'localhost'
s = smtplib.SMTP(smtp_host)
s.sendmail(m['From'], m['To'], m.as_string())
s.quit()
to_addr = 'postmaster@example.com'
from_addr = 'atmark@example.com'
subject = 'メールのテスト'
body = 'このメールはテストメールです。'
send_message(mime_message(from_addr, to_addr, subject, body))
smtplib.sendmail() の from と to は RFC を確認せず書いているので、もしかしたら引っかかるかもしれません("atmark <atmark@example.com>" みたいな形式)。
ファイルを zip して添付
変更するのはメッセージの作成部分だけです。
import smtplib
from email.mime.multipart import MIMEMultipart
from email.mime.base import MIMEBase
from email.mime.text import MIMEText
from email.header import Header
from email.utils import formatdate
from email import encoders
import tempfile, zipfile
import os.path
def mime_email(to, subject, body, attachment_path = None):
encoding = 'utf-8'
m = MIMEMultipart()
m['Subject'] = Header(subject, encoding)
m['To'] = to
m['Date'] = formatdate()
# 本文
m.attach(MIMEText(body, 'plain', encoding))
if attachment_path:
# 添付ファイルを zip でまとめる
temp = tempfile.TemporaryFile()
with zipfile.ZipFile(temp, 'w', compression = zipfile.ZIP_DEFLATED) as zip:
zip.write(attachment_path, arcname = os.path.basename(attachment_path))
temp.seek(0)
# attach file
a = MIMEBase('application', 'zip')
a.set_payload(temp.read())
encoders.encode_base64(a)
m.attach(a)
a.add_header('Content-Disposition', 'attachment', filename = date.today().strftime('attachment.zip'))
return m
zipfile.write(filename) は与えられたパスのファイルをアーカイブします。このとき引数 arcname でアーカイブ時のファイル名を指定できます。指定しないとファイル名がそのまま arcname になりますが、無駄なディレクトリ構造を排除したいので os.path.basename() でファイル名を取得して指定しています。同じファイル名が存在する場合は適宜変更してください。
複数のファイルを zip するときも、次のように zip.write() を続け呼び出すだけです。
attachment_files = ['path_to_file.txt', 'long_path_to_file.txt']
for p in attachment_files:
zip.write(p, arcname = os.path.basename(p))
compression = zipfile.ZIP_DEFLATED を指定することで zip 全体を圧縮しています。compression には以下の値を指定できます。
compression | 説明 | 依存 |
zipfile.ZIP_STORED | 無圧縮 | |
zipfile.DEFLATED | 通常の圧縮 | zlib |
zipfile.BZIP2 | BZIP2 圧縮 | bz2, Python 3.3 以降 |
zipfile.LZMA | LZMA 圧縮 | lzma, Python 3.3 以降 |
compression は zipfile.write() で個別に指定することもできます。