""" Email Utility - ServiceManagerWeb Envío directo de emails desde el backend para flujos críticos (reseteo de contraseña, verificación) sin depender de Celery. """ import asyncio import smtplib import ssl from email.mime.multipart import MIMEMultipart from email.mime.text import MIMEText from typing import Optional import structlog from app.core.config import get_settings settings = get_settings() logger = structlog.get_logger(__name__) def _send_smtp_sync( to_email: str, subject: str, html_content: str, text_content: Optional[str] = None, ) -> None: """ Enviar email de forma síncrona vía SMTP. Llamar desde asyncio.to_thread para no bloquear el event loop. """ msg = MIMEMultipart("alternative") msg["Subject"] = subject msg["From"] = f"{settings.DEFAULT_FROM_NAME} <{settings.DEFAULT_FROM_EMAIL}>" msg["To"] = to_email if text_content: msg.attach(MIMEText(text_content, "plain", "utf-8")) msg.attach(MIMEText(html_content, "html", "utf-8")) if settings.SMTP_USE_SSL: context = ssl.create_default_context() with smtplib.SMTP_SSL(settings.SMTP_HOST, settings.SMTP_PORT, context=context) as server: if settings.SMTP_USER and settings.SMTP_PASSWORD: server.login(settings.SMTP_USER, settings.SMTP_PASSWORD) server.sendmail(settings.DEFAULT_FROM_EMAIL, to_email, msg.as_string()) else: with smtplib.SMTP(settings.SMTP_HOST, settings.SMTP_PORT) as server: if settings.SMTP_USE_TLS: server.starttls() if settings.SMTP_USER and settings.SMTP_PASSWORD: server.login(settings.SMTP_USER, settings.SMTP_PASSWORD) server.sendmail(settings.DEFAULT_FROM_EMAIL, to_email, msg.as_string()) async def send_email( to_email: str, subject: str, html_content: str, text_content: Optional[str] = None, ) -> bool: """ Enviar email de forma asíncrona. Retorna True si el envío fue exitoso, False con log de error si falló. Se diseña para no propagar excepciones (fail-silent) en flujos de UI. """ try: await asyncio.to_thread( _send_smtp_sync, to_email, subject, html_content, text_content, ) logger.info("Email sent", to=to_email, subject=subject) return True except Exception as exc: logger.error("Email send failed", to=to_email, subject=subject, error=str(exc)) return False # ============================================================ # Plantillas HTML inline # ============================================================ def build_password_reset_email(reset_url: str, user_name: str) -> tuple[str, str]: """ Construir HTML y texto plano para email de reseteo de contraseña. Returns: (html_content, text_content) """ html = f"""