Files
orion/docs/features/email-system.md
Samir Boulahtit e9253fbd84 refactor: rename Wizamart to Orion across entire codebase
Replace all ~1,086 occurrences of Wizamart/wizamart/WIZAMART/WizaMart
with Orion/orion/ORION across 184 files. This includes database
identifiers, email addresses, domain references, R2 bucket names,
DNS prefixes, encryption salt, Celery app name, config defaults,
Docker configs, CI configs, documentation, seed data, and templates.

Renames homepage-wizamart.html template to homepage-orion.html.
Fixes duplicate file_pattern key in api.yaml architecture rule.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-14 16:46:56 +01:00

7.9 KiB

Email System

The email system provides multi-provider support with database-stored templates and comprehensive logging for the Orion platform.

Overview

The email system supports:

  • Multiple Providers: SMTP, SendGrid, Mailgun, Amazon SES
  • Multi-language Templates: EN, FR, DE, LB (stored in database)
  • Jinja2 Templating: Variable interpolation in subjects and bodies
  • Email Logging: Track all sent emails for debugging and compliance
  • Debug Mode: Log emails instead of sending during development

Configuration

Environment Variables

Add these settings to your .env file:

# Provider: smtp, sendgrid, mailgun, ses
EMAIL_PROVIDER=smtp
EMAIL_FROM_ADDRESS=noreply@orion.lu
EMAIL_FROM_NAME=Orion
EMAIL_REPLY_TO=

# Behavior
EMAIL_ENABLED=true
EMAIL_DEBUG=false

# SMTP Settings (when EMAIL_PROVIDER=smtp)
SMTP_HOST=smtp.example.com
SMTP_PORT=587
SMTP_USER=
SMTP_PASSWORD=
SMTP_USE_TLS=true
SMTP_USE_SSL=false

# SendGrid (when EMAIL_PROVIDER=sendgrid)
# SENDGRID_API_KEY=SG.your_api_key_here

# Mailgun (when EMAIL_PROVIDER=mailgun)
# MAILGUN_API_KEY=your_api_key_here
# MAILGUN_DOMAIN=mg.yourdomain.com

# Amazon SES (when EMAIL_PROVIDER=ses)
# AWS_ACCESS_KEY_ID=your_access_key
# AWS_SECRET_ACCESS_KEY=your_secret_key
# AWS_REGION=eu-west-1

Debug Mode

Set EMAIL_DEBUG=true to log emails instead of sending them. This is useful during development:

EMAIL_DEBUG=true

Emails will be logged to the console with full details (recipient, subject, body preview).

Database Models

EmailTemplate

Stores multi-language email templates:

Column Type Description
id Integer Primary key
code String(100) Template identifier (e.g., "signup_welcome")
language String(5) Language code (en, fr, de, lb)
name String(255) Human-readable name
description Text Template purpose
category String(50) AUTH, ORDERS, BILLING, SYSTEM, MARKETING
subject String(500) Email subject (supports Jinja2)
body_html Text HTML body
body_text Text Plain text fallback
variables Text JSON list of expected variables
is_active Boolean Enable/disable template

EmailLog

Tracks all sent emails:

Column Type Description
id Integer Primary key
template_code String(100) Template used (if any)
recipient_email String(255) Recipient address
subject String(500) Email subject
status String(20) PENDING, SENT, FAILED, DELIVERED, OPENED
sent_at DateTime When email was sent
error_message Text Error details if failed
provider String(50) Provider used (smtp, sendgrid, etc.)
store_id Integer Related store (optional)
user_id Integer Related user (optional)

Usage

Using EmailService

from app.services.email_service import EmailService

def send_welcome_email(db, user, store):
    email_service = EmailService(db)

    email_service.send_template(
        template_code="signup_welcome",
        to_email=user.email,
        to_name=f"{user.first_name} {user.last_name}",
        language="fr",  # Falls back to "en" if not found
        variables={
            "first_name": user.first_name,
            "merchant_name": store.name,
            "store_code": store.store_code,
            "login_url": f"https://orion.lu/store/{store.store_code}/dashboard",
            "trial_days": 30,
            "tier_name": "Essential",
        },
        store_id=store.id,
        user_id=user.id,
        related_type="signup",
    )

Convenience Function

from app.services.email_service import send_email

