An automated mail sender using Flask-Mail and asynchronous job scheduling with RQ.
- Updated to Python 3.13.3 (from 3.6)
- Upgraded all dependencies to latest compatible versions
- Replaced deprecated packages (e.g., flask-restplus → flask-restx)
- Added comprehensive test suite with pytest
- Improved Docker configuration for better production readiness
- Added health endpoint for monitoring at
/api/health
- Implemented OOP improvements for better code organization and maintainability
- Added comprehensive API documentation with Sphinx
- Created new API endpoints for retrieving scheduled emails
- Added Swagger UI improvements with better response models
- Implemented GitHub Actions for automated testing and CI/CD
This project uses GitHub Actions for continuous integration and deployment:
-
Pull Request Checks (
pr-checks.yml
): Comprehensive validation for PRs- Code quality: Black, isort, flake8, mypy
- Testing: Full test suite with coverage
- Compatibility: Multi-Python version testing (3.9-3.12)
- Documentation: Docstring validation and deprecated import checks
-
Security Analysis (
security-sast.yml
): Python-native security scanning- Static analysis with Bandit, Safety, Semgrep, Pylint
- Automated PR comments with security summaries
- Weekly scheduled security audits
-
CI/CD Pipeline (
ci.yml
): Main integration testing- Unit and integration testing with PostgreSQL/Redis
- Docker availability detection and alternative testing
- Dockerfile validation and container testing when possible
- Application startup verification
Run the local CI simulation script to test your changes:
./test-ci-local.sh
This script checks:
-
Python environment and dependencies
-
Code quality (flake8, mypy)
-
Application startup
-
Docker configuration validation
-
Unit test execution
-
Documentation Builder: Automatically builds and publishes documentation
- Rebuilds when code or documentation changes
- Makes documentation artifacts available for review
The project includes comprehensive automated security analysis with Python-native tools:
- Bandit: Scans Python code for common security issues and vulnerabilities
- Safety: Checks dependencies against known security vulnerability databases
- Semgrep: Advanced security pattern analysis with OWASP rules
- Pylint Security: Code quality checks with security-focused plugins
- Automated scanning on every push and pull request
- Weekly security audits via scheduled workflows
- Security report artifacts stored for 30 days
- PR comments with security analysis summaries
- Critical issue detection with actionable feedback
Security tools are configured via:
pyproject.toml
- Bandit, Black, isort, MyPy, Pytest, Coverage settings.bandit
- Additional Bandit security linter configuration.semgrepignore
- Semgrep ignore patterns for irrelevant files
No external API tokens required - all tools run locally within GitHub Actions.
For local development, use the provided setup script:
# Make the script executable (if needed)
chmod +x setup.sh
# Run the setup script
./setup.sh
# Activate the virtual environment
source venv/bin/activate
This will:
- Create a virtual environment
- Install all dependencies
- Install the package in development mode
- Create a
.env
file from.env.example
if needed
Rename .env.example to .env And then set your variable there.
Quickly run the project using Docker and Docker Compose:
docker-compose up -d
This will start:
- The Flask application
- A worker for processing jobs
- A scheduler for timed jobs
- PostgreSQL database
- Redis for job queuing
Create the database tables:
docker-compose exec app flask create_db
The Mail Scheduler project comes with comprehensive documentation:
A detailed API documentation is available that covers all endpoints, request/response formats, and status codes. To build and view the documentation:
# Generate HTML documentation using the provided script
./generate_docs.py
# Or open the documentation directly (optional --open flag)
./generate_docs.py --open
The documentation includes:
- All API endpoints with examples
- Request and response formats
- Status codes and error handling
- Architecture overview
- Module documentation
Interactive API documentation is also available via Swagger UI at:
http://localhost:8080/api/doc
This provides a way to test the API directly from your browser.
GET /api/health
- Check API healthPOST /api/save_emails
- Schedule a new emailGET /api/events
- List all scheduled emailsGET /api/events/<id>
- Get details of a specific scheduled email
RQ
is a simple job queue for Python backed by
Redis.
Start a worker:
flask rq worker
Start a scheduler:
flask rq scheduler
Monitor the status of the queue:
flask rq info --interval 3
For help on all available commands:
flask rq --help
Go to http://localhost:8080/api/doc for the API documentation.
Or you can run in your terminal:
curl -X POST \
--header 'Content-Type: application/json' \
--header 'Accept: application/json' \
-d '{
"subject": "Email subject",
"content": "Email body",
"timestamp": "10 May 2025 12:00 +08",
"recipients": "[email protected], [email protected]"
}' \
'http://localhost:8080/api/save_emails'
# Run all tests
pytest
# Run with coverage report
pytest --cov=app
# Run a specific test file
pytest tests/api/test_endpoints.py
See the todo.md file for ongoing development tasks and progress.
The application follows object-oriented programming principles for better maintainability and extensibility:
The codebase has been refactored to adhere to SOLID principles:
- Single Responsibility Principle: Each class has a single responsibility (e.g., EventService handles only event operations)
- Open/Closed Principle: Base classes are open for extension but closed for modification
- Liskov Substitution Principle: Services can be used interchangeably where their base types are expected
- Interface Segregation: Classes expose only the methods that clients need
- Dependency Inversion: High-level modules depend on abstractions rather than concrete implementations
Services encapsulate business logic and database operations, following SOLID principles:
# Example service usage
from app.services.event_service import EventService
# Get all events
events = EventService.get_all()
# Create a new event
result = EventService.create({
'name': 'Email Subject',
'notes': 'Email Content'
})
Key service components:
BaseService
: Abstract base class defining common service interfacesEventService
: Handles event-related operationsRecipientService
: Manages recipient data
Models use property decorators for better encapsulation and validation:
class Event(db.Model):
# Database columns with underscore prefix for encapsulation
_email_subject = db.Column('email_subject', db.String, nullable=False)
@property
def email_subject(self) -> str:
"""Get the email subject."""
return self._email_subject
@email_subject.setter
def email_subject(self, value: str) -> None:
"""Set the email subject with validation."""
if not value:
raise ValueError("Email subject cannot be empty")
self._email_subject = value
Flask routes use class-based views for better organization:
class EventListView(MethodView):
"""Class-based view for listing all events."""
def get(self):
"""GET method to display all events."""
events = EventService.get_all()
return render_template('all_events.html', items=events)
# Route registration
event_list_view = EventListView.as_view('all_events')
blueprint.add_url_rule('/', view_func=event_list_view)
Forms use inheritance to reduce code duplication:
class BaseItemsForm(FlaskForm):
"""Base form class with common fields."""
name = StringField('Name', validators=[DataRequired(), Length(min=1, max=254)])
notes = StringField('Notes')
class ItemsForm(BaseItemsForm):
"""Form for adding new items."""
pass
class EditItemsForm(BaseItemsForm):
"""Form for editing existing items."""
pass
For more details on the implementation, refer to:
app/services/base.py
: Abstract base service classapp/services/event_service.py
: Event service implementationapp/event/views.py
: Class-based viewsapp/database/models.py
: Property decorators in modelsapp/event/forms.py
: Form inheritance
The OOP improvements have led to a more organized project structure:
app/
├── __init__.py # Application factory
├── commands.py # CLI commands
├── config.py # Configuration classes
├── extensions.py # Flask extensions
├── api/ # API endpoints
│ └── routes.py
├── database/ # Database models
│ ├── __init__.py
│ └── models.py # Models with property decorators
├── event/ # Web UI
│ ├── forms.py # Form classes with inheritance
│ ├── jobs.py # Background jobs
│ ├── templates/ # Jinja templates
│ └── views.py # Class-based views
└── services/ # Service layer
├── __init__.py
├── base.py # Abstract base service
├── event_service.py # Event service implementation
└── README.md # Service layer documentation
Several design patterns have been implemented:
- Repository Pattern: Service classes abstract database access
- Template Method Pattern: BaseService defines the algorithm structure that subclasses implement
- Adapter Pattern: Legacy methods in services provide backward compatibility
- Factory Pattern: Application factory pattern in app initialization
- Decorator Pattern: Property decorators for model attributes