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:

  1. CSV output: When alerts fire, also write a CSV file logging the breach: alert_log.csv with columns date, metric, actual, threshold, direction, severity.

  2. 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.

  3. 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.

  4. Test mode: Add a --test flag 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:

  1. 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

  2. Write a Python module template_engine.py with a function render_email_template(template_name, context) that: - Loads the template from the templates/ directory - Renders it with the provided context dictionary - Returns the rendered HTML string

  3. 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