How to send beautiful emails with attachments (yes, cat pics too) using only Python

Did you know you can send emails using Python? Not only that, you can send files attached too!

And instead of using the black-text-white-background plain text most people use, you can send your emails as HTML files, to add some flavour to your emails, like this one:

Send emails using Python final email

In just a few minutes, you will learn how to create an SMTP server to send emails using Python to a list of clients, students, friends…

In this lesson you’ll learn how to:

  • Send one email to one person
  • Add information such as your name, the receiver name or a subject to make it more human-like
  • Send one email to multiple people
  • Make a beautiful email using HTML
  • Attach files to the emails we sent
Video version of the lesson

Table of contents
Setting up everything
Simple, one to one email sender
Sending emails to a list of people
Sending beautiful emails with HTML
Add files to your emails – The mandatory cat.gif example
Conclusion

Setting up everything

We are going to send emails using a Gmail account. Before anything, create a gmail account (or use your own) and enable the “Less secure app access” in your account. You can do it here: https://myaccount.google.com/u/0/security?hl=en&pli=1

Less secure access

After that, you are set! Create an environment with Python (I use pipenv, so pipenv shell) and you are ready to go!


Simple, one to one email sender

Let’s start by creating a simple script that sends one text email to other people from your account (I use pipenv, so I create one with pipenv shell):

Create a python file, for example email_server.py, and write:

import smtplib, ssl


# User configuration
sender_email = YOUR_EMAIL
receiver_email = RECEIVER_EMAIL
password = input('Please, type your password:\n')

# Email text
email_body = '''
	This is a test email sent by Python. Isn't that cool?
'''

smtp is a Python module we will use to create an SMTP (Simple Mail Transfer Protocol) session object that will send any email we want. ssl module provides access to TLS (Transport Layer Security) to help us making it more secure.

Then, we’ll add some variables such as the sender and receiver email, a prompt to the user to enter the email password and a body to the email. Replace the values in uppercase with yours. For testing purposes, I use the same email.

After having our configuration, let’s write the function that will send the emails:

print("Sending the email...")
try:
		# Creating a SMTP session | use 587 with TLS, 465 SSL and 25
		server = smtplib.SMTP('smtp.gmail.com', 587)
		# Encrypts the email
		context = ssl.create_default_context()
		server.starttls(context=context)
		# We log in into our Google account
		server.login(sender_email, password)
		# Sending email from sender, to receiver with the email body
		server.sendmail(sender_email, receiver_email, email_body)
		print('Email sent!')
except Exception as e:
		print(f'Oh no! Something bad happened!\n {e}')
finally:
		print('Closing the server...')
		server.quit()

This function will:

  • Create a smtplib.SMTP instance. We use gmail so we send it using the host smtp.gmail.com and the port 587. You can use other providers (such as hotmail) by using their host and port.
  • Encrypt the email using the starttls method.
  • Log into our Google email account
  • Send the email using the sender email, to the receiver email, and the message is just a plain text.
  • Use a try, except to catch all the errors and finallyi close the SMTP instance.

Run the code now and you’ll receive your email!

This image has an empty alt attribute; its file name is image-32.png

But we are not here to send just an email. We can do that ourselves!

Let’s send now multiple emails to a list.


Sending emails to a list of people

To send multiple emails, we only need to perform a few tweaks here and there. Let’s also make our emails more personal adding a subject to the email and information about the sender:

Send emails using Python personalized

First, let’s write more human-like emails:

import smtplib, ssl
from email.mime.text import MIMEText # New line
from email.utils import formataddr  # New line


# User configuration
sender_email = YOUR_EMAIL
sender_name = YOUR NAME  # New line
password = input('Please, type your password:\n')	

receiver_email = RECEIVER_EMAIL
receiver_name = RECEIVER_NAME  # New line

Add the lines with the ‘# New line’ comment. We are importing the MIMEText( Multi-Purpose Internet Mail Extensions ) to configure our emails and a formataddr helper method.

