#!/usr/bin/env python # -*- coding: utf-8 -*- __author__ = 'Zoa Chou' # see http://www.mudoom.com/Article/show/id/29.html for detail import logging import smtplib import mimetypes import socket from email import encoders from email.header import Header from email.mime.text import MIMEText, MIMENonMultipart from email.mime.base import MIMEBase from email.utils import parseaddr, formataddr class Mailer(object): def __init__(self): pass def send_mail(self, smtp_server, from_address, to_address, subject, body, files=None): """ 發送郵件主程式 :param smtp_server: dict 郵件伺服器設定 :keyword host: string smtp伺服器位址 :keyword port: int smtp伺服器連接埠號碼 :keyword user: string 使用者名稱 :keyword passwd: string 密碼 :keyword ssl: bool 是否啟用ssl,預設False :keyword timeout: int 逾時時間,預設10s :param from_address: 寄件者郵箱 :param to_address: 收件者郵箱 :param subject: 郵件標題 :param body: 郵件內容 :param files: 附件 :raise: NetworkError/MailerException """ # 格式化郵件內容 body = self._encode_utf8(body) # 郵件類型 content_type = 'html' if body.startswith('<html>') else 'plain' msg = MIMENonMultipart() if files else MIMEText(body, content_type, 'utf-8') # 格式化郵件資料 msg['From'] = self._format_address(from_address) msg['To'] = ', '.join(self._format_list(to_address)) msg['subject'] = self._encode_utf8(subject) # 構造附件資料 if files: msg.attach(MIMEText(body, content_type, 'utf-8')) cid = 0 for file_name, payload in files: file_name = self._encode_utf8(file_name) main_type, sub_type = self._get_file_type(file_name) if hasattr(payload, 'read'): payload = payload.read() f_name = self._encode_header(file_name) mime = MIMEBase(main_type, sub_type, filename=f_name) mime.add_header('Content-Disposition', 'attachment', filename=f_name) mime.add_header('Content-ID', '<%s>' % cid) mime.add_header('X-Attachment-Id', '%s' % cid) mime.set_payload(payload) encoders.encode_base64(mime) msg.attach(mime) cid += 1 host = smtp_server.get('host') port = smtp_server.get('port') user = smtp_server.get('user') passwd = smtp_server.get('passwd') ssl = smtp_server.get('ssl', False) time_out = smtp_server.get('timeout', 10) # 沒有輸入連接埠則使用預設連接埠 if port is None or port == 0: if ssl: port = 465 else: port = 25 logging.debug('Send mail form %s to %s' % (msg['From'], msg['To'])) try: if ssl: # 開啟ssl串連模式 server = smtplib.SMTP_SSL('%s:%d' % (host, port), timeout=time_out) else: server = smtplib.SMTP('%s:%d' % (host, port), timeout=time_out) # 開啟偵錯模式 # server.set_debuglevel(1) # 如果存在使用者名稱密碼則嘗試登入 if user and passwd: server.login(user, passwd) # 發送郵件 server.sendmail(from_address, to_address, msg.as_string()) logging.debug('Mail sent success.') # 關閉stmp串連 server.quit() except socket.gaierror, e: """ 網路無法串連 """ logging.exception(e) raise NetworkError(e) except smtplib.SMTPServerDisconnected, e: """ 網路連接異常 """ logging.exception(e) raise NetworkError(e) except smtplib.SMTPException, e: """ 郵件發送異常 """ logging.exception(e) raise MailerException(e) def _format_address(self, s): """ 格式化郵件地址 :param s:string 郵件地址 :return: string 格式化後的郵件地址 """ name, address = parseaddr(s) return formataddr((self._encode_header(name), self._encode_utf8(address))) def _encode_header(self, s): """ 格式化符合MIME的頭部資料 :param s: string 待格式化資料 :return: 格式化後的資料 """ return Header(s, 'utf-8').encode() def _encode_utf8(self, s): """ 格式化成utf-8編碼 :param s: string 待格式化資料 :return: string 格式化後的資料 """ if isinstance(s, unicode): return s.encode('utf-8') else: return s def _get_file_type(self, file_name): """ 擷取附件類型 :param file_name: 附件檔案名稱 :return: dict 附件MIME """ s = file_name.lower() pos = s.rfind('.') if pos == -1: return 'application', 'octet-stream' ext = s[pos:] mime = mimetypes.types_map.get(ext, 'application/octet-stream') pos = mime.find('/') if pos == (-1): return mime, '' return mime[:pos], mime[pos+1:] def _format_list(self, address): """ 將收件者地址格式化成list :param address: string/list 收件者郵箱 :return: list 收件者郵箱list """ l = address if isinstance(l, basestring): l = [l] return [self._format_address(s) for s in l] class MailerException(Exception): """ 郵件發送異常類 """ pass class NetworkError(MailerException): """ 網路異常類 """ pass # test for @qq.com if __name__ == '__main__': import sys def prompt(prompt): """ 接收終端輸入的資料 """ sys.stdout.write(prompt + ": ") return sys.stdin.readline().strip() from_address = prompt("From(Only @qq.com)") passwd = prompt("Password") to_address = prompt("To").split(',') subject = prompt("Subject") print "Enter message, end with ^D:" msg = '' while 1: line = sys.stdin.readline() if not line: break msg = msg + line print "Message length is %d" % len(msg) # QQ郵箱預設設定 smtp_server = {'host': 'smtp.qq.com', 'port': None, 'user': from_address, 'passwd': passwd, 'ssl': True} mailer = Mailer() try: mailer.send_mail(smtp_server, from_address, to_address, subject, msg) except MailerException, e: print(e) |