ChatGPT解决这个技术问题 Extra ChatGPT

How to send email to multiple recipients using python smtplib?

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()
It appears OP answered his own question: sendmail needs a list.
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.

s
sorin

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())

the documentation does have the example: tolist =["one@one.org","two@two.org","three@three.org","four@four.org"]
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.
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 docs.python.org/3/library/email.examples.html
You have to use for each traverse that recipients for sendmail, otherwise only first element will receive the mail.
correction to the url mentioned above: docs.python.org/3/library/email.examples.html
A
Adrian Petrescu

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")

This is one strange design decision for smtplib.
recipients does not have to be a list - if a string is given, it is treated as a list with one element. Themsg['To'] string can simply be omitted.
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.
worked for me, and it is consistent with documentation in docs.python.org/2/library/email-examples.html
The string "Howdy" is not a valid RFC822 message. Some mail servers will simply reject it, others will probably guess what the missing parts are, and probably find surprising things to put there. Ultimately, if something actually ends up being delivered somewhere, it will probably not be useful.
H
Has QUIT--Anony-Mousse

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!


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.
This should be the accepted answer, as it actually explains the why and the how.
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?
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.
c
coolguy

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())

what version of python are you using? I get the same problem as the original poster and I am using python 2.7.9
Why not simply recipients = ['john.doe@example.com','john.smith@example.co.uk'] instead of making it a string, and then split it to make a list?
to Woj, because msg['To'] should be a string and s.sendmail should have a list : (sender,>>>LIST HERE<<<,msg.as_string()). That's means,as annoying as it looks,that you can not use one same type [ string or list ] for both fields
Works like a charm for me. Python 3.7.3.
3
3pitt

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)

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 how would you implement BCC in that case?
@3pitt a bit late, but you just send them the same email using s.sendmail(fromaddr, bcc, message).
Some MTAs will strip the Bcc: header before sending, others won't. SMTP doesn't really care what's in the headers; it will actually attempt to deliver to the list you provide in the envelope (here, the sendmail method of smtplib) and completely ignore what's in the headers.
Your message is not a valid RFC822 message, so this should fail spectacularly.
S
Syscall

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())

M
MasterMind

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())

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()
G
Guilherme Henrique Mendes
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.


r
radtek

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()

As long as the input strings are simple short ASCII only text fragments, assembling an email message by pasting them together like this will actually work; but unless you know exactly what you are doing, you will be better off using the email library, which knows what the corner cases are and how to handle content types which are not completely plain text.
R
Robie

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)    

m
miken32

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.


Python 3.7 throws an exception with message: Exception has occurred: ValueError There may be at most 1 To headers in a message
C
Community

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())

Without details, this is rather dubious, but it seems that you managed to use the modern EmailMessage API before it was official. (It was introduced already in 3.3, but became the official and documented one in 3.6.)
S
Skiller Dz

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')

Many of the adornments here were useful with the legacy email.message.message / MIMEText API, but no longer necessary with the modern Python 3.6+ email.message.EmailMessage API.
G
Gwi7d31

There are a lot of answers on here that are technically or partially correct. After reading everyone's answers, I came up with this as a more solid/universal email function. I have confirmed it works and you can pass HTML or plain text for the body. Note that this code does not include attachment code:

import smtplib
import socket

# Import the email modules we'll need
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart

#
# @param [String] email_list
# @param [String] subject_line
# @param [String] error_message
def sendEmailAlert(email_list="default@email.com", subject_line="Default Subject", error_message="Default Error Message"):
    hostname = socket.gethostname()
    # Create message
    msg = MIMEMultipart()
    msg['Subject'] = subject_line
    msg['From'] = f'no-reply@{hostname}'
    msg['To'] = email_list
    msg.attach(MIMEText(error_message, 'html'))
    # Send the message via SMTP server
    s = smtplib.SMTP('localhost') # Change for remote mail server!
    # Verbose debugging
    s.set_debuglevel(2)
    try:
        s.sendmail(msg['From'], msg['To'].split(","), msg.as_string())
    except Exception as e:
        print(f'EMAIL ISSUE: {e}')
    s.quit()

This can obviously be modified to use native Python logging. I am just providing a solid core function. I also can't stress this enough, sendmail() wants a List and NOT a String! Function is for Python3.6+


As an aside, your code seems to be written for Python 3.5 or earlier. The email library was overhauled in 3.6 and is now quite a bit more versatile and logical. Probably throw away what you have and start over with the examples from the email documentation.
K
Kevin G

Try declaring a list variable with all recipients and cc_recipients as strings than looping over them, like this:

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