After the first print but before the try, add this configuration:

# Configurating user's info
msg = MIMEText(email_body, 'plain')
msg['To'] = formataddr((receiver_name, receiver_email))
msg['From'] = formataddr((sender_name, sender_email))
msg['Subject'] = 'Hello, my friend ' + receiver_name

Now, we create a MIMEText instance with email_body as plain text, we set our ‘To’ text to the receiver name and email, the ‘From’ text to our name and email and the subject to ‘Hello, my friend NAME’.

Finally, modify the sender.sendmail method to:

server.sendmail(sender_email, receiver_email, msg.as_string())

Send the email again:

Nice, now has less spammy vibes. Let’s send that 100% normal, not-fake, email to a list of people:

# receiver_email = RECEIVER_EMAIL
# receiver_name = RECEIVER_NAME

receiver_emails = [RECEIVER_EMAIL_1, RECEIVER_EMAIL_2, RECEIVER_EMAIL_3]
receiver_names = [RECEIVER_NAME_1, RECEIVER_NAME_2, RECEIVER_NAME_3]

As we are going to iterate over the receiver emails and names, precede the code bellow the receiver_names variable with this for-loop and tab the code so it belongs to the loop:

for receiver_email, receiver_name in zip(receiver_emails, receiver_names):
    print("Sending the email...")
    # Configurating user's info
    msg = MIMEText(email_body, 'plain')
    ...

This should be your code right now:

import smtplib
import ssl
from email.mime.text import MIMEText  # New line
from email.utils import formataddr  # New line

# User configuration
sender_email = YOUR_EMAIL
sender_name = YOUR NAME
password = input('Please, type your password:\n')

receiver_emails = [RECEIVER_EMAIL_1, RECEIVER_EMAIL_2, RECEIVER_EMAIL_3]
receiver_names = [RECEIVER_NAME_1, RECEIVER_NAME_2, RECEIVER_NAME_3]

# Email text
email_body = '''
	This is a test email sent by Python. Isn't that cool?
'''

for receiver_email, receiver_name in zip(receiver_emails, receiver_names):
    print("Sending the email...")
    # Configurating user's info
    msg = MIMEText(email_body, 'plain')
    msg['To'] = formataddr((receiver_name, receiver_email))
    msg['From'] = formataddr((sender_name, sender_email))
    msg['Subject'] = 'Hello, my friend ' + receiver_name
    try:
        # Creating a SMTP session | use 587 with TLS, 465 SSL and 25
        server = smtplib.SMTP('smtp.gmail.com', 587)
        # Encrypts the email
        context = ssl.create_default_context()
        server.starttls(context=context)
        # We log in into our Google account
        server.login(sender_email, password)
        # Sending email from sender, to receiver with the email body
        server.sendmail(sender_email, receiver_email, msg.as_string())
        print('Email sent!')
    except Exception as e:
        print(f'Oh no! Something bad happened!\n {e}')
    finally:
        print('Closing the server...')
        server.quit()

Run the code and your email will be send to 3 different email address!

Here we are sending an email to 3 different people, but you can send it to hundreds of your students, followers, blog readers, etc! And…

…they will get an ugly email.

I promised you sending beautiful emails with Python that can draw people to whatever you want. So let’s go!


Sending beautiful emails with HTML

It’s pretty simple. Now we are sending a string as a email_body, right?

Instead of that, we are going to send a string that contains HTML code.

First, let’s create our HTML file. Use a code editor such as VS Code or Atom, or create it with an online editor such as https://html5-editor.net/.

Save it into an HTML file (mine is email.html).

Now, place email_body with this:

# Email body
email_html = open('email.html')
email_body = email_html.read()

Replace the old line with the new one:

# msg = MIMEText(email_body, 'plain') # Old line
msg = MIMEText(email_body, 'html') # New line

Run the code and this is the result:

Send emails using Python

Pretty cool, right?

