Chapter 19 Exercises: Email Automation and Notifications
These exercises build progressively from foundational mechanics to production-quality systems. Complete them in order within each tier. You will need a .env file with valid email credentials for exercises in Tier 3 and above; use your own email account to test by sending messages to yourself.
Tier 1 — Comprehension (No Coding Required)
These exercises check your understanding of the concepts before you write any code.
Exercise 1.1: The Email Stack
Match each term on the left with its correct description on the right.
| Term | Description |
|---|---|
| A. SMTP | 1. Encodes binary attachments so they can travel through email |
| B. MIME | 2. The sending protocol — carries your message from server to server |
| C. smtplib | 3. The format specification for structured email content |
| D. email.mime | 4. Python's standard library for constructing MIME messages |
| E. base64 | 5. Python's standard library for connecting to SMTP servers |
Exercise 1.2: True or False
Mark each statement True or False, and write one sentence explaining why.
a. You can use your regular Gmail password in a Python script to send emails.
b. BCC recipients are visible to all other recipients of the email.
c. MIMEMultipart("alternative") is the correct container when you want to send both HTML and plain text versions of the same email.
d. Adding .env to .gitignore prevents the file from being committed to version control.
e. os.environ.get("KEY") raises a KeyError if the variable is not set.
f. Slack webhooks require OAuth authentication to send messages.
Exercise 1.3: Credential Safety Audit
Review the following code snippet and identify every security problem. List each problem and explain how to fix it.
import smtplib
from email.mime.text import MIMEText
GMAIL_USER = "priya.kapoor@acmecorp.com"
GMAIL_PASS = "MyPassword123!"
msg = MIMEText("Report attached. See below.", "plain")
msg["Subject"] = "Weekly Report"
msg["From"] = GMAIL_USER
msg["To"] = "sandra@acmecorp.com,cfo@acmecorp.com"
server = smtplib.SMTP("smtp.gmail.com", 587)
server.login(GMAIL_USER, GMAIL_PASS)
server.send_message(msg)
Exercise 1.4: MIME Structure Diagram
Draw (or describe in words) the MIME structure for an email that contains: - A plain text fallback body - An HTML body - A PDF attachment
Label each part with the correct MIME type and the Python class you would use to create it.
Exercise 1.5: When to Email vs. Slack
For each of the following business notifications, decide whether email or a Slack message is the more appropriate channel, and explain your reasoning in one to two sentences.
a. A daily KPI dashboard for the executive team that they need to reference throughout the day.
b. An alert that a production server just went down, sent to the engineering team at 2:00 AM.
c. An invoice sent to an external client.
d. A reminder to a colleague about an upcoming deadline.
e. A monthly financial report sent to the company's board of directors.
Tier 2 — Guided Practice (Fill in the Blanks / Short Code)
Exercise 2.1: Complete the SMTP Connection
Fill in the blanks to complete a working SMTP connection for Gmail using SSL:
import smtplib
import os
with smtplib.______("smtp.gmail.com", ______) as server:
server.______(os.environ["EMAIL_SENDER"], os.environ["EMAIL_PASSWORD"])
server.______(msg)
Exercise 2.2: Build a MIMEMultipart Message
Complete the code to build an email with both plain text and HTML parts:
from email.mime.________ import MIMEMultipart
from email.mime.text import MIMEText
msg = MIMEMultipart("________") # which subtype goes here?
msg["Subject"] = "Test Email"
msg["From"] = "sender@example.com"
msg["To"] = "recipient@example.com"
plain = "Hello, this is plain text."
html = "<h1>Hello</h1><p>This is HTML.</p>"
msg.attach(MIMEText(plain, "________"))
msg.attach(MIMEText(html, "________"))
Exercise 2.3: Load Credentials from .env
Write a complete .env file and the Python code to load it for an email script. The script should require: EMAIL_SENDER, EMAIL_PASSWORD, EMAIL_SMTP_HOST, EMAIL_SMTP_PORT, and optionally EMAIL_FROM_NAME.
The Python code should:
1. Call load_dotenv()
2. Read each required variable
3. Raise a clear error message if any required variable is missing
Exercise 2.4: Fix the BCC Bug
The following code intends to send a BCC to audit@acmecorp.com, but it is wrong. Identify the bug and write the corrected version.
msg = MIMEMultipart()
msg["Subject"] = "Q4 Report"
msg["From"] = "priya@acmecorp.com"
msg["To"] = "sandra@acmecorp.com"
msg["Bcc"] = "audit@acmecorp.com" # <-- Is this sufficient for BCC?
with smtplib.SMTP_SSL("smtp.gmail.com", 465) as server:
server.login(sender, password)
server.send_message(msg) # <-- What recipients does this actually send to?
Exercise 2.5: Template Formatting
Use Python's f-string formatting to write a function build_invoice_reminder(client_name, invoice_id, amount, due_date, days_overdue) that returns a plain text email body. The function should:
- Address the client by name
- State the invoice number, amount, and original due date
- Mention how many days overdue it is
- Include a polite call to action
Tier 3 — Applied Exercises (Write Working Code)
For these exercises, use your own email account for testing. Send all test emails to yourself.
Exercise 3.1: Hello Email World
Write a complete Python script that:
1. Loads credentials from a .env file
2. Sends a plain text email to yourself with the subject "Chapter 19 Test — Plain Text"
3. The body should include your name, today's date, and the Python version you are running (use sys.version)
4. Prints "Email sent successfully" or "Failed to send" based on the result
Exercise 3.2: HTML Status Report
Write a function send_html_status_report(metrics: dict, recipient: str) that:
1. Takes a dictionary of metric names and values (e.g., {"Revenue": "$45,000", "Units": 312})
2. Builds an HTML email with a properly formatted table (with column headers: Metric, Value)
3. Includes a plain text fallback that lists the same data in a readable format
4. Sends to the provided recipient address
Test it by calling it with at least five made-up metrics.
Exercise 3.3: Attachment Sender
Write a script that: 1. Creates a temporary CSV file with five rows of sample sales data (you define the columns) 2. Sends an email to yourself with the CSV attached 3. The email body (both plain and HTML) should describe what the attachment contains 4. After sending, deletes the temporary CSV file
Exercise 3.4: Slack Webhook Test
Set up a personal Slack workspace (free) and create an Incoming Webhook. Then write a script that:
1. Loads the webhook URL from a .env file
2. Sends three different Slack messages:
- A plain text status message
- A message with basic Slack markdown formatting (bold, italics)
- A Block Kit message with a header and two fields
Exercise 3.5: Personalized Bulk Sender
Write a script that takes a CSV file with columns name, email, metric, value and sends a personalized email to each recipient including their specific metric and value. Requirements:
- One email per recipient (not one email with all recipients in To)
- Subject line includes the recipient's name
- Wait 1 second between sends
- Print progress to the console: "Sending 1/5 to name@example.com..."
- Log success or failure for each send
Tier 4 — Business Scenarios (Multi-Step Projects)
Exercise 4.1: The Automated Daily Digest
Build a "Daily Business Digest" email system:
Data: Create a Python dictionary (or load from JSON) with the following structure:
digest_data = {
"date": "2024-12-05",
"revenue_today": 48250.00,
"revenue_mtd": 892000.00,
"revenue_target_mtd": 900000.00,
"new_orders": 23,
"open_support_tickets": 12,
"key_events": [
"Thornfield Media contract renewed — $84,000 ARR",
"West Region exceeded weekly target by 6%",
]
}
Requirements: 1. Build an HTML email that presents this data clearly, with color coding for metrics that are ahead of/behind target 2. Include a plain text fallback 3. Add a header with the Acme Corp name and date 4. Include a "Key Events" section listing the events as bullet points 5. Send to yourself with a meaningful subject line 6. Write a separate function for each major section of the email (header, metrics, events) to keep the code organized
Exercise 4.2: Threshold Alert System
Extend kpi_alert.py from the chapter with the following enhancements:
-
CSV output: When alerts fire, also write a CSV file logging the breach:
alert_log.csvwith columnsdate,metric,actual,threshold,direction,severity. -
Alert suppression: If the same metric has already been alerted on in the past 24 hours (check
alert_log.csv), do not send a duplicate alert. -
Recovery notification: If a metric was in breach yesterday but is now within range, send a "recovery" email noting that it has returned to acceptable levels.
-
Test mode: Add a
--testflag that prints the email content to the console instead of sending it.
Exercise 4.3: Jinja2 Email Template System
Create a complete Jinja2-based email template system:
-
Create a
templates/directory with two templates: -base_email.html— shared header, footer, CSS styles -report_email.html— extends base, adds a data table section -
Write a Python module
template_engine.pywith a functionrender_email_template(template_name, context)that: - Loads the template from thetemplates/directory - Renders it with the provided context dictionary - Returns the rendered HTML string -
Use the template engine to send a formatted sales report email with at least eight rows of data
Exercise 4.4: Multi-Channel Alert Router
Build an alert routing system that sends notifications through different channels based on severity:
CRITICAL alerts → Email to executive team + Slack to #alerts channel
WARNING alerts → Slack to #operations channel only
INFO alerts → Slack to #daily-digest channel only
Requirements:
1. Define at least six KPIs with different severity levels
2. Build a route_alert(breach) function that determines the channel(s) to use
3. Implement both the email sender and Slack webhook sender
4. Test with sample data that triggers all three severity levels
5. Print a routing report: "Sent CRITICAL alert for [Metric] via: email, slack"
Tier 5 — Capstone Project
Exercise 5.1: Maya's Complete Billing Automation Suite
Build a complete billing communication system for Maya Reyes Consulting. This is a full project that brings together everything in the chapter.
System requirements:
Component 1 — Invoice Generation
- A create_invoice.py script that takes client name, email, amount, and description via command-line arguments
- Generates a text-based invoice (plain text is fine) with a unique invoice ID (format: INV-YYYY-NNN)
- Appends the new invoice to invoices.csv with status "unpaid" and a due date 30 days from today
- Sends the client an email with the invoice details in the body
Component 2 — Payment Reminder Engine
- Extends payment_reminders.py from Case Study 19-2
- Adds a fourth tier: "pre-due reminder" sent 3 days before the due date (friendly, not a warning)
- Generates drafts for all tiers (pre-due, gentle, firm, final)
- Adds an HTML version of each draft in addition to the plain text version
Component 3 — Dashboard Email
- A billing_dashboard.py script that reads invoices.csv and sends Maya a weekly summary email containing:
- Total invoiced this month
- Total collected this month
- Collection rate percentage
- List of outstanding invoices with days overdue
- List of invoices collected this month
Component 4 — Testing and Safety
- A --test-mode flag on all scripts that uses a test recipient (your own email) instead of real clients
- A --dry-run flag that prints output without sending anything
- Proper error handling for all SMTP operations
- A README.txt file (or printed help text) explaining how to set up the .env file and run each component
Deliverables:
- All four Python scripts
- A sample invoices.csv with at least eight rows covering various states (paid, overdue, current)
- A .env.example file showing required variables
- Running all scripts in test mode without errors