File size: 8,145 Bytes
67f8819 10ffab2 67f8819 | 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 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 | """
Email service for sending transactional emails.
Uses SMTP for sending emails. In production, consider using:
- SendGrid, Mailgun, AWS SES, or other email service providers
"""
import aiosmtplib
from email.message import EmailMessage
from typing import Optional
import os
from config import settings
async def send_password_reset_email(email: str, reset_token: str, frontend_url: str = "http://localhost:3002") -> bool:
"""
Send a password reset email to the user.
Args:
email: User's email address
reset_token: Password reset token
frontend_url: Frontend URL for constructing the reset link
Returns:
True if email was sent successfully, False otherwise
"""
if not settings.emails_enabled:
print(f"Email sending disabled. Would send reset email to: {email}")
print(f"Reset token: {reset_token}")
return True
if not settings.email_username or not settings.email_password:
print("Email credentials not configured. Skipping email send.")
print(f"RESET TOKEN FOR {email}: {reset_token}")
return True # Return True to not break the flow in demo mode
try:
# Create the email message
message = EmailMessage()
message["From"] = f"{settings.email_from_name} <{settings.email_from or settings.email_username}>"
message["To"] = email
message["Subject"] = "Reset Your TaskFlow Password"
# Construct the reset link
reset_link = f"{frontend_url}/reset-password?token={reset_token}"
# HTML email body
html_body = f"""
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Reset Your Password</title>
<style>
body {{
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
line-height: 1.6;
color: #333;
max-width: 600px;
margin: 0 auto;
padding: 20px;
background-color: #f4f4f4;
}}
.container {{
background-color: #ffffff;
border-radius: 8px;
padding: 40px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}}
.header {{
text-align: center;
margin-bottom: 30px;
}}
.logo {{
font-size: 24px;
font-weight: bold;
color: #2563eb;
}}
h1 {{
color: #1f2937;
font-size: 24px;
margin-bottom: 20px;
}}
p {{
margin-bottom: 16px;
color: #4b5563;
}}
.button {{
display: inline-block;
padding: 12px 24px;
background-color: #2563eb;
color: #ffffff !important;
text-decoration: none;
border-radius: 6px;
font-weight: 600;
margin: 20px 0;
}}
.button:hover {{
background-color: #1d4ed8;
}}
.footer {{
margin-top: 30px;
padding-top: 20px;
border-top: 1px solid #e5e7eb;
font-size: 12px;
color: #6b7280;
text-align: center;
}}
.warning {{
background-color: #fef3c7;
border-left: 4px solid #f59e0b;
padding: 12px;
margin: 20px 0;
font-size: 14px;
}}
</style>
</head>
<body>
<div class="container">
<div class="header">
<div class="logo">TaskFlow</div>
</div>
<h1>Reset Your Password</h1>
<p>Hello,</p>
<p>We received a request to reset the password for your TaskFlow account. Click the button below to choose a new password:</p>
<p style="text-align: center;">
<a href="{reset_link}" class="button">Reset Password</a>
</p>
<p>Or copy and paste this link into your browser:</p>
<p style="word-break: break-all; color: #2563eb; font-size: 14px;">{reset_link}</p>
<div class="warning">
<strong>Important:</strong> This link will expire in 1 hour for your security. If you didn't request this password reset, please ignore this email.
</div>
<div class="footer">
<p>If you're having trouble clicking the password reset button, copy and paste the URL above into your web browser.</p>
<p>© 2026 TaskFlow. All rights reserved.</p>
</div>
</div>
</body>
</html>
"""
# Plain text alternative
text_body = f"""
Reset Your TaskFlow Password
Hello,
We received a request to reset the password for your TaskFlow account. Click the link below to choose a new password:
{reset_link}
If you're having trouble clicking the link, copy and paste the URL into your web browser.
Important: This link will expire in 1 hour for your security. If you didn't request this password reset, please ignore this email.
© 2026 TaskFlow. All rights reserved.
"""
message.set_content(text_body)
message.add_alternative(html_body, subtype="html")
# Send the email
await aiosmtplib.send(
message,
hostname=settings.email_host,
port=settings.email_port,
username=settings.email_username,
password=settings.email_password,
start_tls=True,
)
print(f"Password reset email sent successfully to: {email}")
return True
except Exception as e:
print(f"Failed to send password reset email to {email}: {str(e)}")
print(f"RESET TOKEN FOR {email}: {reset_token}")
# In demo mode, we don't want to fail the entire flow if email sending fails
return True
async def send_email(
to_email: str,
subject: str,
html_body: str,
text_body: Optional[str] = None
) -> bool:
"""
Generic email sending function.
Args:
to_email: Recipient email address
subject: Email subject
html_body: HTML email body
text_body: Optional plain text alternative
Returns:
True if email was sent successfully, False otherwise
"""
if not settings.emails_enabled:
print(f"Email sending disabled. Would send email to: {to_email}")
return True
if not settings.email_username or not settings.email_password:
print("Email credentials not configured. Skipping email send.")
return True
try:
message = EmailMessage()
message["From"] = f"{settings.email_from_name} <{settings.email_from or settings.email_username}>"
message["To"] = to_email
message["Subject"] = subject
message.set_content(text_body or html_body)
message.add_alternative(html_body, subtype="html")
await aiosmtplib.send(
message,
hostname=settings.email_host,
port=settings.email_port,
username=settings.email_username,
password=settings.email_password,
start_tls=True,
)
print(f"Email sent successfully to: {to_email}")
return True
except Exception as e:
print(f"Failed to send email to {to_email}: {str(e)}")
return False
|