Coverage for utils/email_templates.py: 100.00%
13 statements
« prev ^ index » next coverage.py v7.9.2, created at 2026-01-25 13:05 +0000
« prev ^ index » next coverage.py v7.9.2, created at 2026-01-25 13:05 +0000
1from typing import Dict, Any, Optional
3class EmailTemplate:
4 """Email template with subject, plain text body, and optional HTML body"""
6 def __init__(
7 self,
8 subject: str,
9 body: str,
10 html_body: Optional[str] = None
11 ):
12 self.subject = subject
13 self.body = body
14 self.html_body = html_body
16 def render(self, **kwargs: Any) -> Dict[str, str]:
17 """
18 Render template with variables.
20 Args:
21 **kwargs: Variables to substitute in template
23 Returns:
24 Dict with 'subject', 'body', and optionally 'html_body' keys
25 """
26 result = {
27 "subject": self.subject.format(**kwargs),
28 "body": self.body.format(**kwargs),
29 }
31 if self.html_body:
32 result["html_body"] = self.html_body.format(**kwargs)
34 return result
37# Password Reset Email Template
38PASSWORD_RESET_TEMPLATE = EmailTemplate(
39 subject="Reset your password - {app_name}",
40 body=(
41 "Hi {user_name},\n\n"
42 "You requested a password reset for your {app_name} account.\n\n"
43 "Please click the link below to set a new password:\n{reset_url}\n\n"
44 "This link will expire in 30 minutes.\n\n"
45 "If you did not request this, you can safely ignore this email."
46 ),
47 html_body=(
48 "<!DOCTYPE html>"
49 "<html>"
50 "<head>"
51 "<meta charset='utf-8'>"
52 "<meta name='viewport' content='width=device-width, initial-scale=1.0'>"
53 "</head>"
54 "<body style='margin: 0; padding: 20px; font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, \"Helvetica Neue\", Arial, sans-serif; background-color: #ffffff;'>"
55 "<div style='max-width: 80dvw; margin: 0 auto;'>"
56 "<h3 style='margin: 0 0 16px; color: #212529; line-height: 1.6; font-weight: 500;'>Hi {user_name},</h3>"
57 "<p style='margin: 0 0 20px; color: #212529; font-size: 16px; line-height: 1.6;'>"
58 "You requested a password reset for your <strong>{app_name}</strong> account. <br>Click the button below to set a new password. This link will expire in <strong>30 minutes</strong>."
59 "</p>"
60 "<p style='margin:0 0 28px; user-select: none;'>"
61 "<a href='{reset_url}' "
62 "style='display: inline-block; background-color: #212529; "
63 "color: #ffffff; text-decoration: none; padding: 10px 16px; border-radius: 12px; "
64 "font-size: 16px; font-weight: 500;'"
65 ">"
66 "Reset Password"
67 "</a>"
68 "</p>"
69 "<p style='width: fit-content; margin: 0; padding: 10px 18px; border-radius: 12px; border: 1px solid #e9ecef; background-color: #f8f9fa; color: #495057; font-size: 14px; line-height: 1.6;'>"
70 "If you did not request this password reset, you can safely ignore this email. Your account remains secure."
71 "</p>"
72 "</div>"
73 "</body>"
74 "</html>"
75 ),
76)
78# Email Verification Template
79EMAIL_VERIFICATION_TEMPLATE = EmailTemplate(
80 subject="Verify your email - {app_name}",
81 body=(
82 "Hi {user_name},\n\n"
83 "Please verify your email address for your {app_name} account.\n\n"
84 "Please click the link below to verify your email address:\n{verification_url}\n\n"
85 "This link will expire in {expire_minutes} minutes.\n\n"
86 "If you did not request this email, you can safely ignore it."
87 ),
88 html_body=(
89 "<!DOCTYPE html>"
90 "<html>"
91 "<head>"
92 "<meta charset='utf-8'>"
93 "<meta name='viewport' content='width=device-width, initial-scale=1.0'>"
94 "</head>"
95 "<body style='margin: 0; padding: 20px; font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, \"Helvetica Neue\", Arial, sans-serif; background-color: #ffffff;'>"
96 "<div style='max-width: 80dvw; margin: 0 auto;'>"
97 "<h3 style='margin: 0 0 16px; color: #212529; line-height: 1.6; font-weight: 500;'>Hi {user_name},</h3>"
98 "<p style='margin: 0 0 20px; color: #212529; font-size: 16px; line-height: 1.6;'>"
99 "Please verify your email address for your <strong>{app_name}</strong> account. <br>Please click the button below to verify your email address. This link will expire in <strong>{expire_minutes} minutes</strong>."
100 "</p>"
101 "<p style='margin:0 0 28px; user-select: none;'>"
102 "<a href='{verification_url}' "
103 "style='display: inline-block; background-color: #212529; "
104 "color: #ffffff; text-decoration: none; padding: 10px 16px; border-radius: 12px; "
105 "font-size: 16px; font-weight: 500;'"
106 ">"
107 "Verify Email"
108 "</a>"
109 "</p>"
110 "<p style='width: fit-content; margin: 0; padding: 10px 18px; border-radius: 12px; border: 1px solid #e9ecef; background-color: #f8f9fa; color: #495057; font-size: 14px; line-height: 1.6;'>"
111 "If you did not request this email, you can safely ignore it."
112 "</p>"
113 "</div>"
114 "</body>"
115 "</html>"
116 ),
117)