class EMail: """ Wrapper on the email module. Email object represents emails to be sent to the client. Also provides a clean way to add binary `FileData` attachments Also sets all messages as multipart/alternative for cleaner reading in text-only clients """ def __init__(self, sender='', recipients=(), subject='', alternative=0, reply_to=None, cc=(), email_account=None, expose_recipients=None): from email import Charset Charset.add_charset('utf-8', Charset.QP, Charset.QP, 'utf-8') if isinstance(recipients, string_types): recipients = recipients.replace(';', ',').replace('\n', '') recipients = split_emails(recipients) # remove null recipients = filter(None, (strip(r) for r in recipients)) self.sender = sender self.reply_to = reply_to or sender self.recipients = recipients self.subject = subject self.expose_recipients = expose_recipients self.msg_root = MIMEMultipart('mixed') self.msg_alternative = MIMEMultipart('alternative') self.msg_root.attach(self.msg_alternative) self.cc = cc or [] self.html_set = False self.email_account = email_account or get_outgoing_email_account() def set_html(self, message, text_content=None, footer=None, print_html=None, formatted=None, inline_images=None, header=None): """Attach message in the html portion of multipart/alternative""" if not formatted: formatted = get_formatted_html(self.subject, message, footer, print_html, email_account=self.email_account, header=header) # this is the first html part of a multi-part message, # convert to text well if not self.html_set: if text_content: self.set_text(expand_relative_urls(text_content)) else: self.set_html_as_text(expand_relative_urls(formatted)) self.set_part_html(formatted, inline_images) self.html_set = True def set_text(self, message): """ Attach message in the text portion of multipart/alternative """ from email.mime.text import MIMEText part = MIMEText(message, 'plain', 'utf-8') self.msg_alternative.attach(part) def set_part_html(self, message, inline_images): from email.mime.text import MIMEText has_inline_images = re.search('''embed=['"].*?['"]''', message) if has_inline_images: # process inline images message, _inline_images = replace_filename_with_cid(message) # prepare parts msg_related = MIMEMultipart('related') html_part = MIMEText(message, 'html', 'utf-8') msg_related.attach(html_part) for image in _inline_images: self.add_attachment(image.get('filename'), image.get('filecontent'), content_id=image.get('content_id'), parent=msg_related, inline=True) self.msg_alternative.attach(msg_related) else: self.msg_alternative.attach(MIMEText(message, 'html', 'utf-8')) def set_html_as_text(self, html): """Set plain text from HTML""" self.set_text(to_markdown(html)) def set_message(self, message, mime_type='text/html', as_attachment=0, filename='attachment.html'): """Append the message with MIME content to the root node (as attachment)""" from email.mime.text import MIMEText maintype, subtype = mime_type.split('/') part = MIMEText(message, _subtype=subtype) if as_attachment: part.add_header('Content-Disposition', 'attachment', filename=filename) self.msg_root.attach(part) def attach_file(self, n): """attach a file from the `FileData` table""" from frappe.utils.file_manager import get_file res = get_file(n) if not res: return self.add_attachment(res[0], res[1]) def add_attachment(self, fname, fcontent, content_type=None, parent=None, content_id=None, inline=False): """add attachment""" if not parent: parent = self.msg_root add_attachment(fname, fcontent, content_type, parent, content_id, inline) def add_pdf_attachment(self, name, html, options=None): self.add_attachment(name, get_pdf(html, options), 'application/octet-stream') def validate(self): """validate the Email Addresses""" from frappe.utils import validate_email_add if not self.sender: self.sender = self.email_account.default_sender validate_email_add(strip(self.sender), True) self.reply_to = validate_email_add( strip(self.reply_to) or self.sender, True) self.replace_sender() self.recipients = [strip(r) for r in self.recipients] self.cc = [strip(r) for r in self.cc] for e in self.recipients + (self.cc or []): validate_email_add(e, True) def replace_sender(self): if cint(self.email_account.always_use_account_email_id_as_sender): self.set_header('X-Original-From', self.sender) sender_name, sender_email = parse_addr(self.sender) self.sender = email.utils.formataddr( (sender_name or self.email_account.name, self.email_account.email_id)) def set_message_id(self, message_id, is_notification=False): if message_id: self.msg_root["Message-Id"] = '<' + message_id + '>' else: self.msg_root["Message-Id"] = get_message_id() self.msg_root["isnotification"] = '<notification>' if is_notification: self.msg_root["isnotification"] = '<notification>' def set_in_reply_to(self, in_reply_to): """Used to send the Message-Id of a received email back as In-Reply-To""" self.msg_root["In-Reply-To"] = in_reply_to def make(self): """build into msg_root""" headers = { "Subject": strip(self.subject), "From": self.sender, "To": ', '.join(self.recipients) if self.expose_recipients == "header" else "<!--recipient-->", "Date": email.utils.formatdate(), "Reply-To": self.reply_to if self.reply_to else None, "CC": ', '.join(self.cc) if self.cc and self.expose_recipients == "header" else None, 'X-Frappe-Site': get_url(), } # reset headers as values may be changed. for key, val in iteritems(headers): self.set_header(key, val) # call hook to enable apps to modify msg_root before sending for hook in frappe.get_hooks("make_email_body_message"): frappe.get_attr(hook)(self) def set_header(self, key, value): key = encode(key) value = encode(value) if self.msg_root.has_key(key): del self.msg_root[key] self.msg_root[key] = value def as_string(self): """validate, build message and convert to string""" self.validate() self.make() return self.msg_root.as_string()
class EMail: """ Wrapper on the email module. Email object represents emails to be sent to the client. Also provides a clean way to add binary `FileData` attachments Also sets all messages as multipart/alternative for cleaner reading in text-only clients """ def __init__(self, sender='', recipients=(), subject='', alternative=0, reply_to=None, cc=(), email_account=None): from email.mime.multipart import MIMEMultipart from email import Charset Charset.add_charset('utf-8', Charset.QP, Charset.QP, 'utf-8') if isinstance(recipients, basestring): recipients = recipients.replace(';', ',').replace('\n', '') recipients = split_emails(recipients) # remove null recipients = filter(None, (strip(r) for r in recipients)) self.sender = sender self.reply_to = reply_to or sender self.recipients = recipients self.subject = subject self.msg_root = MIMEMultipart('mixed') self.msg_multipart = MIMEMultipart('alternative') self.msg_root.attach(self.msg_multipart) self.cc = cc or [] self.html_set = False self.email_account = email_account def set_html(self, message, text_content=None, footer=None, print_html=None, formatted=None): """Attach message in the html portion of multipart/alternative""" if not formatted: formatted = get_formatted_html(self.subject, message, footer, print_html, email_account=self.email_account) # this is the first html part of a multi-part message, # convert to text well if not self.html_set: if text_content: self.set_text(expand_relative_urls(text_content)) else: self.set_html_as_text(expand_relative_urls(formatted)) self.set_part_html(formatted) self.html_set = True def set_text(self, message): """ Attach message in the text portion of multipart/alternative """ from email.mime.text import MIMEText part = MIMEText(message, 'plain', 'utf-8') self.msg_multipart.attach(part) def set_part_html(self, message): from email.mime.text import MIMEText part = MIMEText(message, 'html', 'utf-8') self.msg_multipart.attach(part) def set_html_as_text(self, html): """return html2text""" self.set_text(to_markdown(html)) def set_message(self, message, mime_type='text/html', as_attachment=0, filename='attachment.html'): """Append the message with MIME content to the root node (as attachment)""" from email.mime.text import MIMEText maintype, subtype = mime_type.split('/') part = MIMEText(message, _subtype=subtype) if as_attachment: part.add_header('Content-Disposition', 'attachment', filename=filename) self.msg_root.attach(part) def attach_file(self, n): """attach a file from the `FileData` table""" from frappe.utils.file_manager import get_file res = get_file(n) if not res: return self.add_attachment(res[0], res[1]) def add_attachment(self, fname, fcontent, content_type=None): """add attachment""" from email.mime.audio import MIMEAudio from email.mime.base import MIMEBase from email.mime.image import MIMEImage from email.mime.text import MIMEText import mimetypes if not content_type: content_type, encoding = mimetypes.guess_type(fname) if content_type is None: # No guess could be made, or the file is encoded (compressed), so # use a generic bag-of-bits type. content_type = 'application/octet-stream' maintype, subtype = content_type.split('/', 1) if maintype == 'text': # Note: we should handle calculating the charset if isinstance(fcontent, unicode): fcontent = fcontent.encode("utf-8") part = MIMEText(fcontent, _subtype=subtype, _charset="utf-8") elif maintype == 'image': part = MIMEImage(fcontent, _subtype=subtype) elif maintype == 'audio': part = MIMEAudio(fcontent, _subtype=subtype) else: part = MIMEBase(maintype, subtype) part.set_payload(fcontent) # Encode the payload using Base64 from email import encoders encoders.encode_base64(part) # Set the filename parameter if fname: part.add_header(b'Content-Disposition', ("attachment; filename=\"%s\"" % fname).encode('utf-8')) self.msg_root.attach(part) def add_pdf_attachment(self, name, html, options=None): self.add_attachment(name, get_pdf(html, options), 'application/octet-stream') def get_default_sender(self): return get_outgoing_email_account().default_sender def validate(self): """validate the email ids""" from frappe.utils import validate_email_add if not self.sender: self.sender = self.get_default_sender() validate_email_add(strip(self.sender), True) self.reply_to = validate_email_add( strip(self.reply_to) or self.sender, True) self.recipients = [strip(r) for r in self.recipients] self.cc = [strip(r) for r in self.cc] for e in self.recipients + (self.cc or []): validate_email_add(e, True) def set_message_id(self, message_id): self.msg_root["Message-Id"] = "<{0}@{1}>".format( message_id, frappe.local.site) def make(self): """build into msg_root""" headers = { "Subject": strip(self.subject).encode("utf-8"), "From": self.sender.encode("utf-8"), "To": ', '.join(self.recipients).encode("utf-8"), "Date": email.utils.formatdate(), "Reply-To": self.reply_to.encode("utf-8") if self.reply_to else None, "CC": ', '.join(self.cc).encode("utf-8") if self.cc else None, b'X-Frappe-Site': get_url().encode('utf-8'), } # reset headers as values may be changed. for key, val in headers.iteritems(): if self.msg_root.has_key(key): del self.msg_root[key] self.msg_root[key] = val # call hook to enable apps to modify msg_root before sending for hook in frappe.get_hooks("make_email_body_message"): frappe.get_attr(hook)(self) def as_string(self): """validate, build message and convert to string""" self.validate() self.make() return self.msg_root.as_string()
class EMail: """ Wrapper on the email module. Email object represents emails to be sent to the client. Also provides a clean way to add binary `FileData` attachments Also sets all messages as multipart/alternative for cleaner reading in text-only clients """ def __init__(self, sender="", recipients=(), subject="", alternative=0, reply_to=None, cc=()): from email.mime.multipart import MIMEMultipart from email import Charset Charset.add_charset("utf-8", Charset.QP, Charset.QP, "utf-8") if isinstance(recipients, basestring): recipients = recipients.replace(";", ",").replace("\n", "") recipients = split_emails(recipients) # remove null recipients = filter(None, (strip(r) for r in recipients)) self.sender = sender self.reply_to = reply_to or sender self.recipients = recipients self.subject = subject self.msg_root = MIMEMultipart("mixed") self.msg_multipart = MIMEMultipart("alternative") self.msg_root.attach(self.msg_multipart) self.cc = cc or [] self.html_set = False def set_html(self, message, text_content=None, footer=None, print_html=None, formatted=None): """Attach message in the html portion of multipart/alternative""" if not formatted: formatted = get_formatted_html(self.subject, message, footer, print_html) # this is the first html part of a multi-part message, # convert to text well if not self.html_set: if text_content: self.set_text(expand_relative_urls(text_content)) else: self.set_html_as_text(expand_relative_urls(formatted)) self.set_part_html(formatted) self.html_set = True def set_text(self, message): """ Attach message in the text portion of multipart/alternative """ from email.mime.text import MIMEText part = MIMEText(message, "plain", "utf-8") self.msg_multipart.attach(part) def set_part_html(self, message): from email.mime.text import MIMEText part = MIMEText(message, "html", "utf-8") self.msg_multipart.attach(part) def set_html_as_text(self, html): """return html2text""" self.set_text(to_markdown(html)) def set_message(self, message, mime_type="text/html", as_attachment=0, filename="attachment.html"): """Append the message with MIME content to the root node (as attachment)""" from email.mime.text import MIMEText maintype, subtype = mime_type.split("/") part = MIMEText(message, _subtype=subtype) if as_attachment: part.add_header("Content-Disposition", "attachment", filename=filename) self.msg_root.attach(part) def attach_file(self, n): """attach a file from the `FileData` table""" from frappe.utils.file_manager import get_file res = get_file(n) if not res: return self.add_attachment(res[0], res[1]) def add_attachment(self, fname, fcontent, content_type=None): """add attachment""" from email.mime.audio import MIMEAudio from email.mime.base import MIMEBase from email.mime.image import MIMEImage from email.mime.text import MIMEText import mimetypes if not content_type: content_type, encoding = mimetypes.guess_type(fname) if content_type is None: # No guess could be made, or the file is encoded (compressed), so # use a generic bag-of-bits type. content_type = "application/octet-stream" maintype, subtype = content_type.split("/", 1) if maintype == "text": # Note: we should handle calculating the charset if isinstance(fcontent, unicode): fcontent = fcontent.encode("utf-8") part = MIMEText(fcontent, _subtype=subtype, _charset="utf-8") elif maintype == "image": part = MIMEImage(fcontent, _subtype=subtype) elif maintype == "audio": part = MIMEAudio(fcontent, _subtype=subtype) else: part = MIMEBase(maintype, subtype) part.set_payload(fcontent) # Encode the payload using Base64 from email import encoders encoders.encode_base64(part) # Set the filename parameter if fname: part.add_header(b"Content-Disposition", ('attachment; filename="%s"' % fname).encode("utf-8")) self.msg_root.attach(part) def add_pdf_attachment(self, name, html, options=None): self.add_attachment(name, get_pdf(html, options), "application/octet-stream") def get_default_sender(self): return get_outgoing_email_account().default_sender def validate(self): """validate the email ids""" from frappe.utils import validate_email_add if not self.sender: self.sender = self.get_default_sender() validate_email_add(strip(self.sender), True) self.reply_to = validate_email_add(strip(self.reply_to) or self.sender, True) self.recipients = [strip(r) for r in self.recipients] self.cc = [strip(r) for r in self.cc] for e in self.recipients + (self.cc or []): validate_email_add(e, True) def set_message_id(self, message_id): self.msg_root["Message-Id"] = "<{0}@{1}>".format(message_id, frappe.local.site) def make(self): """build into msg_root""" headers = { "Subject": strip(self.subject).encode("utf-8"), "From": self.sender.encode("utf-8"), "To": ", ".join(self.recipients).encode("utf-8"), "Date": email.utils.formatdate(), "Reply-To": self.reply_to.encode("utf-8") if self.reply_to else None, "CC": ", ".join(self.cc).encode("utf-8") if self.cc else None, b"X-Frappe-Site": get_url().encode("utf-8"), } # reset headers as values may be changed. for key, val in headers.iteritems(): if self.msg_root.has_key(key): del self.msg_root[key] self.msg_root[key] = val # call hook to enable apps to modify msg_root before sending for hook in frappe.get_hooks("make_email_body_message"): frappe.get_attr(hook)(self) def as_string(self): """validate, build message and convert to string""" self.validate() self.make() return self.msg_root.as_string()
class EMail: """ Wrapper on the email module. Email object represents emails to be sent to the client. Also provides a clean way to add binary `FileData` attachments Also sets all messages as multipart/alternative for cleaner reading in text-only clients """ def __init__(self, sender='', recipients=(), subject='', alternative=0, reply_to=None, cc=(), email_account=None): from email.mime.multipart import MIMEMultipart from email import Charset Charset.add_charset('utf-8', Charset.QP, Charset.QP, 'utf-8') if isinstance(recipients, basestring): recipients = recipients.replace(';', ',').replace('\n', '') recipients = split_emails(recipients) # remove null recipients = filter(None, (strip(r) for r in recipients)) self.sender = sender self.reply_to = reply_to or sender self.recipients = recipients self.subject = subject self.msg_root = MIMEMultipart('mixed') self.msg_multipart = MIMEMultipart('alternative') self.msg_root.attach(self.msg_multipart) self.cc = cc or [] self.html_set = False self.email_account = email_account or get_outgoing_email_account() def set_html(self, message, text_content = None, footer=None, print_html=None, formatted=None): """Attach message in the html portion of multipart/alternative""" if not formatted: formatted = get_formatted_html(self.subject, message, footer, print_html, email_account=self.email_account) # this is the first html part of a multi-part message, # convert to text well if not self.html_set: if text_content: self.set_text(expand_relative_urls(text_content)) else: self.set_html_as_text(expand_relative_urls(formatted)) self.set_part_html(formatted) self.html_set = True def set_text(self, message): """ Attach message in the text portion of multipart/alternative """ from email.mime.text import MIMEText part = MIMEText(message, 'plain', 'utf-8') self.msg_multipart.attach(part) def set_part_html(self, message): from email.mime.text import MIMEText part = MIMEText(message, 'html', 'utf-8') self.msg_multipart.attach(part) def set_html_as_text(self, html): """return html2text""" self.set_text(to_markdown(html)) def set_message(self, message, mime_type='text/html', as_attachment=0, filename='attachment.html'): """Append the message with MIME content to the root node (as attachment)""" from email.mime.text import MIMEText maintype, subtype = mime_type.split('/') part = MIMEText(message, _subtype = subtype) if as_attachment: part.add_header('Content-Disposition', 'attachment', filename=filename) self.msg_root.attach(part) def attach_file(self, n): """attach a file from the `FileData` table""" from frappe.utils.file_manager import get_file res = get_file(n) if not res: return self.add_attachment(res[0], res[1]) def add_attachment(self, fname, fcontent, content_type=None): """add attachment""" from email.mime.audio import MIMEAudio from email.mime.base import MIMEBase from email.mime.image import MIMEImage from email.mime.text import MIMEText import mimetypes if not content_type: content_type, encoding = mimetypes.guess_type(fname) if content_type is None: # No guess could be made, or the file is encoded (compressed), so # use a generic bag-of-bits type. content_type = 'application/octet-stream' maintype, subtype = content_type.split('/', 1) if maintype == 'text': # Note: we should handle calculating the charset if isinstance(fcontent, unicode): fcontent = fcontent.encode("utf-8") part = MIMEText(fcontent, _subtype=subtype, _charset="utf-8") elif maintype == 'image': part = MIMEImage(fcontent, _subtype=subtype) elif maintype == 'audio': part = MIMEAudio(fcontent, _subtype=subtype) else: part = MIMEBase(maintype, subtype) part.set_payload(fcontent) # Encode the payload using Base64 from email import encoders encoders.encode_base64(part) # Set the filename parameter if fname: part.add_header(b'Content-Disposition', ("attachment; filename=\"%s\"" % fname).encode('utf-8')) self.msg_root.attach(part) def add_pdf_attachment(self, name, html, options=None): self.add_attachment(name, get_pdf(html, options), 'application/octet-stream') def validate(self): """validate the email ids""" from frappe.utils import validate_email_add if not self.sender: self.sender = self.email_account.default_sender validate_email_add(strip(self.sender), True) self.reply_to = validate_email_add(strip(self.reply_to) or self.sender, True) self.replace_sender() self.recipients = [strip(r) for r in self.recipients] self.cc = [strip(r) for r in self.cc] for e in self.recipients + (self.cc or []): validate_email_add(e, True) def replace_sender(self): if cint(self.email_account.always_use_account_email_id_as_sender): self.set_header('X-Original-From', self.sender) sender_name, sender_email = email.utils.parseaddr(self.sender) self.sender = email.utils.formataddr((sender_name or self.email_account.name, self.email_account.email_id)) def set_message_id(self, message_id): if message_id: self.msg_root["Message-Id"] = message_id else: self.msg_root["Message-Id"] = email.utils.make_msgid("{0}".format(frappe.local.site)) self.msg_root["References"] = '<notification>' def set_in_reply_to(self, in_reply_to): """Used to send the Message-Id of a received email back as In-Reply-To""" self.msg_root["In-Reply-To"] = in_reply_to def make(self): """build into msg_root""" headers = { "Subject": strip(self.subject), "From": self.sender, "To": ', '.join(self.recipients), "Date": email.utils.formatdate(), "Reply-To": self.reply_to if self.reply_to else None, "CC": ', '.join(self.cc) if self.cc else None, 'X-Frappe-Site': get_url(), } # reset headers as values may be changed. for key, val in headers.iteritems(): self.set_header(key, val) # call hook to enable apps to modify msg_root before sending for hook in frappe.get_hooks("make_email_body_message"): frappe.get_attr(hook)(self) def set_header(self, key, value): key = encode(key) value = encode(value) if self.msg_root.has_key(key): del self.msg_root[key] self.msg_root[key] = value def as_string(self): """validate, build message and convert to string""" self.validate() self.make() return self.msg_root.as_string()
class EMail: """ Wrapper on the email module. Email object represents emails to be sent to the client. Also provides a clean way to add binary `FileData` attachments Also sets all messages as multipart/alternative for cleaner reading in text-only clients """ def __init__(self, sender='', recipients=(), subject='', alternative=0, reply_to=None, cc=(), email_account=None, expose_recipients=None): from email import Charset Charset.add_charset('utf-8', Charset.QP, Charset.QP, 'utf-8') if isinstance(recipients, basestring): recipients = recipients.replace(';', ',').replace('\n', '') recipients = split_emails(recipients) # remove null recipients = filter(None, (strip(r) for r in recipients)) self.sender = sender self.reply_to = reply_to or sender self.recipients = recipients self.subject = subject self.expose_recipients = expose_recipients self.msg_root = MIMEMultipart('mixed') self.msg_multipart = MIMEMultipart('alternative') self.msg_root.attach(self.msg_multipart) self.cc = cc or [] self.html_set = False self.email_account = email_account or get_outgoing_email_account() def set_html(self, message, text_content=None, footer=None, print_html=None, formatted=None, inline_images=None): """Attach message in the html portion of multipart/alternative""" if not formatted: formatted = get_formatted_html(self.subject, message, footer, print_html, email_account=self.email_account) # this is the first html part of a multi-part message, # convert to text well if not self.html_set: if text_content: self.set_text(expand_relative_urls(text_content)) else: self.set_html_as_text(expand_relative_urls(formatted)) self.set_part_html(formatted, inline_images) self.html_set = True def set_text(self, message): """ Attach message in the text portion of multipart/alternative """ from email.mime.text import MIMEText part = MIMEText(message, 'plain', 'utf-8') self.msg_multipart.attach(part) def set_part_html(self, message, inline_images): from email.mime.text import MIMEText if inline_images: related = MIMEMultipart('related') for image in inline_images: # images in dict like {filename:'', filecontent:'raw'} content_id = random_string(10) # replace filename in message with CID message = re.sub( '''src=['"]{0}['"]'''.format(image.get('filename')), 'src="cid:{0}"'.format(content_id), message) self.add_attachment(image.get('filename'), image.get('filecontent'), None, content_id=content_id, parent=related) html_part = MIMEText(message, 'html', 'utf-8') related.attach(html_part) self.msg_multipart.attach(related) else: self.msg_multipart.attach(MIMEText(message, 'html', 'utf-8')) def set_html_as_text(self, html): """return html2text""" self.set_text(to_markdown(html)) def set_message(self, message, mime_type='text/html', as_attachment=0, filename='attachment.html'): """Append the message with MIME content to the root node (as attachment)""" from email.mime.text import MIMEText maintype, subtype = mime_type.split('/') part = MIMEText(message, _subtype=subtype) if as_attachment: part.add_header('Content-Disposition', 'attachment', filename=filename) self.msg_root.attach(part) def attach_file(self, n): """attach a file from the `FileData` table""" from frappe.utils.file_manager import get_file res = get_file(n) if not res: return self.add_attachment(res[0], res[1]) def add_attachment(self, fname, fcontent, content_type=None, parent=None, content_id=None): """add attachment""" from email.mime.audio import MIMEAudio from email.mime.base import MIMEBase from email.mime.image import MIMEImage from email.mime.text import MIMEText import mimetypes if not content_type: content_type, encoding = mimetypes.guess_type(fname) if content_type is None: # No guess could be made, or the file is encoded (compressed), so # use a generic bag-of-bits type. content_type = 'application/octet-stream' maintype, subtype = content_type.split('/', 1) if maintype == 'text': # Note: we should handle calculating the charset if isinstance(fcontent, unicode): fcontent = fcontent.encode("utf-8") part = MIMEText(fcontent, _subtype=subtype, _charset="utf-8") elif maintype == 'image': part = MIMEImage(fcontent, _subtype=subtype) elif maintype == 'audio': part = MIMEAudio(fcontent, _subtype=subtype) else: part = MIMEBase(maintype, subtype) part.set_payload(fcontent) # Encode the payload using Base64 from email import encoders encoders.encode_base64(part) # Set the filename parameter if fname: part.add_header(b'Content-Disposition', ("attachment; filename=\"%s\"" % fname).encode('utf-8')) if content_id: part.add_header(b'Content-ID', '<{0}>'.format(content_id)) if not parent: parent = self.msg_root parent.attach(part) def add_pdf_attachment(self, name, html, options=None): self.add_attachment(name, get_pdf(html, options), 'application/octet-stream') def validate(self): """validate the Email Addresses""" from frappe.utils import validate_email_add if not self.sender: self.sender = self.email_account.default_sender validate_email_add(strip(self.sender), True) self.reply_to = validate_email_add( strip(self.reply_to) or self.sender, True) self.replace_sender() self.recipients = [strip(r) for r in self.recipients] self.cc = [strip(r) for r in self.cc] for e in self.recipients + (self.cc or []): validate_email_add(e, True) def replace_sender(self): if cint(self.email_account.always_use_account_email_id_as_sender): self.set_header('X-Original-From', self.sender) sender_name, sender_email = parse_addr(self.sender) self.sender = email.utils.formataddr( (sender_name or self.email_account.name, self.email_account.email_id)) def set_message_id(self, message_id, is_notification=False): if message_id: self.msg_root["Message-Id"] = '<' + message_id + '>' else: self.msg_root["Message-Id"] = get_message_id() self.msg_root["isnotification"] = '<notification>' if is_notification: self.msg_root["isnotification"] = '<notification>' def set_in_reply_to(self, in_reply_to): """Used to send the Message-Id of a received email back as In-Reply-To""" self.msg_root["In-Reply-To"] = in_reply_to def make(self): """build into msg_root""" headers = { "Subject": strip(self.subject), "From": self.sender, "To": ', '.join(self.recipients) if self.expose_recipients == "header" else "<!--recipient-->", "Date": email.utils.formatdate(), "Reply-To": self.reply_to if self.reply_to else None, "CC": ', '.join(self.cc) if self.cc and self.expose_recipients == "header" else None, 'X-Frappe-Site': get_url(), } # reset headers as values may be changed. for key, val in iteritems(headers): self.set_header(key, val) # call hook to enable apps to modify msg_root before sending for hook in frappe.get_hooks("make_email_body_message"): frappe.get_attr(hook)(self) def set_header(self, key, value): key = encode(key) value = encode(value) if self.msg_root.has_key(key): del self.msg_root[key] self.msg_root[key] = value def as_string(self): """validate, build message and convert to string""" self.validate() self.make() return self.msg_root.as_string()
def run(self): if len(self.args) == 0: raise UsageError("needs 1 argument") self.server = None self.dataDir = '.' self.count_ok = 0 self.count_nok = 0 if self.options.host is None: raise UsageError("--host must be specified") ## for fn in self.attach_files: ## if not os.path.exists(fn): ## raise OperationFailed("File %s does not exist."%fn) files = [] for pattern in self.args: files += glob.glob(pattern) self.count_todo = len(files) if self.count_todo == 0: self.notice("Nothing to do: no input files found.") return if self.options.recipient is None: recipients = [] else: recipients = getaddresses([self.options.recipient]) sender = self.options.sender subject = self.options.subject """ if the first input file's name ends with .eml, then this becomes the outer message """ if files[0].lower().endswith(".eml"): self.notice("Reading file %s...", files[0]) first = email.message_from_file(codecs.open( files[0], "r", self.options.encoding), _class=MyMessage) if first.has_key('subject'): subject = first["subject"] del first["subject"] first["subject"] = Header(subject, first.get_charset()) self.encodeaddrs(first, 'from') if sender is None: sender = first["from"] self.encodeaddrs(first, 'to', recipients) self.encodeaddrs(first, 'cc', recipients) self.encodeaddrs(first, 'bcc', recipients) del first['bcc'] del files[0] self.count_todo -= 1 else: first = None if len(files) == 0: outer = first else: # Create the enclosing (outer) message outer = MIMEMultipart() # outer.preamble = 'You will not see this in a MIME-aware mail reader.\n' if first is not None: first.add_header('Content-Disposition', 'inline') outer.attach(first) for hdr in ('to', 'cc'): outer[hdr] = first[hdr] outer['subject'] = subject outer['from'] = sender self.notice("Attaching %d files...", self.count_todo) i = 1 for filename in files: self.notice(u"%s (%d/%d)", filename, i, self.count_todo) part = self.file2msg(filename) # Set the filename parameter part.add_header('Content-Disposition', 'attachment', filename=os.path.basename(filename)) outer.attach(part) i += 1 #for part in outer.walk(): #if recipient is None: recipient=part["To"] #if bcc is None: bcc=part["Bcc"] if self.options.subject is not None: del outer['subject'] outer['subject'] = self.options.subject #if self.options.sender is not None: # del outer['from'] # outer['from'] = self.options.sender #del outer['to'] #outer['to'] = recipient #if bcc is not None: # outer['Bcc'] = bcc # print "Bcc:", bcc #headers_i18n(outer) if len(recipients) == 0: for addr in open(opj(self.dataDir, "addrlist.txt")).xreadlines(): addr = addr.strip() if len(addr) != 0 and addr[0] != "#": recipients += getaddresses([addr]) if not outer.has_key("Subject"): raise "Subject header is missing" if not outer.has_key("Date"): outer["Date"] = email.Utils.formatdate(None, True) for k, v in outer.items(): print k, ":", unicode(v) #self.notice(str(outer.keys())) self.notice(_("Message size: %d bytes."), len(str(outer))) self.notice(_("Send this to %d recipients: %s"), len(recipients), ", ".join([a[1] for a in recipients])) sender = parseaddr(unicode(sender))[1] # print "sender:", unicode(sender) # print outer.as_string(unixfrom=0) if not self.confirm("Okay?"): return self.connect() self.sendmsg(outer, sender, recipients) self.server.quit() self.notice(_("Sent to %d recipients."), self.count_ok) if self.count_nok != 0: self.notice(_("%d recipients refused."), self.count_nok)
def run(self): if len(self.args) == 0: raise UsageError("needs 1 argument") self.server = None self.dataDir = "." self.count_ok = 0 self.count_nok = 0 if self.options.host is None: raise UsageError("--host must be specified") ## for fn in self.attach_files: ## if not os.path.exists(fn): ## raise OperationFailed("File %s does not exist."%fn) files = [] for pattern in self.args: files += glob.glob(pattern) self.count_todo = len(files) if self.count_todo == 0: self.notice("Nothing to do: no input files found.") return if self.options.recipient is None: recipients = [] else: recipients = getaddresses([self.options.recipient]) sender = self.options.sender subject = self.options.subject """ if the first input file's name ends with .eml, then this becomes the outer message """ if files[0].lower().endswith(".eml"): self.notice("Reading file %s...", files[0]) first = email.message_from_file(codecs.open(files[0], "r", self.options.encoding), _class=MyMessage) if first.has_key("subject"): subject = first["subject"] del first["subject"] first["subject"] = Header(subject, first.get_charset()) self.encodeaddrs(first, "from") if sender is None: sender = first["from"] self.encodeaddrs(first, "to", recipients) self.encodeaddrs(first, "cc", recipients) self.encodeaddrs(first, "bcc", recipients) del first["bcc"] del files[0] self.count_todo -= 1 else: first = None if len(files) == 0: outer = first else: # Create the enclosing (outer) message outer = MIMEMultipart() # outer.preamble = 'You will not see this in a MIME-aware mail reader.\n' if first is not None: first.add_header("Content-Disposition", "inline") outer.attach(first) for hdr in ("to", "cc"): outer[hdr] = first[hdr] outer["subject"] = subject outer["from"] = sender self.notice("Attaching %d files...", self.count_todo) i = 1 for filename in files: self.notice(u"%s (%d/%d)", filename, i, self.count_todo) part = self.file2msg(filename) # Set the filename parameter part.add_header("Content-Disposition", "attachment", filename=os.path.basename(filename)) outer.attach(part) i += 1 # for part in outer.walk(): # if recipient is None: recipient=part["To"] # if bcc is None: bcc=part["Bcc"] if self.options.subject is not None: del outer["subject"] outer["subject"] = self.options.subject # if self.options.sender is not None: # del outer['from'] # outer['from'] = self.options.sender # del outer['to'] # outer['to'] = recipient # if bcc is not None: # outer['Bcc'] = bcc # print "Bcc:", bcc # headers_i18n(outer) if len(recipients) == 0: for addr in open(opj(self.dataDir, "addrlist.txt")).xreadlines(): addr = addr.strip() if len(addr) != 0 and addr[0] != "#": recipients += getaddresses([addr]) if not outer.has_key("Subject"): raise "Subject header is missing" if not outer.has_key("Date"): outer["Date"] = email.Utils.formatdate(None, True) for k, v in outer.items(): print k, ":", unicode(v) # self.notice(str(outer.keys())) self.notice(_("Message size: %d bytes."), len(str(outer))) self.notice(_("Send this to %d recipients: %s"), len(recipients), ", ".join([a[1] for a in recipients])) sender = parseaddr(unicode(sender))[1] # print "sender:", unicode(sender) # print outer.as_string(unixfrom=0) if not self.confirm("Okay?"): return self.connect() self.sendmsg(outer, sender, recipients) self.server.quit() self.notice(_("Sent to %d recipients."), self.count_ok) if self.count_nok != 0: self.notice(_("%d recipients refused."), self.count_nok)