Files
orion/docs/implementation/email-templates-architecture.md
Samir Boulahtit daec462847 docs: add comprehensive documentation for today's work
Technical Documentation:
- docs/development/architecture-fixes-2026-01.md: Complete guide to
  all architecture validation fixes (62 -> 0 errors)

User Guides:
- docs/guides/email-templates.md: How-to guide for vendors and admins
  to use the email template customization system

Implementation Docs:
- docs/implementation/password-reset-implementation.md: Technical
  documentation for the password reset feature
- Updated email-templates-architecture.md with EmailTemplateService
  documentation and related links

Bugfix:
- Fixed TemplateListItem Pydantic model to match service output
  (languages vs available_languages field name)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-03 19:07:09 +01:00

14 KiB

Email Template System

Overview

The email template system provides comprehensive email customization for the Wizamart platform with the following features:

  • Platform-level templates with vendor overrides
  • Wizamart branding by default (removed for Enterprise whitelabel tier)
  • Platform-only templates that cannot be overridden (billing, subscriptions)
  • Admin UI for editing platform templates
  • Vendor UI for customizing customer-facing emails
  • 4-language support (en, fr, de, lb)
  • Smart language resolution (customer → vendor → platform default)

Architecture

Database Models

EmailTemplate (Platform Templates)

File: models/database/email.py

Column Type Description
id Integer Primary key
code String(100) Unique template identifier
language String(5) Language code (en, fr, de, lb)
name String(255) Human-readable name
description Text Template description
category Enum AUTH, ORDERS, BILLING, SYSTEM, MARKETING
subject String(500) Email subject line (Jinja2)
body_html Text HTML body (Jinja2)
body_text Text Plain text body (Jinja2)
variables JSON List of available variables
is_platform_only Boolean Cannot be overridden by vendors
required_variables Text Comma-separated required variables

Key Methods:

  • get_by_code_and_language(db, code, language) - Get specific template
  • get_overridable_templates(db) - Get templates vendors can customize

VendorEmailTemplate (Vendor Overrides)

File: models/database/vendor_email_template.py

Column Type Description
id Integer Primary key
vendor_id Integer FK to vendors.id
template_code String(100) References EmailTemplate.code
language String(5) Language code
name String(255) Custom name (optional)
subject String(500) Custom subject
body_html Text Custom HTML body
body_text Text Custom plain text body
created_at DateTime Creation timestamp
updated_at DateTime Last update timestamp

Key Methods:

  • get_override(db, vendor_id, code, language) - Get vendor override
  • create_or_update(db, vendor_id, code, language, ...) - Upsert override
  • delete_override(db, vendor_id, code, language) - Revert to platform default
  • get_all_overrides_for_vendor(db, vendor_id) - List all vendor overrides

Unique Constraint

UNIQUE (vendor_id, template_code, language)

Email Template Service

File: app/services/email_template_service.py

The EmailTemplateService encapsulates all email template business logic, keeping API endpoints clean and focused on request/response handling.

Admin Methods

Method Description
list_platform_templates() List all platform templates grouped by code
get_template_categories() Get list of template categories
get_platform_template(code) Get template with all language versions
update_platform_template(code, language, data) Update platform template content
preview_template(code, language, variables) Generate preview with sample data
get_template_logs(code, limit) Get email logs for template

Vendor Methods

Method Description
list_overridable_templates(vendor_id) List templates vendor can customize
get_vendor_template(vendor_id, code, language) Get template (override or platform default)
create_or_update_vendor_override(vendor_id, code, language, data) Save vendor customization
delete_vendor_override(vendor_id, code, language) Revert to platform default
preview_vendor_template(vendor_id, code, language, variables) Preview with vendor branding

Usage Example

from app.services.email_template_service import EmailTemplateService

service = EmailTemplateService(db)

# List templates for admin
templates = service.list_platform_templates()

# Get vendor's view of a template
template_data = service.get_vendor_template(vendor_id, "order_confirmation", "fr")

