"""Async email sending via SMTP. Falls back to logging if SMTP is not configured.""" import logging import os from email.mime.multipart import MIMEMultipart from email.mime.text import MIMEText import aiosmtplib logger = logging.getLogger(__name__) SMTP_HOST = os.getenv("SMTP_HOST", "") SMTP_PORT = int(os.getenv("SMTP_PORT", "587")) SMTP_USER = os.getenv("SMTP_USER", "") SMTP_PASSWORD = os.getenv("SMTP_PASSWORD", "") SMTP_FROM = os.getenv("SMTP_FROM", SMTP_USER) SMTP_SSL = os.getenv("SMTP_SSL", "false").lower() == "true" SMTP_TLS = os.getenv("SMTP_TLS", "true").lower() != "false" SITE_NAME = "Skins & Pins" def _configured() -> bool: return bool(SMTP_HOST and SMTP_USER and SMTP_FROM) async def _send(to: str, subject: str, body_text: str, body_html: str) -> None: msg = MIMEMultipart("alternative") msg["Subject"] = subject msg["From"] = f"{SITE_NAME} <{SMTP_FROM}>" msg["To"] = to msg.attach(MIMEText(body_text, "plain")) msg.attach(MIMEText(body_html, "html")) await aiosmtplib.send( msg, hostname=SMTP_HOST, port=SMTP_PORT, username=SMTP_USER, password=SMTP_PASSWORD, use_tls=SMTP_SSL, start_tls=(SMTP_TLS and not SMTP_SSL), timeout=10, ) async def send_magic_link(to: str, link: str) -> None: subject = f"Your {SITE_NAME} login link" body_text = f"Click the link below to log in (expires in 15 minutes):\n\n{link}\n\nIf you didn't request this, you can ignore this email." body_html = f"""

⛳ {SITE_NAME}

Click the button below to log in. This link expires in 15 minutes.

Log in to {SITE_NAME}

If the button doesn't work, copy this link:
{link}

If you didn't request this, you can ignore this email.

""" if _configured(): try: await _send(to, subject, body_text, body_html) logger.info("Magic link email sent to %s", to) except Exception as e: logger.error("Failed to send magic link email to %s: %s", to, e) else: logger.info("Magic link for %s: %s", to, link) async def send_invitation(to: str, invited_by_name: str, login_url: str) -> None: subject = f"You've been invited to {SITE_NAME}" body_text = f"{invited_by_name} has invited you to join {SITE_NAME}.\n\nLog in here:\n{login_url}" body_html = f"""

⛳ {SITE_NAME}

{invited_by_name} has invited you to join {SITE_NAME} — a golf score tracking app.

Accept invitation

If the button doesn't work, copy this link:
{login_url}

""" if _configured(): try: await _send(to, subject, body_text, body_html) logger.info("Invitation email sent to %s", to) except Exception as e: logger.error("Failed to send invitation email to %s: %s", to, e) else: logger.info("Invitation for %s — login at: %s", to, login_url)