# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # MIT License. See license.txt from __future__ import unicode_literals import frappe import HTMLParser import smtplib from frappe import msgprint, throw, _ from frappe.email.smtp import SMTPServer, get_outgoing_email_account from frappe.email.email_body import get_email, get_formatted_html from frappe.utils.verified_command import get_signed_params, verify_request from html2text import html2text from frappe.utils import get_url, nowdate, encode, now_datetime, add_days, split_emails, cstr from rq.timeouts import JobTimeoutException from frappe.utils.scheduler import log class EmailLimitCrossedError(frappe.ValidationError): pass def send(recipients=None, sender=None, subject=None, message=None, reference_doctype=None, reference_name=None, unsubscribe_method=None, unsubscribe_params=None, unsubscribe_message=None, attachments=None, reply_to=None, cc=(), show_as_cc=(), in_reply_to=None, send_after=None, expose_recipients=False, send_priority=1, communication=None, now=False): """Add email to sending queue (Email Queue) :param recipients: List of recipients. :param sender: Email sender. :param subject: Email subject. :param message: Email message. :param reference_doctype: Reference DocType of caller document. :param reference_name: Reference name of caller document. :param send_priority: Priority for Email Queue, default 1. :param unsubscribe_method: URL method for unsubscribe. Default is `/api/method/frappe.email.queue.unsubscribe`. :param unsubscribe_params: additional params for unsubscribed links. default are name, doctype, email :param attachments: Attachments to be sent. :param reply_to: Reply to be captured here (default inbox) :param in_reply_to: Used to send the Message-Id of a received email back as In-Reply-To. :param send_after: Send this email after the given datetime. If value is in integer, then `send_after` will be the automatically set to no of days from current date. :param communication: Communication link to be set in Email Queue record :param now: Send immediately (don't send in the background) """ if not unsubscribe_method: unsubscribe_method = "/api/method/frappe.email.queue.unsubscribe" if not recipients: return if isinstance(recipients, basestring): recipients = split_emails(recipients) if isinstance(send_after, int): send_after = add_days(nowdate(), send_after) email_account = get_outgoing_email_account(True, append_to=reference_doctype) if not sender or sender == "Administrator": sender = email_account.default_sender check_email_limit(recipients) formatted = get_formatted_html(subject, message, email_account=email_account) try: text_content = html2text(formatted) except HTMLParser.HTMLParseError: text_content = "See html attachment" if reference_doctype and reference_name: unsubscribed = [d.email for d in frappe.db.get_all("Email Unsubscribe", "email", {"reference_doctype": reference_doctype, "reference_name": reference_name})] unsubscribed += [d.email for d in frappe.db.get_all("Email Unsubscribe", "email", {"global_unsubscribe": 1})] else: unsubscribed = [] recipients = [r for r in list(set(recipients)) if r and r not in unsubscribed] for email in recipients: email_content = formatted email_text_context = text_content if reference_doctype and (unsubscribe_message or reference_doctype=="Newsletter"): unsubscribe_link = get_unsubscribe_link( reference_doctype=reference_doctype, reference_name=reference_name, email=email, recipients=recipients, expose_recipients=expose_recipients, unsubscribe_method=unsubscribe_method, unsubscribe_params=unsubscribe_params, unsubscribe_message=unsubscribe_message, show_as_cc=show_as_cc ) email_content = email_content.replace("", unsubscribe_link.html) email_text_context += unsubscribe_link.text # show as cc cc_message = "" if email in show_as_cc: cc_message = _("This email was sent to you as CC") email_content = email_content.replace("", cc_message) email_text_context = cc_message + "\n" + email_text_context # add to queue email_queue = add(email, sender, subject, email_content, email_text_context, reference_doctype, reference_name, attachments, reply_to, cc, in_reply_to, send_after, send_priority, email_account=email_account, communication=communication) if now: send_one(email_queue.name, now=True) def add(email, sender, subject, formatted, text_content=None, reference_doctype=None, reference_name=None, attachments=None, reply_to=None, cc=(), in_reply_to=None, send_after=None, send_priority=1, email_account=None, communication=None): """Add to Email Queue""" e = frappe.new_doc('Email Queue') e.recipient = email e.priority = send_priority try: mail = get_email(email, sender=sender, formatted=formatted, subject=subject, text_content=text_content, attachments=attachments, reply_to=reply_to, cc=cc, email_account=email_account) if in_reply_to: mail.set_in_reply_to(in_reply_to) e.message_id = mail.msg_root["Message-Id"].strip(" <>") e.message = cstr(mail.as_string()) e.sender = mail.sender except frappe.InvalidEmailAddressError: # bad email id - don't add to queue return e.reference_doctype = reference_doctype e.reference_name = reference_name e.communication = communication e.send_after = send_after e.db_insert() return e def check_email_limit(recipients): # if using settings from site_config.json, check email limit # No limit for own email settings smtp_server = SMTPServer() if (smtp_server.email_account and getattr(smtp_server.email_account, "from_site_config", False) or frappe.flags.in_test): monthly_email_limit = frappe.conf.get('limits', {}).get('emails') if frappe.flags.in_test: monthly_email_limit = 500 if not monthly_email_limit: return # get count of mails sent this month this_month = get_emails_sent_this_month() if (this_month + len(recipients)) > monthly_email_limit: throw(_("Cannot send this email. You have crossed the sending limit of {0} emails for this month.").format(monthly_email_limit), EmailLimitCrossedError) def get_emails_sent_this_month(): return frappe.db.sql("""select count(name) from `tabEmail Queue` where status='Sent' and MONTH(creation)=MONTH(CURDATE())""")[0][0] def get_unsubscribe_link(reference_doctype, reference_name, email, recipients, expose_recipients, show_as_cc, unsubscribe_method, unsubscribe_params, unsubscribe_message): email_sent_to = recipients if expose_recipients else [email] email_sent_cc = ", ".join([e for e in email_sent_to if e in show_as_cc]) email_sent_to = ", ".join([e for e in email_sent_to if e not in show_as_cc]) if email_sent_cc: email_sent_message = _("This email was sent to {0} and copied to {1}").format(email_sent_to, email_sent_cc) else: email_sent_message = _("This email was sent to {0}").format(email_sent_to) if not unsubscribe_message: unsubscribe_message = _("Unsubscribe from this list") unsubscribe_url = get_unsubcribed_url(reference_doctype, reference_name, email, unsubscribe_method, unsubscribe_params) html = """