# Create vendor override
service.create_or_update_vendor_override(
    vendor_id=vendor.id,
    code="order_confirmation",
    language="fr",
    subject="Votre commande {{ order_number }}",
    body_html="<html>...</html>",
    body_text="Plain text...",
)

Email Service

File: app/services/email_service.py

Language Resolution

Priority order for determining email language:

  1. Customer preferred language (if customer exists)
  2. Vendor storefront language (vendor.storefront_language)
  3. Platform default (en)
def resolve_language(
    self,
    customer_id: int | None,
    vendor_id: int | None,
    explicit_language: str | None = None
) -> str

Template Resolution

def resolve_template(
    self,
    template_code: str,
    language: str,
    vendor_id: int | None = None
) -> ResolvedTemplate

Resolution order:

  1. If vendor_id provided and template not platform-only:
    • Look for VendorEmailTemplate override
    • Fall back to platform EmailTemplate
  2. If no vendor or platform-only:
    • Use platform EmailTemplate
  3. Language fallback: requested_languageen

Branding Resolution

def get_branding(self, vendor_id: int | None) -> BrandingContext
Scenario Platform Name Platform Logo
No vendor Wizamart Wizamart logo
Standard vendor Wizamart Wizamart logo
Whitelabel vendor Vendor name Vendor logo

Whitelabel is determined by the white_label feature flag on the vendor.


API Endpoints

Admin API

File: app/api/v1/admin/email_templates.py

Method Endpoint Description
GET /api/v1/admin/email-templates List all platform templates
GET /api/v1/admin/email-templates/categories Get template categories
GET /api/v1/admin/email-templates/{code} Get template (all languages)
GET /api/v1/admin/email-templates/{code}/{language} Get specific language version
PUT /api/v1/admin/email-templates/{code}/{language} Update template
POST /api/v1/admin/email-templates/{code}/preview Preview with sample data
POST /api/v1/admin/email-templates/{code}/test Send test email
GET /api/v1/admin/email-templates/{code}/logs View email logs for template

Vendor API

File: app/api/v1/vendor/email_templates.py

Method Endpoint Description
GET /api/v1/vendor/email-templates List overridable templates
GET /api/v1/vendor/email-templates/{code} Get template with override status
GET /api/v1/vendor/email-templates/{code}/{language} Get specific language (override or default)
PUT /api/v1/vendor/email-templates/{code}/{language} Create/update override
DELETE /api/v1/vendor/email-templates/{code}/{language} Reset to platform default
POST /api/v1/vendor/email-templates/{code}/preview Preview with vendor branding
POST /api/v1/vendor/email-templates/{code}/test Send test email

User Interface

Admin UI

Page: /admin/email-templates Template: app/templates/admin/email-templates.html JavaScript: static/admin/js/email-templates.js

Features:

  • Template list with category filtering
  • Edit modal with language tabs (en, fr, de, lb)
  • Platform-only indicator badge
  • Variable reference panel
  • HTML preview in iframe
  • Send test email functionality

Vendor UI

Page: /vendor/{vendor_code}/email-templates Template: app/templates/vendor/email-templates.html JavaScript: static/vendor/js/email-templates.js

Features:

  • List of overridable templates with customization status
  • Language override badges (green = customized)
  • Edit modal with:
    • Language tabs
    • Source indicator (vendor override vs platform default)
    • Platform template reference
    • Revert to default button
  • Preview and test email functionality

Template Categories

Category Description Platform-Only
AUTH Authentication emails (welcome, password reset) No
ORDERS Order-related emails (confirmation, shipped) No
BILLING Subscription/payment emails Yes
SYSTEM System emails (team invites, alerts) No
MARKETING Marketing/promotional emails No

Available Templates

Customer-Facing (Overridable)

Code Category Languages Description
signup_welcome AUTH en, fr, de, lb Welcome email after vendor signup
order_confirmation ORDERS en, fr, de, lb Order confirmation to customer
password_reset AUTH en, fr, de, lb Password reset link
team_invite SYSTEM en Team member invitation

Platform-Only (Not Overridable)

Code Category Languages Description
subscription_welcome BILLING en Subscription confirmation
payment_failed BILLING en Failed payment notification
subscription_cancelled BILLING en Cancellation confirmation
trial_ending BILLING en Trial ending reminder

