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.
 
 
 
 
 
 

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