Locked learning resources

Join us and get access to thousands of tutorials and a community of expert Pythonistas.

Unlock This Lesson

Locked learning resources

This lesson is for members only. Join us and get access to thousands of tutorials and a community of expert Pythonistas.

Unlock This Lesson

Sending Multiple Emails From a CSV File

You may come across the situation, where you want to send a certain message to different contacts. Maybe you also need to slightly change the message for each receiver. In this video, you’ll learn how to use CSV files to do exactly that! In this case you are a teacher, who wants to inform his students about their grades.

00:00 So now you can format your emails and add attachments, but if you want to send these to multiple people, you currently have to change the code each time, which is not ideal.

00:09 In this video, you’ll learn to use a CSV file as an input so that you can fire off a bunch of emails with one script. Over here, I’ve got a contacts.csv file that has the name, email, and grade for a couple of fake people.

00:23 Note the email addresses where I’ve added a plus (+), and then the person’s name to my test email. When Gmail’s routing the email, it’s going to ignore anything after the + sign, so all of these will be sent to my test email but kept separate.

00:38 This is very useful when you’re practicing sending multiple emails at once. So, let’s head back to the editor, and you’re going to start with a blank script this time.

00:47 Grab your imports. You’re going to need the csv library, smtplib, and ssl. Go ahead and make your message template.

00:58 Call that message, do a multiline string, and you’ll say """From: {sender}""", """To: {receiver}""". """Subject: Your Grades""".

01:21 """Hi {name}, your grade is {grade}.""". Our sender will be our email address, but then the receiver, name, and grade will be pulled from that CSV file.

01:36 So, let’s go ahead, make a sender, which in my case is 'catinthehacks@gmail.com'—and actually, that’s 'thecatinthehacks'.

01:50 And password. Like before, use an input().

02:00 Okay. Much of this will be the same. You’ll create a context with ssl, create a default context. Let me bring this down. Okay. With smtplib.SMTP_SSL()and because we’re not really messing around with these anymore, I’m just going to type them in as strings instead of setting variables for them.

02:27 Set your server, your port, make context=context, call this your server,

02:40 then log into that server.

02:48 All right. So up until now, just about all of this should be a review, except now you’re going to use that CSV file. So now open('contacts.csv').

03:03 And just call this file, and then make a reader which is going to be a csv.reader(). Pass in file. You’re going to skip the first row because that’s just header information that we don’t need.

03:18 And then unpack that as for name, email, grade in reader: you’re going to .sendmail(). You need to pass in your sender, the email that it’s going to, and message, which is from the message template above.

03:43 And just open that. sender is going to be sender, email is going to be email, name is name, and grade is grade.

04:00 And actually, before I send this, I think—yeah. This 'To: {receiver}' should actually just be email here to keep all the naming the same.

04:11 Okay! So if this works the way it should, it’ll log into the server like we’ve done before, and then it’s going to go through your CSV file and then for each row in there, it’s going to send an email based on the information that’s in that CSV.

04:23 So, save this and try it out!

04:30 Okay. Passing in the password.

04:36 We don’t see any errors, so let’s hop over. And what do we have here? Okay. So Your Grades, it looks like here’s one for Ron, here’s one for Rabbit, and you can see that the template has been changed for each of them. So this was sent to Ron, and Rabbit, and I think I got one more here. Yeah, there’s the third one there. All right!

04:59 So now you can see that with one script and a CSV file, you can send out multiple emails that are personalized. That’s pretty cool. You can use this kind of system if you need to verify somebody’s email for a website or if you’re running an email list with Python.

05:15 You do need to be careful, though, because it’s very easy to send out way too many spam emails, so this is definitely something to use responsibly. All right!

05:24 In the next video, you’re going to learn how to use Yagmail, which is a library to make working with Gmail even easier. Thanks for watching.

Avatar image for RetiringInComo

RetiringInComo on July 5, 2019

Thanks Joe!!

