Sending Emails with Python
Table of Contents + −
In the last lesson you learned how to handle spreadsheets in Automating Excel Tasks. Now let us take automation one step further. Imagine you just generated a report. Wouldn’t it be nice if Python could email it to your team by itself? That is exactly what we will do here.
🤔 Why Send Emails From Python?
Picture this. Every Monday morning you copy some numbers. You open your mail app. You type the same message. Then you hit send. Same thing, every week. It is boring. It is also easy to forget.
The fix is simple. Python can write the email and send it for you. So you set it up once. After that, a script does the typing and sending forever.
Python ships with everything you need built in. There is no extra install. You use smtplib, which is the module that talks to a mail server. You also use email.message, which helps you build the email itself.
🧩 What Is SMTP?
Before any code, let us understand the one word you will see everywhere here. That word is SMTP.
SMTP stands for Simple Mail Transfer Protocol. In plain words, it is the system that sends mail across the internet. Think of it like the post office. You hand over a letter with a “to” address and a “from” address. The post office then carries it to the right mailbox.
Every email provider runs an SMTP server. Gmail has one. Outlook has one. To send an email from Python, your script connects to one of these servers. It proves who you are by logging in. Then it hands over the message.
Here are the pieces you always need:
- SMTP host — the address of the mail server, like
smtp.gmail.com. - Port — the door number on that server. Port
587is the common one for secure sending. - Login — your email address and a password, so the server trusts you.
- The message — who it is from, who it goes to, the subject, and the body.
🔐 First, a Serious Caution About Passwords
This part matters more than the code. So read it before anything else.
Danger
Never put your real email password in a Python file. And never type it directly into the code. If you push that code to GitHub, the whole world can read your password and take over your account.
Follow these safe habits:
- Use an app password, not your real one. An app password is a special one-time password you generate just for a script. Gmail, Outlook, and others let you create these in your account security settings. If it ever leaks, you delete that one password. Your real account stays safe.
- Read the password from an environment variable, not from the code. An environment variable is a value you store in your operating system, outside your code file. Your script reads it at runtime. So the secret never lives inside the file you share.
Here is how you set an environment variable before running your script. The first block is for macOS or Linux. The second is for Windows PowerShell.
# macOS / Linuxexport EMAIL_USER="alex@example.com"export EMAIL_PASS="your-app-password-here"# Windows PowerShell$env:EMAIL_USER = "alex@example.com"$env:EMAIL_PASS = "your-app-password-here"Then inside Python you read them with os.environ. This line grabs the stored values without ever writing the secret in the file.
import os
email_user = os.environ["EMAIL_USER"]email_pass = os.environ["EMAIL_PASS"]Tip
If os.environ["EMAIL_USER"] raises a KeyError, it means you forgot to set the variable in your terminal. Set it first, then run the script in that same terminal window.
📨 Building the Email Message
Now the fun part. Before we send anything, we build the message itself. We use the EmailMessage class from the email.message module. It is like filling out an envelope. There is the from, the to, the subject, and what is inside.
This code creates one email and fills in all four fields.
from email.message import EmailMessage
message = EmailMessage()message["From"] = "alex@example.com"message["To"] = "riya@example.com"message["Subject"] = "Weekly report is ready"message.set_content("Hi Riya,\n\nThe weekly report is attached below.\n\nThanks,\nAlex")Let us walk through it line by line:
EmailMessage()makes an empty message object, like a blank envelope.message["From"]sets who the email is from.message["To"]sets who receives it. You can pass several addresses separated by commas.message["Subject"]is the subject line the reader sees first.message.set_content(...)is the actual body text. The\ncharacters are line breaks, so the message reads on separate lines.
The why here is simple. By setting each field clearly, the message looks exactly like a normal email when it arrives. Nothing missing. Nothing odd.
🚀 Connecting and Sending
We have a message. Now we connect to the SMTP server, log in, and send it. We will use Gmail’s server in the example. Most people have a Google account, so this fits well.
This full script reads the secret from environment variables, builds the email, then sends it.
import osimport smtplibfrom email.message import EmailMessage
# Read credentials safely from the environmentemail_user = os.environ["EMAIL_USER"]email_pass = os.environ["EMAIL_PASS"]
# Build the messagemessage = EmailMessage()message["From"] = email_usermessage["To"] = "riya@example.com"message["Subject"] = "Weekly report is ready"message.set_content("Hi Riya,\n\nThe weekly report is ready.\n\nThanks,\nAlex")
# Connect, log in, and sendwith smtplib.SMTP("smtp.gmail.com", 587) as server: server.starttls() server.login(email_user, email_pass) server.send_message(message)
print("Email sent successfully!")When this runs with real, valid credentials, it prints a simple confirmation.
Output
Email sent successfully!Note
This is representative output. The line above only appears if you run it with a real email account and a valid app password. The example addresses here will not actually send anything.
Now let us walk through the sending part line by line. Each line does one specific job:
smtplib.SMTP("smtp.gmail.com", 587)opens a connection to Gmail’s mail server on port587.- The
withkeyword means the connection closes by itself when the block finishes, even if something goes wrong. So you never leave a connection hanging open. server.starttls()upgrades the connection to a secure, encrypted one. starttls means “start TLS”, and TLS is the lock that keeps your password and message private while they travel.server.login(...)proves who you are using your email and app password.server.send_message(message)hands the finished email to the server, which delivers it.
Caution
Always call starttls() before login(). If you log in first on an unencrypted connection, your password travels in the open. Many servers will also reject the login outright.
📋 The SMTP Settings You’ll Need
Different providers use different server addresses. Here are the common ones so you can swap them in.
-
Find your provider’s SMTP host
Gmail uses
smtp.gmail.com. Outlook usessmtp.office365.com. Yahoo usessmtp.mail.yahoo.com. Your provider lists this in their help pages. -
Use port 587 with starttls
Port
587paired withstarttls()is the standard secure setup. It works with the code above for most providers. -
Generate an app password
Go to your email account’s security settings and create an app password. Copy it once and store it in your environment variable. You usually cannot view it again later.
-
Run the script in the same terminal
Set the environment variables first, then run
python send_email.pyin that same window so the values are available.
⚠️ Common Mistakes
A few things trip up almost everyone the first time. Watch for these. The biggest one is using your real password instead of an app password.
- Using your real password. It will often be rejected, and it is unsafe. Generate an app password instead.
# ❌ Avoid: real password hard-coded in the fileserver.login("alex@example.com", "myRealPassword123")
# ✅ Good: app password read from the environmentserver.login(os.environ["EMAIL_USER"], os.environ["EMAIL_PASS"])-
Forgetting
starttls(). Without it the connection is not secure and the login usually fails. -
Wrong port. Port
25is often blocked, and465needs a different connection style (SMTP_SSL). Stick with587andstarttls()to start. -
Committing secrets. Never let your credentials end up in Git.
# ❌ Avoid: secrets sitting right in the code you commitemail_pass = "abcd-efgh-ijkl-mnop"
# ✅ Good: secret stays outside the fileemail_pass = os.environ["EMAIL_PASS"]- Expecting it to work without a real account. The example addresses send nothing. You need a real inbox and a valid app password for an email to actually arrive.
✅ Best Practices
Keep these habits and your email scripts stay safe and easy to maintain. The simplest rule is to keep every credential out of your code.
- Read every credential from environment variables, never from the code.
- Use an app password, and delete it the moment you suspect it leaked.
- Always use
with smtplib.SMTP(...)so the connection closes by itself. - Call
starttls()beforelogin(), every single time. - Wrap the sending part in
try/exceptso a failed send does not crash your whole program.
Tip
Want to send to many people from a list? Build the message once, then loop over your recipients and call server.send_message(...) for each one inside the same open connection. Reusing the connection is faster than reconnecting every time.
🧩 What You’ve Learned
A quick recap of what you can now do:
- ✅ Explain what SMTP is in plain words: the system that sends mail.
- ✅ Build an email with
EmailMessage, setting the from, to, subject, and body. - ✅ Connect to an SMTP server, secure it with
starttls(), log in, and send. - ✅ Keep credentials safe by using an app password and environment variables.
- ✅ Avoid the common traps: real passwords, wrong ports, and committed secrets.
Check Your Knowledge
Test what you learned. Pick an answer for each question, then click Check.
- 1
What does SMTP do in plain words?
Why: SMTP (Simple Mail Transfer Protocol) is the system responsible for sending mail, much like a post office carrying letters.
- 2
Why should you read your password from an environment variable?
Why: Reading from the environment keeps the secret out of your code, so pushing the file to GitHub does not leak your password.
- 3
Which call should come right before server.login()?
Why: starttls() upgrades the connection to an encrypted one, so your password is protected before you log in.
- 4
What is an app password?
Why: An app password is a one-purpose password for a script; if it leaks you delete just that one and your real account stays safe.
🚀 What’s Next?
You can now make Python write and send emails on its own. Next we will pull information off the web automatically. That pairs perfectly with sending it out by email.