python で SMTP 認証を行ってメールを送信する
呼称: Snakemail
目的: コマンドラインで使える「ちょっとした」メール送信クライアントを作成する
特徴: 任意のファイルを添付できる(たぶん)、SMTP 認証に対応している
用例: テストスクリプトに組み込んで、実行結果のログをメールに添付して送信する
備考: 大雑把なエラー制御のみで、詳細な SMTP 通信の仕組みを私が分かっていないので注意!
「ちょっとした」用途でコマンドライン(スクリプト)からメールを送信したいことがあります。そんなメール送信クライアントを作ってみました。sendmail コマンドを使えば良いのでは?と思うのは最もですが、任意の SMTP サーバを指定できないという唯一の欠点があります(私がやり方を知らないだけだったらツッコミください)。
同様のツールで id:kyagi さんが Ruby で Ringmail を作っていましたが、これは SMTP 認証に未対応ですよね?(^ ^;;
さらに、SMTP 認証を用いると gmail の SMTP サーバ経由でメールを送信できます。外部のメールサーバへの接続を許可している環境であれば、事実上、メールは自由に送信できますね。
#!/usr/bin/env python # -*- coding: utf-8 -*- import getopt import os import sys import smtplib from email import message_from_string from email import Encoders from email.MIMEBase import MIMEBase from email.MIMEText import MIMEText from email.MIMEMultipart import MIMEMultipart from email.Header import Header from email.Utils import formatdate from mimetypes import guess_type from getpass import getpass class Mail(): """supported text/plain and base64 encoded attachment""" def __init__(self, args): self.server = args[0] self.port = args[1] self.from_addr = args[2] self.to_addr = args[3] self.subject = args[4] self.body = args[5] self.attach = args[6] self.opt_flag = args[7] self.msg = None self.auth_user = None self.auth_pass = None def create_message(self): if self.attach: self.create_multipart_message() else: self.create_text_message() if self.opt_flag['debug'] == 'on': print '-'*36 + ' from here' print self.msg print '-'*36 + ' up here' def create_base_header(self): self.msg['Subject'] = Header(self.subject, 'utf-8') self.msg['From'] = self.from_addr rcpt_to = self.to_addr[0] for addr in self.to_addr[1:]: rcpt_to += ', ' + addr self.msg['To'] = rcpt_to self.msg['Date'] = formatdate() def create_text_message(self): self.msg = MIMEText(self.body, 'plain', 'utf-8') self.create_base_header() def create_multipart_message(self): # base header and body self.msg = MIMEMultipart('mixed') self.create_base_header() text_msg = MIMEText(self.body, 'plain', 'utf-8') self.msg.attach(text_msg) # attachements for att in self.attach: # get mime-type by checking file extension content_type = guess_type(att)[0] main_type, sub_type = content_type.split('/', 1) # add attached file with base64 encode sub_part = MIMEBase(main_type, sub_type) sub_part['Content-ID'] = att sub_part.set_payload(open(att).read()) Encoders.encode_base64(sub_part) sub_part.add_header('Content-Type', content_type, name=att) self.msg.attach(sub_part) def send(self): if self.opt_flag['auth'] == 'on': self.auth_user = raw_input('smtp-auth user: ') self.auth_pass = getpass('smtp-auth passwd: ') try: s = smtplib.SMTP(self.server, self.port) except Exception, err: raise SnakeErrorConnectMailServer, ( err, self.server, self.port) try: s.ehlo() s.starttls() s.ehlo() if self.auth_user and self.auth_pass: s.login(self.auth_user, self.auth_pass) s.sendmail(self.from_addr, self.to_addr, self.msg.as_string()) except Exception, err: raise SnakeErrorSMTP, err finally: s.close() class ParseOption: def __init__(self, args): self.values = self.parse(args) def parse(self, args): """parse command line arguments""" try: opts, ttt = getopt.getopt(args, 'm:p:f:t:s:b:a:Avh', ['server=', 'port=', 'from=', 'to=', 'subject=', 'body=', 'attach=']) except getopt.error, err: raise SnakeErrorUsage, err # initial values server = 'localhost' port = 25 from_addr = os.getenv('USER') + '@' + os.getenv('HOSTNAME') to_addr = [] subject = '' body = '' attach = [] opt_flag = {'auth':'off', 'debug':'off'} for opt, val in opts: if opt == '-h' or opt == '--help': raise SnakeErrorUsage if opt == '-A' or opt == '--Auth': opt_flag['auth'] = 'on' continue if opt == '-v' or opt == '--verbose': opt_flag['debug'] = 'on' continue if opt == '-m' or opt == '--server': server = val continue if opt == '-p' or opt == '--port': port = val continue if opt == '-f' or opt == '--from': from_addr = val continue if opt == '-t' or opt == '--to': to_addr.append(val) continue if opt == '-s' or opt == '--subject': subject = val continue if opt == '-b' or opt == '--body': body = val continue if opt == '-a' or opt == '--attach': attach.append(val) continue if not to_addr: raise SnakeErrorNotMailTo return (server, port, from_addr, to_addr, subject, body, attach, opt_flag) class SnakeError(Exception): """Base class for all exceptions""" def __init__(self, msg): print 'Exception: %s' % (self.__class__.__name__) if msg: sys.stderr.write('%s\n' % (msg)) class SnakeErrorUsage(SnakeError): def __init__(self, msg=None): SnakeError.__init__(self, msg) usage() class SnakeErrorNotMailTo(SnakeError): def __init__(self, msg=None): SnakeError.__init__(self, msg) print 'you have to give "To Address"' usage() class SnakeErrorConnectMailServer(SnakeError): def __init__(self, *args): SnakeError.__init__(self, args[0]) print 'mail server: %s' % (args[1]) print ' port: %s' % (args[2]) class SnakeErrorSMTP(SnakeError): def __init__(self, msg=None): SnakeError.__init__(self, msg) def usage(): print ('Usage: python %s -m mail_server -p port_number ' '-f from_address -t to_address -s subject -b body ' '-a attach -A -v -h\n' ' -A: enable SMTP-Authentication\n' ' -v: enable DEBUG mode\n' ' -h: display usage' % (sys.argv[0])) def main(): """Script Main""" try: opt = ParseOption(sys.argv[1:]) m = Mail(opt.values) m.create_message() m.send() except SnakeError, err: sys.exit(1) if __name__ == '__main__': main()
実行結果。
$ python snakemail.py -A -m smtp.gmail.com -p 587 -t xxx@gmail.com \ -f yyy -s gmail_test -b body_test -a ttt.txt -v ------------------------------------ from here From nobody Sun Jul 5 14:09:25 2009 Content-Type: multipart/mixed; boundary="===============1881580230==" MIME-Version: 1.0 Subject: =?utf-8?q?gmail=5Ftest?= From: yyy@localhost.localdomain To: xxx@gmail.com Date: Sun, 05 Jul 2009 05:09:25 -0000 --===============1881580230== Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: base64 Ym9keV90ZXN0 --===============1881580230== Content-Type: text/plain MIME-Version: 1.0 Content-ID: ttt.txt Content-Transfer-Encoding: base64 Content-Type: text/plain; name="ttt.txt" YWJjCg== --===============1881580230==-- ------------------------------------ up here smtp-auth user:smtp-auth passwd:
リファレンス:
7.1 email -- 電子メールと MIME 処理のためのパッケージ
Pythonでメールを送信したい人のためのサンプル集