Пример #1
0
 def send(self, from_addr, recipients, message):
     # Ensure the message complies with RFC2822: use CRLF line endings
     message = CRLF.join(self.crlf.split(message))
     
     self.log.info("Sending notification through SMTP at %s:%d to %s"
                   % (self.smtp_server, self.smtp_port, recipients))
     server = smtplib.SMTP(self.smtp_server, self.smtp_port)
     # server.set_debuglevel(True)
     if self.use_tls:
         server.ehlo()
         if not server.esmtp_features.has_key('starttls'):
             raise TracError(_("TLS enabled but server does not support " \
                               "TLS"))
         server.starttls()
         server.ehlo()
     if self.smtp_user:
         server.login(self.smtp_user.encode('utf-8'),
                      self.smtp_password.encode('utf-8'))
     start = time.time()
     server.sendmail(from_addr, recipients, message)
     t = time.time() - start
     if t > 5:
         self.log.warning('Slow mail submission (%.2f s), '
                          'check your mail setup' % t)
     if self.use_tls:
         # avoid false failure detection when the server closes
         # the SMTP connection with TLS enabled
         import socket
         try:
             server.quit()
         except socket.sslerror:
             pass
     else:
         server.quit()
Пример #2
0
    def send(self, from_addr, recipients, message):
        # Ensure the message complies with RFC2822: use CRLF line endings
        message = CRLF.join(self.crlf.split(message))

        self.log.info("Sending notification through SMTP at %s:%d to %s" %
                      (self.smtp_server, self.smtp_port, recipients))
        server = smtplib.SMTP(self.smtp_server, self.smtp_port)
        # server.set_debuglevel(True)
        if self.use_tls:
            server.ehlo()
            if not server.esmtp_features.has_key('starttls'):
                raise TracError(_("TLS enabled but server does not support " \
                                  "TLS"))
            server.starttls()
            server.ehlo()
        if self.smtp_user:
            server.login(self.smtp_user.encode('utf-8'),
                         self.smtp_password.encode('utf-8'))
        start = time.time()
        server.sendmail(from_addr, recipients, message)
        t = time.time() - start
        if t > 5:
            self.log.warning('Slow mail submission (%.2f s), '
                             'check your mail setup' % t)
        if self.use_tls:
            # avoid false failure detection when the server closes
            # the SMTP connection with TLS enabled
            import socket
            try:
                server.quit()
            except socket.sslerror:
                pass
        else:
            server.quit()
Пример #3
0
 def format_props(self):
     tkt = self.ticket
     fields = [f for f in tkt.fields if f['name'] not in ('summary', 'cc')]
     width = [0, 0, 0, 0]
     i = 0
     for f in [f['name'] for f in fields if f['type'] != 'textarea']:
         if not tkt.values.has_key(f):
             continue
         fval = tkt[f] or ''
         if fval.find('\n') != -1:
             continue
         idx = 2 * (i % 2)
         if len(f) > width[idx]:
             width[idx] = len(f)
         if len(fval) > width[idx + 1]:
             width[idx + 1] = len(fval)
         i += 1
     format = ('%%%is:  %%-%is  |  ' % (width[0], width[1]),
               ' %%%is:  %%-%is%s' % (width[2], width[3], CRLF))
     l = (width[0] + width[1] + 5)
     sep = l * '-' + '+' + (self.COLS - l) * '-'
     txt = sep + CRLF
     big = []
     i = 0
     for f in [f for f in fields if f['name'] != 'description']:
         fname = f['name']
         if not tkt.values.has_key(fname):
             continue
         fval = tkt[fname] or ''
         if fname in ['owner', 'reporter']:
             fval = obfuscate_email_address(fval)
         if f['type'] == 'textarea' or '\n' in unicode(fval):
             big.append((fname.capitalize(), CRLF.join(fval.splitlines())))
         else:
             txt += format[i % 2] % (fname.capitalize(), fval)
             i += 1
     if i % 2:
         txt += CRLF
     if big:
         txt += sep
         for name, value in big:
             txt += CRLF.join(['', name + ':', value, '', ''])
     txt += sep
     return txt
