"""Best-effort SMTP email sending (#18). A single background worker thread drains a queue, and a send failure is logged, never raised back to the caller -- booking creation must succeed even if the mail relay is down. """ import os import queue import smtplib import threading import traceback from email.message import EmailMessage SMTP_HOST = os.environ.get("SMTP_HOST", "") SMTP_PORT = int(os.environ.get("SMTP_PORT", "587")) SMTP_USERNAME = os.environ.get("SMTP_USERNAME", "") SMTP_PASSWORD = os.environ.get("SMTP_PASSWORD", "") # Used when a client has no domain configured -- see booking_mail.py. MAIL_FALLBACK_FROM = os.environ.get("MAIL_FALLBACK_FROM", "noreply@mivanchenko.de") _queue = queue.Queue() def _send_now(msg): if not SMTP_HOST: print(f"[mailer] SMTP_HOST not configured, skipping send to {msg['To']}", flush=True) return with smtplib.SMTP(SMTP_HOST, SMTP_PORT, timeout=10) as smtp: smtp.starttls() if SMTP_USERNAME: smtp.login(SMTP_USERNAME, SMTP_PASSWORD) smtp.send_message(msg) def _worker(): while True: msg = _queue.get() try: _send_now(msg) except Exception: # noqa: BLE001 print(f"[mailer] send to {msg['To']} failed:", flush=True) traceback.print_exc() finally: _queue.task_done() threading.Thread(target=_worker, daemon=True).start() def send_email(to_addr, subject, html_body, from_addr=None): """Queue an HTML email for best-effort async delivery. Never raises and never blocks the caller on the network -- the actual SMTP conversation happens on the background worker thread.""" msg = EmailMessage() msg["Subject"] = subject msg["From"] = from_addr or MAIL_FALLBACK_FROM msg["To"] = to_addr msg.set_content(html_body, subtype="html") _queue.put(msg)