| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263 |
- import logging
- from abc import ABC, abstractmethod
- from email.message import EmailMessage
- import aiosmtplib
- from app.core.config import settings
- logger = logging.getLogger(__name__)
- class EmailSender(ABC):
- @abstractmethod
- async def send(self, to: str, subject: str, html_body: str) -> None: ...
- class LoggingEmailSender(EmailSender):
- """Dev/test fallback: logs instead of sending. Wire a real provider
- (SES, Postmark, SMTP) behind this interface before going to prod."""
- async def send(self, to: str, subject: str, html_body: str) -> None:
- logger.info(f"[EMAIL to={to}] {subject}\n{html_body}")
- class SmtpEmailSender(EmailSender):
- """Sends via SMTP. Works with any provider that exposes SMTP
- credentials (SES, Postmark, Sendgrid, a plain mailbox, etc)."""
- async def send(self, to: str, subject: str, html_body: str) -> None:
- message = EmailMessage()
- message["From"] = f"{settings.EMAIL_FROM_NAME} <{settings.EMAIL_FROM_ADDRESS}>"
- message["To"] = to
- message["Subject"] = subject
- message.set_content("Ce message nécessite un client compatible HTML.")
- message.add_alternative(html_body, subtype="html")
- try:
- await aiosmtplib.send(
- message,
- hostname=settings.SMTP_HOST,
- port=settings.SMTP_PORT,
- username=settings.SMTP_USERNAME or None,
- password=settings.SMTP_PASSWORD or None,
- start_tls=settings.SMTP_USE_TLS,
- timeout=10,
- )
- except (aiosmtplib.SMTPException, OSError) as exc:
- # Don't let a transient SMTP outage 500 the caller's whole
- # request (e.g. invite-member already committed the DB write) --
- # log loudly so it's visible in monitoring, and let the caller
- # decide whether to surface a degraded-but-successful response.
- logger.error("Failed to send email to %s: %s", to, exc)
- raise EmailDeliveryError(str(exc)) from exc
- class EmailDeliveryError(Exception):
- pass
- def get_email_sender() -> EmailSender:
- if settings.ENVIRONMENT == "PYTEST" or not settings.SMTP_HOST:
- return LoggingEmailSender()
- return SmtpEmailSender()
|