Пример #4
0
 def format_props(self):
     tkt = self.ticket
     fields = [f for f in tkt.fields if f['name'] not in ('summary', 'cc')]
     width = [0, 0, 0, 0]
     i = 0
     for f in [f['name'] for f in fields if f['type'] != 'textarea']:
         if not tkt.values.has_key(f):
             continue
         fval = tkt[f]
         if fval.find('\n') != -1:
             continue
         idx = 2 * (i % 2)
         if len(f) > width[idx]:
             width[idx] = len(f)
         if len(fval) > width[idx + 1]:
             width[idx + 1] = len(fval)
         i += 1
     format = ('%%%is:  %%-%is  |  ' % (width[0], width[1]),
               ' %%%is:  %%-%is%s' % (width[2], width[3], CRLF))
     l = (width[0] + width[1] + 5)
     sep = l * '-' + '+' + (self.COLS - l) * '-'
     txt = sep + CRLF
     big = []
     i = 0
     for f in [f for f in fields if f['name'] != 'description']:
         fname = f['name']
         if not tkt.values.has_key(fname):
             continue
         fval = tkt[fname]
         if f['type'] == 'textarea' or '\n' in unicode(fval):
             big.append((fname.capitalize(), CRLF.join(fval.splitlines())))
         else:
             txt += format[i % 2] % (fname.capitalize(), fval)
             i += 1
     if i % 2:
         txt += CRLF
     if big:
         txt += sep
         for name, value in big:
             txt += CRLF.join(['', name + ':', value, '', ''])
     txt += sep
     return txt
Пример #5
0
 def format_props(self):
     tkt = self.ticket
     fields = [f for f in tkt.fields if f["name"] not in ("summary", "cc", "time", "changetime")]
     width = [0, 0, 0, 0]
     i = 0
     for f in fields:
         if f["type"] == "textarea":
             continue
         fname = f["name"]
         if not fname in tkt.values:
             continue
         fval = tkt[fname] or ""
         if fval.find("\n") != -1:
             continue
         if fname in ["owner", "reporter"]:
             fval = obfuscate_email_address(fval)
         idx = 2 * (i % 2)
         width[idx] = max(self.get_text_width(f["label"]), width[idx])
         width[idx + 1] = max(self.get_text_width(fval), width[idx + 1])
         i += 1
     width_l = width[0] + width[1] + 5
     width_r = width[2] + width[3] + 5
     half_cols = (self.COLS - 1) / 2
     if width_l + width_r + 1 > self.COLS:
         if (width_l > half_cols and width_r > half_cols) or (width[0] > half_cols / 2 or width[2] > half_cols / 2):
             width_l = half_cols
             width_r = half_cols
         elif width_l > width_r:
             width_l = min((self.COLS - 1) * 2 / 3, width_l)
             width_r = self.COLS - width_l - 1
         else:
             width_r = min((self.COLS - 1) * 2 / 3, width_r)
             width_l = self.COLS - width_r - 1
     sep = width_l * "-" + "+" + width_r * "-"
     txt = sep + CRLF
     cell_tmp = [u"", u""]
     big = []
     i = 0
     width_lr = [width_l, width_r]
     for f in [f for f in fields if f["name"] != "description"]:
         fname = f["name"]
         if not tkt.values.has_key(fname):
             continue
         fval = tkt[fname] or ""
         if fname in ["owner", "reporter"]:
             fval = obfuscate_email_address(fval)
         if f["type"] == "textarea" or "\n" in unicode(fval):
             big.append((f["label"], CRLF.join(fval.splitlines())))
         else:
             # Note: f['label'] is a Babel's LazyObject, make sure its
             # __str__ method won't be called.
             str_tmp = u"%s:  %s" % (f["label"], unicode(fval))
             idx = i % 2
             cell_tmp[idx] += wrap(
                 str_tmp,
                 width_lr[idx] - 2 + 2 * idx,
                 (width[2 * idx] - self.get_text_width(f["label"]) + 2 * idx) * " ",
                 2 * " ",
                 CRLF,
             )
             cell_tmp[idx] += CRLF
             i += 1
     cell_l = cell_tmp[0].splitlines()
     cell_r = cell_tmp[1].splitlines()
     for i in range(max(len(cell_l), len(cell_r))):
         if i >= len(cell_l):
             cell_l.append(width_l * " ")
         elif i >= len(cell_r):
             cell_r.append("")
         fmt_width = width_l - self.get_text_width(cell_l[i]) + len(cell_l[i])
         txt += u"%-*s|%s%s" % (fmt_width, cell_l[i], cell_r[i], CRLF)
     if big:
         txt += sep
         for name, value in big:
             txt += CRLF.join(["", name + ":", value, "", ""])
     txt += sep
     return txt
