# -*- coding: utf-8 -*- __all__ = ['Message', 'DEFAULT_MIMETYPE'] import os import mimetypes from email import charset, encoders from email.mime.audio import MIMEAudio from email.mime.base import MIMEBase from email.mime.image import MIMEImage from email.mime.multipart import MIMEMultipart from email.mime.text import MIMEText from email.utils import make_msgid, formatdate from .utils import join_address_list, sanitize_address, text_type, string_type DEFAULT_MIMETYPE = 'application/octet-stream' utf8_charset = charset.Charset('utf-8') utf8_charset.body_encoding = None class Message(object): def __init__(self, subject='', body='', html='', encoding=None, from_address=None, to=None, cc=None, bcc=None, reply_to=None, attachments=None): self.subject = subject self.body = body self.html = html self.from_address = from_address self.reply_to = reply_to self.attachments = attachments or [] self.encoding = encoding if isinstance(to, (text_type, string_type)): self.to = [to] elif to: self.to = list(to) else: self.to = [] if isinstance(cc, (text_type, string_type)): self.cc = [cc] elif cc: self.cc = list(cc) else: self.cc = [] if isinstance(bcc, (text_type, string_type)): self.bcc = [bcc] elif bcc: self.bcc = list(bcc) else: self.bcc = [] def mime_message(self): msg = MIMEMultipart() msg['Subject'] = self.subject msg['From'] = sanitize_address(self.from_address) msg['Date'] = formatdate() msg['Message-ID'] = make_msgid() if self.to: msg['To'] = join_address_list(self.to) if self.cc: msg['Cc'] = join_address_list(self.cc) if self.bcc: msg['Bcc'] = join_address_list(self.bcc) if self.reply_to: msg['Reply-To'] = sanitize_address(self.reply_to) encoding = self.encoding or 'utf-8' if self.body and self.html: alternative = MIMEMultipart('alternative') alternative.attach(MIMEText(self.body, 'plain', encoding)) alternative.attach(MIMEText(self.html, 'html', encoding)) msg.attach(alternative) elif self.body: msg.attach(MIMEText(self.body, 'plain', encoding)) elif self.html: msg.attach(MIMEText(self.html, 'html', encoding)) for attachment in self.attachments: msg.attach(attachment) return msg def raw_message(self): return self.mime_message().as_string() def recipients(self): return self.to + self.cc + self.bcc def send(self, backend): backend.send(self) def attach(self, filename, content, mimetype): maintype, subtype = mimetype.split('/', 1) if maintype == 'text': mime = MIMEText(content, subtype, None) mime.set_payload(content, utf8_charset) elif maintype == 'image': mime = MIMEImage(content, subtype) elif maintype == 'audio': mime = MIMEAudio(content, subtype) else: mime = MIMEBase(maintype, subtype) mime.set_payload(content) encoders.encode_base64(mime) self.attach_mime(filename, mime) def attach_mime(self, filename, mime): if not isinstance(mime, MIMEBase): raise TypeError('"mime" must be an instance of MIMEBase.') if filename: try: filename.encode('ascii') except UnicodeEncodeError: filename = ('utf-8', '', filename) mime.add_header('Content-Disposition', 'attachment', filename=filename) self.attachments.append(mime) def attach_file(self, path, mimetype=None): filename = os.path.basename(path) if not mimetype: mimetype, _ = mimetypes.guess_type(filename) if not mimetype: mimetype = DEFAULT_MIMETYPE maintype, _ = mimetype.split('/', 1) readmode = 'r' if maintype == 'text' else 'rb' with open(path, readmode) as file: try: content = file.read() except UnicodeDecodeError: # tried to read an encoded text file without binary mode. # this will raise an error on Python 3. content = None if content is None: with open(path, 'rb') as file: content = file.read() mimetype = DEFAULT_MIMETYPE self.attach(filename, content, mimetype)