236

After much searching I couldn't find out how to use smtplib.sendmail to send to multiple recipients. The problem was every time the mail would be sent the mail headers would appear to contain multiple addresses, but in fact only the first recipient would receive the email.

The problem seems to be that the email.Message module expects something different than the smtplib.sendmail() function.

In short, to send to multiple recipients you should set the header to be a string of comma delimited email addresses. The sendmail() parameter to_addrs however should be a list of email addresses.

from email.MIMEMultipart import MIMEMultipart
from email.MIMEText import MIMEText
import smtplib

msg = MIMEMultipart()
msg["Subject"] = "Example"
msg["From"] = "me@example.com"
msg["To"] = "malcom@example.com,reynolds@example.com,firefly@example.com"
msg["Cc"] = "serenity@example.com,inara@example.com"
body = MIMEText("example email body")
msg.attach(body)
smtp = smtplib.SMTP("mailhost.example.com", 25)
smtp.sendmail(msg["From"], msg["To"].split(",") + msg["Cc"].split(","), msg.as_string())
smtp.quit()
Martey
  • 1,631
  • 2
  • 13
  • 23
user1148320
  • 2,361
  • 2
  • 12
  • 3
  • 1
    It appears OP answered his own question: `sendmail` needs a list. – Cees Timmerman May 20 '15 at 08:35
  • possible duplicate of [Is there any way to add multiple receivers in Python SMTPlib?](http://stackoverflow.com/questions/8729071/is-there-any-way-to-add-multiple-receivers-in-python-smtplib) – Cees Timmerman May 20 '15 at 08:42
  • 1
    Using Python3 I had to loop through recipients; `for addr in recipients: msg['To'] = addr` and then it worked. Multiple assignments actually appends a new 'To' header for each one. This is a very bizarre interface, I can't even explain how I thought to try it. I was even considering using `subprocess` to call the unix `sendmail` package to save my sanity before I figured this out. – mehtunguh Nov 07 '18 at 21:47

13 Answers13

356

This really works, I spent a lot of time trying multiple variants.

import smtplib
from email.mime.text import MIMEText

s = smtplib.SMTP('smtp.uk.xensource.com')
s.set_debuglevel(1)
msg = MIMEText("""body""")
sender = 'me@example.com'
recipients = ['john.doe@example.com', 'john.smith@example.co.uk']
msg['Subject'] = "subject line"
msg['From'] = sender
msg['To'] = ", ".join(recipients)
s.sendmail(sender, recipients, msg.as_string())
sorin
  • 137,198
  • 150
  • 472
  • 707
  • 4
    the documentation does have the example: `tolist =["one@one.org","two@two.org","three@three.org","four@four.org"]` – chug2k Sep 23 '13 at 22:27
  • 2
    thank you @sorin for this script. I was having a problem to send an email from a python script and with this piece of code, i can now send the email. – fear_matrix Jul 14 '15 at 10:36
  • 15
    This will not send to multiple recipients if you are using Python 3 you need `send_message` instead of `sendmail` as per Antoine's comment below and the Python docs https://docs.python.org/3/library/email.examples.html – cardamom Jun 08 '17 at 16:35
  • 1
    You have to use for each traverse that recipients for sendmail, otherwise only first element will receive the mail. – Johnny Aug 10 '17 at 13:29
  • 2
    correction to the url mentioned above: https://docs.python.org/3/library/email.examples.html – David Oct 18 '17 at 14:41
  • Since Python 3.3 you should use the new API [email.message](https://docs.python.org/3/library/email.message.html) – Guillaume Algis Mar 23 '18 at 16:18
  • 1
    Note, using s.sendmail(sender, recipients, msg.as_string()) works fine for me on 3.7, and s.send_message(msg) also works. – lobi Oct 27 '19 at 20:25
  • It looks like send to all, but only the first address receives the mail. You should use list format in s.sendmail function – Orhan Solak May 28 '21 at 10:07
167

The msg['To'] needs to be a string:

msg['To'] = "a@b.com, b@b.com, c@b.com"

While the recipients in sendmail(sender, recipients, message) needs to be a list:

sendmail("a@a.com", ["a@b.com", "b@b.com", "c@b.com"], "Howdy")
Adrian Petrescu
  • 13,816
  • 5
  • 52
  • 77
dvdhns
  • 2,498
  • 2
  • 11
  • 11
  • 3
    This is one strange design decision for `smtplib.` – Adam Matan Jul 26 '15 at 07:43
  • 2
    `recipients` does not have to be a list - if a string is given, it is treated as a list with one element. The`msg['To']` string can simply be omitted. – Suzana Dec 29 '15 at 15:47
  • 1
    I don't really understand, how 'a@a.com, b@b.com' is parsed so only the first address gets the email. But, thanks! This is the answer, had to put list in there. – antonavy Nov 25 '16 at 13:19
  • worked for me, and it is consistent with documentation in https://docs.python.org/2/library/email-examples.html – Rodrigo Laguna Mar 20 '17 at 18:11
49

You need to understand the difference between the visible address of an email, and the delivery.

msg["To"] is essentially what is printed on the letter. It doesn't actually have any effect. Except that your email client, just like the regular post officer, will assume that this is who you want to send the email to.

The actual delivery however can work quite different. So you can drop the email (or a copy) into the post box of someone completely different.

There are various reasons for this. For example forwarding. The To: header field doesn't change on forwarding, however the email is dropped into a different mailbox.

The smtp.sendmail command now takes care of the actual delivery. email.Message is the contents of the letter only, not the delivery.

In low-level SMTP, you need to give the receipients one-by-one, which is why a list of adresses (not including names!) is the sensible API.

For the header, it can also contain for example the name, e.g. To: First Last <email@addr.tld>, Other User <other@mail.tld>. Your code example therefore is not recommended, as it will fail delivering this mail, since just by splitting it on , you still not not have the valid adresses!

Has QUIT--Anony-Mousse
  • 70,714
  • 12
  • 123
  • 184
  • 2
    RFC 2822 imposes a maximum width of 988 characters for a given header and a recommended width of 78 characters. You will need to ensure you "fold" the header if you have too many addresses. – Steve Hunt Jun 11 '14 at 21:53
  • This should be the accepted answer, as it actually explains the why and the how. – Serrano Sep 01 '16 at 12:34
  • Great answer. What about CC and BCC email fields? I assume we also have to include CC and BCC email in smtp.send. And only CC list (and not BCC list) in the msg fields? – Tagar May 25 '17 at 02:59
  • Yes, that is how it works. Mail servers will likely drop the BCC field (to prevent this from being visible, and I don't think they all do), but they won't parse it. – Has QUIT--Anony-Mousse May 25 '17 at 07:11
27

It works for me.

import smtplib
from email.mime.text import MIMEText

s = smtplib.SMTP('smtp.uk.xensource.com')
s.set_debuglevel(1)
msg = MIMEText("""body""")
sender = 'me@example.com'
recipients = 'john.doe@example.com,john.smith@example.co.uk'
msg['Subject'] = "subject line"
msg['From'] = sender
msg['To'] = recipients
s.sendmail(sender, recipients.split(','), msg.as_string())
coolguy
  • 281
  • 3
  • 3
14

So actually the problem is that SMTP.sendmail and email.MIMEText need two different things.

email.MIMEText sets up the "To:" header for the body of the e-mail. It is ONLY used for displaying a result to the human being at the other end, and like all e-mail headers, must be a single string. (Note that it does not actually have to have anything to do with the people who actually receive the message.)

SMTP.sendmail, on the other hand, sets up the "envelope" of the message for the SMTP protocol. It needs a Python list of strings, each of which has a single address.

So, what you need to do is COMBINE the two replies you received. Set msg['To'] to a single string, but pass the raw list to sendmail:

emails = ['a.com','b.com', 'c.com']
msg['To'] = ', '.join( emails ) 
....
s.sendmail( msg['From'], emails, msg.as_string())
Syscall
  • 16,959
  • 9
  • 22
  • 41
11

I tried the below and it worked like a charm :)

rec_list =  ['first@example.com', 'second@example.com']
rec =  ', '.join(rec_list)

msg['To'] = rec

send_out = smtplib.SMTP('localhost')
send_out.sendmail(me, rec_list, msg.as_string())
MasterMind
  • 111
  • 1
  • 5
  • FYR whole simple code below: `import smtplib from email.mime.multipart import MIMEMultipart from email.mime.text import MIMEText sender = 'myEmailAddress@example.com' rec_list = ['first@example.com', 'second@example.com'] rec = ', '.join(rec_list) msg = MIMEMultipart('alternative') msg['Subject'] = 'The required subject' msg['From'] = sender msg['To'] = rec html = ('whatever html code') htm_part = MIMEText(html, 'html') msg.attach(htm_part) send_out = smtplib.SMTP('localhost') send_out.sendmail(sender, rec_list, msg.as_string()) send_out.quit()` – MasterMind Nov 24 '17 at 04:50
11

The solution below worked for me. It successfully sends an email to multiple recipients, including "CC" and "BCC."

toaddr = ['mailid_1','mailid_2']
cc = ['mailid_3','mailid_4']
bcc = ['mailid_5','mailid_6']
subject = 'Email from Python Code'
fromaddr = 'sender_mailid'
message = "\n  !! Hello... !!"

msg['From'] = fromaddr
msg['To'] = ', '.join(toaddr)
msg['Cc'] = ', '.join(cc)
msg['Bcc'] = ', '.join(bcc)
msg['Subject'] = subject

s.sendmail(fromaddr, (toaddr+cc+bcc) , message)
3pitt
  • 655
  • 7
  • 18
Omkar
  • 2,085
  • 3
  • 15
  • 27
  • 1
    This works partly, to realy hide the BCC you must omit the BCC line bcc = ['mailid_5','mailid_6'] otherwise this will show in the header defeating the purpose of bcc. Tested with gmail and other mail server. – Wil Jul 10 '19 at 06:07
  • @Wil how would you implement BCC in that case? – 3pitt Sep 18 '19 at 15:52
7

I figured this out a few months back and blogged about it. The summary is:

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.

miken32
  • 35,483
  • 13
  • 81
  • 108
  • Python 3.7 throws an exception with message: **Exception has occurred: ValueError** _There may be at most 1 To headers in a message_ – Wojciech Jakubas Oct 19 '18 at 13:47
7

I came up with this importable module function. It uses the gmail email server in this example. Its split into header and message so you can clearly see whats going on:

import smtplib

def send_alert(subject=""):

    to = ['email@one.com', 'email2@another_email.com', 'a3rd@email.com']
    gmail_user = 'me@gmail.com'
    gmail_pwd = 'my_pass'
    smtpserver = smtplib.SMTP("smtp.gmail.com", 587)
    smtpserver.ehlo()
    smtpserver.starttls()
    smtpserver.ehlo
    smtpserver.login(gmail_user, gmail_pwd)
    header = 'To:' + ", ".join(to) + '\n' + 'From: ' + gmail_user + '\n' + 'Subject: ' + subject + '\n'
    msg = header + '\n' + subject + '\n\n'
    smtpserver.sendmail(gmail_user, to, msg)
    smtpserver.close()
radtek
  • 26,590
  • 9
  • 126
  • 97
7
import smtplib
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText

def sender(recipients): 

    body = 'Your email content here'
    msg = MIMEMultipart()

    msg['Subject'] = 'Email Subject'
    msg['From'] = 'your.email@gmail.com'
    msg['To'] = (', ').join(recipients.split(','))

    msg.attach(MIMEText(body,'plain'))

    server = smtplib.SMTP('smtp.gmail.com', 587)
    server.starttls()
    server.login('your.email@gmail.com', 'yourpassword')
    server.send_message(msg)
    server.quit()

if __name__ == '__main__':
    sender('email_1@domain.com,email_2@domain.com')

It only worked for me with send_message function and using the join function in the list whith recipients, python 3.6.

5

Well, the method in this asnwer method did not work for me. I don't know, maybe this is a Python3 (I am using the 3.4 version) or gmail related issue, but after some tries, the solution that worked for me, was the line

s.send_message(msg)

instead of

s.sendmail(sender, recipients, msg.as_string())
Community
  • 1
  • 1
Antoine
  • 664
  • 6
  • 19
5

I use python 3.6 and the following code works for me

email_send = 'xxxxx@xxx.xxx,xxxx@xxx.xxx'
server.sendmail(email_user,email_send.split(','),text)    
Robie
  • 74
  • 1
  • 2
0

you can try this when you write the recpient emails on a text file

from email.mime.text import MIMEText
from email.header import Header
import smtplib

f =  open('emails.txt', 'r').readlines()
for n in f:
     emails = n.rstrip()
server = smtplib.SMTP('smtp.uk.xensource.com')
server.ehlo()
server.starttls()
body = "Test Email"
subject = "Test"
from = "me@example.com"
to = emails
msg = MIMEText(body,'plain','utf-8')
msg['Subject'] = Header(subject, 'utf-8')
msg['From'] =  Header(from, 'utf-8')
msg['To'] = Header(to, 'utf-8')
text = msg.as_string()
try:
   server.send(from, emails, text)
   print('Message Sent Succesfully')
except:
   print('There Was An Error While Sending The Message')
Skiller Dz
  • 739
  • 7
  • 16