def get_emails_header(self, attr): c = Charset(self.charset) c.header_encoding = QP c.body_encoding = 0 r = Charset(self.charset) r.header_encoding = 0 r.body_encoding = 0 h = Header() self.normalize_email_list(attr) emails = self.__getattribute__(attr) for i in range(len(emails)): name, email = emails[i] if i: h.append(',', r) if name: name = name.encode(self.charset, 'xmlcharrefreplace') h.append(name, r if is7bit(name) else c) h.append('<%s>' % email, r) else: h.append(email, r) return h
def create_charset(mime_encoding): """Create an appropriate email charset for the given encoding. Valid options are 'base64' for Base64 encoding, 'qp' for Quoted-Printable, and 'none' for no encoding, in which case mails will be sent as 7bit if the content is all ASCII, or 8bit otherwise. """ charset = Charset() charset.input_charset = 'utf-8' charset.output_charset = 'utf-8' charset.input_codec = 'utf-8' charset.output_codec = 'utf-8' pref = mime_encoding.lower() if pref == 'base64': charset.header_encoding = BASE64 charset.body_encoding = BASE64 elif pref in ('qp', 'quoted-printable'): charset.header_encoding = QP charset.body_encoding = QP elif pref == 'none': charset.header_encoding = SHORTEST charset.body_encoding = None else: raise TracError(_("Invalid email encoding setting: %(mime_encoding)s", mime_encoding=mime_encoding)) return charset
def create_charset(mime_encoding): """Create an appropriate email charset for the given encoding. Valid options are 'base64' for Base64 encoding, 'qp' for Quoted-Printable, and 'none' for no encoding, in which case mails will be sent as 7bit if the content is all ASCII, or 8bit otherwise. """ charset = Charset() charset.input_charset = 'utf-8' charset.output_charset = 'utf-8' charset.input_codec = 'utf-8' charset.output_codec = 'utf-8' pref = mime_encoding.lower() if pref == 'base64': charset.header_encoding = BASE64 charset.body_encoding = BASE64 elif pref in ('qp', 'quoted-printable'): charset.header_encoding = QP charset.body_encoding = QP elif pref == 'none': charset.header_encoding = SHORTEST charset.body_encoding = None else: raise TracError( _("Invalid email encoding setting: %(mime_encoding)s", mime_encoding=mime_encoding)) return charset
def as_message(self, escape_addresses=True): # http://wordeology.com/computer/how-to-send-good-unicode-email-with-python.html # http://stackoverflow.com/questions/31714221/how-to-send-an-email-with-quoted # http://stackoverflow.com/questions/9403265/how-do-i-use-python/9509718#9509718 charset = Charset('utf-8') charset.header_encoding = QP charset.body_encoding = QP msg = MIMEMultipart() # Headers unixfrom = "From %s %s" % ( self.sender.address, self.archived_date.strftime("%c")) header_from = self.sender.address if self.sender.name and self.sender.name != self.sender.address: header_from = "%s <%s>" % (self.sender.name, header_from) header_to = self.mailinglist.name if escape_addresses: header_from = header_from.replace("@", " at ") header_to = header_to.replace("@", " at ") unixfrom = unixfrom.replace("@", " at ") msg.set_unixfrom(unixfrom) headers = ( ("From", header_from), ("To", header_to), ("Subject", self.subject), ) for header_name, header_value in headers: if not header_value: continue try: msg[header_name] = header_value.encode('ascii') except UnicodeEncodeError: msg[header_name] = Header( header_value.encode('utf-8'), charset).encode() tz = get_fixed_timezone(self.timezone) header_date = self.date.astimezone(tz).replace(microsecond=0) # Date format: http://tools.ietf.org/html/rfc5322#section-3.3 msg["Date"] = header_date.strftime("%a, %d %b %Y %H:%M:%S %z") msg["Message-ID"] = "<%s>" % self.message_id if self.in_reply_to: msg["In-Reply-To"] = self.in_reply_to # Body content = self.ADDRESS_REPLACE_RE.sub(r"\1(a)\2", self.content) # Don't use MIMEText, it won't encode to quoted-printable textpart = MIMENonMultipart("text", "plain", charset='utf-8') textpart.set_payload(content, charset=charset) msg.attach(textpart) # Attachments for attachment in self.attachments.order_by("counter"): mimetype = attachment.content_type.split('/', 1) part = MIMEBase(mimetype[0], mimetype[1]) part.set_payload(attachment.content) encode_base64(part) part.add_header('Content-Disposition', 'attachment', filename=attachment.name) msg.attach(part) return msg
def format(self, events, encoding="utf-8"): parts = list() data = templates.Template.format(self, parts, events) parsed = message_from_string(data.encode(encoding)) charset = Charset(encoding) charset.header_encoding = QP msg = MIMEMultipart() msg.set_charset(charset) for key, value in msg.items(): del parsed[key] for key, value in parsed.items(): msg[key] = value for encoded in ["Subject", "Comment"]: if encoded not in msg: continue value = charset.header_encode(msg[encoded]) del msg[encoded] msg[encoded] = value del msg['Content-Transfer-Encoding'] msg['Content-Transfer-Encoding'] = '7bit' msg.attach(MIMEText(parsed.get_payload(), "plain", encoding)) for part in parts: msg.attach(part) return msg
def format(self, events, encoding="utf-8"): parts = list() data = templates.Template.format(self, parts, events) parsed = message_from_string(data.encode(encoding)) charset = Charset(encoding) charset.header_encoding = QP msg = MIMEMultipart() msg.set_charset(charset) for key, value in msg.items(): del parsed[key] for key, value in parsed.items(): msg[key] = value for encoded in ["Subject", "Comment"]: if encoded not in msg: continue value = charset.header_encode(msg[encoded]) del msg[encoded] msg[encoded] = value del msg["Content-Transfer-Encoding"] msg["Content-Transfer-Encoding"] = "7bit" msg.attach(MIMEText(parsed.get_payload(), "plain", encoding)) for part in parts: msg.attach(part) return msg
def format(self, events, encoding="utf-8"): from email import message_from_string from email.mime.multipart import MIMEMultipart from email.mime.text import MIMEText from email.charset import Charset, QP from email.utils import formatdate, make_msgid parts = list() data = templates.Template.format(self, parts, events) parsed = message_from_string(data.encode(encoding)) charset = Charset(encoding) charset.header_encoding = QP msg = MIMEMultipart() msg.set_charset(charset) for key, value in msg.items(): del parsed[key] for key, value in parsed.items(): msg[key] = value for encoded in ["Subject", "Comment"]: if encoded not in msg: continue value = charset.header_encode(msg[encoded]) del msg[encoded] msg[encoded] = value del msg['Content-Transfer-Encoding'] msg['Content-Transfer-Encoding'] = '7bit' msg.attach(MIMEText(parsed.get_payload(), "plain", encoding)) for part in parts: msg.attach(part) return msg
def sendmail(server, from_, to, message): if not isinstance(to, list): to = [to] charset = Charset('UTF-8') charset.header_encoding = QP charset.body_encoding = QP msg = email.message_from_string(message) msg.set_charset(charset) server.sendmail(from_, to, msg.as_string())
def write_patch_file(filename, commit_info, diff): """Write patch file""" if not diff: gbp.log.debug("I won't generate empty diff %s" % filename) return None try: with open(filename, 'wb') as patch: msg = Message() charset = Charset('utf-8') charset.body_encoding = None charset.header_encoding = QP # Write headers name = commit_info['author']['name'] email = commit_info['author']['email'] # Git compat: put name in quotes if special characters found if re.search(r'[,.@()\[\]\\\:;]', name): name = '"%s"' % name from_header = Header(header_name='from') try: from_header.append(name, 'us-ascii') except UnicodeDecodeError: from_header.append(name, charset) from_header.append('<%s>' % email) msg['From'] = from_header date = commit_info['author'].datetime datestr = date.strftime('%a, %-d %b %Y %H:%M:%S %z') msg['Date'] = Header(datestr, 'us-ascii', 'date') subject_header = Header(header_name='subject') try: subject_header.append(commit_info['subject'], 'us-ascii') except UnicodeDecodeError: subject_header.append(commit_info['subject'], charset) msg['Subject'] = subject_header # Write message body if commit_info['body']: # Strip extra linefeeds body = commit_info['body'].rstrip() + '\n' try: msg.set_payload(body.encode('us-ascii')) except (UnicodeEncodeError): msg.set_payload(body, charset) policy = Compat32(max_line_length=77) patch.write(msg.as_bytes(unixfrom=False, policy=policy)) # Write diff patch.write(b'---\n') patch.write(diff) except IOError as err: raise GbpError('Unable to create patch file: %s' % err) return filename
def write_patch_file(filename, commit_info, diff): """Write patch file""" if not diff: gbp.log.debug("I won't generate empty diff %s" % filename) return None try: with open(filename, 'wb') as patch: msg = Message() charset = Charset('utf-8') charset.body_encoding = None charset.header_encoding = QP # Write headers name = commit_info['author']['name'] email = commit_info['author']['email'] # Git compat: put name in quotes if special characters found if re.search("[,.@()\[\]\\\:;]", name): name = '"%s"' % name from_header = Header(header_name='from') try: from_header.append(name, 'us-ascii') except UnicodeDecodeError: from_header.append(name, charset) from_header.append('<%s>' % email) msg['From'] = from_header date = commit_info['author'].datetime datestr = date.strftime('%a, %-d %b %Y %H:%M:%S %z') msg['Date'] = Header(datestr, 'us-ascii', 'date') subject_header = Header(header_name='subject') try: subject_header.append(commit_info['subject'], 'us-ascii') except UnicodeDecodeError: subject_header.append(commit_info['subject'], charset) msg['Subject'] = subject_header # Write message body if commit_info['body']: # Strip extra linefeeds body = commit_info['body'].rstrip() + '\n' try: msg.set_payload(body.encode('us-ascii')) except (UnicodeEncodeError): msg.set_payload(body, charset) patch.write( msg.as_string(unixfrom=False, maxheaderlen=77).encode('utf-8')) # Write diff patch.write(b'---\n') patch.write(diff) except IOError as err: raise GbpError('Unable to create patch file: %s' % err) return filename
def _make_charset(self): charset = Charset() charset.input_charset = 'utf-8' pref = self.mime_encoding.lower() if pref == 'base64': charset.header_encoding = BASE64 charset.body_encoding = BASE64 charset.output_charset = 'utf-8' charset.input_codec = 'utf-8' charset.output_codec = 'utf-8' elif pref in ['qp', 'quoted-printable']: charset.header_encoding = QP charset.body_encoding = QP charset.output_charset = 'utf-8' charset.input_codec = 'utf-8' charset.output_codec = 'utf-8' elif pref == 'none': charset.header_encoding = None charset.body_encoding = None charset.input_codec = None charset.output_charset = 'ascii' else: raise TracError(_('Invalid email encoding setting: %s' % pref)) return charset
def _mail(self, fromaddr, to, subject, payload): # prepare charset = Charset("utf-8") charset.header_encoding = QP charset.body_encoding = QP # create method and set headers msg = Message() msg.set_payload(payload.encode("utf8")) msg.set_charset(charset) msg['Subject'] = Header(subject, "utf8") msg['From'] = fromaddr msg['To'] = to self.server.connect() self.server.sendmail(fromaddr, [to], msg.as_string()) self.server.quit()
def write_patch_file(filename, commit_info, diff): """Write patch file""" if not diff: gbp.log.debug("I won't generate empty diff %s" % filename) return None try: with open(filename, "w") as patch: msg = Message() charset = Charset("utf-8") charset.body_encoding = None charset.header_encoding = QP # Write headers name = commit_info["author"]["name"] email = commit_info["author"]["email"] # Git compat: put name in quotes if special characters found if re.search("[,.@()\[\]\\\:;]", name): name = '"%s"' % name from_header = Header(unicode(name, "utf-8"), charset, 77, "from") from_header.append(unicode("<%s>" % email)) msg["From"] = from_header date = commit_info["author"].datetime datestr = date.strftime("%a, %-d %b %Y %H:%M:%S %z") msg["Date"] = Header(unicode(datestr, "utf-8"), charset, 77, "date") msg["Subject"] = Header(unicode(commit_info["subject"], "utf-8"), charset, 77, "subject") # Write message body if commit_info["body"]: # Strip extra linefeeds body = commit_info["body"].rstrip() + "\n" try: msg.set_payload(body.encode("ascii")) except UnicodeDecodeError: msg.set_payload(body, charset) patch.write(msg.as_string(unixfrom=False)) # Write diff patch.write("---\n") patch.write(diff) except IOError as err: raise GbpError("Unable to create patch file: %s" % err) return filename
def sendmail(subject, text, to=None, cc=None, bcc=None, mail_from=None, html=None): """ Create and send a text/plain message Return a tuple of success or error indicator and message. :param subject: subject of email :type subject: unicode :param text: email body text :type text: unicode :param to: recipients :type to: list :param cc: recipients (CC) :type cc: list :param bcc: recipients (BCC) :type bcc: list :param mail_from: override default mail_from :type mail_from: unicode :param html: html email body text :type html: unicode :rtype: tuple :returns: (is_ok, Description of error or OK message) """ import smtplib import socket from email.message import Message from email.mime.multipart import MIMEMultipart from email.mime.text import MIMEText from email.charset import Charset, QP from email.utils import formatdate, make_msgid cfg = app.cfg if not cfg.mail_enabled: return ( 0, _("Contact administrator: cannot send password recovery e-mail " "because mail configuration is incomplete.")) mail_from = mail_from or cfg.mail_from logging.debug("send mail, from: {0!r}, subj: {1!r}".format( mail_from, subject)) logging.debug("send mail, to: {0!r}".format(to)) if not to and not cc and not bcc: return 1, _("No recipients, nothing to do") subject = subject.encode(CHARSET) # Create a text/plain body using CRLF (see RFC2822) text = text.replace(u'\n', u'\r\n') text = text.encode(CHARSET) # Create a message using CHARSET and quoted printable # encoding, which should be supported better by mail clients. # TODO: check if its really works better for major mail clients text_msg = Message() charset = Charset(CHARSET) charset.header_encoding = QP charset.body_encoding = QP text_msg.set_charset(charset) # work around a bug in python 2.4.3 and above: text_msg.set_payload('=') if text_msg.as_string().endswith('='): text = charset.body_encode(text) text_msg.set_payload(text) if html: msg = MIMEMultipart('alternative') msg.attach(text_msg) html = html.encode(CHARSET) html_msg = MIMEText(html, 'html') html_msg.set_charset(charset) msg.attach(html_msg) else: msg = text_msg address = encodeAddress(mail_from, charset) msg['From'] = address if to: msg['To'] = ','.join(to) if cc: msg['CC'] = ','.join(cc) msg['Date'] = formatdate() msg['Message-ID'] = make_msgid() msg['Subject'] = Header(subject, charset) # See RFC 3834 section 5: msg['Auto-Submitted'] = 'auto-generated' if cfg.mail_sendmail: if bcc: # Set the BCC. This will be stripped later by sendmail. msg['BCC'] = ','.join(bcc) # Set Return-Path so that it isn't set (generally incorrectly) for us. msg['Return-Path'] = address # Send the message if not cfg.mail_sendmail: try: logging.debug( "trying to send mail (smtp) via smtp server '{0}'".format( cfg.mail_smarthost)) host, port = (cfg.mail_smarthost + ':25').split(':')[:2] server = smtplib.SMTP(host, int(port)) try: # server.set_debuglevel(1) if cfg.mail_username is not None and cfg.mail_password is not None: try: # try to do TLS server.ehlo() if server.has_extn('starttls'): server.starttls() server.ehlo() logging.debug( "tls connection to smtp server established") except Exception: logging.debug( "could not establish a tls connection to smtp server, continuing without tls" ) logging.debug( "trying to log in to smtp server using account '{0}'". format(cfg.mail_username)) server.login(cfg.mail_username, cfg.mail_password) server.sendmail(mail_from, (to or []) + (cc or []) + (bcc or []), msg.as_string()) finally: try: server.quit() except AttributeError: # in case the connection failed, SMTP has no "sock" attribute pass except smtplib.SMTPException as e: logging.exception("smtp mail failed with an exception.") return 0, str(e) except (os.error, socket.error) as e: logging.exception("smtp mail failed with an exception.") return ( 0, _("Connection to mailserver '%(server)s' failed: %(reason)s", server=cfg.mail_smarthost, reason=str(e))) else: try: logging.debug("trying to send mail (sendmail)") sendmailp = os.popen(cfg.mail_sendmail, "w") # msg contains everything we need, so this is a simple write sendmailp.write(msg.as_string()) sendmail_status = sendmailp.close() if sendmail_status: logging.error("sendmail failed with status: {0!s}".format( sendmail_status)) return 0, str(sendmail_status) except Exception: logging.exception("sendmail failed with an exception.") return 0, _("Mail not sent") logging.debug("Mail sent successfully") return 1, _("Mail sent successfully")
def sendmail(request, to, subject, text, mail_from=None): """ Create and send a text/plain message Return a tuple of success or error indicator and message. @param request: the request object @param to: recipients (list) @param subject: subject of email (unicode) @param text: email body text (unicode) @param mail_from: override default mail_from @type mail_from: unicode @rtype: tuple @return: (is_ok, Description of error or OK message) """ import smtplib, socket from email.message import Message from email.charset import Charset, QP from email.utils import formatdate, make_msgid _ = request.getText cfg = request.cfg mail_from = mail_from or cfg.mail_from logging.debug("send mail, from: %r, subj: %r" % (mail_from, subject)) logging.debug("send mail, to: %r" % (to, )) if not to: return (1, _("No recipients, nothing to do")) subject = subject.encode(config.charset) # Create a text/plain body using CRLF (see RFC2822) text = text.replace(u'\n', u'\r\n') text = text.encode(config.charset) # Create a message using config.charset and quoted printable # encoding, which should be supported better by mail clients. # TODO: check if its really works better for major mail clients msg = Message() charset = Charset(config.charset) charset.header_encoding = QP charset.body_encoding = QP msg.set_charset(charset) # work around a bug in python 2.4.3 and above: msg.set_payload('=') if msg.as_string().endswith('='): text = charset.body_encode(text) msg.set_payload(text) # Create message headers # Don't expose emails addreses of the other subscribers, instead we # use the same mail_from, e.g. u"Jürgen Wiki <*****@*****.**>" address = encodeAddress(mail_from, charset) msg['From'] = address msg['To'] = address msg['Date'] = formatdate() msg['Message-ID'] = make_msgid() msg['Subject'] = Header(subject, charset) # See RFC 3834 section 5: msg['Auto-Submitted'] = 'auto-generated' if cfg.mail_sendmail: # Set the BCC. This will be stripped later by sendmail. msg['BCC'] = ','.join(to) # Set Return-Path so that it isn't set (generally incorrectly) for us. msg['Return-Path'] = address # Send the message if not cfg.mail_sendmail: try: logging.debug("trying to send mail (smtp) via smtp server '%s'" % cfg.mail_smarthost) host, port = (cfg.mail_smarthost + ':25').split(':')[:2] server = smtplib.SMTP(host, int(port)) try: #server.set_debuglevel(1) if cfg.mail_login: user, pwd = cfg.mail_login.split() try: # try to do tls server.ehlo() if server.has_extn('starttls'): server.starttls() server.ehlo() logging.debug( "tls connection to smtp server established") except: logging.debug( "could not establish a tls connection to smtp server, continuing without tls" ) logging.debug( "trying to log in to smtp server using account '%s'" % user) server.login(user, pwd) server.sendmail(mail_from, to, msg.as_string()) finally: try: server.quit() except AttributeError: # in case the connection failed, SMTP has no "sock" attribute pass except UnicodeError, e: logging.exception("unicode error [%r -> %r]" % ( mail_from, to, )) return (0, str(e)) except smtplib.SMTPException, e: logging.exception("smtp mail failed with an exception.") return (0, str(e))
def as_message(self, escape_addresses=True): # http://wordeology.com/computer/how-to-send-good-unicode-email-with-python.html # http://stackoverflow.com/questions/31714221/how-to-send-an-email-with-quoted # http://stackoverflow.com/questions/9403265/how-do-i-use-python/9509718#9509718 charset = Charset('utf-8') charset.header_encoding = QP charset.body_encoding = QP msg = MIMEMultipart() # Headers unixfrom = "From %s %s" % (self.sender.address, self.archived_date.strftime("%c")) header_from = self.sender.address if self.sender_name and self.sender_name != self.sender.address: header_from = "%s <%s>" % (self.sender_name, header_from) header_to = self.mailinglist.name if escape_addresses: header_from = header_from.replace("@", " at ") header_to = header_to.replace("@", " at ") unixfrom = unixfrom.replace("@", " at ") msg.set_unixfrom(unixfrom) headers = ( ("From", header_from), ("To", header_to), ("Subject", self.subject), ) for header_name, header_value in headers: if not header_value: continue try: msg[header_name] = header_value.encode('ascii') except UnicodeEncodeError: msg[header_name] = Header(header_value.encode('utf-8'), charset).encode() tz = get_fixed_timezone(self.timezone) header_date = self.date.astimezone(tz).replace(microsecond=0) # Date format: http://tools.ietf.org/html/rfc5322#section-3.3 msg["Date"] = header_date.strftime("%a, %d %b %Y %H:%M:%S %z") msg["Message-ID"] = "<%s>" % self.message_id if self.in_reply_to: msg["In-Reply-To"] = self.in_reply_to # Body content = self.ADDRESS_REPLACE_RE.sub(r"\1(a)\2", self.content) # Don't use MIMEText, it won't encode to quoted-printable textpart = MIMENonMultipart("text", "plain", charset='utf-8') textpart.set_payload(content, charset=charset) msg.attach(textpart) # Attachments for attachment in self.attachments.order_by("counter"): mimetype = attachment.content_type.split('/', 1) part = MIMEBase(mimetype[0], mimetype[1]) part.set_payload(attachment.content) encode_base64(part) part.add_header('Content-Disposition', 'attachment', filename=attachment.name) msg.attach(part) return msg
def send(self, emails): if isinstance(emails, Email): emails = [emails] if len([e for e in emails if e.__class__ != Email]): raise TypeError('emails must be Email or list of Email instances') smtpclass = SMTP_SSL if self.ssl else SMTP if self.server == 'localhost': smtp = smtpclass(self.server) else: smtp = smtpclass(self.server, self.port) if self.tls: smtp.starttls() if self.login and self.password: smtp.login(self.login, self.password) for email in emails: c = Charset(email.charset) c.header_encoding = QP c.body_encoding = 0 r = Charset(email.charset) r.header_encoding = 0 r.body_encoding = 0 email.normalize_email_list('rcpt') email.normalize_email_list('cc') email.normalize_email_list('bcc') mime1, mime2 = email.mimetype.split('/') mainpart = MIMEBase(mime1, mime2) if not email.force_7bit: mainpart.set_param('charset', email.charset) if len(email.attachments): message = MIMEMultipart('mixed') message.attach(mainpart) del mainpart['mime-version'] else: message = mainpart message['Date'] = datetime.datetime.now().strftime( '%a, %d %b %Y %H:%M:%S') + (" +%04d" % (time.timezone/-36,)) h = Header(maxlinelen=1000) # FIXME: what is correct max length? fromname = self.fromname.encode(email.charset, 'xmlcharrefreplace') h.append(fromname, r if is7bit(fromname) else c) h.append('<%s>' % self.email, r) message['From'] = h message['To'] = email.get_emails_header('rcpt') if len(email.cc): message['CC'] = email.get_emails_header('cc') if len(email.bcc): message['BCC'] = email.get_emails_header('bcc') subject = email.subject.encode(email.charset, 'xmlcharrefreplace') message['Subject'] = Header(subject, r if is7bit(subject) else c) if email.reply_to: message['Reply-To'] = email.get_emails_header('reply_to') if email.force_7bit: body = email.body.encode('ascii', 'xmlcharrefreplace') else: body = email.body.encode(email.charset, 'xmlcharrefreplace') mainpart.set_payload(body) if is7bit(body): mainpart['Content-Transfer-Encoding'] = '7bit' else: encode_quopri(mainpart) for attachment in email.attachments: if attachment.__class__ != Attachment: raise TypeError("invalid attachment") mimetype = attachment.mimetype if not mimetype: mimetype, encoding = guess_type(attachment.filename) if not mimetype: mimetype = 'application/octet-stream' mime1, mime2 = mimetype.split('/') part = MIMEBase(mime1, mime2) # using newer rfc2231 (not supported by Outlook): # part.set_param('name', attachment.filename.encode('utf-8'), charset = 'utf-8') # hack: using deprecated rfc2047 - supported by Outlook: part.set_param('name', str(Header(attachment.filename))) del part['mime-version'] if attachment.id: part['Content-Disposition'] = 'inline' else: part['Content-Disposition'] = 'attachment' # using newer rfc2231 (not supported by Outlook): # part.set_param('filename', # attachment.filename.encode('utf-8'), # 'Content-Disposition', # charset = 'utf-8') # hack: using deprecated rfc2047 - supported by Outlook: part.set_param('filename', str(Header(attachment.filename)), 'Content-Disposition') if attachment.id: part['Content-ID'] = '<%s>' % attachment.id part.set_payload(attachment.content) encode_base64(part) # Do this AFTER encode_base64(part), or Content-Transfer-Encoding header will duplicate, # or even happen 2 times with different values. if attachment.charset: part.set_charset(attachment.charset) message.attach(part) smtp.sendmail(self.email, [rcpt[1] for rcpt in email.rcpt] + [cc[1] for cc in email.cc] + [bcc[1] for bcc in email.bcc], message.as_string()) smtp.quit()
def sendmail(subject, text, to=None, cc=None, bcc=None, mail_from=None, html=None): """ Create and send a text/plain message Return a tuple of success or error indicator and message. :param subject: subject of email :type subject: unicode :param text: email body text :type text: unicode :param to: recipients :type to: list :param cc: recipients (CC) :type cc: list :param bcc: recipients (BCC) :type bcc: list :param mail_from: override default mail_from :type mail_from: unicode :param html: html email body text :type html: unicode :rtype: tuple :returns: (is_ok, Description of error or OK message) """ import smtplib import socket from email.message import Message from email.mime.multipart import MIMEMultipart from email.mime.text import MIMEText from email.charset import Charset, QP from email.utils import formatdate, make_msgid cfg = app.cfg if not cfg.mail_enabled: return (0, _("Contact administrator: cannot send password recovery e-mail " "because mail configuration is incomplete.")) mail_from = mail_from or cfg.mail_from logging.debug("send mail, from: {0!r}, subj: {1!r}".format(mail_from, subject)) logging.debug("send mail, to: {0!r}".format(to)) if not to and not cc and not bcc: return 1, _("No recipients, nothing to do") subject = subject.encode(CHARSET) # Create a text/plain body using CRLF (see RFC2822) text = text.replace(u'\n', u'\r\n') text = text.encode(CHARSET) # Create a message using CHARSET and quoted printable # encoding, which should be supported better by mail clients. # TODO: check if its really works better for major mail clients text_msg = Message() charset = Charset(CHARSET) charset.header_encoding = QP charset.body_encoding = QP text_msg.set_charset(charset) # work around a bug in python 2.4.3 and above: text_msg.set_payload('=') if text_msg.as_string().endswith('='): text = charset.body_encode(text) text_msg.set_payload(text) if html: msg = MIMEMultipart('alternative') msg.attach(text_msg) html = html.encode(CHARSET) html_msg = MIMEText(html, 'html') html_msg.set_charset(charset) msg.attach(html_msg) else: msg = text_msg address = encodeAddress(mail_from, charset) msg['From'] = address if to: msg['To'] = ','.join(to) if cc: msg['CC'] = ','.join(cc) msg['Date'] = formatdate() msg['Message-ID'] = make_msgid() msg['Subject'] = Header(subject, charset) # See RFC 3834 section 5: msg['Auto-Submitted'] = 'auto-generated' if cfg.mail_sendmail: if bcc: # Set the BCC. This will be stripped later by sendmail. msg['BCC'] = ','.join(bcc) # Set Return-Path so that it isn't set (generally incorrectly) for us. msg['Return-Path'] = address # Send the message if not cfg.mail_sendmail: try: logging.debug("trying to send mail (smtp) via smtp server '{0}'".format(cfg.mail_smarthost)) host, port = (cfg.mail_smarthost + ':25').split(':')[:2] server = smtplib.SMTP(host, int(port)) try: #server.set_debuglevel(1) if cfg.mail_username is not None and cfg.mail_password is not None: try: # try to do TLS server.ehlo() if server.has_extn('starttls'): server.starttls() server.ehlo() logging.debug("tls connection to smtp server established") except: logging.debug("could not establish a tls connection to smtp server, continuing without tls") logging.debug("trying to log in to smtp server using account '{0}'".format(cfg.mail_username)) server.login(cfg.mail_username, cfg.mail_password) server.sendmail(mail_from, (to or []) + (cc or []) + (bcc or []), msg.as_string()) finally: try: server.quit() except AttributeError: # in case the connection failed, SMTP has no "sock" attribute pass except smtplib.SMTPException as e: logging.exception("smtp mail failed with an exception.") return 0, str(e) except (os.error, socket.error) as e: logging.exception("smtp mail failed with an exception.") return (0, _("Connection to mailserver '%(server)s' failed: %(reason)s", server=cfg.mail_smarthost, reason=str(e) )) else: try: logging.debug("trying to send mail (sendmail)") sendmailp = os.popen(cfg.mail_sendmail, "w") # msg contains everything we need, so this is a simple write sendmailp.write(msg.as_string()) sendmail_status = sendmailp.close() if sendmail_status: logging.error("sendmail failed with status: {0!s}".format(sendmail_status)) return 0, str(sendmail_status) except: logging.exception("sendmail failed with an exception.") return 0, _("Mail not sent") logging.debug("Mail sent successfully") return 1, _("Mail sent successfully")
def send_email_report(self, _id, subject, sender, recipients, text_body='', html_body='', cc=None, bcc=None, attachments=None, txt_template='analytics_scheduled_report.txt', html_template='analytics_scheduled_report.html'): lock_id = 'analytics_email:{}'.format(str(_id)) if not lock(lock_id, expire=120): return try: charset = Charset('utf-8') charset.header_encoding = QP charset.body_encoding = QP msg = AnalyticsMessage(subject, sender=sender, recipients=recipients, cc=cc, bcc=bcc, body=text_body, charset=charset) reports = [] if attachments is not None: for attachment in attachments: try: uuid = str(uuid4()) if attachment.get('mimetype') == MIME_TYPES.HTML: reports.append({ 'id': uuid, 'type': 'html', 'html': attachment.get('file') }) else: msg.attach(filename=attachment.get('filename'), content_type='{}; name="{}"'.format( attachment.get('mimetype'), attachment.get('filename')), data=b64decode(attachment.get('file')), disposition='attachment', headers={ 'Content-ID': '<{}>'.format(uuid), 'X-Attachment-Id': uuid }.items()) reports.append({ 'id': uuid, 'type': 'image', 'filename': attachment.get('filename'), 'width': attachment.get('width') }) msg.body += '\n[image: {}]'.format( attachment.get('filename')) except Exception as e: logger.error('Failed to generate attachment.') logger.exception(e) msg.body = render_template(txt_template, text_body=text_body, reports=reports) msg.html = render_template(html_template, html_body=html_body.replace( '\r', '').replace('\n', '<br>'), reports=reports) return app.mail.send(msg) except Exception as e: logger.error('Failed to send report email. Error: {}'.format(str(e))) logger.exception(e) finally: unlock(lock_id, remove=True)
def send(self, emails): if isinstance(emails, Email): emails = [emails] if len([e for e in emails if e.__class__ != Email]): raise TypeError('emails must be Email or list of Email instances') smtpclass = SMTP_SSL if self.ssl else SMTP if self.server == 'localhost': smtp = smtpclass(self.server) else: smtp = smtpclass(self.server, self.port) if self.login and self.password: smtp.login(self.login, self.password) for email in emails: c = Charset(email.charset) c.header_encoding = QP c.body_encoding = 0 r = Charset(email.charset) r.header_encoding = 0 r.body_encoding = 0 mime1, mime2 = email.mimetype.split('/') mainpart = MIMEBase(mime1, mime2) if not email.force_7bit: mainpart.set_param('charset', email.charset) if len(email.attachments): message = MIMEMultipart('mixed') message.attach(mainpart) del mainpart['mime-version'] else: message = mainpart message['Date'] = datetime.datetime.now().strftime( '%a, %d %b %Y %H:%M:%S') + (" +%04d" % (time.timezone / -36, )) h = Header() fromname = self.fromname.encode(email.charset, 'xmlcharrefreplace') h.append(fromname, r if is7bit(fromname) else c) h.append('<%s>' % self.email, r) message['From'] = h message['To'] = email.get_emails_header('rcpt') if len(email.cc): message['CC'] = email.get_emails_header('cc') if len(email.bcc): message['BCC'] = email.get_emails_header('bcc') subject = email.subject.encode(email.charset, 'xmlcharrefreplace') message['Subject'] = Header(subject, r if is7bit(subject) else c) if email.force_7bit: body = email.body.encode('ascii', 'xmlcharrefreplace') else: body = email.body.encode(email.charset, 'xmlcharrefreplace') mainpart.set_payload(body) for hn, hv in email.headers.items(): if hn == 'X-Mailgun-Campaign-Tag': hn = 'X-Mailgun-Tag' message[hn] = hv if is7bit(body): mainpart['Content-Transfer-Encoding'] = '7bit' else: encode_quopri(mainpart) for attachment in email.attachments: if attachment.__class__ != Attachment: raise TypeError("invalid attachment") mimetype = attachment.mimetype if not mimetype: mimetype, encoding = guess_type(attachment.filename) if not mimetype: mimetype = 'application/octet-stream' mime1, mime2 = mimetype.split('/') part = MIMEBase(mime1, mime2) # using newer rfc2231 (not supported by Outlook): # part.set_param('name', attachment.filename.encode('utf-8'), charset = 'utf-8') # hack: using deprecated rfc2047 - supported by Outlook: part.set_param('name', str(Header(attachment.filename))) del part['mime-version'] if attachment.id: part['Content-Disposition'] = 'inline' else: part['Content-Disposition'] = 'attachment' # using newer rfc2231 (not supported by Outlook): # part.set_param('filename', # attachment.filename.encode('utf-8'), # 'Content-Disposition', # charset = 'utf-8') # hack: using deprecated rfc2047 - supported by Outlook: part.set_param('filename', str(Header(attachment.filename)), 'Content-Disposition') if attachment.id: part['Content-ID'] = '<%s>' % attachment.id part.set_payload(attachment.content) encode_base64(part) message.attach(part) emails = email.rcpt + email.cc + email.bcc smtp.sendmail(self.email, [email for _, email in emails], message.as_string()) smtp.quit()