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.

Summary

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