No puede seleccionar más de 25 temas Los temas deben comenzar con una letra o número, pueden incluir guiones ('-') y pueden tener hasta 35 caracteres de largo.
 
 
 
 
 
 

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