"""Email sending service via SMTP. If SMTP credentials are not configured (SMTP_HOST / SMTP_USER empty), emails are logged to console instead — useful for local development. Supabase SMTP settings can be found in: Project Settings → Auth → SMTP Settings (enable custom SMTP) Or use any external provider (SendGrid, Mailgun, Brevo, etc.) and put the credentials in the .env file. """ from __future__ import annotations import logging import smtplib import ssl from email.mime.multipart import MIMEMultipart from email.mime.text import MIMEText from app.core.config import get_settings logger = logging.getLogger(__name__) class EmailService: def __init__(self): self.settings = get_settings() def _is_configured(self) -> bool: return bool(self.settings.SMTP_HOST and self.settings.SMTP_USER) def _send(self, to: str, subject: str, html_body: str, text_body: str) -> None: s = self.settings msg = MIMEMultipart("alternative") msg["Subject"] = subject msg["From"] = s.EMAIL_FROM msg["To"] = to msg.attach(MIMEText(text_body, "plain", "utf-8")) msg.attach(MIMEText(html_body, "html", "utf-8")) ctx = ssl.create_default_context() try: if s.SMTP_PORT == 465: with smtplib.SMTP_SSL(s.SMTP_HOST, s.SMTP_PORT, context=ctx) as srv: srv.login(s.SMTP_USER, s.SMTP_PASSWORD) srv.sendmail(s.EMAIL_FROM, to, msg.as_string()) else: with smtplib.SMTP(s.SMTP_HOST, s.SMTP_PORT) as srv: srv.ehlo() srv.starttls(context=ctx) srv.login(s.SMTP_USER, s.SMTP_PASSWORD) srv.sendmail(s.EMAIL_FROM, to, msg.as_string()) except Exception as exc: logger.error("Failed to send email to %s: %s", to, exc) raise # ── Public send methods ─────────────────────────────────── def send_password_reset_email(self, to_email: str, reset_url: str) -> None: subject = f"Reset your {self.settings.APP_NAME} password" html = f"""

Reset Your Password

You requested a password reset for your {self.settings.APP_NAME} account.

Click the button below. This link expires in 1 hour.

Reset Password

If you did not request this, you can safely ignore this email.

Or paste this link: {reset_url}

""" text = f"Reset your password at: {reset_url}\n\nThis link expires in 1 hour." if self._is_configured(): self._send(to_email, subject, html, text) else: logger.info("[DEV] Password reset email → %s URL: %s", to_email, reset_url) def send_verification_email(self, to_email: str, verify_url: str, name: str) -> None: subject = f"Verify your {self.settings.APP_NAME} email address" html = f"""

Welcome, {name}!

Thanks for signing up. Please verify your email address to activate your account.

Verify Email

This link expires in 24 hours. If you did not sign up, ignore this email.

Or paste this link: {verify_url}

""" text = f"Hi {name},\n\nVerify your {self.settings.APP_NAME} account: {verify_url}\n\nExpires in 24 hours." if self._is_configured(): self._send(to_email, subject, html, text) else: logger.info("[DEV] Verification email → %s URL: %s", to_email, verify_url) def send_new_message_notification( self, to_email: str, agency_name: str, sender_name: str, listing_title: str, dashboard_url: str ) -> None: subject = f"New message from {sender_name} — {self.settings.APP_NAME}" html = f"""

New Message Received

Hi {agency_name},

{sender_name} sent you a message about your listing "{listing_title}".

View Message

Or visit: {dashboard_url}

""" text = ( f"Hi {agency_name},\n\n" f"{sender_name} sent a message about \"{listing_title}\".\n\n" f"View it at: {dashboard_url}" ) if self._is_configured(): self._send(to_email, subject, html, text) else: logger.info("[DEV] New message notification → %s from: %s", to_email, sender_name)