Coverage for opt/mealie/lib/python3.12/site-packages/mealie/services/email/email_service.py: 90%
40 statements
« prev ^ index » next coverage.py v7.15.2, created at 2026-10-07 03:04 +0000
« prev ^ index » next coverage.py v7.15.2, created at 2026-10-07 03:04 +0000
1from pathlib import Path
3from jinja2 import Template
4from pydantic import BaseModel
6from mealie.core.root_logger import get_logger
7from mealie.lang import local_provider
8from mealie.lang.providers import Translator
9from mealie.services._base_service import BaseService
11from .email_senders import ABCEmailSender, DefaultEmailSender
13CWD = Path(__file__).parent
15logger = get_logger()
18class EmailTemplate(BaseModel):
19 subject: str
20 header_text: str
21 message_top: str
22 message_bottom: str
23 button_link: str
24 button_text: str
26 def render_html(self, template: Path) -> str:
27 tmpl = Template(template.read_text())
29 return tmpl.render(data=self.model_dump())
32class EmailService(BaseService):
33 def __init__(self, sender: ABCEmailSender | None = None, locale: str | None = None) -> None:
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:
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:
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:
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:
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)