Learned a ton , also got it to work at work on Outlook. Have one question though can’t seem to combine the Multiple Emails with Multiple Attachments? Any advice?

Avatar image for Joe Tatusko

Joe Tatusko RP Team on July 8, 2019

You’re welcome! How do you have your code structured?

Let me know what type of error you’re seeing as well!

Avatar image for RetiringInComo

RetiringInComo on July 9, 2019

import email, smtplib, ssl, csv

from email import encoders
from email.mime.base import MIMEBase
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText

subject = "An email with attachment from Python"
body = "This is an email with attachment sent from Python"
sender_email = "generic@blah.com"
#receiver_email = ""


# Create a multipart message and set headers
message = MIMEMultipart()
message["From"] = sender_email
message["To"] = receiver_email
message["Subject"] = subject
message["Bcc"] = receiver_email  # Recommended for mass emails

# Add body to email
message.attach(MIMEText(body, "plain"))

filename = "test.xlsx"  # In same directory as script

# Open PDF file in binary mode
with open(filename, "rb") as attachment:
    # Add file as application/octet-stream
    # Email client can usually download this automatically as attachment
    part = MIMEBase("application", "octet-stream")
    part.set_payload(attachment.read())

# Encode file in ASCII characters to send by email    
encoders.encode_base64(part)

# Add header as key/value pair to attachment part
part.add_header(
    "Content-Disposition",
    f"attachment; filename= {filename}",
)

# Add attachment to message and convert message to string
message.attach(part)
text = message.as_string()

# Log in to server using secure context and send email

#with smtplib.SMTP("mailhost:25") as server:
    #server.sendmail(sender_email, receiver_email, text)

with smtplib.SMTP("mailhost:25") as server:
    with open("contacts_file.csv") as file:
        reader = csv.reader(file)
        next(reader)  # Skip header row
        for name, email, grade in reader:
            server.sendmail(
                sender_email,
                email,text
                #message.format(name=name,grade=grade),
            )
Avatar image for RetiringInComo

RetiringInComo on July 9, 2019

This works but sends the same message and attachment. I commented out the message.format last line since it is not supported with Mime.Base. Ultimately what I am trying to do would be to have a csv file that has name, message, attachment as the headers and use that file to send multiple emails to different people and include different attachments.

So to use your example what if you also wanted to send those three students a copy of their report (lets say the reports were excel files). Then Ron Obvious would get his email stating he got a B+ and it would also include an excel attachment, Killer Rabbit would get his grade and his own attachment etc…

Avatar image for Joe Tatusko

Joe Tatusko RP Team on July 11, 2019

Gotcha! So what you would want to do is save the attachment filepath in your csv.

Try to structure your code something like the following:

open the csv file -> loop through the rows -> create your email object -> open attachment and attach to email -> send email

It looks to me like you’re creating the email object, attaching your file, and then looping through the contacts csv so the same email object and attachment get sent over and over. Let me know if that makes sense!

Avatar image for Abby Jones

Abby Jones on July 15, 2019

I cannot proceed any further, I am getting this:

Traceback (most recent call last):
  File "/home/abbyrjones72/Documents/remote_repos/emailer/email.py", line 1, in <module>
    import smtplib, ssl
  File "/home/abbyrjones72/anaconda3/lib/python3.7/smtplib.py", line 47, in <module>
    import email.utils
  File "/home/abbyrjones72/Documents/remote_repos/emailer/email.py", line 17, in <module>
    with smtplib.SMTP_SSL("stmp.gmail.com", 465, context=context) as server:
AttributeError: module 'smtplib' has no attribute 'SMTP_SSL'

Here is my code:

import smtplib, ssl
import csv


message = """\
    From: {sender}
    To: {email}
    Subject: Your Grades

    Hi {name}, your grade is {grade}.

    """
sender = "abbyrjones72dev@gmail.com"
password = input("Please enter a password: ")
context = ssl.create_default_context()

