Chapter 19 Key Takeaways: Email Automation and Notifications
The Core Mental Model
Email automation has two phases that never overlap: construction and transmission. You build the message completely — headers, body parts, attachments — and then hand it off to the SMTP server for delivery. The email module handles construction; smtplib handles transmission. Keep these roles distinct in your mind and in your code.
The Non-Negotiable Security Rules
Rule 1: Never hardcode credentials in source code. The risk is not only sharing your code intentionally. Credentials in code end up in version control history, log files, clipboard history, and editor crash files. Always use environment variables.
Rule 2: Always use an App Password, never your main password. Gmail, Outlook, and most modern email providers require App Passwords for third-party application access when two-factor authentication is enabled. App Passwords can be revoked independently without changing your main password.
Rule 3: Add .env to .gitignore before your first commit.
It takes ten seconds and prevents a potentially irreversible mistake. Include a .env.example with placeholder values so teammates know what variables are needed.
Rule 4: Treat the Slack webhook URL as a credential.
A webhook URL gives anyone write access to your Slack channels. Store it in .env, not in your code.
The Key Technical Facts
smtplib connection modes:
- SMTP_SSL("smtp.gmail.com", 465) — encrypted from the start (Gmail standard)
- SMTP("smtp.gmail.com", 587) + .starttls() — starts unencrypted, upgrades (Outlook/Microsoft standard)
- Always use a with statement so the connection closes cleanly
MIME types you will use most:
- MIMEMultipart("alternative") — for HTML + plain text (same content, different formats)
- MIMEMultipart("mixed") — for email + attachments
- MIMEMultipart("related") — for HTML + inline images
- MIMEText(body, "plain") — plain text body part
- MIMEText(html, "html") — HTML body part
- MIMEBase("application", "octet-stream") — generic binary attachment
BCC works differently than To and CC:
- To and CC addresses go in message headers — everyone can see them
- BCC addresses go in the to_addrs parameter of sendmail() only — never in a header
- Using send_message() will miss BCC recipients because it reads addresses from headers
os.environ.get("KEY") vs. os.environ["KEY"]:
- .get() returns None if the variable is not set (use for optional variables)
- ["KEY"] raises a KeyError if the variable is not set (use for required variables)
- load_dotenv() does not override variables already set in the environment — this is intentional and correct
Template Strategies by Complexity
| Complexity | Tool | When to Use |
|---|---|---|
| Simple, one-off | f-strings or .format() |
Fewer than three variable substitutions |
| Moderate, repeating | f-strings with structure | Plain text emails, small HTML tables |
| Complex HTML | Jinja2 | Multi-section emails, loops over data, conditional sections |
Jinja2 offers template inheritance ({% extends %}), loops ({% for %}), conditionals ({% if %}), and filters — capabilities that f-strings simply do not have for multi-block template structures.
The Conditional Alert Pattern
The highest-value email automation pattern in business:
1. Load current metrics (from CSV, database, or API)
2. Compare each metric against defined thresholds
3. IF any breaches found: build and send alert
4. IF no breaches: log "all clear" and exit
5. Run on a schedule (cron, Task Scheduler, cloud scheduler)
Key design decisions:
- Define thresholds in a separate data structure (not buried in if statements)
- Distinguish severity levels (critical vs. warning) — route differently
- Add suppression logic so you do not send the same alert every 5 minutes
- Add recovery notifications so teams know when an issue resolves
Email vs. Slack: The Decision Framework
Choose email when: - The recipient is external (client, vendor, regulator) - You need a formal record of the communication - The message includes a large file attachment - The message needs to persist and be searchable
Choose Slack when: - The audience is an internal team - Speed and visibility matter more than formality - The notification is high-frequency (daily or more often) - You want the team to discuss the alert in thread
Consider neither when the notification is so frequent that people learn to ignore it. Alert fatigue is real. An alert that fires every hour for a condition that is chronically "broken" trains people to stop looking.
Reliability Patterns
Always wrap SMTP operations in try/except. At minimum, catch:
- smtplib.SMTPAuthenticationError — credential problem (do not retry)
- smtplib.SMTPRecipientsRefused — bad address (do not retry)
- smtplib.SMTPConnectError — network problem (retry with backoff)
- smtplib.SMTPException — catch-all for other SMTP errors
Log, do not print. Use Python's logging module instead of print() in production scripts. Logs can be directed to files, monitoring systems, and aggregation services.
Test with yourself first. Before deploying automation that targets real recipients, always run a test cycle where all emails go to your own address. Verify the formatting, the attachments, and the subject line look right before pointing it at your boss.
What the Characters Learned
Priya (Acme Corp): Automated Sandra's Monday report by separating the pipeline into four clean stages: load, aggregate, generate, send. The 90-minute weekly manual task became a 6:31 AM arrival in Sandra's inbox — before Sandra's laptop was even open. The key insight: mechanical work belongs to Python, not to people.
Maya (Freelance Consultant): Built a payment reminder system that generates draft emails rather than sending them automatically. This design choice — generate, review, send — balances automation efficiency against relationship risk. The right level of automation is not always "fully automatic." Sometimes the most valuable step is reducing the mechanical work while preserving human judgment at decision points.
Common Mistakes to Avoid
-
Committing credentials to git. If you catch yourself doing this, revoke the credentials immediately.
-
Forgetting the plain text fallback. HTML-only emails score worse with spam filters and are unreadable in text-only clients.
-
Opening a new SMTP connection for every email in a loop. For small lists this is fine. For large lists, reuse the connection by keeping the server object alive across iterations.
-
Not handling SMTP errors. Silent failures mean you think the report went out, but nobody received it.
-
Using your main account password instead of an App Password. Modern providers reject this for accounts with 2FA; even when it works, it is a security anti-pattern.
-
Putting BCC addresses in a
msg["Bcc"]header. This defeats the purpose of BCC — recipients can see the header and know who is BCC'd. -
Skipping the test cycle. Sending an incorrectly formatted report to your executive team's inbox is memorable for the wrong reasons.