debian-quixote3/quixote/sendmail.py

255 lines
9.4 KiB
Python

"""quixote.sendmail
Tools for sending mail from Quixote applications.
"""
import re
from smtplib import SMTP
import quixote
rfc822_specials_re = re.compile(r'[\(\)\<\>\@\,\;\:\\\"\.\[\]]')
class RFC822Mailbox:
"""
In RFC 822, a "mailbox" is either a bare e-mail address or a bare
e-mail address coupled with a chunk of text, most often someone's
name. Eg. the following are all "mailboxes" in the RFC 822 grammar:
luser@example.com
Joe Luser <luser@example.com>
Paddy O'Reilly <paddy@example.ie>
"Smith, John" <smith@example.com>
Dick & Jane <dickjane@example.net>
"Tom, Dick, & Harry" <tdh@example.org>
This class represents an (addr_spec, real_name) pair and takes care
of quoting the real_name according to RFC 822's rules for you.
Just use the format() method and it will spit out a properly-
quoted RFC 822 "mailbox".
"""
def __init__(self, *args):
"""RFC822Mailbox(addr_spec : string, name : string)
RFC822Mailbox(addr_spec : string)
RFC822Mailbox((addr_spec : string, name : string))
RFC822Mailbox((addr_spec : string))
Create a new RFC822Mailbox instance. The variety of call
signatures is purely for your convenience.
"""
if (len(args) == 1 and type(args[0]) is tuple):
args = args[0]
if len(args) == 1:
addr_spec = args[0]
real_name = None
elif len(args) == 2:
(addr_spec, real_name) = args
else:
raise TypeError(
"invalid number of arguments: "
"expected 1 or 2 strings or "
"a tuple of 1 or 2 strings")
self.addr_spec = addr_spec
self.real_name = real_name
def __str__(self):
return self.addr_spec
def __repr__(self):
return "<%s at %x: %s>" % (self.__class__.__name__, id(self), self)
def format(self):
if self.real_name and rfc822_specials_re.search(self.real_name):
return '"%s" <%s>' % (self.real_name.replace('"', '\\"'),
self.addr_spec)
elif self.real_name:
return '%s <%s>' % (self.real_name, self.addr_spec)
else:
return self.addr_spec
def _ensure_mailbox(s):
"""_ensure_mailbox(s : string |
(string,) |
(string, string) |
RFC822Mailbox |
None)
-> RFC822Mailbox | None
If s is a string, or a tuple of 1 or 2 strings, returns an
RFC822Mailbox encapsulating them as an addr_spec and real_name. If
s is already an RFC822Mailbox, returns s. If s is None, returns
None.
"""
if s is None or isinstance(s, RFC822Mailbox):
return s
else:
return RFC822Mailbox(s)
# Maximum number of recipients that will be explicitly listed in
# any single message header. Eg. if MAX_HEADER_RECIPIENTS is 10,
# there could be up to 10 "To" recipients and 10 "CC" recipients
# explicitly listed in the message headers.
MAX_HEADER_RECIPIENTS = 10
def _add_recip_headers(headers, field_name, addrs):
if not addrs:
return
addrs = [addr.format() for addr in addrs]
if len(addrs) == 1:
headers.append("%s: %s" % (field_name, addrs[0]))
elif len(addrs) <= MAX_HEADER_RECIPIENTS:
headers.append("%s: %s," % (field_name, addrs[0]))
for addr in addrs[1:-1]:
headers.append(" %s," % addr)
headers.append(" %s" % addrs[-1])
else:
headers.append("%s: (long recipient list suppressed) : ;" % field_name)
def sendmail(subject, msg_body, to_addrs,
from_addr=None, cc_addrs=None,
extra_headers=None,
smtp_sender=None, smtp_recipients=None,
mail_server=None, mail_debug_addr=None,
config=None):
"""
Send an email message to a list of recipients via a local SMTP
server. In normal use, you supply a list of primary recipient
e-mail addresses in 'to_addrs', an optional list of secondary
recipient addresses in 'cc_addrs', and a sender address in
'from_addr'. sendmail() then constructs a message using those
addresses, 'subject', and 'msg_body', and mails the message to every
recipient address. (Specifically, it connects to the mail server
named in the MAIL_SERVER config variable -- default "localhost" --
and instructs the server to send the message to every recipient
address in 'to_addrs' and 'cc_addrs'.)
'from_addr' is optional because web applications often have a common
e-mail sender address, such as "webmaster@example.com". Just set
the Quixote config variable MAIL_FROM, and it will be used as the
default sender (both header and envelope) for all e-mail sent by
sendmail().
E-mail addresses can be specified a number of ways. The most
efficient is to supply instances of RFC822Mailbox, which bundles a
bare e-mail address (aka "addr_spec" from the RFC 822 grammar) and
real name together in a readily-formattable object. You can also
supply an (addr_spec, real_name) tuple, or an addr_spec on its own.
The latter two are converted into RFC822Mailbox objects for
formatting, which is why it may be more efficient to construct
RFC822Mailbox objects yourself.
Thus, the following are all equivalent in terms of who gets the
message:
sendmail(to_addrs=["joe@example.com"], ...)
sendmail(to_addrs=[("joe@example.com", "Joe User")], ...)
sendmail(to_addrs=[RFC822Mailbox("joe@example.com", "Joe User")], ...)
...although the "To" header will be slightly different. In the
first case, it will be
To: joe@example.com
while in the other two, it will be:
To: Joe User <joe@example.com>
which is a little more user-friendly.
In more advanced usage, you might wish to specify the SMTP sender
and recipient addresses separately. For example, if you want your
application to send mail to users that looks like it comes from a
real human being, but you don't want that human being to get the
bounce messages from the mailing, you might do this:
sendmail(to_addrs=user_list,
...,
from_addr=("realuser@example.com", "A Real User"),
smtp_sender="postmaster@example.com")
End users will see mail from "A Real User <realuser@example.com>" in
their inbox, but bounces will go to postmaster@example.com.
One use of different header and envelope recipients is for
testing/debugging. If you want to test that your application is
sending the right mail to bigboss@example.com without filling
bigboss' inbox with dross, you might do this:
sendmail(to_addrs=["bigboss@example.com"],
...,
smtp_recipients=["developers@example.com"])
This is so useful that it's a Quixote configuration option: just set
MAIL_DEBUG_ADDR to (eg.) "developers@example.com", and every message
that sendmail() would send out is diverted to the debug address.
Generally raises an exception on any SMTP errors; see smtplib (in
the standard library documentation) for details.
"""
if not mail_server and config is None:
from quixote import get_publisher
config = get_publisher().config
from_addr = from_addr or config.mail_from
mail_server = mail_server or config.mail_server
if config is not None:
mail_debug_addr = mail_debug_addr or config.mail_debug_addr
if not isinstance(to_addrs, list):
raise TypeError("'to_addrs' must be a list")
if not (cc_addrs is None or isinstance(cc_addrs, list)):
raise TypeError("'cc_addrs' must be a list or None")
# Make sure we have a "From" address
if from_addr is None:
raise RuntimeError(
"no from_addr supplied, and MAIL_FROM not set in config file")
# Ensure all of our addresses are really RFC822Mailbox objects.
from_addr = _ensure_mailbox(from_addr)
to_addrs = list(map(_ensure_mailbox, to_addrs))
if cc_addrs:
cc_addrs = list(map(_ensure_mailbox, cc_addrs))
# Start building the message headers.
headers = ["From: %s" % from_addr.format(),
"Subject: %s" % subject]
_add_recip_headers(headers, "To", to_addrs)
if cc_addrs:
_add_recip_headers(headers, "Cc", cc_addrs)
if extra_headers:
headers.extend(extra_headers)
if mail_debug_addr:
debug1 = ("[debug mode, message actually sent to %s]\n"
% mail_debug_addr)
if smtp_recipients:
debug2 = ("[original SMTP recipients: %s]\n"
% ", ".join(smtp_recipients))
else:
debug2 = ""
sep = ("-"*72) + "\n"
msg_body = debug1 + debug2 + sep + msg_body
smtp_recipients = [mail_debug_addr]
if smtp_sender is None:
smtp_sender = from_addr.addr_spec
else:
smtp_sender = _ensure_mailbox(smtp_sender).addr_spec
if smtp_recipients is None:
smtp_recipients = [addr.addr_spec for addr in to_addrs]
if cc_addrs:
smtp_recipients.extend([addr.addr_spec for addr in cc_addrs])
else:
smtp_recipients = [_ensure_mailbox(recip).addr_spec
for recip in smtp_recipients]
message = "\n".join(headers) + "\n\n" + msg_body
smtp = SMTP(mail_server)
smtp.sendmail(smtp_sender, smtp_recipients, message)
smtp.quit()