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.
 
 
 
 
 
 

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