But what if we want to send also invoices, pdfs, excel files, videos, or some other file?


Add files to your emails – The mandatory cat.gif example

To add files, instead of sending one object, the HTML, we are going to send multiple parts. One being the HTML, other the gif. To do so, add this:

import smtplib, ssl
from email.mime.text import MIMEText
from email.utils import formataddr
from email.mime.multipart import MIMEMultipart # New line
from email.mime.base import MIMEBase # New line
from email import encoders # New line

....
# Email body
email_html = open('email.html')
email_body = email_html.read()

filename = 'cat.gif' # New line

Remember to add a file called ‘cat.gif’ to the folder or replace it with one of your own.

Replace the msg = MIMEText(email_body, ‘html’) creation with this:

    msg = MIMEMultipart()
    msg['To'] = formataddr((receiver_name, receiver_email))
    msg['From'] = formataddr((sender_name, sender_email))
    msg['Subject'] = 'Hello, my friend ' + receiver_name

   msg.attach(MIMEText(email_body, 'html'))

This will create the first part of the body, the one with the HTML, now let’s add the attachment:

try:
			# Open PDF file in binary mode
			with open(filename, "rb") 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}",
			)

			msg.attach(part)
		except Exception as e:
				print(f'Oh no! We didn\'t found the attachment!\n{e}')

This will:

  • Load the file attached
  • Encode it
  • Add it to the header as attachment
  • Add the part to the MIMEMultipart
  • Throw and error if the file isn’t found.

Check your code with mine:

import smtplib
import ssl
from email.mime.text import MIMEText
from email.utils import formataddr
from email.mime.multipart import MIMEMultipart  # New line
from email.mime.base import MIMEBase  # New line
from email import encoders  # New line

# User configuration
sender_email = YOUR_EMAIL
sender_name = YOUR_NAME
password = input('Please, type your password:\n')

receiver_emails = [RECEIVER_EMAIL_1, RECEIVER_EMAIL_2, RECEIVER_EMAIL_3]
receiver_names = [RECEIVER_NAME_1, RECEIVER_NAME_2, RECEIVER_NAME_3]

# Email body
email_html = open('email.html')
email_body = email_html.read()

filename = 'cat.gif'

for receiver_email, receiver_name in zip(receiver_emails, receiver_names):
		print("Sending the email...")
		# Configurating user's info
		msg = MIMEMultipart()
		msg['To'] = formataddr((receiver_name, receiver_email))
		msg['From'] = formataddr((sender_name, sender_email))
		msg['Subject'] = 'Hello, my friend ' + receiver_name
		
		msg.attach(MIMEText(email_body, 'html'))

		try:
			# Open PDF file in binary mode
			with open(filename, "rb") 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}",
			)

			msg.attach(part)
		except Exception as e:
				print(f'Oh no! We didn\'t found the attachment!\n{e}')
				break

		try:
				# Creating a SMTP session | use 587 with TLS, 465 SSL and 25
				server = smtplib.SMTP('smtp.gmail.com', 587)
				# Encrypts the email
				context = ssl.create_default_context()
				server.starttls(context=context)
				# We log in into our Google account
				server.login(sender_email, password)
				# Sending email from sender, to receiver with the email body
				server.sendmail(sender_email, receiver_email, msg.as_string())
				print('Email sent!')
		except Exception as e:
				print(f'Oh no! Something bad happened!\n{e}')
				break
		finally:
				print('Closing the server...')
				server.quit()

Run the code… and now the attachment is in the email!

Send emails using Python

Conclusion

Writing code that lets you send an email to your email list, with attachment files and way more attractive than the boring plain black-text-white-background formula, is easy.

While this is a one-file script, you can easily incorporate it to an actual Python framework (Flask, Django….) web app, another program you are actually using or improve the current one to perform more functions.

Creating and sending emails never has been so easy with Python!


My Youtube tutorial videos

Final code on Github

Reach to me on Twitter

How to send emails from Gmail with Yagmail