# app/services/email_service.py """ Email service with placeholder implementation. This service provides email sending functionality with a simple console/log-based placeholder that can be easily replaced with a real email provider (SendGrid, SES, etc.) """ import logging from abc import ABC, abstractmethod from app.core.config import settings logger = logging.getLogger(__name__) class EmailBackend(ABC): """Abstract base class for email backends.""" @abstractmethod async def send_email( self, to: list[str], subject: str, html_content: str, text_content: str | None = None, ) -> bool: """Send an email.""" class ConsoleEmailBackend(EmailBackend): """ Console/log-based email backend for development and testing. This backend logs email content instead of actually sending emails. Replace this with a real backend (SMTP, SendGrid, SES) for production. """ async def send_email( self, to: list[str], subject: str, html_content: str, text_content: str | None = None, ) -> bool: """ Log email content to console/logs. Args: to: List of recipient email addresses subject: Email subject html_content: HTML version of the email text_content: Plain text version of the email Returns: True if "sent" successfully """ logger.info("=" * 80) logger.info("EMAIL SENT (Console Backend)") logger.info("=" * 80) logger.info(f"To: {', '.join(to)}") logger.info(f"Subject: {subject}") logger.info("-" * 80) if text_content: logger.info("Plain Text Content:") logger.info(text_content) logger.info("-" * 80) logger.info("HTML Content:") logger.info(html_content) logger.info("=" * 80) return True class SMTPEmailBackend(EmailBackend): """ SMTP email backend for production use. TODO: Implement SMTP sending with proper error handling. This is a placeholder for future implementation. """ def __init__(self, host: str, port: int, username: str, password: str): self.host = host self.port = port self.username = username self.password = password async def send_email( self, to: list[str], subject: str, html_content: str, text_content: str | None = None, ) -> bool: """Send email via SMTP.""" # TODO: Implement SMTP sending logger.warning("SMTP backend not yet implemented, falling back to console") console_backend = ConsoleEmailBackend() return await console_backend.send_email(to, subject, html_content, text_content) class EmailService: """ High-level email service that uses different backends. This service provides a clean interface for sending various types of emails and can be configured to use different backends (console, SMTP, SendGrid, etc.) """ def __init__(self, backend: EmailBackend | None = None): """ Initialize email service with a backend. Args: backend: Email backend to use. Defaults to ConsoleEmailBackend. """ self.backend = backend or ConsoleEmailBackend() async def send_password_reset_email( self, to_email: str, reset_token: str, user_name: str | None = None ) -> bool: """ Send password reset email. Args: to_email: Recipient email address reset_token: Password reset token user_name: User's name for personalization Returns: True if email sent successfully """ # Generate reset URL reset_url = f"{settings.FRONTEND_URL}/reset-password?token={reset_token}" # Prepare email content subject = "Password Reset Request" # Plain text version text_content = f""" Hello{" " + user_name if user_name else ""}, You requested a password reset for your account. Click the link below to reset your password: {reset_url} This link will expire in 1 hour. If you didn't request this, please ignore this email. Best regards, The {settings.PROJECT_NAME} Team """ # HTML version html_content = f"""

Password Reset

Hello{" " + user_name if user_name else ""},

You requested a password reset for your account. Click the button below to reset your password:

Reset Password

Or copy and paste this link into your browser:

{reset_url}

This link will expire in 1 hour.

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

""" try: return await self.backend.send_email( to=[to_email], subject=subject, html_content=html_content, text_content=text_content, ) except Exception as e: logger.error(f"Failed to send password reset email to {to_email}: {e!s}") return False async def send_email_verification( self, to_email: str, verification_token: str, user_name: str | None = None ) -> bool: """ Send email verification email. Args: to_email: Recipient email address verification_token: Email verification token user_name: User's name for personalization Returns: True if email sent successfully """ # Generate verification URL verification_url = ( f"{settings.FRONTEND_URL}/verify-email?token={verification_token}" ) # Prepare email content subject = "Verify Your Email Address" # Plain text version text_content = f""" Hello{" " + user_name if user_name else ""}, Thank you for signing up! Please verify your email address by clicking the link below: {verification_url} This link will expire in 24 hours. If you didn't create an account, please ignore this email. Best regards, The {settings.PROJECT_NAME} Team """ # HTML version html_content = f"""

Verify Your Email

Hello{" " + user_name if user_name else ""},

Thank you for signing up! Please verify your email address by clicking the button below:

Verify Email

Or copy and paste this link into your browser:

{verification_url}

This link will expire in 24 hours.

If you didn't create an account, please ignore this email.

""" try: return await self.backend.send_email( to=[to_email], subject=subject, html_content=html_content, text_content=text_content, ) except Exception as e: logger.error(f"Failed to send verification email to {to_email}: {e!s}") return False # Global email service instance email_service = EmailService()