• Our Office
  • 429 A-Block DHA EME Lahore

 

Building a Motivational Quote Email Sender with Python

Python
SMTP
SOLID Principles
Testing
Automation

๐Ÿ“– 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.

99%
Code Coverage
6
Modules
526
Test Lines
0
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
                )
๐Ÿ’ก Key Learning: For Gmail, you need to use App Passwords instead of your regular password. This was a crucial security lesson about OAuth and application-specific credentials.

๐Ÿ—๏ธ 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
๐Ÿ’ก Key Learning: Python’s 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(...)
๐Ÿ’ก Key Learning: Mocking is essential for testing email functionality. You don’t want to send actual emails during testing! I used 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

  1. Load Configuration: The app reads settings from config.json
  2. Check Schedule: Determines if today is the scheduled day
  3. Get Quote: Randomly selects a motivational quote
  4. Send Emails: Sends personalized emails to all recipients
  5. 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.

 

Project Stats: 6 Python modules | 526 test lines | 99% coverage | 0 bugs

 

Leave A Comment

All fields marked with an asterisk (*) are required