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>
This commit is contained in:
102
alembic/versions/v0a1b2c3d4e5_add_vendor_email_settings.py
Normal file
102
alembic/versions/v0a1b2c3d4e5_add_vendor_email_settings.py
Normal file
@@ -0,0 +1,102 @@
|
||||
# alembic/versions/v0a1b2c3d4e5_add_vendor_email_settings.py
|
||||
"""Add vendor email settings table.
|
||||
|
||||
Revision ID: v0a1b2c3d4e5
|
||||
Revises: u9c0d1e2f3g4
|
||||
Create Date: 2026-01-05
|
||||
|
||||
Changes:
|
||||
- Create vendor_email_settings table for vendor SMTP/email provider configuration
|
||||
- Vendors must configure this to send transactional emails
|
||||
- Premium providers (SendGrid, Mailgun, SES) are tier-gated (Business+)
|
||||
"""
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = "v0a1b2c3d4e5"
|
||||
down_revision = "u9c0d1e2f3g4"
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
# Create vendor_email_settings table
|
||||
op.create_table(
|
||||
"vendor_email_settings",
|
||||
sa.Column("id", sa.Integer(), nullable=False),
|
||||
sa.Column("vendor_id", sa.Integer(), nullable=False),
|
||||
# Sender Identity
|
||||
sa.Column("from_email", sa.String(255), nullable=False),
|
||||
sa.Column("from_name", sa.String(100), nullable=False),
|
||||
sa.Column("reply_to_email", sa.String(255), nullable=True),
|
||||
# Signature/Footer
|
||||
sa.Column("signature_text", sa.Text(), nullable=True),
|
||||
sa.Column("signature_html", sa.Text(), nullable=True),
|
||||
# Provider Configuration
|
||||
sa.Column("provider", sa.String(20), nullable=False, default="smtp"),
|
||||
# SMTP Settings
|
||||
sa.Column("smtp_host", sa.String(255), nullable=True),
|
||||
sa.Column("smtp_port", sa.Integer(), nullable=True, default=587),
|
||||
sa.Column("smtp_username", sa.String(255), nullable=True),
|
||||
sa.Column("smtp_password", sa.String(500), nullable=True),
|
||||
sa.Column("smtp_use_tls", sa.Boolean(), nullable=False, default=True),
|
||||
sa.Column("smtp_use_ssl", sa.Boolean(), nullable=False, default=False),
|
||||
# SendGrid Settings
|
||||
sa.Column("sendgrid_api_key", sa.String(500), nullable=True),
|
||||
# Mailgun Settings
|
||||
sa.Column("mailgun_api_key", sa.String(500), nullable=True),
|
||||
sa.Column("mailgun_domain", sa.String(255), nullable=True),
|
||||
# Amazon SES Settings
|
||||
sa.Column("ses_access_key_id", sa.String(100), nullable=True),
|
||||
sa.Column("ses_secret_access_key", sa.String(500), nullable=True),
|
||||
sa.Column("ses_region", sa.String(50), nullable=True, default="eu-west-1"),
|
||||
# Status & Verification
|
||||
sa.Column("is_configured", sa.Boolean(), nullable=False, default=False),
|
||||
sa.Column("is_verified", sa.Boolean(), nullable=False, default=False),
|
||||
sa.Column("last_verified_at", sa.DateTime(timezone=True), nullable=True),
|
||||
sa.Column("verification_error", sa.Text(), nullable=True),
|
||||
# Timestamps
|
||||
sa.Column("created_at", sa.DateTime(), nullable=False),
|
||||
sa.Column("updated_at", sa.DateTime(), nullable=False),
|
||||
# Constraints
|
||||
sa.PrimaryKeyConstraint("id"),
|
||||
sa.ForeignKeyConstraint(
|
||||
["vendor_id"],
|
||||
["vendors.id"],
|
||||
name="fk_vendor_email_settings_vendor_id",
|
||||
ondelete="CASCADE",
|
||||
),
|
||||
sa.UniqueConstraint("vendor_id", name="uq_vendor_email_settings_vendor_id"),
|
||||
)
|
||||
|
||||
# Create indexes
|
||||
op.create_index(
|
||||
"ix_vendor_email_settings_id",
|
||||
"vendor_email_settings",
|
||||
["id"],
|
||||
unique=False,
|
||||
)
|
||||
op.create_index(
|
||||
"ix_vendor_email_settings_vendor_id",
|
||||
"vendor_email_settings",
|
||||
["vendor_id"],
|
||||
unique=True,
|
||||
)
|
||||
op.create_index(
|
||||
"idx_vendor_email_settings_configured",
|
||||
"vendor_email_settings",
|
||||
["vendor_id", "is_configured"],
|
||||
)
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
# Drop indexes
|
||||
op.drop_index("idx_vendor_email_settings_configured", table_name="vendor_email_settings")
|
||||
op.drop_index("ix_vendor_email_settings_vendor_id", table_name="vendor_email_settings")
|
||||
op.drop_index("ix_vendor_email_settings_id", table_name="vendor_email_settings")
|
||||
|
||||
# Drop table
|
||||
op.drop_table("vendor_email_settings")
|
||||
Reference in New Issue
Block a user