Building a Motivational Quote Email Sender with Python
๐ Project Overview
On Day 32 of my Python journey, I built a Motivational Quote Email Sender โ an automated system that sends inspiring quotes to recipients on a scheduled day each week. But this wasn’t just about sending emails; it was about learning professional software development practices, including SOLID principles, clean architecture, and comprehensive testing.
Code Coverage
Modules
Test Lines
Linter Errors
Key Features
Scheduled Sending
Configure which day of the week to send quotes automatically.
Multiple Recipients
Send personalized emails to multiple recipients at once.
JSON Configuration
Easy-to-edit configuration file for all settings.
SOLID Architecture
Professional code structure following best practices.
Comprehensive Tests
99% code coverage with robust test suite.
Error Handling
Graceful error handling for all edge cases.
๐ฏ What I Learned
1. SOLID Principles in Action
I implemented all five SOLID principles to create maintainable, scalable code:
- Single Responsibility: Each class has one job (QuoteService, EmailService, Scheduler, ConfigLoader)
- Open/Closed: Code is open for extension but closed for modification
- Dependency Inversion: High-level modules don’t depend on low-level modules; both depend on abstractions
๐ง Working with SMTP and Email Automation
I learned how to send emails programmatically using Python’s smtplib library. Here’s the core email sending logic:
class SMTPEmailSender(EmailSender):
def send_email(self, sender, receivers, subject, body):
with smtplib.SMTP(self.smtp_host, self.smtp_port) as connection:
connection.starttls() # Secure the connection
connection.login(user=sender_email, password=password)
for receiver in receivers:
connection.sendmail(
from_addr=sender_email,
to_addrs=receiver_email,
msg=personalized_message
)
๐๏ธ Dependency Inversion Principle
One of the most important patterns I learned was the Dependency Inversion Principle. Instead of the EmailService directly depending on SMTPEmailSender, I created an abstract EmailSender interface:
class EmailSender(ABC):
"""Abstract base class for email sending"""
@abstractmethod
def send_email(self, sender, receivers, subject, body):
pass
class EmailService:
def __init__(self, email_sender: EmailSender):
self.email_sender = email_sender # Depends on abstraction!
This makes the code incredibly flexible โ I could easily swap SMTP for SendGrid, AWS SES, or any other email service without changing the EmailService class!
๐ Working with datetime and Scheduling
I built a scheduler that determines if today matches the target day:
def should_run_today(self) -> bool:
now = dt.datetime.now()
current_weekday = now.weekday() # 0=Monday, 6=Sunday
return current_weekday == self.target_weekday
datetime.weekday() returns 0 for Monday and 6 for Sunday, which is different from some other languages!โ๏ธ Configuration Management
I learned to externalize configuration using JSON files, making the application configurable without code changes:
{
"sender": {
"email": "your-email@gmail.com",
"password": "your-app-password",
"name": "Your Name"
},
"schedule": {
"send_day": 0,
"comment": "0=Monday, 1=Tuesday, ..., 6=Sunday"
}
}
๐งช Comprehensive Testing
The most challenging but rewarding part was writing comprehensive tests. I achieved 99% code coverage by testing:
- โ Normal operation scenarios
- โ Error handling and edge cases
- โ Mock SMTP connections to avoid sending real emails
- โ File not found scenarios
- โ Invalid configuration handling
- โ Authentication failures
def test_send_email_authentication_error(self):
"""Test SMTP authentication failure"""
mock_smtp.return_value.__enter__.return_value.login.side_effect = \
smtplib.SMTPAuthenticationError(535, "Authentication failed")
with self.assertRaises(ValueError):
self.sender.send_email(...)
unittest.mock to simulate SMTP connections.๐๏ธ Project Architecture
The project is organized into distinct modules, each with a single responsibility:
- main.py โ Entry point, orchestrates all components
- config_loader.py โ Loads and validates configuration
- quote_service.py โ Manages quote retrieval
- email_service.py โ Handles email sending via SMTP
- scheduler.py โ Determines when to run
- test_quote_emailer.py โ Comprehensive test suite
๐ How It Works
- Load Configuration: The app reads settings from
config.json - Check Schedule: Determines if today is the scheduled day
- Get Quote: Randomly selects a motivational quote
- Send Emails: Sends personalized emails to all recipients
- Report Status: Logs success or provides helpful error messages
๐ญ Challenges & Solutions
Challenge 1: Gmail Authentication
Initially, I tried using my regular Gmail password, which failed. I learned that Gmail requires App Passwords for less secure apps.
Solution: Generate an App Password from Google Account settings.
Challenge 2: Testing SMTP Connections
How do you test email functionality without sending real emails?
Solution: Use unittest.mock.patch to mock the SMTP connection and verify method calls.
Challenge 3: Managing Multiple Dependencies
As the project grew, managing dependencies between classes became complex.
Solution: Implement Dependency Injection โ pass dependencies through constructors rather than creating them internally.
๐ Key Takeaways
๐๏ธ Architecture Matters
Following SOLID principles makes code easier to maintain, test, and extend.
๐งช Test Early, Test Often
Writing tests alongside code helps catch bugs early and ensures reliability.
๐ Abstractions are Powerful
Dependency Inversion makes code flexible and swappable.
โ๏ธ Configuration > Hardcoding
Externalizing configuration makes apps more flexible and reusable.
๐จ Error Handling is Essential
Proper exception handling provides better user experience and debugging.
๐ Documentation Helps
Clear docstrings and comments make code understandable.
๐ฎ Future Improvements
- Add HTML email templates for richer formatting
- Implement a web dashboard for managing quotes and schedules
- Add support for multiple scheduling patterns (daily, specific dates, etc.)
- Integrate with a quote API for endless inspiration
- Add email analytics to track open rates
- Deploy as a cloud function for true automation
๐ก Conclusion
This project taught me that software engineering is more than just making code work โ it’s about writing maintainable, testable, and scalable code. The SOLID principles aren’t just academic concepts; they’re practical tools that make real differences in code quality.
Day 32 was a milestone in my Python journey. I didn’t just build an email sender; I learned how professional developers structure applications, handle dependencies, and ensure code quality through testing.