Пример #6
0
    def send(self, torcpts, ccrcpts, mime_headers={}):
        from email.MIMEText import MIMEText
        from email.Utils import formatdate
        stream = self.template.generate(**self.data)
        body = stream.render('text')
        projname = self.config.get('project', 'name')
        public_cc = self.config.getbool('notification', 'use_public_cc')
        headers = {}
        headers['X-Mailer'] = 'Trac %s, by Edgewall Software' % __version__
        headers['X-Trac-Version'] =  __version__
        headers['X-Trac-Project'] =  projname
        headers['X-URL'] = self.config.get('project', 'url')
        headers['Precedence'] = 'bulk'
        headers['Auto-Submitted'] = 'auto-generated'
        headers['Subject'] = self.subject
        headers['From'] = (self.from_name or projname, self.from_email)
        headers['Reply-To'] = self.replyto_email

        def build_addresses(rcpts):
            """Format and remove invalid addresses"""
            return filter(lambda x: x, \
                          [self.get_smtp_address(addr) for addr in rcpts])

        def remove_dup(rcpts, all):
            """Remove duplicates"""
            tmp = []
            for rcpt in rcpts:
                if not rcpt in all:
                    tmp.append(rcpt)
                    all.append(rcpt)
            return (tmp, all)

        toaddrs = build_addresses(torcpts)
        ccaddrs = build_addresses(ccrcpts)

        recipients = []
        (toaddrs, recipients) = remove_dup(toaddrs, recipients)
        (ccaddrs, recipients) = remove_dup(ccaddrs, recipients)

        # if there is not valid recipient, leave immediately
        if len(recipients) < 1:
            self.env.log.info('no recipient for account change notification')
            return

        pcc = []
        if public_cc:
            pcc += ccaddrs
            if toaddrs:
                headers['To'] = ', '.join(toaddrs)
        if pcc:
            headers['Cc'] = ', '.join(pcc)
        headers['Date'] = formatdate()
        # sanity check
        if not self._charset.body_encoding:
            try:
                dummy = body.encode('ascii')
            except UnicodeDecodeError:
                raise TracError(_("Ticket contains non-ASCII chars. " \
                                  "Please change encoding setting"))
        msg = MIMEText(body, 'plain')
        # Message class computes the wrong type from MIMEText constructor,
        # which does not take a Charset object as initializer. Reset the
        # encoding type to force a new, valid evaluation
        del msg['Content-Transfer-Encoding']
        msg.set_charset(self._charset)
        self.add_headers(msg, headers);
        self.add_headers(msg, mime_headers);
        self.env.log.info("Sending SMTP notification to %s"%recipients)
        msgtext = msg.as_string()
        # Ensure the message complies with RFC2822: use CRLF line endings
        recrlf = re.compile("\r?\n")
        msgtext = CRLF.join(recrlf.split(msgtext))
        NotificationSystem(self.env).send_email(msg['From'], recipients,
                msgtext)
