Files
nexgen_mirrors 41bbdb4bef feat(email): add provider abstraction and HTML templates
Add pluggable email provider system supporting SMTP, Mailgun, and SendGrid
with factory pattern for runtime provider selection. Includes branded HTML
email templates for verification, password reset, MFA notifications, and
organization invites.

Also rebrands all email content from Gatehouse to Secuird, adds email
provider configuration options, and fixes duplicate log handlers in
development mode.
2026-04-04 16:55:00 +10:30

86 lines
2.7 KiB
Python

"""Email provider interfaces and factory."""
import logging
import os
from abc import ABC, abstractmethod
from dataclasses import dataclass
from typing import Optional
logger = logging.getLogger(__name__)
@dataclass
class EmailMessage:
"""Email message data structure."""
to: str
subject: str
body: str
html_body: Optional[str] = None
from_address: Optional[str] = None
class EmailProvider(ABC):
"""Abstract base class for email providers."""
@abstractmethod
def send(self, message: EmailMessage) -> bool:
"""
Send an email message.
Args:
message: EmailMessage instance containing email details
Returns:
bool: True if email was sent successfully, False otherwise
"""
pass
class NoOpEmailProvider(EmailProvider):
"""No-op email provider that logs and returns False."""
def send(self, message: EmailMessage) -> bool:
"""Log that emails are disabled and return False."""
logger.info(f"Email disabled - would send to={message.to} subject={message.subject}")
return False
class EmailProviderFactory:
"""Factory for creating email provider instances."""
@staticmethod
def get_provider() -> EmailProvider:
"""
Create an email provider based on EMAIL_PROVIDER config.
Returns:
EmailProvider: An instance of the appropriate email provider
"""
provider_name = os.getenv("EMAIL_PROVIDER", "smtp").lower()
if provider_name == "smtp":
try:
from gatehouse_app.services.providers.smtp_provider import SmtpEmailProvider
return SmtpEmailProvider()
except ImportError:
logger.warning("SMTP provider not implemented, using no-op provider")
return NoOpEmailProvider()
if provider_name == "mailgun":
try:
from gatehouse_app.services.providers.mailgun_provider import MailgunEmailProvider
return MailgunEmailProvider()
except ImportError:
logger.warning("Mailgun provider not implemented, using no-op provider")
return NoOpEmailProvider()
if provider_name == "sendgrid":
try:
from gatehouse_app.services.providers.sendgrid_provider import SendGridEmailProvider
return SendGridEmailProvider()
except ImportError:
logger.warning("SendGrid provider not implemented, using no-op provider")
return NoOpEmailProvider()
logger.error(f"Invalid EMAIL_PROVIDER value: {provider_name}, defaulting to no-op provider")
return NoOpEmailProvider()