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