Updated a docstring on a couple of email functions to stop the complaints.
[django.git] / django / core / mail.py
blob72343cb4dfff85a997b6a681106ec96807b42526
1 """
2 Tools for sending email.
3 """
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
13 import mimetypes
14 import os
15 import smtplib
16 import socket
17 import time
18 import random
20 # Don't BASE64-encode UTF-8 messages so that we avoid unwanted attention from
21 # some spam filters.
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):
31 def __str__(self):
32 return self.get_fqdn()
34 def get_fqdn(self):
35 if not hasattr(self, '_fqdn'):
36 self._fqdn = socket.getfqdn()
37 return self._fqdn
39 DNS_NAME = CachedDnsName()
41 # Copied from Python standard library and modified to used the cached hostname
42 # for performance.
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.
50 """
51 timeval = time.time()
52 utcdate = time.strftime('%Y%m%d%H%M%S', time.gmtime(timeval))
53 try:
54 pid = os.getpid()
55 except AttributeError:
56 # Not getpid() in Jython, for example.
57 pid = 1
58 randint = random.randrange(100000)
59 if idstring is None:
60 idstring = ''
61 else:
62 idstring = '.' + idstring
63 idhost = DNS_NAME
64 msgid = '<%s.%s.%s%s@%s>' % (utcdate, pid, randint, idstring, idhost)
65 return msgid
67 class BadHeaderError(ValueError):
68 pass
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))
74 try:
75 val = force_unicode(val).encode('ascii')
76 except UnicodeEncodeError:
77 if name.lower() in ('to', 'from', 'cc'):
78 result = []
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)
84 else:
85 val = Header(force_unicode(val), settings.DEFAULT_CHARSET)
86 return name, val
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):
99 """
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
113 def open(self):
115 Ensure we have a connection to the email server. Returns whether or not
116 a new connection was required.
118 if self.connection:
119 # Nothing to do if the connection is already open.
120 return False
121 try:
122 self.connection = smtplib.SMTP(self.host, self.port)
123 if self.use_tls:
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)
129 return True
130 except:
131 if not self.fail_silently:
132 raise
134 def close(self):
135 """Close the connection to the email server."""
136 try:
137 try:
138 self.connection.quit()
139 except socket.sslerror:
140 # This happens when calling quit() on a TLS connection
141 # sometimes.
142 self.connection.close()
143 except:
144 if self.fail_silently:
145 return
146 raise
147 finally:
148 self.connection = None
150 def send_messages(self, email_messages):
152 Send one or more EmailMessage objects and return the number of email
153 messages sent.
155 if not email_messages:
156 return
157 new_conn_created = self.open()
158 if not self.connection:
159 # We failed silently on open(). Trying to send would be pointless.
160 return
161 num_sent = 0
162 for message in email_messages:
163 sent = self._send(message)
164 if sent:
165 num_sent += 1
166 if new_conn_created:
167 self.close()
168 return num_sent
170 def _send(self, email_message):
171 """A helper method that does the actual sending."""
172 if not email_message.to:
173 return False
174 try:
175 self.connection.sendmail(email_message.from_email,
176 email_message.recipients(),
177 email_message.message().as_string())
178 except:
179 if not self.fail_silently:
180 raise
181 return False
182 return True
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
196 recipients).
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
200 conversions.
202 if to:
203 self.to = list(to)
204 else:
205 self.to = []
206 if bcc:
207 self.bcc = list(bcc)
208 else:
209 self.bcc = []
210 self.from_email = from_email or settings.DEFAULT_FROM_EMAIL
211 self.subject = subject
212 self.body = body
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
222 def message(self):
223 encoding = self.encoding or settings.DEFAULT_CHARSET
224 msg = SafeMIMEText(smart_str(self.body, settings.DEFAULT_CHARSET), self.content_subtype, encoding)
225 if self.attachments:
226 body_msg = msg
227 msg = SafeMIMEMultipart(_subtype=self.multipart_subtype)
228 if self.body:
229 msg.attach(body_msg)
230 for attachment in self.attachments:
231 if isinstance(attachment, MIMEBase):
232 msg.attach(attachment)
233 else:
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()
240 if self.bcc:
241 msg['Bcc'] = ', '.join(self.bcc)
242 for name, value in self.extra_headers.items():
243 msg[name] = value
244 return msg
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)
269 else:
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
282 object.
284 if mimetype is None:
285 mimetype, _ = mimetypes.guess_type(filename)
286 if mimetype is None:
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)
292 else:
293 # Encode non-text attachments with base64.
294 attachment = MIMEBase(basetype, subtype)
295 attachment.set_payload(content)
296 Encoders.encode_base64(attachment)
297 if filename:
298 attachment.add_header('Content-Disposition', 'attachment', filename=filename)
299 return attachment
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
305 made easier.
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)