Пример #7
0
    def send(self, torcpts, ccrcpts, mime_headers={}):
        """
        this method is based NotifyEmail in trac/notification.py

        As the default trac NotifyEmail class assumes alot, and will overwrite headers
        we do not call our ancestor class method here, but send the mail direct
        """
        from email.MIMEText import MIMEText
        from email.Utils import formatdate

        stream = self.template.generate(**self.data)
        body = stream.render('text')
        projname = self.config.get('project', 'name')
        
        headers = {}
        headers['X-Mailer'] = 'Trac %s, by Edgewall Software' % __version__
        headers['X-Trac-Version'] =  __version__
        headers['X-Trac-Project'] =  projname
        headers['X-URL'] = self.env.project_url
        headers['Precedence'] = 'bulk'
        headers['Auto-Submitted'] = 'auto-generated'
        headers['Subject'] = self.subject
        headers['From'] = (self.from_name or projname, self.from_email)
        headers['Reply-To'] = self.reply_to_email
        
        # add Message-ID and In-Reply-To for threaded mail clients
        if self.action == 'post_created':
            headers['Message-ID'] = self.get_message_id(projname, self.blog.name)
        else:
            headers['Message-ID'] = self.get_message_id(projname, self.blog.name, self.time)
            headers['In-Reply-To'] = headers['References'] = self.get_message_id(projname, self.blog.name)


        def build_addresses(rcpts):
            """Format and remove invalid addresses"""
            return filter(lambda x: x, [self.get_smtp_address(addr) for addr in rcpts])

        def remove_dup(rcpts, all):
            """Remove duplicates"""
            tmp = []
            for rcpt in rcpts:
                if not rcpt in all:
                    tmp.append(rcpt)
                    all.append(rcpt)
            return (tmp, all)

        toaddrs = build_addresses(torcpts)
        ccaddrs = build_addresses(ccrcpts)
        accparam = self.config.getlist('fullblog-notification', 'smtp_always_cc')
        accaddrs = accparam and build_addresses(accparam) or []

        recipients = []
        (toaddrs, recipients) = remove_dup(toaddrs, recipients)
        (ccaddrs, recipients) = remove_dup(ccaddrs, recipients)
        (accaddrs, recipients) = remove_dup(accaddrs, recipients)

        # if there is not valid recipient, leave immediately
        if len(recipients) < 1:
            self.env.log.info('no recipient for a fullblog notification')
            return

        cc = accaddrs + ccaddrs
        if cc:
            headers['Cc'] = ', '.join(cc)
        if toaddrs:
            headers['To'] = ', '.join(toaddrs)
        headers['Date'] = formatdate()
        # sanity check
        if not self._charset.body_encoding:
            try:
                dummy = body.encode('ascii')
            except UnicodeDecodeError:
                raise TracError(_("Blog post contains non-Ascii chars. " \
                                  "Please change encoding setting"))

        msg = MIMEText(body, 'plain')
        # Message class computes the wrong type from MIMEText constructor,
        # which does not take a Charset object as initializer. Reset the
        # encoding type to force a new, valid evaluation
        del msg['Content-Transfer-Encoding']
        msg.set_charset(self._charset)

        self.add_headers(msg, headers);
        self.add_headers(msg, mime_headers);
        self.env.log.info("Sending SMTP notification to %s:%d to %s"
                           % (self.smtp_server, self.smtp_port, recipients))
        msgtext = msg.as_string()
        # Ensure the message complies with RFC2822: use CRLF line endings
        recrlf = re.compile("\r?\n")
        msgtext = CRLF.join(recrlf.split(msgtext))
        try:
            self.server.sendmail(msg['From'], recipients, msgtext)
        except Exception, err:
            self.env.log.debug('Notification could not be sent: %r', err)
Пример #8
0
 def format_props(self):
     tkt = self.ticket
     fields = [f for f in tkt.fields 
               if f['name'] not in ('summary', 'cc', 'time', 'changetime')]
     width = [0, 0, 0, 0]
     i = 0
     for f in fields:
         if f['type'] == 'textarea':
             continue
         fname = f['name']
         if not fname in tkt.values:
             continue
         fval = tkt[fname] or ''
         if fval.find('\n') != -1:
             continue
         if fname in ['owner', 'reporter']:
             fval = obfuscate_email_address(fval)
         idx = 2 * (i % 2)
         width[idx] = max(self.get_text_width(f['label']), width[idx])
         width[idx + 1] = max(self.get_text_width(fval), width[idx + 1])
         i += 1
     width_l = width[0] + width[1] + 5
     width_r = width[2] + width[3] + 5
     half_cols = (self.COLS - 1) / 2
     if width_l + width_r + 1 > self.COLS:
         if ((width_l > half_cols and width_r > half_cols) or 
                 (width[0] > half_cols / 2 or width[2] > half_cols / 2)):
             width_l = half_cols
             width_r = half_cols
         elif width_l > width_r:
             width_l = min((self.COLS - 1) * 2 / 3, width_l)
             width_r = self.COLS - width_l - 1
         else:
             width_r = min((self.COLS - 1) * 2 / 3, width_r)         
             width_l = self.COLS - width_r - 1
     sep = width_l * '-' + '+' + width_r * '-'
     txt = sep + CRLF
     cell_tmp = [u'', u'']
     big = []
     i = 0
     width_lr = [width_l, width_r]
     for f in [f for f in fields if f['name'] != 'description']:
         fname = f['name']
         if not tkt.values.has_key(fname):
             continue
         fval = tkt[fname] or ''
         if fname in ['owner', 'reporter']:
             fval = obfuscate_email_address(fval)
         if f['type'] == 'textarea' or '\n' in unicode(fval):
             big.append((f['label'], CRLF.join(fval.splitlines())))
         else:
             # Note: f['label'] is a Babel's LazyObject, make sure its
             # __str__ method won't be called.
             str_tmp = u'%s:  %s' % (f['label'], unicode(fval))
             idx = i % 2
             cell_tmp[idx] += wrap(str_tmp, width_lr[idx] - 2 + 2 * idx,
                                   (width[2 * idx]
                                    - self.get_text_width(f['label'])
                                    + 2 * idx) * ' ',
                                   2 * ' ', CRLF)
             cell_tmp[idx] += CRLF
             i += 1
     cell_l = cell_tmp[0].splitlines()
     cell_r = cell_tmp[1].splitlines()
     for i in range(max(len(cell_l), len(cell_r))):
         if i >= len(cell_l):
             cell_l.append(width_l * ' ')
         elif i >= len(cell_r):
             cell_r.append('')
         fmt_width = width_l - self.get_text_width(cell_l[i]) \
                     + len(cell_l[i])
         txt += u'%-*s|%s%s' % (fmt_width, cell_l[i], cell_r[i], CRLF)
     if big:
         txt += sep
         for name, value in big:
             txt += CRLF.join(['', name + ':', value, '', ''])
     txt += sep
     return txt