Template Variables

Common Variables (Injected Automatically)

Variable Description
platform_name "Wizamart" or vendor name (whitelabel)
platform_logo_url Platform logo URL
support_email Support email address
vendor_name Vendor business name
vendor_logo_url Vendor logo URL

Template-Specific Variables

signup_welcome

  • first_name, company_name, email, vendor_code
  • login_url, trial_days, tier_name

order_confirmation

  • customer_name, order_number, order_total
  • order_items_count, order_date, shipping_address

password_reset

  • customer_name, reset_link, expiry_hours

team_invite

  • invitee_name, inviter_name, vendor_name
  • role, accept_url, expires_in_days

Migration

File: alembic/versions/u9c0d1e2f3g4_add_vendor_email_templates.py

Run migration:

alembic upgrade head

The migration:

  1. Adds is_platform_only and required_variables columns to email_templates
  2. Creates vendor_email_templates table
  3. Adds unique constraint on (vendor_id, template_code, language)
  4. Creates indexes for performance

Seeding Templates

File: scripts/seed_email_templates.py

Run seed script:

python scripts/seed_email_templates.py

The script:

  • Creates/updates all platform templates
  • Supports all 4 languages for customer-facing templates
  • Sets is_platform_only flag for billing templates

Security Considerations

  1. XSS Prevention: HTML templates are rendered server-side with Jinja2 escaping
  2. Access Control: Vendors can only view/edit their own overrides
  3. Platform-only Protection: API enforces is_platform_only flag
  4. Template Validation: Jinja2 syntax validated before save
  5. Rate Limiting: Test email sending subject to rate limits
  6. Token Hashing: Password reset tokens stored as SHA256 hashes

Usage Examples

Sending a Template Email

from app.services.email_service import EmailService

email_svc = EmailService(db)
email_log = email_svc.send_template(
    template_code="order_confirmation",
    to_email="customer@example.com",
    variables={
        "customer_name": "John Doe",
        "order_number": "ORD-12345",
        "order_total": "€99.99",
        "order_items_count": "3",
        "order_date": "2024-01-15",
        "shipping_address": "123 Main St, Luxembourg"
    },
    vendor_id=vendor.id,  # Optional: enables vendor override lookup
    customer_id=customer.id,  # Optional: for language resolution
    language="fr"  # Optional: explicit language override
)

Creating a Vendor Override

from models.database.vendor_email_template import VendorEmailTemplate

override = VendorEmailTemplate.create_or_update(
    db=db,
    vendor_id=vendor.id,
    template_code="order_confirmation",
    language="fr",
    subject="Confirmation de votre commande {{ order_number }}",
    body_html="<html>...</html>",
    body_text="Plain text version..."
)
db.commit()

Reverting to Platform Default

VendorEmailTemplate.delete_override(
    db=db,
    vendor_id=vendor.id,
    template_code="order_confirmation",
    language="fr"
)
db.commit()

File Structure

├── alembic/versions/
│   └── u9c0d1e2f3g4_add_vendor_email_templates.py
├── app/
│   ├── api/v1/
│   │   ├── admin/
│   │   │   └── email_templates.py
│   │   └── vendor/
│   │       └── email_templates.py
│   ├── routes/
│   │   ├── admin_pages.py  (route added)
│   │   └── vendor_pages.py (route added)
│   ├── services/
│   │   ├── email_service.py (enhanced)
│   │   └── email_template_service.py (new - business logic)
│   └── templates/
│       ├── admin/
│       │   ├── email-templates.html
│       │   └── partials/sidebar.html (link added)
│       └── vendor/
│           ├── email-templates.html
│           └── partials/sidebar.html (link added)
├── models/
│   ├── database/
│   │   ├── email.py (enhanced)
│   │   └── vendor_email_template.py
│   └── schema/
│       └── email.py
├── scripts/
│   └── seed_email_templates.py (enhanced)
└── static/
    ├── admin/js/
    │   └── email-templates.js
    └── vendor/js/
        └── email-templates.js