send_email(
    db=db,
    template_code="order_confirmation",
    to_email="customer@example.com",
    language="en",
    variables={"order_number": "ORD-001"},
)

Sending Raw Emails

For one-off emails without templates:

email_service = EmailService(db)

email_service.send_raw(
    to_email="user@example.com",
    subject="Custom Subject",
    body_html="<h1>Hello</h1><p>Custom message</p>",
    body_text="Hello\n\nCustom message",
)

Email Templates

Creating Templates

Templates use Jinja2 syntax for variable interpolation:

<p>Hello {{ first_name }},</p>
<p>Welcome to {{ merchant_name }}!</p>

Seeding Templates

Run the seed script to populate default templates:

python scripts/seed/seed_email_templates.py

This creates templates for:

  • signup_welcome (en, fr, de, lb)

Available Variables

For signup_welcome:

Variable Description
first_name User's first name
merchant_name Store merchant name
email User's email address
store_code Store code for dashboard URL
login_url Direct link to dashboard
trial_days Number of trial days
tier_name Subscription tier name

Provider Setup

SMTP

Standard SMTP configuration:

EMAIL_PROVIDER=smtp
SMTP_HOST=smtp.gmail.com
SMTP_PORT=587
SMTP_USER=your-email@gmail.com
SMTP_PASSWORD=your-app-password
SMTP_USE_TLS=true

SendGrid

  1. Create account at sendgrid.com
  2. Generate API key in Settings > API Keys
  3. Configure:
EMAIL_PROVIDER=sendgrid
SENDGRID_API_KEY=SG.xxxxxxxxxxxxxxxxxxxx
  1. Install package: pip install sendgrid

Mailgun

  1. Create account at mailgun.com
  2. Add and verify your domain
  3. Get API key from Domain Settings
  4. Configure:
EMAIL_PROVIDER=mailgun
MAILGUN_API_KEY=key-xxxxxxxxxxxxxxxx
MAILGUN_DOMAIN=mg.yourdomain.com

Amazon SES

  1. Set up SES in AWS Console
  2. Verify sender domain/email
  3. Create IAM user with SES permissions
  4. Configure:
EMAIL_PROVIDER=ses
AWS_ACCESS_KEY_ID=AKIAXXXXXXXXXXXXXXXX
AWS_SECRET_ACCESS_KEY=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
AWS_REGION=eu-west-1
  1. Install package: pip install boto3

Email Logging

All emails are logged to the email_logs table. Query examples:

# Get failed emails
failed = db.query(EmailLog).filter(
    EmailLog.status == EmailStatus.FAILED.value
).all()

# Get emails for a store
store_emails = db.query(EmailLog).filter(
    EmailLog.store_id == store_id
).order_by(EmailLog.created_at.desc()).all()

# Get recent signup emails
signups = db.query(EmailLog).filter(
    EmailLog.template_code == "signup_welcome",
    EmailLog.created_at >= datetime.now() - timedelta(days=7)
).all()

Language Fallback

The system automatically falls back to English if a template isn't available in the requested language:

  1. Request template for "de" (German)
  2. If not found, try "en" (English)
  3. If still not found, return None (log error)

Testing

Run email service tests:

pytest tests/unit/services/test_email_service.py -v

Test coverage includes:

  • Provider abstraction (Debug, SMTP, etc.)
  • Template rendering with Jinja2
  • Language fallback behavior
  • Email sending success/failure
  • EmailLog model methods
  • Template variable handling

Architecture

app/services/email_service.py    # Email service with provider abstraction
models/database/email.py         # EmailTemplate and EmailLog models
app/core/config.py               # Email configuration settings
scripts/seed/seed_email_templates.py  # Template seeding script

Provider Abstraction

The system uses a strategy pattern for email providers:

EmailProvider (ABC)
├── SMTPProvider
├── SendGridProvider
├── MailgunProvider
├── SESProvider
└── DebugProvider

Each provider implements the send() method with the same signature, making it easy to switch providers via configuration.

Future Enhancements

Planned improvements:

  1. Email Queue: Background task queue for high-volume sending
  2. Webhook Tracking: Track deliveries, opens, clicks via provider webhooks
  3. Template Editor: Admin UI for editing templates
  4. A/B Testing: Test different email versions
  5. Scheduled Emails: Send emails at specific times