mails.py 3.91 KB
Newer Older
1
import smtplib
2
import logging
3
from email.mime.text import MIMEText
4
from email.utils import make_msgid
5 6
from collections import defaultdict
from sys import stderr
7
from time import strftime
8
import email.charset
9 10
from threading import Thread
import queue
11
import atexit
12 13 14 15 16

# Switch to quoted-printable so that we don't get something completely
# unreadable for non-ASCII chars if we have to look at raw email
email.charset.add_charset('utf-8', email.charset.QP, email.charset.QP, 'utf-8')

Colomban Wendling's avatar
Colomban Wendling committed
17

18 19 20 21 22 23 24
class ThreadedSMTP(object):
    """A helper class managing a thread sending emails through smtplib"""

    def __init__(self):
        self._queue = queue.Queue()
        self._loop = True
        self._thread = Thread(target=self.__loop)
25
        self._thread.daemon = True
26
        self._thread.start()
27 28
        # properly clean up on quit
        atexit.register(self.quit)
29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48

    def quit(self):
        if self._loop:
            self._loop = False
            self._queue.put(((), {}))  # put a dummy item to wake up the thread
            self._queue.join()
            self._thread.join()

    def __server_quit(self, server=None):
        if server is not None:
            server.quit()
        return None

    def __loop(self):
        from . import config
        host = config.emails.smtp_host
        timeout = config.emails.smtp_keepalive_timeout

        server = None
        while self._loop or not self._queue.empty():
49
            task_eaten = True
50 51 52 53
            try:
                args, kwargs = self._queue.get(timeout=timeout)
            except queue.Empty:
                server = self.__server_quit(server)
54
                task_eaten = False  # we didn't eat a task, just timeout
55 56 57 58 59 60 61
            else:
                if len(args) or len(kwargs):  # ignore empty items
                    try:
                        if server is None:
                            server = smtplib.SMTP(host)
                        server.sendmail(*args, **kwargs)
                    except Exception as e:
62
                        logging.warning("Couldn't send email: %s" % str(e))
63
            finally:
64
                if task_eaten:
65
                    self._queue.task_done()
66 67 68 69 70 71 72 73 74 75 76 77
        self.__server_quit(server)

    def sendmail(self, *args, **kwargs):
        self._queue.put((args, kwargs))


_mailer = ThreadedSMTP()


def quit():
    _mailer.quit()

78 79

def send_email(subject, body, extra_headers={}):
80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95
    from . import config

    # encode / decode is a fix that didn't make it into Debian Wheezy
    # http://bugs.python.org/issue16948
    msg = MIMEText(body.encode('utf-8').decode('latin1'), 'plain', 'utf-8')

    msg['Subject'] = subject
    msg['From']    = config.emails.addr_from
    msg['To']      = ", ".join(config.emails.to)
    msg['Date']    = strftime('%a, %d %b %Y %H:%M:%S %z')

    for (key, val) in extra_headers.items():
        msg[key] = val

    _mailer.sendmail(config.emails.addr_from, config.emails.to,
                     msg.as_string())
96

97

98
def send_email_for_check(check):
99
    from . import config
100
    # ensure we do not traceback with unknown substitutions
101
    subject = config.emails.subject_tpl.format_map(
Colomban Wendling's avatar
Colomban Wendling committed
102 103 104 105
        defaultdict(lambda: "<no substitution>",
                    state='OK' if check.ok else 'Problem',
                    check=check.__class__.__name__,
                    dest=check.target_name))
106 107

    msg_text = ''
108 109 110
    if not check.ok:
        msg_text += ("Check %s failed:\n%s" %
                    (str(check), check.errmsg.strip()))
111

112
    extra_headers = {}
113
    extra_headers['Message-ID'] = make_msgid(type(check).__name__)
114 115
    # if check is OK it's a follow up, so set In-Reply-To
    if check.ok and hasattr(check, 'mails_msgid'):
116 117 118
        extra_headers['In-Reply-To'] = check.mails_msgid
        extra_headers['References'] = check.mails_msgid
    check.mails_msgid = extra_headers['Message-ID']
119

120 121
    send_email(subject, msg_text, extra_headers)

122

Jonathan Michalon's avatar
Jonathan Michalon committed
123 124
def send_email_report(text):
    from . import config
125
    send_email(config.emails.report.subject, text)