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

459 lines
14 KiB
Markdown

# 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
```sql
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
```python
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`)
```python
def resolve_language(
self,
customer_id: int | None,
vendor_id: int | None,
explicit_language: str | None = None
) -> str
```
### Template Resolution
```python
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_language``en`
### Branding Resolution
```python
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:
```bash
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:
```bash
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
```python
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
```python
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
```python
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
```
---
## Related Documentation
- [Email Templates User Guide](../guides/email-templates.md) - How to use the email template system
- [Password Reset Implementation](./password-reset-implementation.md) - Password reset feature using email templates
- [Architecture Fixes (January 2026)](../development/architecture-fixes-2026-01.md) - Architecture validation fixes