You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

207 lines
7.6 KiB

  1. # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
  2. # MIT License. See license.txt
  3. from __future__ import unicode_literals
  4. import frappe
  5. import HTMLParser
  6. from frappe import msgprint, throw, _
  7. from frappe.email.smtp import SMTPServer, get_outgoing_email_account
  8. from frappe.email.email_body import get_email, get_formatted_html
  9. from frappe.utils.verified_command import get_signed_params, verify_request
  10. from html2text import html2text
  11. from frappe.utils import get_url, nowdate, encode, now_datetime, add_days
  12. class BulkLimitCrossedError(frappe.ValidationError): pass
  13. def send(recipients=None, sender=None, subject=None, message=None, reference_doctype=None,
  14. reference_name=None, unsubscribe_method=None, unsubscribe_params=None, unsubscribe_message=None,
  15. attachments=None, reply_to=None, cc=(), message_id=None, send_after=None):
  16. """Add email to sending queue (Bulk Email)
  17. :param recipients: List of recipients.
  18. :param sender: Email sender.
  19. :param subject: Email subject.
  20. :param message: Email message.
  21. :param reference_doctype: Reference DocType of caller document.
  22. :param reference_name: Reference name of caller document.
  23. :param unsubscribe_method: URL method for unsubscribe. Default is `/api/method/frappe.email.bulk.unsubscribe`.
  24. :param unsubscribe_params: additional params for unsubscribed links. default are name, doctype, email
  25. :param attachments: Attachments to be sent.
  26. :param reply_to: Reply to be captured here (default inbox)
  27. :param message_id: Used for threading. If a reply is received to this email, Message-Id is sent back as In-Reply-To in received email.
  28. :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.
  29. """
  30. if not unsubscribe_method:
  31. unsubscribe_method = "/api/method/frappe.email.bulk.unsubscribe"
  32. if not recipients:
  33. return
  34. if isinstance(recipients, basestring):
  35. recipients = recipients.split(",")
  36. if isinstance(send_after, int):
  37. send_after = add_days(nowdate(), send_after)
  38. if not sender or sender == "Administrator":
  39. email_account = get_outgoing_email_account()
  40. sender = email_account.get("sender") or email_account.email_id
  41. check_bulk_limit(recipients)
  42. formatted = get_formatted_html(subject, message)
  43. try:
  44. text_content = html2text(formatted)
  45. except HTMLParser.HTMLParseError:
  46. text_content = "See html attachment"
  47. if reference_doctype and reference_name:
  48. unsubscribed = [d.email for d in frappe.db.get_all("Email Unsubscribe", "email",
  49. {"reference_doctype": reference_doctype, "reference_name": reference_name})]
  50. else:
  51. unsubscribed = []
  52. for email in filter(None, list(set(recipients))):
  53. if email not in unsubscribed:
  54. email_content = formatted
  55. email_text_context = text_content
  56. if reference_doctype:
  57. unsubscribe_url = get_unsubcribed_url(reference_doctype, reference_name, email,
  58. unsubscribe_method, unsubscribe_params)
  59. # add to queue
  60. email_content = add_unsubscribe_link(email_content, email, reference_doctype,
  61. reference_name, unsubscribe_url, unsubscribe_message)
  62. email_text_context += "\n" + _("This email was sent to {0}. To unsubscribe click on this link: {1}").format(email, unsubscribe_url)
  63. add(email, sender, subject, email_content, email_text_context, reference_doctype,
  64. reference_name, attachments, reply_to, cc, message_id, send_after)
  65. def add(email, sender, subject, formatted, text_content=None,
  66. reference_doctype=None, reference_name=None, attachments=None, reply_to=None,
  67. cc=(), message_id=None, send_after=None):
  68. """add to bulk mail queue"""
  69. e = frappe.new_doc('Bulk Email')
  70. e.sender = sender
  71. e.recipient = email
  72. try:
  73. mail = get_email(email, sender=e.sender, formatted=formatted, subject=subject,
  74. text_content=text_content, attachments=attachments, reply_to=reply_to, cc=cc)
  75. if message_id:
  76. mail.set_message_id(message_id)
  77. e.message = mail.as_string()
  78. except frappe.InvalidEmailAddressError:
  79. # bad email id - don't add to queue
  80. return
  81. e.reference_doctype = reference_doctype
  82. e.reference_name = reference_name
  83. e.send_after = send_after
  84. e.insert(ignore_permissions=True)
  85. def check_bulk_limit(recipients):
  86. this_month = frappe.db.sql("""select count(*) from `tabBulk Email` where
  87. MONTH(creation)=MONTH(CURDATE())""")[0][0]
  88. # No limit for own email settings
  89. smtp_server = SMTPServer()
  90. if smtp_server.email_account and not getattr(smtp_server.email_account,
  91. "from_site_config", False) or frappe.flags.in_test:
  92. monthly_bulk_mail_limit = frappe.conf.get('monthly_bulk_mail_limit') or 500
  93. if (this_month + len(recipients)) > monthly_bulk_mail_limit:
  94. throw(_("Email limit {0} crossed").format(monthly_bulk_mail_limit),
  95. BulkLimitCrossedError)
  96. def add_unsubscribe_link(message, email, reference_doctype, reference_name, unsubscribe_url, unsubscribe_message):
  97. unsubscribe_link = """<div style="padding: 7px; text-align: center; color: #8D99A6;">
  98. {email}. <a href="{unsubscribe_url}" style="color: #8D99A6; text-decoration: underline;
  99. target="_blank">{unsubscribe_message}.
  100. </a>
  101. </div>""".format(unsubscribe_url = unsubscribe_url,
  102. email= _("This email was sent to {0}").format(email),
  103. unsubscribe_message = unsubscribe_message or _("Unsubscribe from this list"))
  104. message = message.replace("<!--unsubscribe link here-->", unsubscribe_link)
  105. return message
  106. def get_unsubcribed_url(reference_doctype, reference_name, email, unsubscribe_method, unsubscribe_params):
  107. params = {"email": email.encode("utf-8"),
  108. "doctype": reference_doctype.encode("utf-8"),
  109. "name": reference_name.encode("utf-8")}
  110. if unsubscribe_params:
  111. params.update(unsubscribe_params)
  112. query_string = get_signed_params(params)
  113. # for test
  114. frappe.local.flags.signed_query_string = query_string
  115. return get_url(unsubscribe_method + "?" + get_signed_params(params))
  116. @frappe.whitelist(allow_guest=True)
  117. def unsubscribe(doctype, name, email):
  118. # unsubsribe from comments and communications
  119. if not verify_request():
  120. return
  121. frappe.get_doc({
  122. "doctype": "Email Unsubscribe",
  123. "email": email,
  124. "reference_doctype": doctype,
  125. "reference_name": name
  126. }).insert(ignore_permissions=True)
  127. frappe.db.commit()
  128. return_unsubscribed_page(email, doctype, name)
  129. def return_unsubscribed_page(email, doctype, name):
  130. frappe.respond_as_web_page(_("Unsubscribed"), _("{0} has left the conversation in {1} {2}").format(email, _(doctype), name))
  131. def flush(from_test=False):
  132. """flush email queue, every time: called from scheduler"""
  133. smtpserver = SMTPServer()
  134. auto_commit = not from_test
  135. if frappe.flags.mute_emails or frappe.conf.get("mute_emails") or False:
  136. msgprint(_("Emails are muted"))
  137. from_test = True
  138. for i in xrange(500):
  139. email = frappe.db.sql("""select * from `tabBulk Email` where
  140. status='Not Sent' and ifnull(send_after, "2000-01-01 00:00:00") < %s
  141. order by creation asc limit 1 for update""", now_datetime(), as_dict=1)
  142. if email:
  143. email = email[0]
  144. else:
  145. break
  146. frappe.db.sql("""update `tabBulk Email` set status='Sending' where name=%s""",
  147. (email["name"],), auto_commit=auto_commit)
  148. try:
  149. if not from_test:
  150. smtpserver.setup_email_account(email.reference_doctype)
  151. smtpserver.sess.sendmail(email["sender"], email["recipient"], encode(email["message"]))
  152. frappe.db.sql("""update `tabBulk Email` set status='Sent' where name=%s""",
  153. (email["name"],), auto_commit=auto_commit)
  154. except Exception, e:
  155. frappe.db.sql("""update `tabBulk Email` set status='Error', error=%s
  156. where name=%s""", (unicode(e), email["name"]), auto_commit=auto_commit)
  157. def clear_outbox():
  158. """remove mails older than 30 days in Outbox"""
  159. frappe.db.sql("""delete from `tabBulk Email` where
  160. datediff(now(), creation) > 30""")