# app/modules/messaging/models/store_email_settings.py """ Store Email Settings model for store-specific email configuration. This model stores store SMTP/email provider settings, enabling stores to: - Send emails from their own domain/email address - Use their own SMTP server or email provider (tier-gated) - Customize sender name, reply-to address, and signature Architecture: - Stores MUST configure email settings to send transactional emails - Platform emails (billing, subscription) still use platform settings - Advanced providers (SendGrid, Mailgun, SES) are tier-gated (Business+) - "Powered by Orion" footer is added for Essential/Professional tiers """ import enum from datetime import UTC, datetime from sqlalchemy import ( Boolean, Column, DateTime, ForeignKey, Index, Integer, String, Text, ) from sqlalchemy.orm import relationship from app.core.database import Base from models.database.base import TimestampMixin class EmailProvider(str, enum.Enum): """Supported email providers.""" SMTP = "smtp" # Standard SMTP (all tiers) SENDGRID = "sendgrid" # SendGrid API (Business+ tier) MAILGUN = "mailgun" # Mailgun API (Business+ tier) SES = "ses" # Amazon SES (Business+ tier) # Providers that require Business+ tier PREMIUM_EMAIL_PROVIDERS = { EmailProvider.SENDGRID, EmailProvider.MAILGUN, EmailProvider.SES, } class StoreEmailSettings(Base, TimestampMixin): """ Store email configuration for sending transactional emails. This is a one-to-one relationship with Store. Stores must configure this to send emails to their customers. """ __tablename__ = "store_email_settings" id = Column(Integer, primary_key=True, index=True) store_id = Column( Integer, ForeignKey("stores.id", ondelete="CASCADE"), unique=True, nullable=False, index=True, ) # ========================================================================= # Sender Identity (Required) # ========================================================================= from_email = Column(String(255), nullable=False) # e.g., orders@storeshop.lu from_name = Column(String(100), nullable=False) # e.g., "StoreShop" reply_to_email = Column(String(255), nullable=True) # Optional reply-to address # ========================================================================= # Email Signature/Footer (Optional) # ========================================================================= signature_text = Column(Text, nullable=True) # Plain text signature signature_html = Column(Text, nullable=True) # HTML signature (footer) # ========================================================================= # Provider Configuration # ========================================================================= provider = Column( String(20), default=EmailProvider.SMTP.value, nullable=False, ) # ========================================================================= # SMTP Settings (used when provider=smtp) # ========================================================================= smtp_host = Column(String(255), nullable=True) smtp_port = Column(Integer, nullable=True, default=587) smtp_username = Column(String(255), nullable=True) smtp_password = Column(String(500), nullable=True) # Encrypted at rest smtp_use_tls = Column(Boolean, default=True, nullable=False) smtp_use_ssl = Column(Boolean, default=False, nullable=False) # For port 465 # ========================================================================= # SendGrid Settings (used when provider=sendgrid, Business+ tier) # ========================================================================= sendgrid_api_key = Column(String(500), nullable=True) # Encrypted at rest # ========================================================================= # Mailgun Settings (used when provider=mailgun, Business+ tier) # ========================================================================= mailgun_api_key = Column(String(500), nullable=True) # Encrypted at rest mailgun_domain = Column(String(255), nullable=True) # ========================================================================= # Amazon SES Settings (used when provider=ses, Business+ tier) # ========================================================================= ses_access_key_id = Column(String(100), nullable=True) ses_secret_access_key = Column(String(500), nullable=True) # Encrypted at rest ses_region = Column(String(50), nullable=True, default="eu-west-1") # ========================================================================= # Status & Verification # ========================================================================= is_configured = Column(Boolean, default=False, nullable=False) # Has complete config is_verified = Column(Boolean, default=False, nullable=False) # Test email succeeded last_verified_at = Column(DateTime(timezone=True), nullable=True) verification_error = Column(Text, nullable=True) # Last verification error message # ========================================================================= # Relationship # ========================================================================= store = relationship("Store", back_populates="email_settings") # ========================================================================= # Indexes # ========================================================================= __table_args__ = ( Index("idx_store_email_settings_configured", "store_id", "is_configured"), ) def __repr__(self) -> str: return f"" # ========================================================================= # Helper Methods # ========================================================================= def is_smtp_configured(self) -> bool: """Check if SMTP settings are complete.""" if self.provider != EmailProvider.SMTP.value: return False return bool( self.smtp_host and self.smtp_port and self.smtp_username and self.smtp_password ) def is_sendgrid_configured(self) -> bool: """Check if SendGrid settings are complete.""" if self.provider != EmailProvider.SENDGRID.value: return False return bool(self.sendgrid_api_key) def is_mailgun_configured(self) -> bool: """Check if Mailgun settings are complete.""" if self.provider != EmailProvider.MAILGUN.value: return False return bool(self.mailgun_api_key and self.mailgun_domain) def is_ses_configured(self) -> bool: """Check if Amazon SES settings are complete.""" if self.provider != EmailProvider.SES.value: return False return bool( self.ses_access_key_id and self.ses_secret_access_key and self.ses_region ) def is_provider_configured(self) -> bool: """Check if the current provider is fully configured.""" provider_checks = { EmailProvider.SMTP.value: self.is_smtp_configured, EmailProvider.SENDGRID.value: self.is_sendgrid_configured, EmailProvider.MAILGUN.value: self.is_mailgun_configured, EmailProvider.SES.value: self.is_ses_configured, } check_fn = provider_checks.get(self.provider) return check_fn() if check_fn else False def is_fully_configured(self) -> bool: """Check if email settings are fully configured (identity + provider).""" return bool( self.from_email and self.from_name and self.is_provider_configured() ) def update_configuration_status(self) -> None: """Update the is_configured flag based on current settings.""" self.is_configured = self.is_fully_configured() def mark_verified(self) -> None: """Mark settings as verified (test email succeeded).""" self.is_verified = True self.last_verified_at = datetime.now(UTC) self.verification_error = None def mark_verification_failed(self, error: str) -> None: """Mark settings as verification failed.""" self.is_verified = False self.verification_error = error def requires_premium_tier(self) -> bool: """Check if current provider requires Business+ tier.""" return self.provider in [p.value for p in PREMIUM_EMAIL_PROVIDERS] def to_dict(self) -> dict: """Convert to dictionary for API responses (excludes sensitive data).""" return { "id": self.id, "store_id": self.store_id, "from_email": self.from_email, "from_name": self.from_name, "reply_to_email": self.reply_to_email, "signature_text": self.signature_text, "signature_html": self.signature_html, "provider": self.provider, # SMTP (mask password) "smtp_host": self.smtp_host, "smtp_port": self.smtp_port, "smtp_username": self.smtp_username, "smtp_password_set": bool(self.smtp_password), "smtp_use_tls": self.smtp_use_tls, "smtp_use_ssl": self.smtp_use_ssl, # SendGrid (mask API key) "sendgrid_api_key_set": bool(self.sendgrid_api_key), # Mailgun (mask API key) "mailgun_api_key_set": bool(self.mailgun_api_key), "mailgun_domain": self.mailgun_domain, # SES (mask credentials) "ses_access_key_id_set": bool(self.ses_access_key_id), "ses_region": self.ses_region, # Status "is_configured": self.is_configured, "is_verified": self.is_verified, "last_verified_at": self.last_verified_at.isoformat() if self.last_verified_at else None, "verification_error": self.verification_error, "requires_premium_tier": self.requires_premium_tier(), # Timestamps "created_at": self.created_at.isoformat() if self.created_at else None, "updated_at": self.updated_at.isoformat() if self.updated_at else None, } __all__ = ["EmailProvider", "PREMIUM_EMAIL_PROVIDERS", "StoreEmailSettings"]