Sending emails manually is fine when you send one or two. But what about daily reports to your team? Getting alerted when your web scraper finds something? Emailing yourself when a backup finishes?
That's where Python email automation comes in. No third-party services, no paid APIs — just Python's built-in smtplib.
Setup: Gmail App Password (2 Minutes)
Before writing code, you need an App Password. Google blocks "less secure apps" from using regular passwords.
- Go to Google Account Security
- Enable 2-Step Verification if you haven't
- Search for "App Passwords" → Select "Mail" → "Other" → name it "Python Script"
- Copy the 16-character password
Then set it as an environment variable so it never touches your code:
export GMAIL_APP_PASSWORD="xxxx xxxx xxxx xxxx"
Your First Automated Email in 10 Lines
import smtplib
from email.mime.text import MIMEText
SENDER = "yourname@gmail.com"
PASSWORD = "xxxx xxxx xxxx xxxx" # App Password
RECEIVER = "yourname@gmail.com"
msg = MIMEText("Hey! This email was sent by a Python script.")
msg["Subject"] = "My First Automated Email"
msg["From"] = SENDER
msg["To"] = RECEIVER
with smtplib.SMTP_SSL("smtp.gmail.com", 465) as server:
server.login(SENDER, PASSWORD)
server.send_message(msg)
print("Email sent!")
Keeping Secrets Safe
Hardcoding passwords = bad. If you commit it to GitHub, bots scrape it within minutes. Use environment variables:
import os
SENDER = os.environ["GMAIL_ADDRESS"]
PASSWORD = os.environ["GMAIL_APP_PASSWORD"]
Now run with:
GMAIL_ADDRESS="you@gmail.com" \
GMAIL_APP_PASSWORD="your-app-password" \
python send_email.py
HTML Emails That Look Professional
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
msg = MIMEMultipart("alternative")
msg["Subject"] = "Weekly Price Report"
msg["From"] = SENDER
msg["To"] = RECEIVER
text = "Plain text fallback for email clients that block HTML"
html = """
<html><body>
<h2>Weekly Price Report</h2>
<table border="1" cellpadding="8">
<tr style="background:#f0f0f0"><th>Product</th><th>Price</th><th>Change</th></tr>
<tr><td>Coffee Maker</td><td style="color:green">$39.99</td><td>↓ $10.00</td></tr>
</table>
</body></html>"""
msg.attach(MIMEText(text, "plain"))
msg.attach(MIMEText(html, "html"))
with smtplib.SMTP_SSL("smtp.gmail.com", 465) as server:
server.login(SENDER, PASSWORD)
server.send_message(msg)
Adding File Attachments
from email.mime.base import MIMEBase
from email import encoders
def attach_file(msg, filepath):
with open(filepath, "rb") as f:
part = MIMEBase("application", "octet-stream")
part.set_payload(f.read())
encoders.encode_base64(part)
part.add_header(
"Content-Disposition",
f'attachment; filename="{os.path.basename(filepath)}"',
)
msg.attach(part)
# Usage
msg = MIMEMultipart()
msg["Subject"] = "Monthly Backup Report"
msg["From"] = SENDER
msg["To"] = RECEIVER
msg.attach(MIMEText("Backup completed. Log attached."))
attach_file(msg, "/var/log/backup.log")
Gmail Sending Limits
| Limit | Free Account |
|---|---|
| Daily quota | 500 emails |
| Rate | ~1 email/second |
For personal automation, 500/day is more than enough. For bulk newsletters, use Mailgun or SendGrid.
Real Use Case: Price Drop Alert
"""price-alert.py — Check price and email if it drops."""
import os, json, smtplib
from email.mime.text import MIMEText
from pathlib import Path
PRICE_FILE = Path.home() / ".price-history.json"
SENDER = os.environ["GMAIL_ADDRESS"]
PASSWORD = os.environ["GMAIL_APP_PASSWORD"]
def get_price():
# Your web scraping logic here
return 39.99
def send_alert(current, previous):
drop = previous - current
msg = MIMEText(f"Price dropped from ${previous:.2f} to ${current:.2f} (↓ ${drop:.2f})")
msg["Subject"] = f"💰 Price Drop Alert: ${previous} → ${current}"
msg["From"] = SENDER
msg["To"] = SENDER
with smtplib.SMTP_SSL("smtp.gmail.com", 465) as server:
server.login(SENDER, PASSWORD)
server.send_message(msg)
history = json.loads(PRICE_FILE.read_text()) if PRICE_FILE.exists() else {}
current = get_price()
previous = history.get("last_price", current)
if current < previous:
send_alert(current, previous)
history.update({"last_price": current})
PRICE_FILE.write_text(json.dumps(history, indent=2))
Schedule with cron:
0 8 * * * /usr/bin/python3 /home/user/price-alert.py
Now every morning at 8 AM, it checks the price and emails you if it drops. Wake up to savings.
What's Next?
You now have the foundation for a complete automation pipeline: collect data → process it → email results → schedule with cron.
The full guide on my blog includes alternate email providers (Outlook, Yahoo, QQ Mail), more attachment examples, and debugging tips: Send Emails with Python — Full Guide
What's the first automated email you'd build? A backup notification? A price tracker? Let me know in the comments!
Top comments (0)