Files
orion/docs/implementation/email-settings.md
Samir Boulahtit e9253fbd84 refactor: rename Wizamart to Orion across entire codebase
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>
2026-02-14 16:46:56 +01:00

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 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
# 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

  1. Database (admin_settings table) - Highest priority
  2. 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.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