mails.py 3.75 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 49 50 51 52 53 54 55 56 57 58 59

    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():
            try:
                args, kwargs = self._queue.get(timeout=timeout)
            except queue.Empty:
                server = self.__server_quit(server)
            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:
60
                        logging.warning("Couldn't send email: %s" % str(e))
61
                self._queue.task_done()
62 63 64 65 66 67 68 69 70 71 72 73
        self.__server_quit(server)

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


_mailer = ThreadedSMTP()


def quit():
    _mailer.quit()

74 75

def send_email(subject, body, extra_headers={}):
76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91
    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())
92

93

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

    msg_text = ''
104 105 106
    if not check.ok:
        msg_text += ("Check %s failed:\n%s" %
                    (str(check), check.errmsg.strip()))
107

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

116 117
    send_email(subject, msg_text, extra_headers)

118

Jonathan Michalon's avatar
Jonathan Michalon committed
119 120
def send_email_report(text):
    from . import config
121
    send_email(config.emails.report.subject, text)