fix: resolve email settings architecture violations and add tests/docs

- Fix API-002 in admin/settings.py: use service layer for DB delete
- Fix API-001/API-003 in vendor/email_settings.py: add Pydantic response
  models, remove HTTPException raises
- Fix SVC-002/SVC-006 in vendor_email_settings_service.py: use domain
  exceptions, change db.commit() to db.flush()
- Add unit tests for VendorEmailSettingsService
- Add integration tests for vendor and admin email settings APIs
- Add user guide (docs/guides/email-settings.md)
- Add developer guide (docs/implementation/email-settings.md)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-01-05 22:38:10 +01:00
parent 36603178c3
commit 84a523cd7b
9 changed files with 1765 additions and 85 deletions

View File

@@ -0,0 +1,254 @@
# Email Settings Guide
This guide covers email configuration for both **vendors** and **platform administrators**. The Wizamart platform uses a layered email system where vendors manage their own email sending while the platform handles system-level communications.
## Overview
The email system has two distinct configurations:
| Aspect | Platform (Admin) | Vendor |
|--------|-----------------|--------|
| Purpose | System emails (billing, admin notifications) | Customer-facing emails (orders, marketing) |
| Configuration | Environment variables (.env) + Database overrides | Database (per-vendor) |
| Cost | Platform owner pays | Vendor pays |
| Providers | SMTP, SendGrid, Mailgun, SES | SMTP (all tiers), Premium providers (Business+) |
---
## Vendor Email Settings
### Getting Started
As a vendor, you need to configure email settings to send emails to your customers. This includes order confirmations, shipping updates, and marketing emails.
#### Accessing Email Settings
1. Log in to your Vendor Dashboard
2. Navigate to **Settings** from the sidebar
3. Click on the **Email** tab
### Available Providers
| Provider | Tier Required | Best For |
|----------|---------------|----------|
| SMTP | All tiers | Standard email servers, most common |
| SendGrid | Business+ | High-volume transactional emails |
| Mailgun | Business+ | Developer-friendly API |
| Amazon SES | Business+ | AWS ecosystem, cost-effective |
### Configuring SMTP
SMTP is available for all subscription tiers. Common SMTP providers include:
- Gmail (smtp.gmail.com:587)
- Microsoft 365 (smtp.office365.com:587)
- Your hosting provider's SMTP server
**Required Fields:**
- **From Email**: The sender email address (e.g., orders@yourstore.com)
- **From Name**: The sender display name (e.g., "Your Store")
- **SMTP Host**: Your SMTP server address
- **SMTP Port**: Usually 587 (TLS) or 465 (SSL)
- **SMTP Username**: Your login username
- **SMTP Password**: Your login password
- **Use TLS**: Enable for port 587 (recommended)
- **Use SSL**: Enable for port 465
### Configuring Premium Providers (Business+)
If you have a Business or Enterprise subscription, you can use premium email providers:
#### SendGrid
1. Create a SendGrid account at [sendgrid.com](https://sendgrid.com)
2. Generate an API key
3. Enter the API key in your vendor settings
#### Mailgun
1. Create a Mailgun account at [mailgun.com](https://mailgun.com)
2. Add and verify your domain
3. Get your API key from the dashboard
4. Enter the API key and domain in your settings
#### Amazon SES
1. Set up SES in your AWS account
2. Verify your sender domain/email
3. Create IAM credentials with SES permissions
4. Enter the access key, secret key, and region
### Verifying Your Configuration
After configuring your email settings:
1. Click **Save Settings**
2. Enter a test email address in the **Test Email** field
3. Click **Send Test**
4. Check your inbox for the test email
If the test fails, check:
- Your credentials are correct
- Your IP/domain is not blocked
- For Gmail: Allow "less secure apps" or use an app password
### Email Warning Banner
Until you configure and verify your email settings, you'll see a warning banner at the top of your dashboard. This ensures you don't forget to set up email before your store goes live.
---
## Platform Admin Email Settings
### Overview
Platform administrators can configure system-wide email settings for platform communications like:
- Subscription billing notifications
- Admin alerts
- Platform-wide announcements
### Configuration Sources
Admin email settings support two configuration sources:
1. **Environment Variables (.env)** - Default configuration
2. **Database Overrides** - Override .env via the admin UI
Database settings take priority over .env values.
### Accessing Admin Email Settings
1. Log in to the Admin Panel
2. Navigate to **Settings**
3. Click on the **Email** tab
### Viewing Current Configuration
The Email tab shows:
- **Provider**: Current email provider (SMTP, SendGrid, etc.)
- **From Email**: Sender email address
- **From Name**: Sender display name
- **Status**: Whether email is configured and enabled
- **DB Overrides**: Whether database overrides are active
### Editing Settings
Click **Edit Settings** to modify the email configuration:
1. Select the email provider
2. Enter the required credentials
3. Configure enabled/debug flags
4. Click **Save Email Settings**
### Resetting to .env Defaults
If you've made database overrides and want to revert to .env configuration:
1. Click **Reset to .env Defaults**
2. Confirm the action
This removes all email settings from the database, reverting to .env values.
### Testing Configuration
1. Enter a test email address
2. Click **Send Test**
3. Check your inbox
---
## Environment Variables Reference
For platform configuration via .env:
```env
# Provider: smtp, sendgrid, mailgun, ses
EMAIL_PROVIDER=smtp
# Sender identity
EMAIL_FROM_ADDRESS=noreply@yourplatform.com
EMAIL_FROM_NAME=Your Platform
EMAIL_REPLY_TO=support@yourplatform.com
# Behavior
EMAIL_ENABLED=true
EMAIL_DEBUG=false
# SMTP Configuration
SMTP_HOST=smtp.example.com
SMTP_PORT=587
SMTP_USER=your-username
SMTP_PASSWORD=your-password
SMTP_USE_TLS=true
SMTP_USE_SSL=false
# SendGrid
SENDGRID_API_KEY=your-api-key
# Mailgun
MAILGUN_API_KEY=your-api-key
MAILGUN_DOMAIN=mg.yourdomain.com
# Amazon SES
AWS_ACCESS_KEY_ID=your-access-key
AWS_SECRET_ACCESS_KEY=your-secret-key
AWS_REGION=eu-west-1
```
---
## Tier-Based Branding
The email system includes tier-based branding for vendor emails:
| Tier | Branding |
|------|----------|
| Essential | "Powered by Wizamart" footer |
| Professional | "Powered by Wizamart" footer |
| Business | No branding (white-label) |
| Enterprise | No branding (white-label) |
Business and Enterprise tier vendors get completely white-labeled emails with no Wizamart branding.
---
## Troubleshooting
### Common Issues
**"Email sending is disabled"**
- Check that `EMAIL_ENABLED=true` in .env
- Or enable it in the admin settings
**"Connection refused" on SMTP**
- Verify SMTP host and port
- Check firewall rules
- Ensure TLS/SSL settings match your server
**"Authentication failed"**
- Double-check username/password
- For Gmail, use an App Password
- For Microsoft 365, check MFA requirements
**"SendGrid error: 403"**
- Verify your API key has Mail Send permissions
- Check sender identity is verified
**Premium provider not available**
- Upgrade to Business or Enterprise tier
- Contact support if you have the right tier but can't access
### Debug Mode
Enable debug mode to log emails instead of sending them:
- Set `EMAIL_DEBUG=true` in .env
- Or enable "Debug mode" in admin settings
Debug mode logs the email content to the server logs without actually sending.
---
## Security Best Practices
1. **Never share API keys or passwords** in logs or frontend
2. **Use environment variables** for sensitive credentials
3. **Enable TLS** for SMTP connections
4. **Verify sender domains** with your email provider
5. **Monitor email logs** for delivery issues
6. **Rotate credentials** periodically

View File

@@ -0,0 +1,308 @@
# Email Settings Implementation
This document describes the technical implementation of the email settings system for both vendor and platform (admin) configurations.
## Architecture Overview
```
┌─────────────────────────────────────────────────────────────────────────┐
│ Email System Architecture │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────────┐ ┌──────────────────┐ │
│ │ Platform Email │ │ Vendor Email │ │
│ │ (Admin/Billing)│ │ (Customer-facing) │ │
│ └────────┬─────────┘ └────────┬─────────┘ │
│ │ │ │
│ ▼ ▼ │
│ ┌──────────────────┐ ┌──────────────────┐ │
│ │ get_platform_ │ │ get_vendor_ │ │
│ │ email_config(db) │ │ provider() │ │
│ └────────┬─────────┘ └────────┬─────────┘ │
│ │ │ │
│ ▼ ▼ │
│ ┌──────────────────┐ ┌──────────────────┐ │
│ │ AdminSettings DB │ │VendorEmailSettings│ │
│ │ (.env fallback)│ │ (per vendor) │ │
│ └────────┬─────────┘ └────────┬─────────┘ │
│ │ │ │
│ └───────────┬───────────────┘ │
│ ▼ │
│ ┌──────────────────┐ │
│ │ EmailService │ │
│ │ send_raw() │ │
│ └────────┬─────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────┐ │
│ │ Email Providers │ │
│ │ SMTP/SG/MG/SES │ │
│ └──────────────────┘ │
└─────────────────────────────────────────────────────────────────────────┘
```
## Database Models
### VendorEmailSettings
```python
# models/database/vendor_email_settings.py
class VendorEmailSettings(Base):
__tablename__ = "vendor_email_settings"
id: int
vendor_id: int # FK to vendors.id (one-to-one)
# Sender Identity
from_email: str
from_name: str
reply_to_email: str | None
# Signature
signature_text: str | None
signature_html: str | None
# Provider
provider: str = "smtp" # smtp, sendgrid, mailgun, ses
# SMTP Settings
smtp_host: str | None
smtp_port: int = 587
smtp_username: str | None
smtp_password: str | None
smtp_use_tls: bool = True
smtp_use_ssl: bool = False
# SendGrid
sendgrid_api_key: str | None
# Mailgun
mailgun_api_key: str | None
mailgun_domain: str | None
# SES
ses_access_key_id: str | None
ses_secret_access_key: str | None
ses_region: str = "eu-west-1"
# Status
is_configured: bool = False
is_verified: bool = False
last_verified_at: datetime | None
verification_error: str | None
```
### Admin Settings (Platform Email)
Platform email settings are stored in the generic `admin_settings` table with category="email":
```python
# Keys stored in admin_settings table
EMAIL_SETTING_KEYS = {
"email_provider",
"email_from_address",
"email_from_name",
"email_reply_to",
"smtp_host",
"smtp_port",
"smtp_user",
"smtp_password",
"smtp_use_tls",
"smtp_use_ssl",
"sendgrid_api_key",
"mailgun_api_key",
"mailgun_domain",
"aws_access_key_id",
"aws_secret_access_key",
"aws_region",
"email_enabled",
"email_debug",
}
```
## API Endpoints
### Vendor Email Settings
| Endpoint | Method | Description |
|----------|--------|-------------|
| `/api/v1/vendor/email-settings` | GET | Get current email settings |
| `/api/v1/vendor/email-settings` | PUT | Create/update email settings |
| `/api/v1/vendor/email-settings` | DELETE | Delete email settings |
| `/api/v1/vendor/email-settings/status` | GET | Get configuration status |
| `/api/v1/vendor/email-settings/providers` | GET | Get available providers for tier |
| `/api/v1/vendor/email-settings/verify` | POST | Send test email |
### Admin Email Settings
| Endpoint | Method | Description |
|----------|--------|-------------|
| `/api/v1/admin/settings/email/status` | GET | Get effective email config |
| `/api/v1/admin/settings/email/settings` | PUT | Update email settings in DB |
| `/api/v1/admin/settings/email/settings` | DELETE | Reset to .env defaults |
| `/api/v1/admin/settings/email/test` | POST | Send test email |
## Services
### VendorEmailSettingsService
Location: `app/services/vendor_email_settings_service.py`
Key methods:
- `get_settings(vendor_id)` - Get settings for a vendor
- `create_or_update(vendor_id, data, current_tier)` - Create/update settings
- `delete(vendor_id)` - Delete settings
- `verify_settings(vendor_id, test_email)` - Send test email
- `get_available_providers(tier)` - Get providers for subscription tier
### EmailService Integration
The EmailService (`app/services/email_service.py`) uses:
1. **Platform Config**: `get_platform_email_config(db)` checks database first, then .env
2. **Vendor Config**: `get_vendor_provider(settings)` creates provider from VendorEmailSettings
3. **Provider Selection**: `send_raw()` uses vendor provider when `vendor_id` provided and `is_platform_email=False`
```python
# EmailService.send_raw() flow
def send_raw(self, to_email, subject, body_html, vendor_id=None, is_platform_email=False):
if vendor_id and not is_platform_email:
# Use vendor's email provider
vendor_settings = self._get_vendor_email_settings(vendor_id)
if vendor_settings and vendor_settings.is_configured:
provider = get_vendor_provider(vendor_settings)
else:
# Use platform provider (DB config > .env)
provider = self.provider # Set in __init__ via get_platform_provider(db)
```
## Tier-Based Features
### Premium Provider Gating
Premium providers (SendGrid, Mailgun, SES) are gated to Business+ tiers:
```python
PREMIUM_EMAIL_PROVIDERS = {EmailProvider.SENDGRID, EmailProvider.MAILGUN, EmailProvider.SES}
PREMIUM_TIERS = {TierCode.BUSINESS, TierCode.ENTERPRISE}
def create_or_update(self, vendor_id, data, current_tier):
provider = data.get("provider", "smtp")
if provider in [p.value for p in PREMIUM_EMAIL_PROVIDERS]:
if current_tier not in PREMIUM_TIERS:
raise AuthorizationException(...)
```
### White-Label Branding
Emails include "Powered by Wizamart" footer for non-whitelabel tiers:
```python
WHITELABEL_TIERS = {"business", "enterprise"}
POWERED_BY_FOOTER_HTML = """
<div style="margin-top: 30px; ...">
<p>Powered by <a href="https://wizamart.com">Wizamart</a></p>
</div>
"""
def _inject_powered_by_footer(self, body_html, vendor_id):
tier = self._get_vendor_tier(vendor_id)
if tier and tier.lower() in WHITELABEL_TIERS:
return body_html # No footer for business/enterprise
return body_html.replace("</body>", f"{POWERED_BY_FOOTER_HTML}</body>")
```
## Configuration Priority
### Platform Email
1. **Database** (admin_settings table) - Highest priority
2. **Environment Variables** (.env) - Fallback
```python
def get_platform_email_config(db: Session) -> dict:
def get_db_setting(key: str) -> str | None:
setting = db.query(AdminSetting).filter(AdminSetting.key == key).first()
return setting.value if setting else None
# Check DB first, fallback to .env
db_provider = get_db_setting("email_provider")
config["provider"] = db_provider if db_provider else settings.email_provider
...
```
### Vendor Email
Vendors have their own dedicated settings table with no fallback - they must configure their own email.
## Frontend Components
### Vendor Settings Page
- **Location**: `app/templates/vendor/settings.html`, `static/vendor/js/settings.js`
- **Alpine.js State**: `emailSettings`, `emailForm`, `hasEmailChanges`
- **Methods**: `loadEmailSettings()`, `saveEmailSettings()`, `sendTestEmail()`
### Admin Settings Page
- **Location**: `app/templates/admin/settings.html`, `static/admin/js/settings.js`
- **Alpine.js State**: `emailSettings`, `emailForm`, `emailEditMode`
- **Methods**: `loadEmailSettings()`, `saveEmailSettings()`, `resetEmailSettings()`, `sendTestEmail()`
### Warning Banner
Shows until email is configured:
```html
<!-- app/templates/shared/macros/feature_gate.html -->
{% macro email_settings_warning() %}
<div x-data="emailSettingsWarning()" x-show="showWarning">
Configure email settings to send emails to customers.
</div>
{% endmacro %}
```
## Testing
### Unit Tests
Location: `tests/unit/services/test_vendor_email_settings_service.py`
Tests:
- Read operations (get_settings, get_status, is_configured)
- Write operations (create_or_update, delete)
- Tier validation (premium providers)
- Verification (mock SMTP)
- Provider availability
### Integration Tests
Locations:
- `tests/integration/api/v1/vendor/test_email_settings.py`
- `tests/integration/api/v1/admin/test_email_settings.py`
Tests:
- CRUD operations via API
- Authentication/authorization
- Validation errors
- Status endpoints
## Files Modified/Created
### New Files
- `models/database/vendor_email_settings.py` - Model
- `alembic/versions/v0a1b2c3d4e5_add_vendor_email_settings.py` - Migration
- `app/services/vendor_email_settings_service.py` - Service
- `app/api/v1/vendor/email_settings.py` - API endpoints
- `scripts/install.py` - Installation wizard
### Modified Files
- `app/services/email_service.py` - Added platform config, vendor providers
- `app/api/v1/admin/settings.py` - Added email endpoints
- `app/templates/admin/settings.html` - Email tab
- `app/templates/vendor/settings.html` - Email tab
- `static/admin/js/settings.js` - Email JS
- `static/vendor/js/settings.js` - Email JS
- `static/vendor/js/init-alpine.js` - Warning banner component