How to Send Automated Emails with Python: A Step-by-Step Guide
I still remember the morning I realized I’d spent three hours manually formatting and sending the same weekly report to my team. As I hit “send” on the fourteenth email, I thought: There has to be a better way. So I sat down, opened my laptop, and wrote a Python script that did it in three seconds flat. That day, I went from being an email copy-paste zombie to someone who could trust a machine to handle the tedious stuff. If you’ve ever wanted to send automated emails with Python—whether it’s for a personal project, a small business, or just to impress your boss—this guide will walk you through every step, from a bare-bones text message to a scheduled, fully automated system.
Why Automate Emails with Python? Real Examples of What You Can Build
Before we dive into code, let me give you three concrete scenarios where Python email automation saves real time. First: weekly report generation. I used to pull data from a database, paste it into a spreadsheet, and email it to stakeholders. Now my script runs every Monday at 9 AM, attaches a PDF, and sends it to a list of recipients—no human intervention. Second: transactional notifications—think password reset links or order confirmations. A simple Python script can handle hundreds of these per day, each personalized with the user’s name and details. Third: personal reminders—I have a script that texts me (via email gateway) when my blog’s daily traffic drops below a threshold. The point is, automating emails isn’t just for big tech companies; it’s a skill you can use for your own side projects, client work, or even to automate chores like sending birthday greetings to friends. The best part? It’s all built on Python’s standard library—no extra software needed.
What You’ll Need Before You Start: Python Setup and Essential Libraries
To send automated emails with Python, you need three things: Python installed (version 3.6 or later is fine), an email account that supports SMTP, and the two built-in libraries we’ll use. If you’re on macOS or Linux, Python is likely preinstalled; on Windows, download it from python.org and check “Add Python to PATH” during installation. The libraries are smtplib (for connecting to the SMTP server) and email (for constructing the message). Both come with Python, so no pip install is required. One common pain point I’ve seen: people forget to enable “less secure app access” or generate an app password for their email account. If you’re using Gmail, you’ll need to create an app-specific password (under Google Account > Security > App passwords) because your regular password won’t work with SMTP. For Outlook or Yahoo, check their respective SMTP settings pages. Trust me, debugging a connection timeout because of an authentication error is no fun—get this right upfront.
Step 1: Crafting a Simple Text Email with smtplib
Let’s write the simplest possible email sender. Open your favorite Python editor (or even a terminal with python), and type this:
import smtplib
sender_email = "you@gmail.com"
receiver_email = "friend@example.com"
password = "your_app_password"
message = """Subject: Hello from Python!
This is a test email sent from a Python script."""
with smtplib.SMTP_SSL("smtp.gmail.com", 465) as server:
server.login(sender_email, password)
server.sendmail(sender_email, receiver_email, message)
Let me break this down. First, we import smtplib. Then we define sender, receiver, and password—never hardcode sensitive credentials in production; use environment variables or a config file. The message string starts with Subject: on the first line, followed by a blank line, then the body. This is the RFC 2822 format that email servers expect. We connect to Gmail’s SMTP server using SMTP_SSL on port 465 (or you can use .starttls() on port 587). After logging in, sendmail() sends the message. Run it, and if everything is configured correctly, your recipient gets a plain-text email. When I first ran this, I felt like a wizard—until I realized the email looked boring. That’s where Step 2 comes in.
Step 2: Adding HTML Content and Attachments with the email Module
Plain text is fine for internal alerts, but for real-world use, you’ll want HTML emails that look professional or include attachments. The email module’s MIMEMultipart class is your friend. Here’s an example that sends an HTML message with a PDF attachment:
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from email.mime.base import MIMEBase
from email import encoders
import smtplib
msg = MIMEMultipart()
msg["From"] = "you@gmail.com"
msg["To"] = "friend@example.com"
msg["Subject"] = "Monthly Report"
# HTML body
html = """Hi there!
Here is your report.
"""
msg.attach(MIMEText(html, "html"))
# Attachment
filename = "report.pdf"
with open(filename, "rb") as attachment:
part = MIMEBase("application", "octet-stream")
part.set_payload(attachment.read())
encoders.encode_base64(part)
part.add_header("Content-Disposition", f"attachment; filename={filename}")
msg.attach(part)
with smtplib.SMTP_SSL("smtp.gmail.com", 465) as server:
server.login("you@gmail.com", "app_password")
server.send_message(msg)
Notice I used send_message() instead of sendmail()—that’s the modern way to send MIME messages. The MIMEText part contains the HTML, and the MIMEBase part handles the attachment. A gotcha I once hit: if the file path is wrong, the script crashes silently. Always use absolute paths or check the file exists before attaching. This pattern covers 90% of what you’ll ever need: send a nice-looking email with a file.
Step 3: Connecting to Popular Email Services (Gmail, Outlook, Yahoo)
Different email providers use different SMTP servers and ports. Here’s a cheat sheet I keep pinned to my wall:
- Gmail: smtp.gmail.com, port 465 (SSL) or 587 (TLS). Requires an app password.
- Outlook/Hotmail: smtp.office365.com, port 587 (TLS). Use your regular password or an app password.
- Yahoo: smtp.mail.yahoo.com, port 465 (SSL). Also requires an app password.
If you’re using a custom domain with a service like Zoho or Fastmail, look up their SMTP settings in their help docs. When I switched from Gmail to a custom domain, I spent an hour debugging because I forgot to enable SMTP access in the admin panel. The key is: always test with a simple SMTP_SSL connection first. If you get an “Authentication error,” double-check your password or app password. If you get a “Connection refused,” the port or server name is wrong. One more tip: for Outlook, you might need to use SMTP (not SMTP_SSL) and call .starttls() explicitly—I’ve seen that work when the SSL variant fails.
Step 4: Scheduling Your Emails to Run Automatically
So you’ve got a working script. Now you want it to run every day at 8 AM. On macOS or Linux, you use cron. Open your terminal and type crontab -e, then add a line like this:
0 8 * * * /usr/bin/python3 /home/you/scripts/send_report.py
This runs the script at 8:00 AM every day. The five fields are minute, hour, day of month, month, day of week. The * means “every.” I once spent hours debugging why my cron job wasn’t working—turns out the script used relative paths for attachments. Always use absolute paths in cron jobs. On Windows, use Task Scheduler: create a basic task, set the trigger (e.g., daily at 8 AM), and set the action to start your Python script (make sure to use the full path to python.exe and the full path to your script). If you prefer a Python-only solution, you can use the schedule library (install with pip install schedule):
import schedule
import time
def job():
# your email sending code here
print("Email sent!")
schedule.every().day.at("08:00").do(job)
while True:
schedule.run_pending()
time.sleep(60)
This approach is easier for beginners but keeps a Python process running—fine for a personal server, less ideal for production. I personally use cron because it’s built into the OS and doesn’t consume memory all day.
Real-World Pitfalls and How to Avoid Them (Rate Limits, Spam Filters, Secrets)
After automating emails for a year, I’ve hit every wall. Here’s what to watch for:
- Rate limits: Gmail allows 500 emails per day for personal accounts; any more and your account gets temporarily locked. For high-volume needs, use a transactional email service like SendGrid or Amazon SES. I once sent 600 emails in a test and couldn’t log into my Gmail for 24 hours. Lesson learned.
- Spam filters: If your HTML email has broken tags, too many images, or looks like a sales pitch, it’ll land in spam. Keep the design clean, include a plain-text alternative, and avoid link shorteners. I also recommend sending a test to yourself first—check which folder it lands in.
- Exposed secrets: Never commit your email password to GitHub. Use environment variables or a
.envfile. Why? Because I once accidentally pushed a config file with my Gmail password to a public repo, and within hours someone tried to send spam through my account. Useos.getenv("EMAIL_PASSWORD")instead of hardcoding. - Attachment errors: If your script runs from a different directory (like cron), the file won’t be found. Use
os.path.abspath(__file__)to get the script’s directory and build absolute paths from there.
One final piece of advice: start small. Send a single email to yourself first, then add HTML, then attachments, then scheduling. Each step builds confidence. I promise, once you see your first automated email land in your inbox, you’ll never go back to manual sending.
Frequently Asked Questions
Do I need a paid email provider to send automated emails with Python?
No, you can start with any free email account (like Gmail) that supports SMTP, though free accounts have sending limits (e.g., 500 per day for Gmail). For higher volumes, consider a paid service like SendGrid (free tier up to 100 emails/day) or Amazon SES.
Why does my Python email script work locally but fail when I schedule it?
Most likely a path issue (e.g., file attachments) or environment variables not being loaded. Use absolute paths and test the scheduled task manually. Also, ensure the Python executable and script paths are fully specified.
How do I send emails to multiple recipients without them seeing each other’s addresses?
Use the Bcc field in your MIMEMultipart message. Add addresses to the Bcc header or pass them separately in the sendmail() call as a list. The To field can contain your own address or a placeholder.
Is smtplib secure enough for production use?
Yes, when used with starttls() or SMTP_SSL, it’s secure. However, for high-volume or critical systems, consider dedicated services like SendGrid or Amazon SES for reliability and better deliverability.
What is the easiest way to test email sending without spamming real users?
Use a fake SMTP server like Mailtrap or run python -m smtpd -c DebuggingServer -n localhost:1025 in another terminal to capture all outgoing emails. This way you can see exactly what your script sends without delivering to real inboxes.
Now you have everything you need to start automating. Write your first script today—it’s a small effort that pays off every single time your inbox fills up automatically.