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>
12 KiB
12 KiB
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
# 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":
# 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 storecreate_or_update(store_id, data, current_tier)- Create/update settingsdelete(store_id)- Delete settingsverify_settings(store_id, test_email)- Send test emailget_available_providers(tier)- Get providers for subscription tier
EmailService Integration
The EmailService (app/services/email_service.py) uses:
- Platform Config:
get_platform_email_config(db)checks database first, then .env - Store Config:
get_store_provider(settings)creates provider from StoreEmailSettings - Provider Selection:
send_raw()uses store provider whenstore_idprovided andis_platform_email=False
# 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:
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:
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
- Database (admin_settings table) - Highest priority
- Environment Variables (.env) - Fallback
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:
<!-- 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.pytests/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- Modelalembic/versions/v0a1b2c3d4e5_add_store_email_settings.py- Migrationapp/services/store_email_settings_service.py- Serviceapp/api/v1/store/email_settings.py- API endpointsscripts/seed/install.py- Installation wizard
Modified Files
app/services/email_service.py- Added platform config, store providersapp/api/v1/admin/settings.py- Added email endpointsapp/templates/admin/settings.html- Email tabapp/templates/store/settings.html- Email tabstatic/admin/js/settings.js- Email JSstatic/store/js/settings.js- Email JSstatic/store/js/init-alpine.js- Warning banner component