@@ -379,7 +379,8 @@ def sendmail(recipients=[], sender="", subject="No Subject", message="No Message | |||
unsubscribe_method=None, unsubscribe_params=None, unsubscribe_message=None, | |||
attachments=None, content=None, doctype=None, name=None, reply_to=None, | |||
cc=[], message_id=None, in_reply_to=None, send_after=None, expose_recipients=None, | |||
send_priority=1, communication=None, retry=1, now=None, read_receipt=None, is_notification=False): | |||
send_priority=1, communication=None, retry=1, now=None, read_receipt=None, is_notification=False, | |||
inline_images=None): | |||
"""Send email using user's default **Email Account** or global default **Email Account**. | |||
@@ -401,6 +402,7 @@ def sendmail(recipients=[], sender="", subject="No Subject", message="No Message | |||
:param send_after: Send after the given datetime. | |||
:param expose_recipients: Display all recipients in the footer message - "This email was sent to" | |||
:param communication: Communication link to be set in Email Queue record | |||
:param inline_images: List of inline images as {"filename", "filecontent"}. All src properties will be replaced with random Content-Id | |||
""" | |||
message = content or message | |||
@@ -418,7 +420,8 @@ def sendmail(recipients=[], sender="", subject="No Subject", message="No Message | |||
unsubscribe_method=unsubscribe_method, unsubscribe_params=unsubscribe_params, unsubscribe_message=unsubscribe_message, | |||
attachments=attachments, reply_to=reply_to, cc=cc, message_id=message_id, in_reply_to=in_reply_to, | |||
send_after=send_after, expose_recipients=expose_recipients, send_priority=send_priority, | |||
communication=communication, now=now, read_receipt=read_receipt, is_notification=is_notification) | |||
communication=communication, now=now, read_receipt=read_receipt, is_notification=is_notification, | |||
inline_images=inline_images) | |||
whitelisted = [] | |||
guest_methods = [] | |||
@@ -2,19 +2,20 @@ | |||
# MIT License. See license.txt | |||
from __future__ import unicode_literals | |||
import frappe | |||
import frappe, re | |||
from frappe.utils.pdf import get_pdf | |||
from frappe.email.smtp import get_outgoing_email_account | |||
from frappe.utils import (get_url, scrub_urls, strip, expand_relative_urls, cint, | |||
split_emails, to_markdown, markdown, encode, random_string) | |||
split_emails, to_markdown, markdown, encode, random_string, parse_addr) | |||
import email.utils | |||
from frappe.utils import parse_addr | |||
from six import iteritems | |||
from email.mime.multipart import MIMEMultipart | |||
def get_email(recipients, sender='', msg='', subject='[No Subject]', | |||
text_content = None, footer=None, print_html=None, formatted=None, attachments=None, | |||
content=None, reply_to=None, cc=[], email_account=None, expose_recipients=None): | |||
content=None, reply_to=None, cc=[], email_account=None, expose_recipients=None, | |||
inline_images=[]): | |||
"""send an html email as multipart with attachments and all""" | |||
content = content or msg | |||
emailobj = EMail(sender, recipients, subject, reply_to=reply_to, cc=cc, email_account=email_account, expose_recipients=expose_recipients) | |||
@@ -22,7 +23,8 @@ def get_email(recipients, sender='', msg='', subject='[No Subject]', | |||
if not content.strip().startswith("<"): | |||
content = markdown(content) | |||
emailobj.set_html(content, text_content, footer=footer, print_html=print_html, formatted=formatted) | |||
emailobj.set_html(content, text_content, footer=footer, | |||
print_html=print_html, formatted=formatted, inline_images=inline_images) | |||
if isinstance(attachments, dict): | |||
attachments = [attachments] | |||
@@ -39,7 +41,6 @@ class EMail: | |||
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.mime.multipart import MIMEMultipart | |||
from email import Charset | |||
Charset.add_charset('utf-8', Charset.QP, Charset.QP, 'utf-8') | |||
@@ -64,7 +65,8 @@ class EMail: | |||
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): | |||
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) | |||
@@ -77,7 +79,7 @@ class EMail: | |||
else: | |||
self.set_html_as_text(expand_relative_urls(formatted)) | |||
self.set_part_html(formatted) | |||
self.set_part_html(formatted, inline_images) | |||
self.html_set = True | |||
def set_text(self, message): | |||
@@ -88,10 +90,28 @@ class EMail: | |||
part = MIMEText(message, 'plain', 'utf-8') | |||
self.msg_multipart.attach(part) | |||
def set_part_html(self, message): | |||
def set_part_html(self, message, inline_images): | |||
from email.mime.text import MIMEText | |||
part = MIMEText(message, 'html', 'utf-8') | |||
self.msg_multipart.attach(part) | |||
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""" | |||
@@ -118,7 +138,8 @@ class EMail: | |||
self.add_attachment(res[0], res[1]) | |||
def add_attachment(self, fname, fcontent, content_type=None): | |||
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 | |||
@@ -155,8 +176,13 @@ class EMail: | |||
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)) | |||
self.msg_root.attach(part) | |||
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') | |||
@@ -21,7 +21,7 @@ def send(recipients=None, sender=None, subject=None, message=None, reference_doc | |||
reference_name=None, unsubscribe_method=None, unsubscribe_params=None, unsubscribe_message=None, | |||
attachments=None, reply_to=None, cc=[], message_id=None, in_reply_to=None, send_after=None, | |||
expose_recipients=None, send_priority=1, communication=None, now=False, read_receipt=None, | |||
queue_separately=False, is_notification=False, add_unsubscribe_link=1): | |||
queue_separately=False, is_notification=False, add_unsubscribe_link=1, inline_images=None): | |||
"""Add email to sending queue (Email Queue) | |||
:param recipients: List of recipients. | |||
@@ -42,6 +42,7 @@ def send(recipients=None, sender=None, subject=None, message=None, reference_doc | |||
:param queue_separately: Queue each email separately | |||
:param is_notification: Marks email as notification so will not trigger notifications from system | |||
:param add_unsubscribe_link: Send unsubscribe link in the footer of the Email, default 1. | |||
:param inline_images: List of inline images as {"filename", "filecontent"}. All src properties will be replaced with random Content-Id | |||
""" | |||
if not unsubscribe_method: | |||
unsubscribe_method = "/api/method/frappe.email.queue.unsubscribe" | |||
@@ -112,6 +113,7 @@ def send(recipients=None, sender=None, subject=None, message=None, reference_doc | |||
read_receipt=read_receipt, | |||
queue_separately=queue_separately, | |||
is_notification = is_notification, | |||
inline_images = inline_images, | |||
now=now) | |||
@@ -152,7 +154,8 @@ def get_email_queue(recipients, sender, subject, **kwargs): | |||
reply_to=kwargs.get('reply_to'), | |||
cc=kwargs.get('cc'), | |||
email_account=kwargs.get('email_account'), | |||
expose_recipients=kwargs.get('expose_recipients')) | |||
expose_recipients=kwargs.get('expose_recipients'), | |||
inline_images=kwargs.get('inline_images')) | |||
mail.set_message_id(kwargs.get('message_id'),kwargs.get('is_notification')) | |||
if kwargs.get('read_receipt'): | |||
@@ -431,7 +434,7 @@ def prepare_message(email, recipient, recipients_list): | |||
message = email.message | |||
if not message: | |||
return "" | |||
if email.add_unsubscribe_link and email.reference_doctype: # is missing the check for unsubscribe message but will not add as there will be no unsubscribe url | |||
unsubscribe_url = get_unsubcribed_url(email.reference_doctype, email.reference_name, recipient, | |||
email.unsubscribe_method, email.unsubscribe_params) | |||
@@ -461,19 +464,19 @@ def clear_outbox(): | |||
Called daily via scheduler. | |||
Note: Used separate query to avoid deadlock | |||
""" | |||
email_queues = frappe.db.sql_list("""select name from `tabEmail Queue` | |||
email_queues = frappe.db.sql_list("""select name from `tabEmail Queue` | |||
where priority=0 and datediff(now(), modified) > 31""") | |||
if email_queues: | |||
frappe.db.sql("""delete from `tabEmail Queue` where name in (%s)""" | |||
frappe.db.sql("""delete from `tabEmail Queue` where name in (%s)""" | |||
% ','.join(['%s']*len(email_queues)), tuple(email_queues)) | |||
frappe.db.sql("""delete from `tabEmail Queue Recipient` where parent in (%s)""" | |||
frappe.db.sql("""delete from `tabEmail Queue Recipient` where parent in (%s)""" | |||
% ','.join(['%s']*len(email_queues)), tuple(email_queues)) | |||
for dt in ("Email Queue", "Email Queue Recipient"): | |||
frappe.db.sql(""" | |||
update `tab{0}` | |||
update `tab{0}` | |||
set status='Expired' | |||
where datediff(curdate(), modified) > 7 and status='Not Sent'""".format(dt)) |