"""
Email service for sending password reset emails via Gmail SMTP.
This module provides utilities for:
- Sending HTML emails via Gmail SMTP
- Rendering password reset email templates
- Managing SMTP connection and configuration
"""
import os
import smtplib
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from typing import Optional
from pathlib import Path
# SMTP configuration from environment variables
SMTP_HOST = os.getenv("SMTP_HOST", "smtp.gmail.com")
SMTP_PORT = int(os.getenv("SMTP_PORT", "587"))
SMTP_USERNAME = os.getenv("SMTP_USERNAME", "")
SMTP_PASSWORD = os.getenv("SMTP_PASSWORD", "")
SMTP_USE_TLS = os.getenv("SMTP_USE_TLS", "true").lower() == "true"
EMAIL_FROM = os.getenv("EMAIL_FROM", SMTP_USERNAME)
EMAIL_FROM_NAME = os.getenv("EMAIL_FROM_NAME", "Todo Application")
FRONTEND_URL = os.getenv("FRONTEND_URL", "http://localhost:3000")
def send_email(to_email: str, subject: str, html_content: str) -> bool:
"""
Send an HTML email via Gmail SMTP.
Args:
to_email: Recipient email address
subject: Email subject line
html_content: HTML content of the email
Returns:
True if email sent successfully, False otherwise
Example:
>>> success = send_email(
... "user@example.com",
... "Password Reset",
... "
Reset your password
"
... )
>>> print(success)
True
"""
try:
# Create message
message = MIMEMultipart("alternative")
message["Subject"] = subject
message["From"] = f"{EMAIL_FROM_NAME} <{EMAIL_FROM}>"
message["To"] = to_email
# Attach HTML content
html_part = MIMEText(html_content, "html")
message.attach(html_part)
# Connect to SMTP server and send email
with smtplib.SMTP(SMTP_HOST, SMTP_PORT) as server:
if SMTP_USE_TLS:
server.starttls()
# Login if credentials provided
if SMTP_USERNAME and SMTP_PASSWORD:
server.login(SMTP_USERNAME, SMTP_PASSWORD)
# Send email
server.send_message(message)
return True
except Exception as e:
print(f"Failed to send email to {to_email}: {str(e)}")
return False
def render_password_reset_email(reset_link: str, user_email: str) -> str:
"""
Render the password reset email HTML template.
Args:
reset_link: Full URL for password reset
user_email: User's email address
Returns:
Rendered HTML email content
Example:
>>> html = render_password_reset_email(
... "http://localhost:3000/reset-password?token=abc123",
... "user@example.com"
... )
"""
# Try to load template from file
template_path = Path(__file__).parent.parent.parent / "templates" / "password_reset_email.html"
if template_path.exists():
with open(template_path, "r", encoding="utf-8") as f:
template = f.read()
else:
# Fallback inline template
template = """
Password Reset
Password Reset Request
|
|
Hello,
We received a request to reset the password for your account associated with {{USER_EMAIL}}.
Click the button below to reset your password. This link will expire in 15 minutes.
If you didn't request a password reset, you can safely ignore this email. Your password will not be changed.
If the button doesn't work, copy and paste this link into your browser:
{{RESET_LINK}}
|
|
© 2026 Todo Application. All rights reserved.
|
|
"""
# Replace placeholders
html = template.replace("{{RESET_LINK}}", reset_link)
html = html.replace("{{USER_EMAIL}}", user_email)
return html
def send_password_reset_email(to_email: str, reset_token: str) -> bool:
"""
Send a password reset email to the user.
Args:
to_email: User's email address
reset_token: Password reset token
Returns:
True if email sent successfully, False otherwise
Example:
>>> success = send_password_reset_email("user@example.com", "abc123def456")
>>> print(success)
True
"""
# Construct reset link
reset_link = f"{FRONTEND_URL}/reset-password?token={reset_token}"
# Render email HTML
html_content = render_password_reset_email(reset_link, to_email)
# Send email
return send_email(
to_email=to_email,
subject="Reset Your Password - Todo Application",
html_content=html_content
)