""" Email utilities for Cortex — invite links and future notifications. Uses smtplib.SMTP_SSL (port 465). Both plain-text and HTML bodies are sent. SMTP credentials come from config.settings (set in .env). """ import logging import smtplib import ssl from email.headerregistry import Address from email.message import EmailMessage from config import settings logger = logging.getLogger(__name__) def send_email( to_email: str, subject: str, body_html: str, body_text: str, to_name: str = "", ) -> bool: """ Send an email via SMTP_SSL. Returns True on success, False on any failure. Logs errors but never raises — callers can check the return value. """ if not settings.smtp_server: logger.error("SMTP not configured (SMTP_SERVER is empty)") return False msg = EmailMessage() msg["Subject"] = subject msg["From"] = Address( display_name=settings.smtp_from_name, addr_spec=settings.smtp_from_email, ) msg["To"] = Address( display_name=to_name or to_email, addr_spec=to_email, ) msg.set_content(body_text) msg.add_alternative(f"{body_html}", subtype="html") logger.info("sending email to %s — %s", to_email, subject) try: ctx = ssl.create_default_context() with smtplib.SMTP_SSL(settings.smtp_server, settings.smtp_port, context=ctx) as server: if settings.smtp_username and settings.smtp_password: server.login(settings.smtp_username, settings.smtp_password) server.send_message(msg) logger.info("email sent to %s", to_email) return True except Exception as e: logger.error("failed to send email to %s: %s", to_email, e) return False def send_invite_email(to_email: str, username: str, token: str, to_name: str = "") -> bool: """Send a Cortex invite link to a new user.""" url = f"{settings.cortex_base_url}/setup/{token}" body_text = f"""\ You've been invited to Cortex. Click the link below to set your password and create your persona: {url} This link expires in 72 hours and can only be used once. — Cortex """ body_html = f"""\

You've been invited to Cortex.

Click the link below to set your password and create your persona:

Set up my account →

Or copy this link:
{url}

This link expires in 72 hours and can only be used once.

""" return send_email( to_email=to_email, subject="You've been invited to Cortex", body_html=body_html, body_text=body_text, to_name=to_name or username, )