Multiple recipients with Python’s smtplib sendmail method
I’m currently the defacto maintainer for the Solaris 11++ gate tools, and recently found a need to add support to our mail utility for sending to multiple recipients. We use the mail utility from most of our Mercurial gatehooks so we can notify Relevant People(tm) when things happen.
TL;DR – skip to the Summary
Anyway, my first cut of the change didn’t work, even though the debug log seemed to show the right stuff:
lib $ ./mail.py -n -s "This is a test, please to be ignoring" -x "nofilter" -r jmcp -T alan@domain,valerie@domain,bart@domain This is yet another test, this time with text in the body. jmcp . send: 'ehlo localhostname\r\n' reply: '250-localhostname Hello localhostname [myIPaddr], pleased to meet you\r\n' reply: '250-ENHANCEDSTATUSCODES\r\n' reply: '250-PIPELINING\r\n' reply: '250-EXPN\r\n' reply: '250-VERB\r\n' reply: '250-8BITMIME\r\n' reply: '250-SIZE\r\n' reply: '250-DSN\r\n' reply: '250-ETRN\r\n' reply: '250-DELIVERBY\r\n' reply: '250 HELP\r\n' reply: retcode (250); Msg: localhostname Hello localhostname [myIPaddr], pleased to meet you ENHANCEDSTATUSCODES PIPELINING EXPN VERB 8BITMIME SIZE DSN ETRN DELIVERBY HELP send: 'mail FROM:<jmcp> size=264\r\n' reply: '250 2.1.0 <jmcp>... Sender ok\r\n' reply: retcode (250); Msg: 2.1.0 <jmcp>... Sender ok send: 'rcpt TO:<alan@domain>\r\n' reply: '250 2.1.5 <alan@domain>... Recipient ok\r\n' reply: retcode (250); Msg: 2.1.5 <alan@domain>... Recipient ok send: 'data\r\n' reply: '354 Enter mail, end with "." on a line by itself\r\n' reply: retcode (354); Msg: Enter mail, end with "." on a line by itself data: (354, 'Enter mail, end with "." on a line by itself') send: 'From: jmcp\r\nTo: alan@domain, valerie@domain,\r\n\tbart@domain\r\nSubject: This is a test, please to be ignoring\r\nX-gate-filter: nofilter\r\nX-gate-script: ./mail.py\r\n\r\n\r\nThis is yet another test, this time with text in the body.\r\n\r\njmcp\r\n..\r\n.\r\n' reply: '250 2.0.0 q050HwfD015209 Message accepted for delivery\r\n' reply: retcode (250); Msg: 2.0.0 q050HwfD015209 Message accepted for delivery data: (250, '2.0.0 q050HwfD015209 Message accepted for delivery') send: 'quit\r\n' reply: '221 2.0.0 localhostname closing connection\r\n' reply: retcode (221); Msg: 2.0.0 localhostname closing connection
There’s only one rcpt
in that output, which isn’t what I wanted at all.
After much re-reading of the python library pdf I realised that I needed to
add the recipients as a list, rather than a string. One quick mod later and I
was adding elements correctly – or so I thought. Having an actual list in the
email headers made the email.Message.as_string()
method die horribly. Back
to the drawing board.
I eventually realised that I should use the email.Message.add_header()
method instead of the lazy shorthand of just appending to msg['To']
. That
got the right headers added and email.Message.as_string()
didn’t die any
more. However, calling
connection.sendmail(self.msg['From'], self.msg.get('To'), self.msg.as_string())
still only gave me one recipient. Fortunately, there’s
email.Message.get_all()
, which does what I want.
So the summary is this: If you want to use smtplib to send email to multiple
recipients, use email.Message.add_header('To', eachRecipientAsString)
to
add them, and then when you invoke the sendmail method, use
email.Message.get_all('To')
send the message to all of them. Ditto for Cc
and Bcc recipients.
For the curious, here’s a link to the diff of the lib/mail.py changes