Coverage for opt/mealie/lib/python3.12/site-packages/mealie/services/email/email_service.py: 81%
40 statements
« prev ^ index » next coverage.py v7.10.6, created at 2025-11-25 15:48 +0000
« prev ^ index » next coverage.py v7.10.6, created at 2025-11-25 15:48 +0000
1from pathlib import Path 1a
3from jinja2 import Template 1a
4from pydantic import BaseModel 1a
6from mealie.core.root_logger import get_logger 1a
7from mealie.lang import local_provider 1a
8from mealie.lang.providers import Translator 1a
9from mealie.services._base_service import BaseService 1a
11from .email_senders import ABCEmailSender, DefaultEmailSender 1a
13CWD = Path(__file__).parent 1a
15logger = get_logger() 1a
18class EmailTemplate(BaseModel): 1a
19 subject: str 1a
20 header_text: str 1a
21 message_top: str 1a
22 message_bottom: str 1a
23 button_link: str 1a
24 button_text: str 1a
26 def render_html(self, template: Path) -> str: 1a
27 tmpl = Template(template.read_text())
29 return tmpl.render(data=self.model_dump())
32class EmailService(BaseService): 1a
33 def __init__(self, sender: ABCEmailSender | None = None, locale: str | None = None) -> None: 1a
34 self.templates_dir = CWD / "templates"
35 self.default_template = self.templates_dir / "default.html"
36 self.sender: ABCEmailSender = sender or DefaultEmailSender()
37 self.translator: Translator = local_provider(locale)
39 super().__init__()
41 def send_email(self, email_to: str, data: EmailTemplate) -> bool: 1a
42 if not self.settings.SMTP_ENABLE: 42 ↛ 45line 42 didn't jump to line 45 because the condition on line 42 was always true
43 return False
45 return self.sender.send(email_to, data.subject, data.render_html(self.default_template))
47 def send_forgot_password(self, address: str, reset_password_url: str) -> bool: 1a
48 forgot_password = EmailTemplate(
49 subject=self.translator.t("emails.password.subject"),
50 header_text=self.translator.t("emails.password.header_text"),
51 message_top=self.translator.t("emails.password.message_top"),
52 message_bottom=self.translator.t("emails.password.message_bottom"),
53 button_link=reset_password_url,
54 button_text=self.translator.t("emails.password.button_text"),
55 )
56 return self.send_email(address, forgot_password)
58 def send_invitation(self, address: str, invitation_url: str) -> bool: 1a
59 invitation = EmailTemplate(
60 subject=self.translator.t("emails.invitation.subject"),
61 header_text=self.translator.t("emails.invitation.header_text"),
62 message_top=self.translator.t("emails.invitation.message_top"),
63 message_bottom=self.translator.t("emails.invitation.message_bottom"),
64 button_link=invitation_url,
65 button_text=self.translator.t("emails.invitation.button_text"),
66 )
67 return self.send_email(address, invitation)
69 def send_test_email(self, address: str) -> bool: 1a
70 test_email = EmailTemplate(
71 subject=self.translator.t("emails.test.subject"),
72 header_text=self.translator.t("emails.test.header_text"),
73 message_top=self.translator.t("emails.test.message_top"),
74 message_bottom=self.translator.t("emails.test.message_bottom"),
75 button_link=self.settings.BASE_URL,
76 button_text=self.translator.t("emails.test.button_text"),
77 )
78 return self.send_email(address, test_email)