Files
orion/docs/implementation/email-settings.md
Samir Boulahtit 7a9dda282d refactor(scripts): reorganize scripts/ into seed/ and validate/ subfolders
Move 9 init/seed scripts into scripts/seed/ and 7 validation scripts
(+ validators/ subfolder) into scripts/validate/ to reduce clutter in
the root scripts/ directory. Update all references across Makefile,
CI/CD configs, pre-commit hooks, docs (~40 files), and Python imports.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-09 21:35:53 +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 Wizamart" footer for non-whitelabel tiers:

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