2 Tools for sending email.
5 from django
.conf
import settings
6 from django
.utils
.encoding
import smart_str
, force_unicode
7 from email
import Charset
, Encoders
8 from email
.MIMEText
import MIMEText
9 from email
.MIMEMultipart
import MIMEMultipart
10 from email
.MIMEBase
import MIMEBase
11 from email
.Header
import Header
12 from email
.Utils
import formatdate
, parseaddr
, formataddr
20 # Don't BASE64-encode UTF-8 messages so that we avoid unwanted attention from
22 Charset
.add_charset('utf-8', Charset
.SHORTEST
, Charset
.QP
, 'utf-8')
24 # Default MIME type to use on attachments (if it is not explicitly given
25 # and cannot be guessed).
26 DEFAULT_ATTACHMENT_MIME_TYPE
= 'application/octet-stream'
28 # Cache the hostname, but do it lazily: socket.getfqdn() can take a couple of
29 # seconds, which slows down the restart of the server.
30 class CachedDnsName(object):
32 return self
.get_fqdn()
35 if not hasattr(self
, '_fqdn'):
36 self
._fqdn
= socket
.getfqdn()
39 DNS_NAME
= CachedDnsName()
41 # Copied from Python standard library and modified to used the cached hostname
43 def make_msgid(idstring
=None):
44 """Returns a string suitable for RFC 2822 compliant Message-ID, e.g:
46 <20020201195627.33539.96671@nightshade.la.mastaler.com>
48 Optional idstring if given is a string used to strengthen the
49 uniqueness of the message id.
52 utcdate
= time
.strftime('%Y%m%d%H%M%S', time
.gmtime(timeval
))
55 except AttributeError:
56 # Not getpid() in Jython, for example.
58 randint
= random
.randrange(100000)
62 idstring
= '.' + idstring
64 msgid
= '<%s.%s.%s%s@%s>' % (utcdate
, pid
, randint
, idstring
, idhost
)
67 class BadHeaderError(ValueError):
70 def forbid_multi_line_headers(name
, val
):
71 "Forbids multi-line headers, to prevent header injection."
72 if '\n' in val
or '\r' in val
:
73 raise BadHeaderError("Header values can't contain newlines (got %r for header %r)" % (val
, name
))
75 val
= force_unicode(val
).encode('ascii')
76 except UnicodeEncodeError:
77 if name
.lower() in ('to', 'from', 'cc'):
79 for item
in val
.split(', '):
80 nm
, addr
= parseaddr(item
)
81 nm
= str(Header(nm
, settings
.DEFAULT_CHARSET
))
82 result
.append(formataddr((nm
, str(addr
))))
83 val
= ', '.join(result
)
85 val
= Header(force_unicode(val
), settings
.DEFAULT_CHARSET
)
88 class SafeMIMEText(MIMEText
):
89 def __setitem__(self
, name
, val
):
90 name
, val
= forbid_multi_line_headers(name
, val
)
91 MIMEText
.__setitem
__(self
, name
, val
)
93 class SafeMIMEMultipart(MIMEMultipart
):
94 def __setitem__(self
, name
, val
):
95 name
, val
= forbid_multi_line_headers(name
, val
)
96 MIMEMultipart
.__setitem
__(self
, name
, val
)
98 class SMTPConnection(object):
100 A wrapper that manages the SMTP network connection.
103 def __init__(self
, host
=None, port
=None, username
=None, password
=None,
104 use_tls
=None, fail_silently
=False):
105 self
.host
= host
or settings
.EMAIL_HOST
106 self
.port
= port
or settings
.EMAIL_PORT
107 self
.username
= username
or settings
.EMAIL_HOST_USER
108 self
.password
= password
or settings
.EMAIL_HOST_PASSWORD
109 self
.use_tls
= (use_tls
is not None) and use_tls
or settings
.EMAIL_USE_TLS
110 self
.fail_silently
= fail_silently
111 self
.connection
= None
115 Ensure we have a connection to the email server. Returns whether or not
116 a new connection was required.
119 # Nothing to do if the connection is already open.
122 self
.connection
= smtplib
.SMTP(self
.host
, self
.port
)
124 self
.connection
.ehlo()
125 self
.connection
.starttls()
126 self
.connection
.ehlo()
127 if self
.username
and self
.password
:
128 self
.connection
.login(self
.username
, self
.password
)
131 if not self
.fail_silently
:
135 """Close the connection to the email server."""
138 self
.connection
.quit()
139 except socket
.sslerror
:
140 # This happens when calling quit() on a TLS connection
142 self
.connection
.close()
144 if self
.fail_silently
:
148 self
.connection
= None
150 def send_messages(self
, email_messages
):
152 Send one or more EmailMessage objects and return the number of email
155 if not email_messages
:
157 new_conn_created
= self
.open()
158 if not self
.connection
:
159 # We failed silently on open(). Trying to send would be pointless.
162 for message
in email_messages
:
163 sent
= self
._send
(message
)
170 def _send(self
, email_message
):
171 """A helper method that does the actual sending."""
172 if not email_message
.to
:
175 self
.connection
.sendmail(email_message
.from_email
,
176 email_message
.recipients(),
177 email_message
.message().as_string())
179 if not self
.fail_silently
:
184 class EmailMessage(object):
186 A container for email information.
188 content_subtype
= 'plain'
189 multipart_subtype
= 'mixed'
190 encoding
= None # None => use settings default
192 def __init__(self
, subject
='', body
='', from_email
=None, to
=None, bcc
=None,
193 connection
=None, attachments
=None, headers
=None):
195 Initialise a single email message (which can be sent to multiple
198 All strings used to create the message can be unicode strings (or UTF-8
199 bytestrings). The SafeMIMEText class will handle any necessary encoding
210 self
.from_email
= from_email
or settings
.DEFAULT_FROM_EMAIL
211 self
.subject
= subject
213 self
.attachments
= attachments
or []
214 self
.extra_headers
= headers
or {}
215 self
.connection
= connection
217 def get_connection(self
, fail_silently
=False):
218 if not self
.connection
:
219 self
.connection
= SMTPConnection(fail_silently
=fail_silently
)
220 return self
.connection
223 encoding
= self
.encoding
or settings
.DEFAULT_CHARSET
224 msg
= SafeMIMEText(smart_str(self
.body
, settings
.DEFAULT_CHARSET
), self
.content_subtype
, encoding
)
227 msg
= SafeMIMEMultipart(_subtype
=self
.multipart_subtype
)
230 for attachment
in self
.attachments
:
231 if isinstance(attachment
, MIMEBase
):
232 msg
.attach(attachment
)
234 msg
.attach(self
._create
_attachment
(*attachment
))
235 msg
['Subject'] = self
.subject
236 msg
['From'] = self
.from_email
237 msg
['To'] = ', '.join(self
.to
)
238 msg
['Date'] = formatdate()
239 msg
['Message-ID'] = make_msgid()
241 msg
['Bcc'] = ', '.join(self
.bcc
)
242 for name
, value
in self
.extra_headers
.items():
246 def recipients(self
):
248 Returns a list of all recipients of the email (includes direct
249 addressees as well as Bcc entries).
251 return self
.to
+ self
.bcc
253 def send(self
, fail_silently
=False):
254 """Send the email message."""
255 return self
.get_connection(fail_silently
).send_messages([self
])
257 def attach(self
, filename
=None, content
=None, mimetype
=None):
259 Attaches a file with the given filename and content. The filename can
260 be omitted (useful for multipart/alternative messages) and the mimetype
261 is guessed, if not provided.
263 If the first parameter is a MIMEBase subclass it is inserted directly
264 into the resulting message attachments.
266 if isinstance(filename
, MIMEBase
):
267 assert content
== mimetype
== None
268 self
.attachments
.append(filename
)
270 assert content
is not None
271 self
.attachments
.append((filename
, content
, mimetype
))
273 def attach_file(self
, path
, mimetype
=None):
274 """Attaches a file from the filesystem."""
275 filename
= os
.path
.basename(path
)
276 content
= open(path
, 'rb').read()
277 self
.attach(filename
, content
, mimetype
)
279 def _create_attachment(self
, filename
, content
, mimetype
=None):
281 Convert the filename, content, mimetype triple into a MIME attachment
285 mimetype
, _
= mimetypes
.guess_type(filename
)
287 mimetype
= DEFAULT_ATTACHMENT_MIME_TYPE
288 basetype
, subtype
= mimetype
.split('/', 1)
289 if basetype
== 'text':
290 attachment
= SafeMIMEText(smart_str(content
,
291 settings
.DEFAULT_CHARSET
), subtype
, settings
.DEFAULT_CHARSET
)
293 # Encode non-text attachments with base64.
294 attachment
= MIMEBase(basetype
, subtype
)
295 attachment
.set_payload(content
)
296 Encoders
.encode_base64(attachment
)
298 attachment
.add_header('Content-Disposition', 'attachment', filename
=filename
)
301 class EmailMultiAlternatives(EmailMessage
):
303 A version of EmailMessage that makes it easy to send multipart/alternative
304 messages. For example, including text and HTML versions of the text is
307 multipart_subtype
= 'alternative'
309 def attach_alternative(self
, content
, mimetype
=None):
310 """Attach an alternative content representation."""
311 self
.attach(content
=content
, mimetype
=mimetype
)
313 def send_mail(subject
, message
, from_email
, recipient_list
, fail_silently
=False, auth_user
=None, auth_password
=None):
315 Easy wrapper for sending a single message to a recipient list. All members
316 of the recipient list will see the other recipients in the 'To' field.
318 If auth_user is None, the EMAIL_HOST_USER setting is used.
319 If auth_password is None, the EMAIL_HOST_PASSWORD setting is used.
321 Note: The API for this method is frozen. New code wanting to extend the
322 functionality should use the EmailMessage class directly.
324 connection
= SMTPConnection(username
=auth_user
, password
=auth_password
,
325 fail_silently
=fail_silently
)
326 return EmailMessage(subject
, message
, from_email
, recipient_list
, connection
=connection
).send()
328 def send_mass_mail(datatuple
, fail_silently
=False, auth_user
=None, auth_password
=None):
330 Given a datatuple of (subject, message, from_email, recipient_list), sends
331 each message to each recipient list. Returns the number of e-mails sent.
333 If from_email is None, the DEFAULT_FROM_EMAIL setting is used.
334 If auth_user and auth_password are set, they're used to log in.
335 If auth_user is None, the EMAIL_HOST_USER setting is used.
336 If auth_password is None, the EMAIL_HOST_PASSWORD setting is used.
338 Note: The API for this method is frozen. New code wanting to extend the
339 functionality should use the EmailMessage class directly.
341 connection
= SMTPConnection(username
=auth_user
, password
=auth_password
,
342 fail_silently
=fail_silently
)
343 messages
= [EmailMessage(subject
, message
, sender
, recipient
) for subject
, message
, sender
, recipient
in datatuple
]
344 return connection
.send_messages(messages
)
346 def mail_admins(subject
, message
, fail_silently
=False):
347 "Sends a message to the admins, as defined by the ADMINS setting."
348 EmailMessage(settings
.EMAIL_SUBJECT_PREFIX
+ subject
, message
,
349 settings
.SERVER_EMAIL
, [a
[1] for a
in
350 settings
.ADMINS
]).send(fail_silently
=fail_silently
)
352 def mail_managers(subject
, message
, fail_silently
=False):
353 "Sends a message to the managers, as defined by the MANAGERS setting."
354 EmailMessage(settings
.EMAIL_SUBJECT_PREFIX
+ subject
, message
,
355 settings
.SERVER_EMAIL
, [a
[1] for a
in
356 settings
.MANAGERS
]).send(fail_silently
=fail_silently
)