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

1from typing import Dict, Any, Optional 

2 

3class EmailTemplate: 

4 """Email template with subject, plain text body, and optional HTML body""" 

5 

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 

15 

16 def render(self, **kwargs: Any) -> Dict[str, str]: 

17 """ 

18 Render template with variables. 

19  

20 Args: 

21 **kwargs: Variables to substitute in template 

22  

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 } 

30 

31 if self.html_body: 

32 result["html_body"] = self.html_body.format(**kwargs) 

33 

34 return result 

35 

36 

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) 

77 

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)