Пример #9
0
 def send(self, from_addr, recipients, message):
     """Send message to recipients via e-mail."""
     # Ensure the message complies with RFC2822: use CRLF line endings
     message = CRLF.join(re.split("\r?\n", message))
     self.email_sender.send(from_addr, recipients, message)
Пример #10
0
 def format_props(self):
     tkt = self.ticket
     fields = [f for f in tkt.fields 
               if f['name'] not in ('summary', 'cc', 'time', 'changetime')]
     width = [0, 0, 0, 0]
     i = 0
     for f in fields:
         if f['type'] == 'textarea':
             continue
         fname = f['name']
         if not fname in tkt.values:
             continue
         fval = tkt[fname] or ''
         if fval.find('\n') != -1:
             continue
         if fname in ['owner', 'reporter']:
             fval = obfuscate_email_address(fval)
         idx = 2 * (i % 2)
         width[idx] = max(self.get_text_width(f['label']), width[idx])
         width[idx + 1] = max(self.get_text_width(fval), width[idx + 1])
         i += 1
     width_l = width[0] + width[1] + 5
     width_r = width[2] + width[3] + 5
     half_cols = (self.COLS - 1) / 2
     if width_l + width_r + 1 > self.COLS:
         if ((width_l > half_cols and width_r > half_cols) or 
                 (width[0] > half_cols / 2 or width[2] > half_cols / 2)):
             width_l = half_cols
             width_r = half_cols
         elif width_l > width_r:
             width_l = min((self.COLS - 1) * 2 / 3, width_l)
             width_r = self.COLS - width_l - 1
         else:
             width_r = min((self.COLS - 1) * 2 / 3, width_r)         
             width_l = self.COLS - width_r - 1
     sep = width_l * '-' + '+' + width_r * '-'
     txt = sep + CRLF
     cell_tmp = [u'', u'']
     big = []
     i = 0
     width_lr = [width_l, width_r]
     for f in [f for f in fields if f['name'] != 'description']:
         fname = f['name']
         if not tkt.values.has_key(fname):
             continue
         fval = tkt[fname] or ''
         if fname in ['owner', 'reporter']:
             fval = obfuscate_email_address(fval)
         if f['type'] == 'textarea' or '\n' in unicode(fval):
             big.append((f['label'], CRLF.join(fval.splitlines())))
         else:
             # Note: f['label'] is a Babel's LazyObject, make sure its
             # __str__ method won't be called.
             str_tmp = u'%s:  %s' % (f['label'], unicode(fval))
             idx = i % 2
             cell_tmp[idx] += wrap(str_tmp, width_lr[idx] - 2 + 2 * idx,
                                   (width[2 * idx]
                                    - self.get_text_width(f['label'])
                                    + 2 * idx) * ' ',
                                   2 * ' ', CRLF)
             cell_tmp[idx] += CRLF
             i += 1
     cell_l = cell_tmp[0].splitlines()
     cell_r = cell_tmp[1].splitlines()
     for i in range(max(len(cell_l), len(cell_r))):
         if i >= len(cell_l):
             cell_l.append(width_l * ' ')
         elif i >= len(cell_r):
             cell_r.append('')
         fmt_width = width_l - self.get_text_width(cell_l[i]) \
                     + len(cell_l[i])
         txt += u'%-*s|%s%s' % (fmt_width, cell_l[i], cell_r[i], CRLF)
     if big:
         txt += sep
         for name, value in big:
             txt += CRLF.join(['', name + ':', value, '', ''])
     txt += sep
     return txt
