2
Tools for sending email.
11
from email import Charset, Encoders
12
from email.MIMEText import MIMEText
13
from email.MIMEMultipart import MIMEMultipart
14
from email.MIMEBase import MIMEBase
15
from email.Header import Header
16
from email.Utils import formatdate, parseaddr, formataddr
18
from django.conf import settings
19
from django.utils.encoding import smart_str, force_unicode
21
# Don't BASE64-encode UTF-8 messages so that we avoid unwanted attention from
23
Charset.add_charset('utf-8', Charset.SHORTEST, Charset.QP, 'utf-8')
25
# Default MIME type to use on attachments (if it is not explicitly given
26
# and cannot be guessed).
27
DEFAULT_ATTACHMENT_MIME_TYPE = 'application/octet-stream'
29
# Cache the hostname, but do it lazily: socket.getfqdn() can take a couple of
30
# seconds, which slows down the restart of the server.
31
class CachedDnsName(object):
33
return self.get_fqdn()
36
if not hasattr(self, '_fqdn'):
37
self._fqdn = socket.getfqdn()
40
DNS_NAME = CachedDnsName()
42
# Copied from Python standard library, with the following modifications:
43
# * Used cached hostname for performance.
44
# * Added try/except to support lack of getpid() in Jython (#5496).
45
def make_msgid(idstring=None):
46
"""Returns a string suitable for RFC 2822 compliant Message-ID, e.g:
48
<20020201195627.33539.96671@nightshade.la.mastaler.com>
50
Optional idstring if given is a string used to strengthen the
51
uniqueness of the message id.
54
utcdate = time.strftime('%Y%m%d%H%M%S', time.gmtime(timeval))
57
except AttributeError:
58
# No getpid() in Jython, for example.
60
randint = random.randrange(100000)
64
idstring = '.' + idstring
66
msgid = '<%s.%s.%s%s@%s>' % (utcdate, pid, randint, idstring, idhost)
69
class BadHeaderError(ValueError):
72
def forbid_multi_line_headers(name, val):
73
"""Forbids multi-line headers, to prevent header injection."""
74
val = force_unicode(val)
75
if '\n' in val or '\r' in val:
76
raise BadHeaderError("Header values can't contain newlines (got %r for header %r)" % (val, name))
78
val = val.encode('ascii')
79
except UnicodeEncodeError:
80
if name.lower() in ('to', 'from', 'cc'):
82
for item in val.split(', '):
83
nm, addr = parseaddr(item)
84
nm = str(Header(nm, settings.DEFAULT_CHARSET))
85
result.append(formataddr((nm, str(addr))))
86
val = ', '.join(result)
88
val = Header(val, settings.DEFAULT_CHARSET)
90
if name.lower() == 'subject':
94
class SafeMIMEText(MIMEText):
95
def __setitem__(self, name, val):
96
name, val = forbid_multi_line_headers(name, val)
97
MIMEText.__setitem__(self, name, val)
99
class SafeMIMEMultipart(MIMEMultipart):
100
def __setitem__(self, name, val):
101
name, val = forbid_multi_line_headers(name, val)
102
MIMEMultipart.__setitem__(self, name, val)
104
class SMTPConnection(object):
106
A wrapper that manages the SMTP network connection.
109
def __init__(self, host=None, port=None, username=None, password=None,
110
use_tls=None, fail_silently=False):
111
self.host = host or settings.EMAIL_HOST
112
self.port = port or settings.EMAIL_PORT
113
self.username = username or settings.EMAIL_HOST_USER
114
self.password = password or settings.EMAIL_HOST_PASSWORD
115
self.use_tls = (use_tls is not None) and use_tls or settings.EMAIL_USE_TLS
116
self.fail_silently = fail_silently
117
self.connection = None
121
Ensures we have a connection to the email server. Returns whether or
122
not a new connection was required (True or False).
125
# Nothing to do if the connection is already open.
128
# If local_hostname is not specified, socket.getfqdn() gets used.
129
# For performance, we use the cached FQDN for local_hostname.
130
self.connection = smtplib.SMTP(self.host, self.port,
131
local_hostname=DNS_NAME.get_fqdn())
133
self.connection.ehlo()
134
self.connection.starttls()
135
self.connection.ehlo()
136
if self.username and self.password:
137
self.connection.login(self.username, self.password)
140
if not self.fail_silently:
144
"""Closes the connection to the email server."""
147
self.connection.quit()
148
except socket.sslerror:
149
# This happens when calling quit() on a TLS connection
151
self.connection.close()
153
if self.fail_silently:
157
self.connection = None
159
def send_messages(self, email_messages):
161
Sends one or more EmailMessage objects and returns the number of email
164
if not email_messages:
166
new_conn_created = self.open()
167
if not self.connection:
168
# We failed silently on open(). Trying to send would be pointless.
171
for message in email_messages:
172
sent = self._send(message)
179
def _send(self, email_message):
180
"""A helper method that does the actual sending."""
181
if not email_message.recipients():
184
self.connection.sendmail(email_message.from_email,
185
email_message.recipients(),
186
email_message.message().as_string())
188
if not self.fail_silently:
193
class EmailMessage(object):
195
A container for email information.
197
content_subtype = 'plain'
198
mixed_subtype = 'mixed'
199
encoding = None # None => use settings default
201
def __init__(self, subject='', body='', from_email=None, to=None, bcc=None,
202
connection=None, attachments=None, headers=None):
204
Initialize a single email message (which can be sent to multiple
207
All strings used to create the message can be unicode strings (or UTF-8
208
bytestrings). The SafeMIMEText class will handle any necessary encoding
212
assert not isinstance(to, basestring), '"to" argument must be a list or tuple'
217
assert not isinstance(bcc, basestring), '"bcc" argument must be a list or tuple'
221
self.from_email = from_email or settings.DEFAULT_FROM_EMAIL
222
self.subject = subject
224
self.attachments = attachments or []
225
self.extra_headers = headers or {}
226
self.connection = connection
228
def get_connection(self, fail_silently=False):
229
if not self.connection:
230
self.connection = SMTPConnection(fail_silently=fail_silently)
231
return self.connection
234
encoding = self.encoding or settings.DEFAULT_CHARSET
235
msg = SafeMIMEText(smart_str(self.body, settings.DEFAULT_CHARSET),
236
self.content_subtype, encoding)
237
msg = self._create_message(msg)
238
msg['Subject'] = self.subject
239
msg['From'] = self.extra_headers.pop('From', self.from_email)
240
msg['To'] = ', '.join(self.to)
242
# Email header names are case-insensitive (RFC 2045), so we have to
243
# accommodate that when doing comparisons.
244
header_names = [key.lower() for key in self.extra_headers]
245
if 'date' not in header_names:
246
msg['Date'] = formatdate()
247
if 'message-id' not in header_names:
248
msg['Message-ID'] = make_msgid()
249
for name, value in self.extra_headers.items():
253
def recipients(self):
255
Returns a list of all recipients of the email (includes direct
256
addressees as well as Bcc entries).
258
return self.to + self.bcc
260
def send(self, fail_silently=False):
261
"""Sends the email message."""
262
if not self.recipients():
263
# Don't bother creating the network connection if there's nobody to
266
return self.get_connection(fail_silently).send_messages([self])
268
def attach(self, filename=None, content=None, mimetype=None):
270
Attaches a file with the given filename and content. The filename can
271
be omitted and the mimetype is guessed, if not provided.
273
If the first parameter is a MIMEBase subclass it is inserted directly
274
into the resulting message attachments.
276
if isinstance(filename, MIMEBase):
277
assert content == mimetype == None
278
self.attachments.append(filename)
280
assert content is not None
281
self.attachments.append((filename, content, mimetype))
283
def attach_file(self, path, mimetype=None):
284
"""Attaches a file from the filesystem."""
285
filename = os.path.basename(path)
286
content = open(path, 'rb').read()
287
self.attach(filename, content, mimetype)
289
def _create_message(self, msg):
290
return self._create_attachments(msg)
292
def _create_attachments(self, msg):
295
msg = SafeMIMEMultipart(_subtype=self.mixed_subtype)
298
for attachment in self.attachments:
299
if isinstance(attachment, MIMEBase):
300
msg.attach(attachment)
302
msg.attach(self._create_attachment(*attachment))
305
def _create_mime_attachment(self, content, mimetype):
307
Converts the content, mimetype pair into a MIME attachment object.
309
basetype, subtype = mimetype.split('/', 1)
310
if basetype == 'text':
311
attachment = SafeMIMEText(smart_str(content,
312
settings.DEFAULT_CHARSET), subtype, settings.DEFAULT_CHARSET)
314
# Encode non-text attachments with base64.
315
attachment = MIMEBase(basetype, subtype)
316
attachment.set_payload(content)
317
Encoders.encode_base64(attachment)
320
def _create_attachment(self, filename, content, mimetype=None):
322
Converts the filename, content, mimetype triple into a MIME attachment
326
mimetype, _ = mimetypes.guess_type(filename)
328
mimetype = DEFAULT_ATTACHMENT_MIME_TYPE
329
attachment = self._create_mime_attachment(content, mimetype)
331
attachment.add_header('Content-Disposition', 'attachment',
335
class EmailMultiAlternatives(EmailMessage):
337
A version of EmailMessage that makes it easy to send multipart/alternative
338
messages. For example, including text and HTML versions of the text is
341
alternative_subtype = 'alternative'
343
def __init__(self, subject='', body='', from_email=None, to=None, bcc=None,
344
connection=None, attachments=None, headers=None, alternatives=None):
346
Initialize a single email message (which can be sent to multiple
349
All strings used to create the message can be unicode strings (or UTF-8
350
bytestrings). The SafeMIMEText class will handle any necessary encoding
353
super(EmailMultiAlternatives, self).__init__(subject, body, from_email, to, bcc, connection, attachments, headers)
354
self.alternatives=alternatives or []
356
def attach_alternative(self, content, mimetype):
357
"""Attach an alternative content representation."""
358
assert content is not None
359
assert mimetype is not None
360
self.alternatives.append((content, mimetype))
362
def _create_message(self, msg):
363
return self._create_attachments(self._create_alternatives(msg))
365
def _create_alternatives(self, msg):
366
if self.alternatives:
368
msg = SafeMIMEMultipart(_subtype=self.alternative_subtype)
371
for alternative in self.alternatives:
372
msg.attach(self._create_mime_attachment(*alternative))
375
def send_mail(subject, message, from_email, recipient_list,
376
fail_silently=False, auth_user=None, auth_password=None):
378
Easy wrapper for sending a single message to a recipient list. All members
379
of the recipient list will see the other recipients in the 'To' field.
381
If auth_user is None, the EMAIL_HOST_USER setting is used.
382
If auth_password is None, the EMAIL_HOST_PASSWORD setting is used.
384
Note: The API for this method is frozen. New code wanting to extend the
385
functionality should use the EmailMessage class directly.
387
connection = SMTPConnection(username=auth_user, password=auth_password,
388
fail_silently=fail_silently)
389
return EmailMessage(subject, message, from_email, recipient_list,
390
connection=connection).send()
392
def send_mass_mail(datatuple, fail_silently=False, auth_user=None,
395
Given a datatuple of (subject, message, from_email, recipient_list), sends
396
each message to each recipient list. Returns the number of e-mails sent.
398
If from_email is None, the DEFAULT_FROM_EMAIL setting is used.
399
If auth_user and auth_password are set, they're used to log in.
400
If auth_user is None, the EMAIL_HOST_USER setting is used.
401
If auth_password is None, the EMAIL_HOST_PASSWORD setting is used.
403
Note: The API for this method is frozen. New code wanting to extend the
404
functionality should use the EmailMessage class directly.
406
connection = SMTPConnection(username=auth_user, password=auth_password,
407
fail_silently=fail_silently)
408
messages = [EmailMessage(subject, message, sender, recipient)
409
for subject, message, sender, recipient in datatuple]
410
return connection.send_messages(messages)
412
def mail_admins(subject, message, fail_silently=False):
413
"""Sends a message to the admins, as defined by the ADMINS setting."""
414
if not settings.ADMINS:
416
EmailMessage(settings.EMAIL_SUBJECT_PREFIX + subject, message,
417
settings.SERVER_EMAIL, [a[1] for a in settings.ADMINS]
418
).send(fail_silently=fail_silently)
420
def mail_managers(subject, message, fail_silently=False):
421
"""Sends a message to the managers, as defined by the MANAGERS setting."""
422
if not settings.MANAGERS:
424
EmailMessage(settings.EMAIL_SUBJECT_PREFIX + subject, message,
425
settings.SERVER_EMAIL, [a[1] for a in settings.MANAGERS]
426
).send(fail_silently=fail_silently)