docs: comprehensive email template system documentation
Update implementation docs to reflect completed email system: - Database models (EmailTemplate, VendorEmailTemplate) - Email service enhancements (language/template/branding resolution) - Admin and Vendor API endpoints - UI features and pages - Template variables and categories - Usage examples and file structure 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,289 +1,396 @@
|
||||
# Email System Enhancements - Implementation Plan
|
||||
# Email Template System
|
||||
|
||||
## Overview
|
||||
|
||||
Enhance the email system to support:
|
||||
- Platform-level templates with vendor overrides
|
||||
- Wizamart branding by default (removed for Enterprise whitelabel tier)
|
||||
- Platform-only templates that cannot be overridden
|
||||
- Admin UI for editing platform templates
|
||||
- 4-language support (en, fr, de, lb)
|
||||
- Smart language resolution (customer → vendor → platform default)
|
||||
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)
|
||||
|
||||
---
|
||||
|
||||
## Phase 1: Database & Model Foundation
|
||||
## Architecture
|
||||
|
||||
### 1.1 Update EmailTemplate Model
|
||||
### Database Models
|
||||
|
||||
#### EmailTemplate (Platform Templates)
|
||||
**File:** `models/database/email.py`
|
||||
|
||||
Add columns:
|
||||
- `is_platform_only: bool = False` - Cannot be overridden by vendors
|
||||
- `required_variables: JSON` - List of variables that must be provided
|
||||
| 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 |
|
||||
|
||||
```python
|
||||
is_platform_only = Column(Boolean, default=False, nullable=False)
|
||||
required_variables = Column(JSON, default=list)
|
||||
```
|
||||
|
||||
### 1.2 Create VendorEmailTemplate Model
|
||||
**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`
|
||||
|
||||
```python
|
||||
class VendorEmailTemplate(Base):
|
||||
__tablename__ = "vendor_email_templates"
|
||||
| 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 |
|
||||
|
||||
id: int
|
||||
vendor_id: int (FK → vendors.id)
|
||||
template_code: str # References EmailTemplate.code
|
||||
language: str # en, fr, de, lb
|
||||
subject: str
|
||||
body_html: str
|
||||
body_text: str
|
||||
is_active: bool = True
|
||||
created_at: datetime
|
||||
updated_at: datetime
|
||||
**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: (vendor_id, template_code, language)
|
||||
### Unique Constraint
|
||||
```sql
|
||||
UNIQUE (vendor_id, template_code, language)
|
||||
```
|
||||
|
||||
### 1.3 Migration
|
||||
|
||||
**File:** `alembic/versions/xxx_add_vendor_email_templates.py`
|
||||
|
||||
- Add columns to email_templates table
|
||||
- Create vendor_email_templates table
|
||||
- Add indexes for lookup performance
|
||||
|
||||
---
|
||||
|
||||
## Phase 2: Email Service Enhancement
|
||||
|
||||
### 2.1 Language Resolution
|
||||
|
||||
**Priority order for customer-facing emails:**
|
||||
1. `customer.preferred_language` (if customer exists)
|
||||
2. `vendor.storefront_language`
|
||||
3. Platform default (`en`)
|
||||
|
||||
**Priority order for vendor-facing emails:**
|
||||
1. `vendor.preferred_language`
|
||||
2. Platform default (`en`)
|
||||
|
||||
### 2.2 Template Resolution
|
||||
## 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
|
||||
) -> tuple[str, str, str]: # subject, body_html, body_text
|
||||
"""
|
||||
Resolve template with vendor override support.
|
||||
|
||||
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 → en
|
||||
"""
|
||||
) -> ResolvedTemplate
|
||||
```
|
||||
|
||||
### 2.3 Branding Resolution
|
||||
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) -> dict:
|
||||
"""
|
||||
Get branding variables for email.
|
||||
|
||||
Returns:
|
||||
- platform_name: "Wizamart" or vendor.name (if whitelabel)
|
||||
- platform_logo: Wizamart logo or vendor logo (if whitelabel)
|
||||
- support_email: platform or vendor support email
|
||||
- vendor_name: Always vendor.name
|
||||
- vendor_logo: Always vendor logo
|
||||
"""
|
||||
if vendor_id:
|
||||
vendor = self._get_vendor(vendor_id)
|
||||
if has_feature(vendor, "white_label"):
|
||||
return {
|
||||
"platform_name": vendor.name,
|
||||
"platform_logo": vendor.logo_url,
|
||||
...
|
||||
}
|
||||
return {
|
||||
"platform_name": "Wizamart",
|
||||
"platform_logo": PLATFORM_LOGO_URL,
|
||||
...
|
||||
}
|
||||
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.
|
||||
|
||||
---
|
||||
|
||||
## Phase 3: Admin API & UI
|
||||
## API Endpoints
|
||||
|
||||
### 3.1 Admin API Endpoints
|
||||
### Admin API
|
||||
|
||||
**File:** `app/api/v1/admin/email_templates.py`
|
||||
|
||||
| Method | Endpoint | Description |
|
||||
|--------|----------|-------------|
|
||||
| GET | `/email-templates` | List all platform templates |
|
||||
| GET | `/email-templates/{code}` | Get template (all languages) |
|
||||
| PUT | `/email-templates/{code}/{language}` | Update template |
|
||||
| POST | `/email-templates/{code}/preview` | Preview with sample data |
|
||||
| POST | `/email-templates/{code}/test` | Send test email |
|
||||
| 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 |
|
||||
|
||||
### 3.2 Admin UI
|
||||
|
||||
**File:** `app/templates/admin/email-templates.html`
|
||||
|
||||
Features:
|
||||
- List view with template codes, categories, language badges
|
||||
- Edit view with:
|
||||
- Language tabs (en, fr, de, lb)
|
||||
- Subject field
|
||||
- Rich text editor for body_html
|
||||
- Plain text preview for body_text
|
||||
- Variable reference panel
|
||||
- Preview pane
|
||||
- Test send button
|
||||
- Platform-only badge/indicator
|
||||
- Required variables display
|
||||
|
||||
---
|
||||
|
||||
## Phase 4: Vendor API & UI
|
||||
|
||||
### 4.1 Vendor API Endpoints
|
||||
### Vendor API
|
||||
|
||||
**File:** `app/api/v1/vendor/email_templates.py`
|
||||
|
||||
| Method | Endpoint | Description |
|
||||
|--------|----------|-------------|
|
||||
| GET | `/email-templates` | List overridable templates |
|
||||
| GET | `/email-templates/{code}` | Get vendor override or default |
|
||||
| PUT | `/email-templates/{code}/{language}` | Create/update override |
|
||||
| DELETE | `/email-templates/{code}/{language}` | Reset to platform default |
|
||||
| POST | `/email-templates/{code}/preview` | Preview with vendor branding |
|
||||
| POST | `/email-templates/{code}/test` | Send test email |
|
||||
| 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 |
|
||||
|
||||
### 4.2 Vendor UI
|
||||
---
|
||||
|
||||
**File:** `app/templates/vendor/email-templates.html`
|
||||
## User Interface
|
||||
|
||||
### Admin UI
|
||||
|
||||
**Page:** `/admin/email-templates`
|
||||
**Template:** `app/templates/admin/email-templates.html`
|
||||
**JavaScript:** `static/admin/js/email-templates.js`
|
||||
|
||||
Features:
|
||||
- List view with templates, showing "Customized" vs "Using Default"
|
||||
- Edit view similar to admin but:
|
||||
- Can only edit non-platform-only templates
|
||||
- "Reset to Default" button
|
||||
- Shows platform default as reference
|
||||
- Tier gating: Only accessible for Business+ tiers
|
||||
- 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
|
||||
|
||||
---
|
||||
|
||||
## Phase 5: Missing Templates
|
||||
## Template Categories
|
||||
|
||||
### Platform-Only Templates (Billing/Subscription)
|
||||
|
||||
| Code | Category | Description |
|
||||
|------|----------|-------------|
|
||||
| `tier_upgrade` | BILLING | Vendor upgraded subscription |
|
||||
| `tier_downgrade` | BILLING | Vendor downgraded subscription |
|
||||
| `payment_failed` | BILLING | Subscription payment failed |
|
||||
| `subscription_cancelled` | BILLING | Subscription cancelled |
|
||||
| `trial_ending` | BILLING | Trial period ending soon |
|
||||
| `usage_limit_warning` | BILLING | Approaching usage limits |
|
||||
|
||||
### Overridable Templates
|
||||
|
||||
| Code | Category | Description |
|
||||
|------|----------|-------------|
|
||||
| `team_invite` | AUTH | Team member invitation |
|
||||
| `order_shipped` | ORDERS | Order has shipped |
|
||||
| `shipping_update` | ORDERS | Shipping status update |
|
||||
| `low_stock_alert` | SYSTEM | Low inventory warning |
|
||||
| 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 |
|
||||
|
||||
---
|
||||
|
||||
## Language Support Matrix
|
||||
## Available Templates
|
||||
|
||||
| Language | Code | Platform Default |
|
||||
|----------|------|------------------|
|
||||
| English | `en` | Yes (fallback) |
|
||||
| French | `fr` | No |
|
||||
| German | `de` | No |
|
||||
| Luxembourgish | `lb` | No |
|
||||
### Customer-Facing (Overridable)
|
||||
|
||||
**Fallback Logic:**
|
||||
```
|
||||
requested_language → en (if template not found)
|
||||
| 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
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Files to Create/Modify
|
||||
|
||||
### Phase 1 (Database)
|
||||
| File | Action |
|
||||
|------|--------|
|
||||
| `alembic/versions/xxx_add_vendor_email_templates.py` | Create |
|
||||
| `models/database/email.py` | Modify |
|
||||
| `models/database/vendor_email_template.py` | Create |
|
||||
| `models/database/__init__.py` | Modify |
|
||||
| `models/schema/email.py` | Create |
|
||||
|
||||
### Phase 2 (Service)
|
||||
| File | Action |
|
||||
|------|--------|
|
||||
| `app/services/email_service.py` | Modify |
|
||||
|
||||
### Phase 3 (Admin)
|
||||
| File | Action |
|
||||
|------|--------|
|
||||
| `app/api/v1/admin/email_templates.py` | Create |
|
||||
| `app/api/v1/admin/__init__.py` | Modify |
|
||||
| `app/templates/admin/email-templates.html` | Create |
|
||||
| `app/routes/admin_pages.py` | Modify |
|
||||
| `static/admin/js/email-templates.js` | Create |
|
||||
|
||||
### Phase 4 (Vendor)
|
||||
| File | Action |
|
||||
|------|--------|
|
||||
| `app/api/v1/vendor/email_templates.py` | Create |
|
||||
| `app/api/v1/vendor/__init__.py` | Modify |
|
||||
| `app/templates/vendor/email-templates.html` | Create |
|
||||
| `app/routes/vendor_pages.py` | Modify |
|
||||
| `static/vendor/js/email-templates.js` | Create |
|
||||
|
||||
### Phase 5 (Templates)
|
||||
| File | Action |
|
||||
|------|--------|
|
||||
| `scripts/seed_email_templates.py` | Modify |
|
||||
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
|
||||
|
||||
---
|
||||
|
||||
## Execution Order
|
||||
## Seeding Templates
|
||||
|
||||
1. **Phase 1**: Database migration and models (~30 min)
|
||||
2. **Phase 2**: Email service enhancement (~45 min)
|
||||
3. **Phase 3**: Admin API and UI (~1-2 hours)
|
||||
4. **Phase 4**: Vendor API and UI (~1-2 hours)
|
||||
5. **Phase 5**: Add missing templates (~30 min)
|
||||
6. **Testing**: Integration tests for all phases
|
||||
**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**: Sanitize HTML in templates before storage
|
||||
2. **Access Control**: Vendors can only edit their own templates
|
||||
3. **Platform-only Protection**: API enforces is_platform_only flag
|
||||
4. **Template Validation**: Ensure required variables are present in template
|
||||
5. **Rate Limiting**: Test email sending limited to prevent abuse
|
||||
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)
|
||||
│ └── 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
|
||||
```
|
||||
|
||||
Reference in New Issue
Block a user