recipients = ["malcom@example.com","reynolds@example.com", "firefly@example.com"]
cc_recipients=["serenity@example.com", "inara@example.com"]
msg = MIMEMultipart()
msg["Subject"] = "Example"
msg["From"] = "me@example.com"
msg["To"] = ', '.join(recipients)
msg["Cc"] = ', '.join(cc_recipients)
body = MIMEText("example email body")
msg.attach(body)
smtp = smtplib.SMTP("mailhost.example.com", 25)
for recipient in recipients:
    smtp.sendmail(msg["From"], recipient, msg.as_string())
for cc_recipient in cc_recipients:
    smtp.sendmail(msg["From"], cc_recipient, msg.as_string())
smtp.quit()

Several of the examples here needlessly create a multipart with only a single body part. Obviously, there is no need for a multipart container when there are not multiple body parts. However, this also suffers from using the legacy pre-3.6 API in fairly recent code; you should probably throw away what you have and start over with the new API.
t
tripleee

This is an old question. My main reason to post a new answer is to explain how to solve the problem with the modern email library in Python 3.6+ and how it differs from the old version; but first, let's recap what Anony-Mousse wrote in their answer from 2012.

SMTP doesn't care at all what's in the headers. The list of recipients you pass in to the sendmail method are what actually determine where the message will be delivered.

In SMTP parlance, this is called the message's envelope. On the protocol level, you connect to the server, then tell it who the message is from (MAIL FROM: SMTP verb) and who to send it to (RCPT TO:), then separately transmit the message itself (DATA) with headers and body as one oblique string blob.

The modern smtplib simplifies the Python side of this by providing a send_message method which actually sends to the recipients specified in the message's headers.

The modern email library provides an EmailMessage object which replaces all the various individual MIME types which you had to use in the past to assemble a message from smaller parts. You can add attachments without separately constructing them, and build various more complex multipart structures if you need to, but you normally don't have to. Just create a message and populate the parts you want.

Notice that the following is heavily commented; on the whole, the new EmailMessage API is more succinct and more versatile than the old API.

from email.message import EmailMessage

msg = EmailMessage()

# This example uses explicit strings to emphasize that
# that's what these header eventually get turned into
msg["From"] = "me@example.org"
msg["To"] = "main.recipient@example.net, other.main.recipient@example.org"
msg["Cc"] = "secondary@example.com, tertiary@example.eu"
msg["Bcc"] = "invisible@example.int, undisclosed@example.org.au"
msg["Subject"] = "Hello from the other side"

msg.set_content("This is the main text/plain message.")
# You can put an HTML body instead by adding a subtype string argument "html"
# msg.set_content("<p>This is the main text/html message.</p>", "html")

# You can add attachments of various types as you see fit;
# if there are no other parts, the message will be a simple
# text/plain or text/html, but Python will change it into a
# suitable multipart/related or etc if you add more parts
with open("image.png", "rb") as picture:
    msg.add_attachment(picture.read(), maintype="image", subtype="png")

# Which port to use etc depends on the mail server.
# Traditionally, port 25 is SMTP, but modern SMTP MSA submission uses 587.
# Some servers accept encrypted SMTP_SSL on port 465.
# Here, we use SMTP instead of SMTP_SSL, but pivot to encrypted
# traffic with STARTTLS after the initial handshake.
with smtplib.SMTP("smtp.example.org", 587) as server:
    # Some servers insist on this, others are more lenient ...
    # It is technically required by ESMTP, so let's do it
    # (If you use server.login() Python will perform an EHLO first
    # if you haven't done that already, but let's cover all bases)
    server.ehlo()
    # Whether or not to use STARTTLS depends on the mail server
    server.starttls()
    # Bewilderingly, some servers require a second EHLO after STARTTLS!
    server.ehlo()
    # Login is the norm rather than the exception these days
    # but if you are connecting to a local mail server which is
    # not on the public internet, this might not be useful or even possible
    server.login("me.myself@example.org", "xyzzy")

    # Finally, send the message
    server.send_message(msg)

The ultimate visibility of the Bcc: header depends on the mail server. If you want to be really sure that the recipients are not visible to each other, perhaps don't put a Bcc: header at all, and separately enumerate the envelope recipients in the envelope like you used to have to with sendmail (send_message lets you do that too, but you don't have to if you just want to send to the recipients named in the headers).

This obviously sends a single message to all recipients in one go. That is generally what you should be doing if you are sending the same message to a lot of people. However, if each message is unique, you will need to loop over the recipients and create and send a new message for each. (Merely wishing to put the recipient's name and address in the To: header is probably not enough to warrant sending many more messages than required, but of course, sometimes you have unique content for each recipient in the body, too.)