File size: 7,596 Bytes
1941764 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 | """
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",
... "<h1>Reset your password</h1>"
... )
>>> 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 = """
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Password Reset</title>
</head>
<body style="margin: 0; padding: 0; font-family: Arial, sans-serif; background-color: #f4f4f4;">
<table width="100%" cellpadding="0" cellspacing="0" style="background-color: #f4f4f4; padding: 20px;">
<tr>
<td align="center">
<table width="600" cellpadding="0" cellspacing="0" style="background-color: #ffffff; border-radius: 8px; overflow: hidden; box-shadow: 0 2px 4px rgba(0,0,0,0.1);">
<tr>
<td style="background-color: #4F46E5; padding: 30px; text-align: center;">
<h1 style="color: #ffffff; margin: 0; font-size: 24px;">Password Reset Request</h1>
</td>
</tr>
<tr>
<td style="padding: 40px 30px;">
<p style="color: #333333; font-size: 16px; line-height: 1.6; margin: 0 0 20px 0;">
Hello,
</p>
<p style="color: #333333; font-size: 16px; line-height: 1.6; margin: 0 0 20px 0;">
We received a request to reset the password for your account associated with <strong>{{USER_EMAIL}}</strong>.
</p>
<p style="color: #333333; font-size: 16px; line-height: 1.6; margin: 0 0 30px 0;">
Click the button below to reset your password. This link will expire in 15 minutes.
</p>
<table width="100%" cellpadding="0" cellspacing="0">
<tr>
<td align="center">
<a href="{{RESET_LINK}}" style="display: inline-block; background-color: #4F46E5; color: #ffffff; text-decoration: none; padding: 14px 40px; border-radius: 6px; font-size: 16px; font-weight: bold;">Reset Password</a>
</td>
</tr>
</table>
<p style="color: #666666; font-size: 14px; line-height: 1.6; margin: 30px 0 0 0;">
If you didn't request a password reset, you can safely ignore this email. Your password will not be changed.
</p>
<p style="color: #666666; font-size: 14px; line-height: 1.6; margin: 20px 0 0 0;">
If the button doesn't work, copy and paste this link into your browser:
</p>
<p style="color: #4F46E5; font-size: 14px; word-break: break-all; margin: 10px 0 0 0;">
{{RESET_LINK}}
</p>
</td>
</tr>
<tr>
<td style="background-color: #f8f8f8; padding: 20px 30px; text-align: center; border-top: 1px solid #e0e0e0;">
<p style="color: #999999; font-size: 12px; margin: 0;">
© 2026 Todo Application. All rights reserved.
</p>
</td>
</tr>
</table>
</td>
</tr>
</table>
</body>
</html>
"""
# 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
)
|