""" Minimal SMTP sender for the rates app. Reuses the stack's shared SMTP integration (managed once in the Settings app, same as the tasks app's mailer) via central_settings — no per-app mail secrets. Fire-and-forget: a mail failure must never break the scheduler. """ import logging import os import smtplib from email.message import EmailMessage from services.central_settings import get_integration_sync logger = logging.getLogger(__name__) HOTEL_NAME = os.getenv("VITE_HOTEL_NAME", "Hotel") def send_mail(to: str, subject: str, body: str) -> bool: """Send a plain-text email through the central SMTP integration. Returns True on success. Never raises — logs and returns False instead.""" if not to: return False cfg = get_integration_sync("smtp") if not cfg or not cfg.get("host"): logger.warning("SMTP not configured in central settings — email skipped") return False try: msg = EmailMessage() msg["From"] = cfg.get("from") or f"{HOTEL_NAME} Rates " msg["To"] = to msg["Subject"] = subject if cfg.get("reply_to"): msg["Reply-To"] = cfg["reply_to"] msg.set_content(body) port = int(cfg.get("port") or 587) with smtplib.SMTP(cfg["host"], port, timeout=15) as s: if port != 465: try: s.starttls() except smtplib.SMTPException: pass # server without STARTTLS (e.g. local relay) if cfg.get("user"): s.login(cfg["user"], cfg.get("pass") or "") s.send_message(msg) return True except Exception as e: logger.error(f"Email send failed: {e}") return False