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.
 
 
 
 
 
 

298 lines
7.9 KiB

  1. """
  2. Sends email via outgoing server specified in "Control Panel"
  3. Allows easy adding of Attachments of "File" objects
  4. """
  5. import webnotes
  6. import webnotes.defs
  7. from webnotes import msgprint
  8. import email
  9. class EMail:
  10. """
  11. Wrapper on the email module. Email object represents emails to be sent to the client.
  12. Also provides a clean way to add binary `FileData` attachments
  13. Also sets all messages as multipart/alternative for cleaner reading in text-only clients
  14. """
  15. def __init__(self, sender='', recipients=[], subject='', from_defs=0, alternative=0, reply_to=None):
  16. from email.mime.multipart import MIMEMultipart
  17. from email import Charset
  18. Charset.add_charset('utf-8', Charset.QP, Charset.QP, 'utf-8')
  19. if type(recipients)==str:
  20. recipients = recipients.replace(';', ',')
  21. recipients = recipients.split(',')
  22. self.from_defs = from_defs
  23. self.sender = sender
  24. self.reply_to = reply_to or sender
  25. self.recipients = recipients
  26. self.subject = subject
  27. self.msg_root = MIMEMultipart('mixed')
  28. self.msg_multipart = MIMEMultipart('alternative')
  29. self.msg_root.attach(self.msg_multipart)
  30. self.cc = []
  31. def set_text(self, message):
  32. """
  33. Attach message in the text portion of multipart/alternative
  34. """
  35. from email.mime.text import MIMEText
  36. msg = unicode(message, 'utf-8')
  37. part = MIMEText(msg.encode('utf-8'), 'plain', 'UTF-8')
  38. self.msg_multipart.attach(part)
  39. def set_html(self, message):
  40. """
  41. Attach message in the html portion of multipart/alternative
  42. """
  43. from email.mime.text import MIMEText
  44. part = MIMEText(message, 'html')
  45. self.msg_multipart.attach(part)
  46. def set_message(self, message, mime_type='text/html', as_attachment=0, filename='attachment.html'):
  47. """
  48. Append the message with MIME content to the root node (as attachment)
  49. """
  50. from email.mime.text import MIMEText
  51. maintype, subtype = mime_type.split('/')
  52. part = MIMEText(message, _subtype = subtype)
  53. if as_attachment:
  54. part.add_header('Content-Disposition', 'attachment', filename=filename)
  55. self.msg_root.attach(part)
  56. def attach_file(self, n):
  57. """
  58. attach a file from the `FileData` table
  59. """
  60. from webnotes.utils.file_manager import get_file
  61. res = get_file(n)
  62. if not res:
  63. return
  64. self.add_attachment(res[0], res[1])
  65. def add_attachment(self, fname, fcontent, content_type=None):
  66. from email.mime.audio import MIMEAudio
  67. from email.mime.base import MIMEBase
  68. from email.mime.image import MIMEImage
  69. from email.mime.text import MIMEText
  70. import mimetypes
  71. if not content_type:
  72. content_type, encoding = mimetypes.guess_type(fname)
  73. if content_type is None:
  74. # No guess could be made, or the file is encoded (compressed), so
  75. # use a generic bag-of-bits type.
  76. content_type = 'application/octet-stream'
  77. maintype, subtype = content_type.split('/', 1)
  78. if maintype == 'text':
  79. # Note: we should handle calculating the charset
  80. part = MIMEText(fcontent, _subtype=subtype)
  81. elif maintype == 'image':
  82. part = MIMEImage(fcontent, _subtype=subtype)
  83. elif maintype == 'audio':
  84. part = MIMEAudio(fcontent, _subtype=subtype)
  85. else:
  86. part = MIMEBase(maintype, subtype)
  87. part.set_payload(fcontent)
  88. # Encode the payload using Base64
  89. from email import encoders
  90. encoders.encode_base64(part)
  91. # Set the filename parameter
  92. if fname:
  93. part.add_header('Content-Disposition', 'attachment', filename=fname)
  94. self.msg_root.attach(part)
  95. def validate(self):
  96. """
  97. validate the email ids
  98. """
  99. if not self.sender:
  100. self.sender = webnotes.conn.get_value('Control Panel',None,'auto_email_id')
  101. from webnotes.utils import validate_email_add
  102. # validate ids
  103. if self.sender and (not validate_email_add(self.sender)):
  104. webnotes.msgprint("%s is not a valid email id" % self.sender, raise_exception = 1)
  105. if self.reply_to and (not validate_email_add(self.reply_to)):
  106. webnotes.msgprint("%s is not a valid email id" % self.reply_to, raise_exception = 1)
  107. for e in self.recipients:
  108. if not validate_email_add(e):
  109. webnotes.msgprint("%s is not a valid email id" % e, raise_exception = 1)
  110. def setup(self):
  111. """
  112. setup the SMTP (outgoing) server from `Control Panel` or defs.py
  113. """
  114. if self.from_defs:
  115. self.server = getattr(webnotes.defs,'mail_server','')
  116. self.login = getattr(webnotes.defs,'mail_login','')
  117. self.port = getattr(webnotes.defs,'mail_port',None)
  118. self.password = getattr(webnotes.defs,'mail_password','')
  119. self.use_ssl = getattr(webnotes.defs,'use_ssl',0)
  120. else:
  121. import webnotes.model.doc
  122. from webnotes.utils import cint
  123. # get defaults from control panel
  124. cp = webnotes.model.doc.Document('Control Panel','Control Panel')
  125. self.server = cp.outgoing_mail_server or getattr(webnotes.defs,'mail_server','')
  126. self.login = cp.mail_login or getattr(webnotes.defs,'mail_login','')
  127. self.port = cp.mail_port or getattr(webnotes.defs,'mail_port',None)
  128. self.password = cp.mail_password or getattr(webnotes.defs,'mail_password','')
  129. self.use_ssl = cint(cp.use_ssl)
  130. def make_msg(self):
  131. self.msg_root['Subject'] = self.subject
  132. self.msg_root['From'] = self.sender
  133. self.msg_root['To'] = ', '.join([r.strip() for r in self.recipients])
  134. if self.reply_to and self.reply_to != self.sender:
  135. self.msg_root['Reply-To'] = self.reply_to
  136. if self.cc:
  137. self.msg_root['CC'] = ', '.join([r.strip() for r in self.cc])
  138. def add_to_queue(self):
  139. # write to a file called "email_queue" or as specified in email
  140. q = EmailQueue()
  141. q.push({
  142. 'server': self.server,
  143. 'port': self.port,
  144. 'use_ssl': self.use_ssl,
  145. 'login': self.login,
  146. 'password': self.password,
  147. 'sender': self.sender,
  148. 'recipients': self.recipients,
  149. 'msg': self.msg_root.as_string()
  150. })
  151. q.close()
  152. def send(self, send_now = 0):
  153. """
  154. send the message
  155. """
  156. from webnotes.utils import cint
  157. self.setup()
  158. self.validate()
  159. self.make_msg()
  160. if (not send_now) and getattr(webnotes.defs, 'batch_emails', 0):
  161. self.add_to_queue()
  162. return
  163. import smtplib
  164. sess = smtplib.SMTP(self.server, self.port or None)
  165. if self.use_ssl:
  166. sess.ehlo()
  167. sess.starttls()
  168. sess.ehlo()
  169. ret = sess.login(self.login, self.password)
  170. # check if logged correctly
  171. if ret[0]!=235:
  172. msgprint(ret[1])
  173. raise Exception
  174. sess.sendmail(self.sender, self.recipients, self.msg_root.as_string())
  175. try:
  176. sess.quit()
  177. except:
  178. pass
  179. # ===========================================
  180. # Email Queue
  181. # Maintains a list of emails in a file
  182. # Flushes them when called from cron
  183. # Defs settings:
  184. # email_queue: (filename) [default: email_queue.py]
  185. #
  186. # From the scheduler, call: flush(qty)
  187. # ===========================================
  188. class EmailQueue:
  189. def __init__(self):
  190. self.server = self.login = self.sess = None
  191. self.filename = getattr(webnotes.defs, 'email_queue', 'email_queue.py')
  192. try:
  193. f = open(self.filename, 'r')
  194. self.queue = eval(f.read() or '[]')
  195. f.close()
  196. except IOError, e:
  197. if e.args[0]==2:
  198. self.queue = []
  199. else:
  200. raise e
  201. def push(self, email):
  202. self.queue.append(email)
  203. def close(self):
  204. f = open(self.filename, 'w')
  205. f.write(str(self.queue))
  206. f.close()
  207. def get_smtp_session(self, e):
  208. if self.server==e['server'] and self.login==e['login'] and self.sess:
  209. return self.sess
  210. webnotes.msgprint('getting server')
  211. import smtplib
  212. sess = smtplib.SMTP(e['server'], e['port'] or None)
  213. if self.use_ssl:
  214. sess.ehlo()
  215. sess.starttls()
  216. sess.ehlo()
  217. ret = sess.login(e['login'], e['password'])
  218. # check if logged correctly
  219. if ret[0]!=235:
  220. webnotes.msgprint(ret[1])
  221. raise Exception
  222. self.sess = sess
  223. self.server, self.login = e['server'], e['login']
  224. return sess
  225. def flush(self, qty = 100):
  226. f = open(self.filename, 'r')
  227. self.queue = eval(f.read() or '[]')
  228. if len(self.queue) < 100:
  229. qty = len(self.queue)
  230. for i in range(qty):
  231. e = self.queue[i]
  232. sess = self.get_smtp_session(e)
  233. sess.sendmail(e['sender'], e['recipients'], e['msg'])
  234. self.queue = self.queue[:(len(self.queue) - qty)]
  235. self.close()