summaryrefslogtreecommitdiff
path: root/pgweb/mailqueue/util.py
blob: 2ef25b3d21eb73931d4eecfa40f93d25e6977be5 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
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
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
from django.db import transaction

from datetime import datetime
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from email.mime.nonmultipart import MIMENonMultipart
from email.utils import formatdate, formataddr
from email.utils import make_msgid
from email import encoders, charset
from email.header import Header

from .models import QueuedMail, LastSent


def _encoded_email_header(name, email):
    if name:
        return formataddr((str(Header(name, 'utf-8')), email))
    return email


# Default for utf-8 in python is to encode subject with "shortest" and body with "base64". For our texts,
# make it always quoted printable, for easier reading and testing.
_utf8_charset = charset.Charset('utf-8')
_utf8_charset.header_encoding = charset.QP
_utf8_charset.body_encoding = charset.QP


def send_simple_mail(sender, receiver, subject, msgtxt, attachments=None, usergenerated=False, cc=None, replyto=None, sendername=None, receivername=None, messageid=None, suppress_auto_replies=True, is_auto_reply=False, htmlbody=None, headers={}, staggertype=None, stagger=None):
    # attachment format, each is a tuple of (name, mimetype,contents)
    # content should be *binary* and not base64 encoded, since we need to
    # use the base64 routines from the email library to get a properly
    # formatted output message
    msg = MIMEMultipart()
    msg['Subject'] = subject
    msg['To'] = _encoded_email_header(receivername, receiver)
    msg['From'] = _encoded_email_header(sendername, sender)
    if cc:
        msg['Cc'] = cc
    if replyto:
        msg['Reply-To'] = replyto
    msg['Date'] = formatdate(localtime=True)
    if messageid:
        msg['Message-ID'] = messageid
    else:
        msg['Message-ID'] = make_msgid()
    if suppress_auto_replies:
        # Do our best to set some headers to indicate that auto-replies like out of office
        # messages should not be sent to this email.
        msg['X-Auto-Response-Suppress'] = 'All'

    # Is this email auto-generated or auto-replied?
    if is_auto_reply:
        msg['Auto-Submitted'] = 'auto-replied'
    elif not usergenerated:
        msg['Auto-Submitted'] = 'auto-generated'

    for h, v in headers.items():
        if h in msg:
            # Replace the existing header -- the one specified is supposedly overriding it
            msg.replace_header(h, v)
        else:
            msg.add_header(h, v)

    if htmlbody:
        mpart = MIMEMultipart("alternative")
        mpart.attach(MIMEText(msgtxt, _charset=_utf8_charset))
        mpart.attach(MIMEText(htmlbody, 'html', _charset=_utf8_charset))
        msg.attach(mpart)
    else:
        # Just a plaintext body, so append it directly
        msg.attach(MIMEText(msgtxt, _charset='utf-8'))

    if attachments:
        for a in attachments:
            main, sub = a['contenttype'].split('/')
            part = MIMENonMultipart(main, sub)
            part.set_payload(a['content'])
            part.add_header('Content-Disposition', a.get('disposition', 'attachment; filename="%s"' % a['filename']))
            if 'id' in a:
                part.add_header('Content-ID', a['id'])

            encoders.encode_base64(part)
            msg.attach(part)

    with transaction.atomic():
        if staggertype and stagger:
            # Don't send a second one too close after another one of this class.
            ls, created = LastSent.objects.get_or_create(type=staggertype, defaults={'lastsent': datetime.now()})

            sendat = ls.lastsent = ls.lastsent + stagger
            ls.save(update_fields=['lastsent'])
        else:
            sendat = datetime.now()

        # Just write it to the queue, so it will be transactionally rolled back
        QueuedMail(sender=sender, receiver=receiver, fullmsg=msg.as_string(), usergenerated=usergenerated, sendat=sendat).save()
        if cc:
            # Write a second copy for the cc, wihch will be delivered
            # directly to the recipient. (The sender doesn't parse the
            # message content to extract cc fields).
            QueuedMail(sender=sender, receiver=cc, fullmsg=msg.as_string(), usergenerated=usergenerated, sendat=sendat).save()