Files
orion/app/api/v1/vendor/email_settings.py
Samir Boulahtit 36603178c3 feat: add email settings with database overrides for admin and vendor
Platform Email Settings (Admin):
- Add GET/PUT/DELETE /admin/settings/email/* endpoints
- Settings stored in admin_settings table override .env values
- Support all providers: SMTP, SendGrid, Mailgun, Amazon SES
- Edit mode UI with provider-specific configuration forms
- Reset to .env defaults functionality
- Test email to verify configuration

Vendor Email Settings:
- Add VendorEmailSettings model with one-to-one vendor relationship
- Migration: v0a1b2c3d4e5_add_vendor_email_settings.py
- Service: vendor_email_settings_service.py with tier validation
- API endpoints: /vendor/email-settings/* (CRUD, status, verify)
- Email tab in vendor settings page with full configuration
- Warning banner until email is configured (like billing warnings)
- Premium providers (SendGrid, Mailgun, SES) tier-gated to Business+

Email Service Updates:
- get_platform_email_config(db) checks DB first, then .env
- Configurable provider classes accept config dict
- EmailService uses database-aware providers
- Vendor emails use vendor's own SMTP (Wizamart doesn't pay)
- "Powered by Wizamart" footer for Essential/Professional tiers
- White-label (no footer) for Business/Enterprise tiers

Other:
- Add scripts/install.py for first-time platform setup
- Add make install target
- Update init-prod to include email template seeding

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-05 22:23:47 +01:00

226 lines
7.2 KiB
Python

# app/api/v1/vendor/email_settings.py
"""
Vendor email settings API endpoints.
Allows vendors to configure their email sending settings:
- SMTP configuration (all tiers)
- Advanced providers: SendGrid, Mailgun, SES (Business+ tier)
- Sender identity (from_email, from_name, reply_to)
- Signature/footer customization
- Configuration verification via test email
Vendor Context: Uses token_vendor_id from JWT token (authenticated vendor API pattern).
"""
import logging
from fastapi import APIRouter, Depends, HTTPException
from pydantic import BaseModel, EmailStr, Field
from sqlalchemy.orm import Session
from app.api.deps import get_current_vendor_api
from app.core.database import get_db
from app.exceptions import NotFoundError, ValidationError, AuthorizationError
from app.services.vendor_email_settings_service import VendorEmailSettingsService
from app.services.subscription_service import subscription_service
from models.database.user import User
router = APIRouter(prefix="/email-settings")
logger = logging.getLogger(__name__)
# =============================================================================
# SCHEMAS
# =============================================================================
class EmailSettingsUpdate(BaseModel):
"""Schema for creating/updating email settings."""
# Sender Identity (Required)
from_email: EmailStr = Field(..., description="Sender email address")
from_name: str = Field(..., min_length=1, max_length=100, description="Sender name")
reply_to_email: EmailStr | None = Field(None, description="Reply-to email address")
# Signature (Optional)
signature_text: str | None = Field(None, description="Plain text signature")
signature_html: str | None = Field(None, description="HTML signature/footer")
# Provider
provider: str = Field("smtp", description="Email provider: smtp, sendgrid, mailgun, ses")
# SMTP Settings
smtp_host: str | None = Field(None, description="SMTP server hostname")
smtp_port: int | None = Field(587, ge=1, le=65535, description="SMTP server port")
smtp_username: str | None = Field(None, description="SMTP username")
smtp_password: str | None = Field(None, description="SMTP password")
smtp_use_tls: bool = Field(True, description="Use STARTTLS")
smtp_use_ssl: bool = Field(False, description="Use SSL/TLS (port 465)")
# SendGrid
sendgrid_api_key: str | None = Field(None, description="SendGrid API key")
# Mailgun
mailgun_api_key: str | None = Field(None, description="Mailgun API key")
mailgun_domain: str | None = Field(None, description="Mailgun sending domain")
# SES
ses_access_key_id: str | None = Field(None, description="AWS access key ID")
ses_secret_access_key: str | None = Field(None, description="AWS secret access key")
ses_region: str | None = Field("eu-west-1", description="AWS region")
class VerifyEmailRequest(BaseModel):
"""Schema for verifying email settings."""
test_email: EmailStr = Field(..., description="Email address to send test email to")
# =============================================================================
# ENDPOINTS
# =============================================================================
@router.get("")
def get_email_settings(
current_user: User = Depends(get_current_vendor_api),
db: Session = Depends(get_db),
):
"""
Get current email settings for the vendor.
Returns settings with sensitive fields masked.
"""
vendor_id = current_user.token_vendor_id
service = VendorEmailSettingsService(db)
settings = service.get_settings(vendor_id)
if not settings:
return {
"configured": False,
"settings": None,
"message": "Email settings not configured. Configure SMTP to send emails to customers.",
}
return {
"configured": settings.is_configured,
"verified": settings.is_verified,
"settings": settings.to_dict(),
}
@router.get("/status")
def get_email_status(
current_user: User = Depends(get_current_vendor_api),
db: Session = Depends(get_db),
):
"""
Get email configuration status.
Used by frontend to show warning banner if not configured.
"""
vendor_id = current_user.token_vendor_id
service = VendorEmailSettingsService(db)
return service.get_status(vendor_id)
@router.get("/providers")
def get_available_providers(
current_user: User = Depends(get_current_vendor_api),
db: Session = Depends(get_db),
):
"""
Get available email providers for current tier.
Returns list of providers with availability status.
"""
vendor_id = current_user.token_vendor_id
service = VendorEmailSettingsService(db)
# Get vendor's current tier
tier = subscription_service.get_current_tier(db, vendor_id)
return {
"providers": service.get_available_providers(tier),
"current_tier": tier.value if tier else None,
}
@router.put("")
def update_email_settings(
data: EmailSettingsUpdate,
current_user: User = Depends(get_current_vendor_api),
db: Session = Depends(get_db),
):
"""
Create or update email settings.
Premium providers (SendGrid, Mailgun, SES) require Business+ tier.
"""
vendor_id = current_user.token_vendor_id
service = VendorEmailSettingsService(db)
# Get vendor's current tier for validation
tier = subscription_service.get_current_tier(db, vendor_id)
try:
settings = service.create_or_update(
vendor_id=vendor_id,
data=data.model_dump(exclude_unset=True),
current_tier=tier,
)
return {
"success": True,
"message": "Email settings updated successfully",
"settings": settings.to_dict(),
}
except AuthorizationError as e:
raise HTTPException(status_code=403, detail=str(e))
except ValidationError as e:
raise HTTPException(status_code=400, detail=str(e))
@router.post("/verify")
def verify_email_settings(
data: VerifyEmailRequest,
current_user: User = Depends(get_current_vendor_api),
db: Session = Depends(get_db),
):
"""
Verify email settings by sending a test email.
Sends a test email to the provided address and updates verification status.
"""
vendor_id = current_user.token_vendor_id
service = VendorEmailSettingsService(db)
try:
result = service.verify_settings(vendor_id, data.test_email)
if result["success"]:
return result
else:
raise HTTPException(status_code=400, detail=result["message"])
except NotFoundError as e:
raise HTTPException(status_code=404, detail=str(e))
except ValidationError as e:
raise HTTPException(status_code=400, detail=str(e))
@router.delete("")
def delete_email_settings(
current_user: User = Depends(get_current_vendor_api),
db: Session = Depends(get_db),
):
"""
Delete email settings.
Warning: This will disable email sending for the vendor.
"""
vendor_id = current_user.token_vendor_id
service = VendorEmailSettingsService(db)
if service.delete(vendor_id):
return {"success": True, "message": "Email settings deleted"}
else:
raise HTTPException(status_code=404, detail="Email settings not found")