Replace all ~1,086 occurrences of Wizamart/wizamart/WIZAMART/WizaMart with Orion/orion/ORION across 184 files. This includes database identifiers, email addresses, domain references, R2 bucket names, DNS prefixes, encryption salt, Celery app name, config defaults, Docker configs, CI configs, documentation, seed data, and templates. Renames homepage-wizamart.html template to homepage-orion.html. Fixes duplicate file_pattern key in api.yaml architecture rule. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
309 lines
12 KiB
Markdown
309 lines
12 KiB
Markdown
# Email Settings Implementation
|
|
|
|
This document describes the technical implementation of the email settings system for both store and platform (admin) configurations.
|
|
|
|
## Architecture Overview
|
|
|
|
```
|
|
┌─────────────────────────────────────────────────────────────────────────┐
|
|
│ Email System Architecture │
|
|
├─────────────────────────────────────────────────────────────────────────┤
|
|
│ │
|
|
│ ┌──────────────────┐ ┌──────────────────┐ │
|
|
│ │ Platform Email │ │ Store Email │ │
|
|
│ │ (Admin/Billing)│ │ (Customer-facing) │ │
|
|
│ └────────┬─────────┘ └────────┬─────────┘ │
|
|
│ │ │ │
|
|
│ ▼ ▼ │
|
|
│ ┌──────────────────┐ ┌──────────────────┐ │
|
|
│ │ get_platform_ │ │ get_store_ │ │
|
|
│ │ email_config(db) │ │ provider() │ │
|
|
│ └────────┬─────────┘ └────────┬─────────┘ │
|
|
│ │ │ │
|
|
│ ▼ ▼ │
|
|
│ ┌──────────────────┐ ┌──────────────────┐ │
|
|
│ │ AdminSettings DB │ │StoreEmailSettings│ │
|
|
│ │ (.env fallback)│ │ (per store) │ │
|
|
│ └────────┬─────────┘ └────────┬─────────┘ │
|
|
│ │ │ │
|
|
│ └───────────┬───────────────┘ │
|
|
│ ▼ │
|
|
│ ┌──────────────────┐ │
|
|
│ │ EmailService │ │
|
|
│ │ send_raw() │ │
|
|
│ └────────┬─────────┘ │
|
|
│ │ │
|
|
│ ▼ │
|
|
│ ┌──────────────────┐ │
|
|
│ │ Email Providers │ │
|
|
│ │ SMTP/SG/MG/SES │ │
|
|
│ └──────────────────┘ │
|
|
└─────────────────────────────────────────────────────────────────────────┘
|
|
```
|
|
|
|
## Database Models
|
|
|
|
### StoreEmailSettings
|
|
|
|
```python
|
|
# models/database/store_email_settings.py
|
|
|
|
class StoreEmailSettings(Base):
|
|
__tablename__ = "store_email_settings"
|
|
|
|
id: int
|
|
store_id: int # FK to stores.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
|
|
|
|
### Store Email Settings
|
|
|
|
| Endpoint | Method | Description |
|
|
|----------|--------|-------------|
|
|
| `/api/v1/store/email-settings` | GET | Get current email settings |
|
|
| `/api/v1/store/email-settings` | PUT | Create/update email settings |
|
|
| `/api/v1/store/email-settings` | DELETE | Delete email settings |
|
|
| `/api/v1/store/email-settings/status` | GET | Get configuration status |
|
|
| `/api/v1/store/email-settings/providers` | GET | Get available providers for tier |
|
|
| `/api/v1/store/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
|
|
|
|
### StoreEmailSettingsService
|
|
|
|
Location: `app/services/store_email_settings_service.py`
|
|
|
|
Key methods:
|
|
- `get_settings(store_id)` - Get settings for a store
|
|
- `create_or_update(store_id, data, current_tier)` - Create/update settings
|
|
- `delete(store_id)` - Delete settings
|
|
- `verify_settings(store_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. **Store Config**: `get_store_provider(settings)` creates provider from StoreEmailSettings
|
|
3. **Provider Selection**: `send_raw()` uses store provider when `store_id` provided and `is_platform_email=False`
|
|
|
|
```python
|
|
# EmailService.send_raw() flow
|
|
def send_raw(self, to_email, subject, body_html, store_id=None, is_platform_email=False):
|
|
if store_id and not is_platform_email:
|
|
# Use store's email provider
|
|
store_settings = self._get_store_email_settings(store_id)
|
|
if store_settings and store_settings.is_configured:
|
|
provider = get_store_provider(store_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, store_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 Orion" 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://orion.lu">Orion</a></p>
|
|
</div>
|
|
"""
|
|
|
|
def _inject_powered_by_footer(self, body_html, store_id):
|
|
tier = self._get_store_tier(store_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
|
|
...
|
|
```
|
|
|
|
### Store Email
|
|
|
|
Stores have their own dedicated settings table with no fallback - they must configure their own email.
|
|
|
|
## Frontend Components
|
|
|
|
### Store Settings Page
|
|
|
|
- **Location**: `app/templates/store/settings.html`, `static/store/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_store_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/store/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/store_email_settings.py` - Model
|
|
- `alembic/versions/v0a1b2c3d4e5_add_store_email_settings.py` - Migration
|
|
- `app/services/store_email_settings_service.py` - Service
|
|
- `app/api/v1/store/email_settings.py` - API endpoints
|
|
- `scripts/seed/install.py` - Installation wizard
|
|
|
|
### Modified Files
|
|
- `app/services/email_service.py` - Added platform config, store providers
|
|
- `app/api/v1/admin/settings.py` - Added email endpoints
|
|
- `app/templates/admin/settings.html` - Email tab
|
|
- `app/templates/store/settings.html` - Email tab
|
|
- `static/admin/js/settings.js` - Email JS
|
|
- `static/store/js/settings.js` - Email JS
|
|
- `static/store/js/init-alpine.js` - Warning banner component
|