fix: resolve email settings architecture violations and add tests/docs
- Fix API-002 in admin/settings.py: use service layer for DB delete - Fix API-001/API-003 in vendor/email_settings.py: add Pydantic response models, remove HTTPException raises - Fix SVC-002/SVC-006 in vendor_email_settings_service.py: use domain exceptions, change db.commit() to db.flush() - Add unit tests for VendorEmailSettingsService - Add integration tests for vendor and admin email settings APIs - Add user guide (docs/guides/email-settings.md) - Add developer guide (docs/implementation/email-settings.md) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
254
docs/guides/email-settings.md
Normal file
254
docs/guides/email-settings.md
Normal file
@@ -0,0 +1,254 @@
|
||||
# Email Settings Guide
|
||||
|
||||
This guide covers email configuration for both **vendors** and **platform administrators**. The Wizamart platform uses a layered email system where vendors manage their own email sending while the platform handles system-level communications.
|
||||
|
||||
## Overview
|
||||
|
||||
The email system has two distinct configurations:
|
||||
|
||||
| Aspect | Platform (Admin) | Vendor |
|
||||
|--------|-----------------|--------|
|
||||
| Purpose | System emails (billing, admin notifications) | Customer-facing emails (orders, marketing) |
|
||||
| Configuration | Environment variables (.env) + Database overrides | Database (per-vendor) |
|
||||
| Cost | Platform owner pays | Vendor pays |
|
||||
| Providers | SMTP, SendGrid, Mailgun, SES | SMTP (all tiers), Premium providers (Business+) |
|
||||
|
||||
---
|
||||
|
||||
## Vendor Email Settings
|
||||
|
||||
### Getting Started
|
||||
|
||||
As a vendor, you need to configure email settings to send emails to your customers. This includes order confirmations, shipping updates, and marketing emails.
|
||||
|
||||
#### Accessing Email Settings
|
||||
|
||||
1. Log in to your Vendor Dashboard
|
||||
2. Navigate to **Settings** from the sidebar
|
||||
3. Click on the **Email** tab
|
||||
|
||||
### Available Providers
|
||||
|
||||
| Provider | Tier Required | Best For |
|
||||
|----------|---------------|----------|
|
||||
| SMTP | All tiers | Standard email servers, most common |
|
||||
| SendGrid | Business+ | High-volume transactional emails |
|
||||
| Mailgun | Business+ | Developer-friendly API |
|
||||
| Amazon SES | Business+ | AWS ecosystem, cost-effective |
|
||||
|
||||
### Configuring SMTP
|
||||
|
||||
SMTP is available for all subscription tiers. Common SMTP providers include:
|
||||
- Gmail (smtp.gmail.com:587)
|
||||
- Microsoft 365 (smtp.office365.com:587)
|
||||
- Your hosting provider's SMTP server
|
||||
|
||||
**Required Fields:**
|
||||
- **From Email**: The sender email address (e.g., orders@yourstore.com)
|
||||
- **From Name**: The sender display name (e.g., "Your Store")
|
||||
- **SMTP Host**: Your SMTP server address
|
||||
- **SMTP Port**: Usually 587 (TLS) or 465 (SSL)
|
||||
- **SMTP Username**: Your login username
|
||||
- **SMTP Password**: Your login password
|
||||
- **Use TLS**: Enable for port 587 (recommended)
|
||||
- **Use SSL**: Enable for port 465
|
||||
|
||||
### Configuring Premium Providers (Business+)
|
||||
|
||||
If you have a Business or Enterprise subscription, you can use premium email providers:
|
||||
|
||||
#### SendGrid
|
||||
1. Create a SendGrid account at [sendgrid.com](https://sendgrid.com)
|
||||
2. Generate an API key
|
||||
3. Enter the API key in your vendor settings
|
||||
|
||||
#### Mailgun
|
||||
1. Create a Mailgun account at [mailgun.com](https://mailgun.com)
|
||||
2. Add and verify your domain
|
||||
3. Get your API key from the dashboard
|
||||
4. Enter the API key and domain in your settings
|
||||
|
||||
#### Amazon SES
|
||||
1. Set up SES in your AWS account
|
||||
2. Verify your sender domain/email
|
||||
3. Create IAM credentials with SES permissions
|
||||
4. Enter the access key, secret key, and region
|
||||
|
||||
### Verifying Your Configuration
|
||||
|
||||
After configuring your email settings:
|
||||
|
||||
1. Click **Save Settings**
|
||||
2. Enter a test email address in the **Test Email** field
|
||||
3. Click **Send Test**
|
||||
4. Check your inbox for the test email
|
||||
|
||||
If the test fails, check:
|
||||
- Your credentials are correct
|
||||
- Your IP/domain is not blocked
|
||||
- For Gmail: Allow "less secure apps" or use an app password
|
||||
|
||||
### Email Warning Banner
|
||||
|
||||
Until you configure and verify your email settings, you'll see a warning banner at the top of your dashboard. This ensures you don't forget to set up email before your store goes live.
|
||||
|
||||
---
|
||||
|
||||
## Platform Admin Email Settings
|
||||
|
||||
### Overview
|
||||
|
||||
Platform administrators can configure system-wide email settings for platform communications like:
|
||||
- Subscription billing notifications
|
||||
- Admin alerts
|
||||
- Platform-wide announcements
|
||||
|
||||
### Configuration Sources
|
||||
|
||||
Admin email settings support two configuration sources:
|
||||
|
||||
1. **Environment Variables (.env)** - Default configuration
|
||||
2. **Database Overrides** - Override .env via the admin UI
|
||||
|
||||
Database settings take priority over .env values.
|
||||
|
||||
### Accessing Admin Email Settings
|
||||
|
||||
1. Log in to the Admin Panel
|
||||
2. Navigate to **Settings**
|
||||
3. Click on the **Email** tab
|
||||
|
||||
### Viewing Current Configuration
|
||||
|
||||
The Email tab shows:
|
||||
- **Provider**: Current email provider (SMTP, SendGrid, etc.)
|
||||
- **From Email**: Sender email address
|
||||
- **From Name**: Sender display name
|
||||
- **Status**: Whether email is configured and enabled
|
||||
- **DB Overrides**: Whether database overrides are active
|
||||
|
||||
### Editing Settings
|
||||
|
||||
Click **Edit Settings** to modify the email configuration:
|
||||
|
||||
1. Select the email provider
|
||||
2. Enter the required credentials
|
||||
3. Configure enabled/debug flags
|
||||
4. Click **Save Email Settings**
|
||||
|
||||
### Resetting to .env Defaults
|
||||
|
||||
If you've made database overrides and want to revert to .env configuration:
|
||||
|
||||
1. Click **Reset to .env Defaults**
|
||||
2. Confirm the action
|
||||
|
||||
This removes all email settings from the database, reverting to .env values.
|
||||
|
||||
### Testing Configuration
|
||||
|
||||
1. Enter a test email address
|
||||
2. Click **Send Test**
|
||||
3. Check your inbox
|
||||
|
||||
---
|
||||
|
||||
## Environment Variables Reference
|
||||
|
||||
For platform configuration via .env:
|
||||
|
||||
```env
|
||||
# Provider: smtp, sendgrid, mailgun, ses
|
||||
EMAIL_PROVIDER=smtp
|
||||
|
||||
# Sender identity
|
||||
EMAIL_FROM_ADDRESS=noreply@yourplatform.com
|
||||
EMAIL_FROM_NAME=Your Platform
|
||||
EMAIL_REPLY_TO=support@yourplatform.com
|
||||
|
||||
# Behavior
|
||||
EMAIL_ENABLED=true
|
||||
EMAIL_DEBUG=false
|
||||
|
||||
# SMTP Configuration
|
||||
SMTP_HOST=smtp.example.com
|
||||
SMTP_PORT=587
|
||||
SMTP_USER=your-username
|
||||
SMTP_PASSWORD=your-password
|
||||
SMTP_USE_TLS=true
|
||||
SMTP_USE_SSL=false
|
||||
|
||||
# SendGrid
|
||||
SENDGRID_API_KEY=your-api-key
|
||||
|
||||
# Mailgun
|
||||
MAILGUN_API_KEY=your-api-key
|
||||
MAILGUN_DOMAIN=mg.yourdomain.com
|
||||
|
||||
# Amazon SES
|
||||
AWS_ACCESS_KEY_ID=your-access-key
|
||||
AWS_SECRET_ACCESS_KEY=your-secret-key
|
||||
AWS_REGION=eu-west-1
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Tier-Based Branding
|
||||
|
||||
The email system includes tier-based branding for vendor emails:
|
||||
|
||||
| Tier | Branding |
|
||||
|------|----------|
|
||||
| Essential | "Powered by Wizamart" footer |
|
||||
| Professional | "Powered by Wizamart" footer |
|
||||
| Business | No branding (white-label) |
|
||||
| Enterprise | No branding (white-label) |
|
||||
|
||||
Business and Enterprise tier vendors get completely white-labeled emails with no Wizamart branding.
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Common Issues
|
||||
|
||||
**"Email sending is disabled"**
|
||||
- Check that `EMAIL_ENABLED=true` in .env
|
||||
- Or enable it in the admin settings
|
||||
|
||||
**"Connection refused" on SMTP**
|
||||
- Verify SMTP host and port
|
||||
- Check firewall rules
|
||||
- Ensure TLS/SSL settings match your server
|
||||
|
||||
**"Authentication failed"**
|
||||
- Double-check username/password
|
||||
- For Gmail, use an App Password
|
||||
- For Microsoft 365, check MFA requirements
|
||||
|
||||
**"SendGrid error: 403"**
|
||||
- Verify your API key has Mail Send permissions
|
||||
- Check sender identity is verified
|
||||
|
||||
**Premium provider not available**
|
||||
- Upgrade to Business or Enterprise tier
|
||||
- Contact support if you have the right tier but can't access
|
||||
|
||||
### Debug Mode
|
||||
|
||||
Enable debug mode to log emails instead of sending them:
|
||||
- Set `EMAIL_DEBUG=true` in .env
|
||||
- Or enable "Debug mode" in admin settings
|
||||
|
||||
Debug mode logs the email content to the server logs without actually sending.
|
||||
|
||||
---
|
||||
|
||||
## Security Best Practices
|
||||
|
||||
1. **Never share API keys or passwords** in logs or frontend
|
||||
2. **Use environment variables** for sensitive credentials
|
||||
3. **Enable TLS** for SMTP connections
|
||||
4. **Verify sender domains** with your email provider
|
||||
5. **Monitor email logs** for delivery issues
|
||||
6. **Rotate credentials** periodically
|
||||
308
docs/implementation/email-settings.md
Normal file
308
docs/implementation/email-settings.md
Normal file
@@ -0,0 +1,308 @@
|
||||
# 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 = """
|
||||
<div style="margin-top: 30px; ...">
|
||||
<p>Powered by <a href="https://wizamart.com">Wizamart</a></p>
|
||||
</div>
|
||||
"""
|
||||
|
||||
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("</body>", f"{POWERED_BY_FOOTER_HTML}</body>")
|
||||
```
|
||||
|
||||
## 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
|
||||
<!-- app/templates/shared/macros/feature_gate.html -->
|
||||
{% macro email_settings_warning() %}
|
||||
<div x-data="emailSettingsWarning()" x-show="showWarning">
|
||||
Configure email settings to send emails to customers.
|
||||
</div>
|
||||
{% 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
|
||||
Reference in New Issue
Block a user