Пример #11
0
 def send(self, from_addr, recipients, message):
     """Send message to recipients via e-mail."""
     # Ensure the message complies with RFC2822: use CRLF line endings
     message = CRLF.join(re.split('\r?\n', message))
     self.email_sender.send(from_addr, recipients, message)
Пример #12
0
    def send(self, torcpts, ccrcpts, mime_headers={}):
        from email.MIMEText import MIMEText
        from email.Utils import formatdate
        stream = self.template.generate(**self.data)
        body = stream.render('text')
        projname = self.config.get('project', 'name')
        public_cc = self.config.getbool('notification', 'use_public_cc')
        headers = {}
        headers['X-Mailer'] = 'Trac %s, by Edgewall Software' % __version__
        headers['X-Trac-Version'] =  __version__
        headers['X-Trac-Project'] =  projname
        headers['X-URL'] = self.config.get('project', 'url')
        headers['Precedence'] = 'bulk'
        headers['Auto-Submitted'] = 'auto-generated'
        headers['Subject'] = self.subject
        headers['From'] = (self.from_name or projname, self.from_email)
        headers['Reply-To'] = self.replyto_email

        def build_addresses(rcpts):
            """Format and remove invalid addresses"""
            return filter(lambda x: x, \
                          [self.get_smtp_address(addr) for addr in rcpts])

        def remove_dup(rcpts, all):
            """Remove duplicates"""
            tmp = []
            for rcpt in rcpts:
                if not rcpt in all:
                    tmp.append(rcpt)
                    all.append(rcpt)
            return (tmp, all)

        toaddrs = build_addresses(torcpts)
        ccaddrs = build_addresses(ccrcpts)
        accparam = self.config.get('notification', 'smtp_always_cc')
        accaddrs = accparam and \
                   build_addresses(accparam.replace(',', ' ').split()) or []
        bccparam = self.config.get('notification', 'smtp_always_bcc')
        bccaddrs = bccparam and \
                   build_addresses(bccparam.replace(',', ' ').split()) or []

        recipients = []
        (toaddrs, recipients) = remove_dup(toaddrs, recipients)
        (ccaddrs, recipients) = remove_dup(ccaddrs, recipients)
        (accaddrs, recipients) = remove_dup(accaddrs, recipients)
        (bccaddrs, recipients) = remove_dup(bccaddrs, recipients)
        
        # if there is not valid recipient, leave immediately
        if len(recipients) < 1:
            self.env.log.info('no recipient for a ticket notification')
            return

        pcc = accaddrs
        if public_cc:
            pcc += ccaddrs
            if toaddrs:
                headers['To'] = ', '.join(toaddrs)
        if pcc:
            headers['Cc'] = ', '.join(pcc)
        headers['Date'] = formatdate()
        msg = MIMEText(body, 'plain')
        # Message class computes the wrong type from MIMEText constructor,
        # which does not take a Charset object as initializer. Reset the
        # encoding type to force a new, valid evaluation
        del msg['Content-Transfer-Encoding']
        msg.set_charset(self._charset)
        self.add_headers(msg, headers);
        self.add_headers(msg, mime_headers);
        self.env.log.info("Sending SMTP notification to %s:%d to %s"
                           % (self.smtp_server, self.smtp_port, recipients))
        msgtext = msg.as_string()
        # Ensure the message complies with RFC2822: use CRLF line endings
        recrlf = re.compile("\r?\n")
        msgtext = CRLF.join(recrlf.split(msgtext))
        start = time.time()
        self.server.sendmail(msg['From'], recipients, msgtext)
        t = time.time() - start
        if t > 5:
            self.env.log.warning('Slow mail submission (%.2f s), '
                                 'check your mail setup' % t)