with smtplib.SMTP_SSL("stmp.gmail.com", 465, context=context) as server:
    server.login(sender, password)
    with open("contacts.csv") as file:
        reader = csv.reader(file)
        # skip first header row
        next(reader)
        for name, email, grade in reader:
            server.sendmail(
                sender,
                email,
                message.format(sender=sender, email=email, name=name, grade=grade),
            )
Avatar image for Dan Bader

Dan Bader RP Team on July 15, 2019

@Abby: The problem is that your script is called email.py which shadows (hides) Python’s built-in email module. So instead of importing the email module from the standard library Python is trying to import your local email.py script—which doesn’t have an smtplib class, hence the AttributeError.

The easiest way to solve this is to rename your local email.py script file to something else :) More info here in this StackOverflow thread.

Avatar image for RetiringInComo

RetiringInComo on July 15, 2019

Thanks for the clue, got this to work fabiously!!

import email, smtplib, ssl, csv

from email import encoders
from email.mime.base import MIMEBase
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText

from_address = ""


with smtplib.SMTP("mailhost:25") as server:
    with open("contacts_file.csv") as file:
            reader = csv.reader(file)
            next(reader)  # Skip header row
            for name, subject_line, body, email, grade, attach in reader:

                # Create a multipart message and set headers
                message = MIMEMultipart()
                message["From"] = from_address
                message["To"] = email
                message["Subject"] = subject_line

                filename = str(attach)

                # Add body to email
                message.attach(MIMEText(body, "plain"))

                with open(filename, "rb") as attachment:
                    # Add file as application/octet-stream
                    # Email client can usually download this automatically as attachment
                    part = MIMEBase("application", "octet-stream")
                    part.set_payload(attachment.read())  

                    # Encode file in ASCII characters to send by email    
                    encoders.encode_base64(part)

                    # Add header as key/value pair to attachment part
                    part.add_header(
                    "Content-Disposition",f"attachment; filename= {filename}",)

                # Add attachment to message and convert message to string
                message.attach(part)
                text = message.as_string()

                # Use server to send email
                server.sendmail(from_address, email, text)
Avatar image for Abby Jones

Abby Jones on July 16, 2019

No, this was only after my original file main.py did the same thing.

Avatar image for Joe Tatusko

Joe Tatusko RP Team on July 16, 2019

That’s great! Glad you were able to work it out :D

Avatar image for Abby Jones

Abby Jones on July 16, 2019

Nothing worked out lol, it doesn’t work.

Avatar image for Abby Jones

Abby Jones on July 16, 2019

I’m sorry, I will figure it out.

Avatar image for Abby Jones

Abby Jones on July 16, 2019

It is now working. I made some transcription errors in a few places. Frustration will do that. Thank you Dan and Joe!

Avatar image for Joe Tatusko

Joe Tatusko RP Team on July 16, 2019

Sorry, was replying to RetiringInComo haha

Awesome that you got it too! Glad we (well, mainly Dan :P) could help out.

Avatar image for Abby Jones

Abby Jones on July 16, 2019

Yes, I am going to give that link full attention after this fiasco. This really is yet another wonderful tutorial.

Avatar image for beauenslow

beauenslow on Aug. 9, 2019

Is there a way to send multiple emails from a csv file and have the message sent as html. I have been having trouble making this work. Can you help?

Avatar image for Joe Tatusko

Joe Tatusko RP Team on Aug. 13, 2019

Sure thing! This would be similar to RetiringInComo’s question above. You need to structure your code in a way that you’re making an HTML email for each row of the CSV:

open the csv file -> loop through the rows -> create your HTML email object -> send email

Where this gets tricky is what you put in the ‘body’ column of the CSV. I’d start with adding the HTML strings directly into the CSV, then start working at how you could keep the critical information in the CSV and use formatted strings to make an HTML template that is kept outside of the CSV

Avatar image for Kalesis

Kalesis on Sept. 7, 2019

Hello, excellent course.

If with gmail to test we add “my +”, which is the simile for Office365 / Outlook, 7 Yahoo, etc.

Thank you

Become a Member to join the conversation.