feat: add email system with multi-provider support
Implements a comprehensive email system with: - Multi-provider support (SMTP, SendGrid, Mailgun, Amazon SES) - Database-stored templates with i18n (EN, FR, DE, LB) - Jinja2 template rendering with variable interpolation - Email logging for debugging and compliance - Debug mode for development (logs instead of sending) - Welcome email integration in signup flow New files: - models/database/email.py: EmailTemplate and EmailLog models - app/services/email_service.py: Provider abstraction and service - scripts/seed_email_templates.py: Template seeding script - tests/unit/services/test_email_service.py: 28 unit tests - docs/features/email-system.md: Complete documentation 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
331
docs/features/email-system.md
Normal file
331
docs/features/email-system.md
Normal file
@@ -0,0 +1,331 @@
|
||||
# Email System
|
||||
|
||||
The email system provides multi-provider support with database-stored templates and comprehensive logging for the Wizamart platform.
|
||||
|
||||
## Overview
|
||||
|
||||
The email system supports:
|
||||
|
||||
- **Multiple Providers**: SMTP, SendGrid, Mailgun, Amazon SES
|
||||
- **Multi-language Templates**: EN, FR, DE, LB (stored in database)
|
||||
- **Jinja2 Templating**: Variable interpolation in subjects and bodies
|
||||
- **Email Logging**: Track all sent emails for debugging and compliance
|
||||
- **Debug Mode**: Log emails instead of sending during development
|
||||
|
||||
## Configuration
|
||||
|
||||
### Environment Variables
|
||||
|
||||
Add these settings to your `.env` file:
|
||||
|
||||
```env
|
||||
# Provider: smtp, sendgrid, mailgun, ses
|
||||
EMAIL_PROVIDER=smtp
|
||||
EMAIL_FROM_ADDRESS=noreply@wizamart.com
|
||||
EMAIL_FROM_NAME=Wizamart
|
||||
EMAIL_REPLY_TO=
|
||||
|
||||
# Behavior
|
||||
EMAIL_ENABLED=true
|
||||
EMAIL_DEBUG=false
|
||||
|
||||
# SMTP Settings (when EMAIL_PROVIDER=smtp)
|
||||
SMTP_HOST=smtp.example.com
|
||||
SMTP_PORT=587
|
||||
SMTP_USER=
|
||||
SMTP_PASSWORD=
|
||||
SMTP_USE_TLS=true
|
||||
SMTP_USE_SSL=false
|
||||
|
||||
# SendGrid (when EMAIL_PROVIDER=sendgrid)
|
||||
# SENDGRID_API_KEY=SG.your_api_key_here
|
||||
|
||||
# Mailgun (when EMAIL_PROVIDER=mailgun)
|
||||
# MAILGUN_API_KEY=your_api_key_here
|
||||
# MAILGUN_DOMAIN=mg.yourdomain.com
|
||||
|
||||
# Amazon SES (when EMAIL_PROVIDER=ses)
|
||||
# AWS_ACCESS_KEY_ID=your_access_key
|
||||
# AWS_SECRET_ACCESS_KEY=your_secret_key
|
||||
# AWS_REGION=eu-west-1
|
||||
```
|
||||
|
||||
### Debug Mode
|
||||
|
||||
Set `EMAIL_DEBUG=true` to log emails instead of sending them. This is useful during development:
|
||||
|
||||
```env
|
||||
EMAIL_DEBUG=true
|
||||
```
|
||||
|
||||
Emails will be logged to the console with full details (recipient, subject, body preview).
|
||||
|
||||
## Database Models
|
||||
|
||||
### EmailTemplate
|
||||
|
||||
Stores multi-language email templates:
|
||||
|
||||
| Column | Type | Description |
|
||||
|--------|------|-------------|
|
||||
| id | Integer | Primary key |
|
||||
| code | String(100) | Template identifier (e.g., "signup_welcome") |
|
||||
| language | String(5) | Language code (en, fr, de, lb) |
|
||||
| name | String(255) | Human-readable name |
|
||||
| description | Text | Template purpose |
|
||||
| category | String(50) | AUTH, ORDERS, BILLING, SYSTEM, MARKETING |
|
||||
| subject | String(500) | Email subject (supports Jinja2) |
|
||||
| body_html | Text | HTML body |
|
||||
| body_text | Text | Plain text fallback |
|
||||
| variables | Text | JSON list of expected variables |
|
||||
| is_active | Boolean | Enable/disable template |
|
||||
|
||||
### EmailLog
|
||||
|
||||
Tracks all sent emails:
|
||||
|
||||
| Column | Type | Description |
|
||||
|--------|------|-------------|
|
||||
| id | Integer | Primary key |
|
||||
| template_code | String(100) | Template used (if any) |
|
||||
| recipient_email | String(255) | Recipient address |
|
||||
| subject | String(500) | Email subject |
|
||||
| status | String(20) | PENDING, SENT, FAILED, DELIVERED, OPENED |
|
||||
| sent_at | DateTime | When email was sent |
|
||||
| error_message | Text | Error details if failed |
|
||||
| provider | String(50) | Provider used (smtp, sendgrid, etc.) |
|
||||
| vendor_id | Integer | Related vendor (optional) |
|
||||
| user_id | Integer | Related user (optional) |
|
||||
|
||||
## Usage
|
||||
|
||||
### Using EmailService
|
||||
|
||||
```python
|
||||
from app.services.email_service import EmailService
|
||||
|
||||
def send_welcome_email(db, user, vendor):
|
||||
email_service = EmailService(db)
|
||||
|
||||
email_service.send_template(
|
||||
template_code="signup_welcome",
|
||||
to_email=user.email,
|
||||
to_name=f"{user.first_name} {user.last_name}",
|
||||
language="fr", # Falls back to "en" if not found
|
||||
variables={
|
||||
"first_name": user.first_name,
|
||||
"company_name": vendor.name,
|
||||
"vendor_code": vendor.vendor_code,
|
||||
"login_url": f"https://wizamart.com/vendor/{vendor.vendor_code}/dashboard",
|
||||
"trial_days": 30,
|
||||
"tier_name": "Essential",
|
||||
},
|
||||
vendor_id=vendor.id,
|
||||
user_id=user.id,
|
||||
related_type="signup",
|
||||
)
|
||||
```
|
||||
|
||||
### Convenience Function
|
||||
|
||||
```python
|
||||
from app.services.email_service import send_email
|
||||
|
||||
send_email(
|
||||
db=db,
|
||||
template_code="order_confirmation",
|
||||
to_email="customer@example.com",
|
||||
language="en",
|
||||
variables={"order_number": "ORD-001"},
|
||||
)
|
||||
```
|
||||
|
||||
### Sending Raw Emails
|
||||
|
||||
For one-off emails without templates:
|
||||
|
||||
```python
|
||||
email_service = EmailService(db)
|
||||
|
||||
email_service.send_raw(
|
||||
to_email="user@example.com",
|
||||
subject="Custom Subject",
|
||||
body_html="<h1>Hello</h1><p>Custom message</p>",
|
||||
body_text="Hello\n\nCustom message",
|
||||
)
|
||||
```
|
||||
|
||||
## Email Templates
|
||||
|
||||
### Creating Templates
|
||||
|
||||
Templates use Jinja2 syntax for variable interpolation:
|
||||
|
||||
```html
|
||||
<p>Hello {{ first_name }},</p>
|
||||
<p>Welcome to {{ company_name }}!</p>
|
||||
```
|
||||
|
||||
### Seeding Templates
|
||||
|
||||
Run the seed script to populate default templates:
|
||||
|
||||
```bash
|
||||
python scripts/seed_email_templates.py
|
||||
```
|
||||
|
||||
This creates templates for:
|
||||
|
||||
- `signup_welcome` (en, fr, de, lb)
|
||||
|
||||
### Available Variables
|
||||
|
||||
For `signup_welcome`:
|
||||
|
||||
| Variable | Description |
|
||||
|----------|-------------|
|
||||
| first_name | User's first name |
|
||||
| company_name | Vendor company name |
|
||||
| email | User's email address |
|
||||
| vendor_code | Vendor code for dashboard URL |
|
||||
| login_url | Direct link to dashboard |
|
||||
| trial_days | Number of trial days |
|
||||
| tier_name | Subscription tier name |
|
||||
|
||||
## Provider Setup
|
||||
|
||||
### SMTP
|
||||
|
||||
Standard SMTP configuration:
|
||||
|
||||
```env
|
||||
EMAIL_PROVIDER=smtp
|
||||
SMTP_HOST=smtp.gmail.com
|
||||
SMTP_PORT=587
|
||||
SMTP_USER=your-email@gmail.com
|
||||
SMTP_PASSWORD=your-app-password
|
||||
SMTP_USE_TLS=true
|
||||
```
|
||||
|
||||
### SendGrid
|
||||
|
||||
1. Create account at [sendgrid.com](https://sendgrid.com)
|
||||
2. Generate API key in Settings > API Keys
|
||||
3. Configure:
|
||||
|
||||
```env
|
||||
EMAIL_PROVIDER=sendgrid
|
||||
SENDGRID_API_KEY=SG.xxxxxxxxxxxxxxxxxxxx
|
||||
```
|
||||
|
||||
4. Install package: `pip install sendgrid`
|
||||
|
||||
### Mailgun
|
||||
|
||||
1. Create account at [mailgun.com](https://mailgun.com)
|
||||
2. Add and verify your domain
|
||||
3. Get API key from Domain Settings
|
||||
4. Configure:
|
||||
|
||||
```env
|
||||
EMAIL_PROVIDER=mailgun
|
||||
MAILGUN_API_KEY=key-xxxxxxxxxxxxxxxx
|
||||
MAILGUN_DOMAIN=mg.yourdomain.com
|
||||
```
|
||||
|
||||
### Amazon SES
|
||||
|
||||
1. Set up SES in AWS Console
|
||||
2. Verify sender domain/email
|
||||
3. Create IAM user with SES permissions
|
||||
4. Configure:
|
||||
|
||||
```env
|
||||
EMAIL_PROVIDER=ses
|
||||
AWS_ACCESS_KEY_ID=AKIAXXXXXXXXXXXXXXXX
|
||||
AWS_SECRET_ACCESS_KEY=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||
AWS_REGION=eu-west-1
|
||||
```
|
||||
|
||||
5. Install package: `pip install boto3`
|
||||
|
||||
## Email Logging
|
||||
|
||||
All emails are logged to the `email_logs` table. Query examples:
|
||||
|
||||
```python
|
||||
# Get failed emails
|
||||
failed = db.query(EmailLog).filter(
|
||||
EmailLog.status == EmailStatus.FAILED.value
|
||||
).all()
|
||||
|
||||
# Get emails for a vendor
|
||||
vendor_emails = db.query(EmailLog).filter(
|
||||
EmailLog.vendor_id == vendor_id
|
||||
).order_by(EmailLog.created_at.desc()).all()
|
||||
|
||||
# Get recent signup emails
|
||||
signups = db.query(EmailLog).filter(
|
||||
EmailLog.template_code == "signup_welcome",
|
||||
EmailLog.created_at >= datetime.now() - timedelta(days=7)
|
||||
).all()
|
||||
```
|
||||
|
||||
## Language Fallback
|
||||
|
||||
The system automatically falls back to English if a template isn't available in the requested language:
|
||||
|
||||
1. Request template for "de" (German)
|
||||
2. If not found, try "en" (English)
|
||||
3. If still not found, return None (log error)
|
||||
|
||||
## Testing
|
||||
|
||||
Run email service tests:
|
||||
|
||||
```bash
|
||||
pytest tests/unit/services/test_email_service.py -v
|
||||
```
|
||||
|
||||
Test coverage includes:
|
||||
|
||||
- Provider abstraction (Debug, SMTP, etc.)
|
||||
- Template rendering with Jinja2
|
||||
- Language fallback behavior
|
||||
- Email sending success/failure
|
||||
- EmailLog model methods
|
||||
- Template variable handling
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
app/services/email_service.py # Email service with provider abstraction
|
||||
models/database/email.py # EmailTemplate and EmailLog models
|
||||
app/core/config.py # Email configuration settings
|
||||
scripts/seed_email_templates.py # Template seeding script
|
||||
```
|
||||
|
||||
### Provider Abstraction
|
||||
|
||||
The system uses a strategy pattern for email providers:
|
||||
|
||||
```
|
||||
EmailProvider (ABC)
|
||||
├── SMTPProvider
|
||||
├── SendGridProvider
|
||||
├── MailgunProvider
|
||||
├── SESProvider
|
||||
└── DebugProvider
|
||||
```
|
||||
|
||||
Each provider implements the `send()` method with the same signature, making it easy to switch providers via configuration.
|
||||
|
||||
## Future Enhancements
|
||||
|
||||
Planned improvements:
|
||||
|
||||
1. **Email Queue**: Background task queue for high-volume sending
|
||||
2. **Webhook Tracking**: Track deliveries, opens, clicks via provider webhooks
|
||||
3. **Template Editor**: Admin UI for editing templates
|
||||
4. **A/B Testing**: Test different email versions
|
||||
5. **Scheduled Emails**: Send emails at specific times
|
||||
Reference in New Issue
Block a user