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

Powered by Wizamart

""" 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("", f"{POWERED_BY_FOOTER_HTML}") ``` ## 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 {% macro email_settings_warning() %}
Configure email settings to send emails to customers.
{% 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