Skip to content

Commit abb6eae

Browse files
committed
docz and logz
1 parent 6ba6d23 commit abb6eae

File tree

1 file changed

+90
-11
lines changed

1 file changed

+90
-11
lines changed

app/services/smtp.py

+90-11
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,24 @@
1818

1919
@define
2020
class SMTPEmailService(metaclass=SingletonMetaNoArgs):
21+
"""
22+
SMTPEmailService provides a reusable interface to send emails via an SMTP server.
23+
24+
This service supports plaintext and HTML emails, and also allows
25+
sending template-based emails using the Jinja2 template engine.
26+
27+
It is implemented as a singleton to ensure that only one SMTP connection is maintained
28+
throughout the application lifecycle, optimizing resource usage.
29+
30+
Attributes:
31+
server_host (str): SMTP server hostname or IP address.
32+
server_port (int): Port number for the SMTP connection.
33+
username (str): SMTP username for authentication.
34+
password (str): SMTP password for authentication.
35+
templates (Jinja2Templates): Jinja2Templates instance for loading and rendering email templates.
36+
server (smtplib.SMTP): An SMTP object for sending emails, initialized after object creation.
37+
"""
38+
2139
# SMTP configuration
2240
server_host: str = field(default=global_settings.smtp.server)
2341
server_port: int = field(default=global_settings.smtp.port)
@@ -26,15 +44,21 @@ class SMTPEmailService(metaclass=SingletonMetaNoArgs):
2644

2745
# Dependencies
2846
templates: Jinja2Templates = field(
29-
factory=lambda: Jinja2Templates(global_settings.templates_dir)
47+
factory=lambda: Jinja2Templates(global_settings.smtp.template_path)
3048
)
3149
server: smtplib.SMTP = field(init=False) # Deferred initialization in post-init
3250

3351
def __attrs_post_init__(self):
34-
"""Initialize the SMTP server connection after object creation."""
52+
"""
53+
Initializes the SMTP server connection after the object is created.
54+
55+
This method sets up a secure connection to the SMTP server, including STARTTLS encryption
56+
and logs in using the provided credentials.
57+
"""
3558
self.server = smtplib.SMTP(self.server_host, self.server_port)
36-
self.server.starttls()
59+
self.server.starttls() # Upgrade the connection to secure TLS
3760
self.server.login(self.username, self.password)
61+
logger.info("SMTPEmailService initialized successfully and connected to SMTP server.")
3862

3963
def _prepare_email(
4064
self,
@@ -44,14 +68,28 @@ def _prepare_email(
4468
body_text: str,
4569
body_html: str,
4670
) -> MIMEMultipart:
47-
"""Prepare the email message."""
71+
"""
72+
Prepares a MIME email message with the given plaintext and HTML content.
73+
74+
Args:
75+
sender (EmailStr): The email address of the sender.
76+
recipients (list[EmailStr]): A list of recipient email addresses.
77+
subject (str): The subject line of the email.
78+
body_text (str): The plaintext content of the email.
79+
body_html (str): The HTML content of the email (optional).
80+
81+
Returns:
82+
MIMEMultipart: A MIME email object ready to be sent.
83+
"""
4884
msg = MIMEMultipart()
4985
msg["From"] = sender
5086
msg["To"] = ",".join(recipients)
5187
msg["Subject"] = subject
88+
# Add plain text and HTML content (if provided)
5289
msg.attach(MIMEText(body_text, "plain"))
5390
if body_html:
5491
msg.attach(MIMEText(body_html, "html"))
92+
logger.debug(f"Prepared email from {sender} to {recipients}.")
5593
return msg
5694

5795
def send_email(
@@ -62,9 +100,29 @@ def send_email(
62100
body_text: str = "",
63101
body_html: str = None,
64102
):
65-
"""Send a regular email (plain text or HTML)."""
66-
msg = self._prepare_email(sender, recipients, subject, body_text, body_html)
67-
self.server.sendmail(sender, recipients, msg.as_string())
103+
"""
104+
Sends an email to the specified recipients.
105+
106+
Supports plaintext and HTML email content. This method constructs
107+
the email message using `_prepare_email` and sends it using the SMTP server.
108+
109+
Args:
110+
sender (EmailStr): The email address of the sender.
111+
recipients (list[EmailStr]): A list of recipient email addresses.
112+
subject (str): The subject line of the email.
113+
body_text (str): The plaintext content of the email.
114+
body_html (str): The HTML content of the email (optional).
115+
116+
Raises:
117+
smtplib.SMTPException: If the email cannot be sent.
118+
"""
119+
try:
120+
msg = self._prepare_email(sender, recipients, subject, body_text, body_html)
121+
self.server.sendmail(sender, recipients, msg.as_string())
122+
logger.info(f"Email sent successfully to {recipients} from {sender}.")
123+
except smtplib.SMTPException as e:
124+
logger.error("Failed to send email", exc_info=e)
125+
raise
68126

69127
def send_template_email(
70128
self,
@@ -74,7 +132,28 @@ def send_template_email(
74132
context: dict,
75133
sender: EmailStr,
76134
):
77-
"""Send an email using a template with the provided context."""
78-
template_str = self.templates.get_template(template)
79-
body_html = template_str.render(context)
80-
self.send_email(sender, recipients, subject, body_html=body_html)
135+
"""
136+
Sends an email using a Jinja2 template.
137+
138+
This method renders the template with the provided context and sends it
139+
to the specified recipients.
140+
141+
Args:
142+
recipients (list[EmailStr]): A list of recipient email addresses.
143+
subject (str): The subject line of the email.
144+
template (str): The name of the template file in the templates directory.
145+
context (dict): A dictionary of values to render the template with.
146+
sender (EmailStr): The email address of the sender.
147+
148+
Raises:
149+
jinja2.TemplateNotFound: If the specified template is not found.
150+
smtplib.SMTPException: If the email cannot be sent.
151+
"""
152+
try:
153+
template_str = self.templates.get_template(template)
154+
body_html = template_str.render(context) # Render the HTML using context variables
155+
self.send_email(sender, recipients, subject, body_html=body_html)
156+
logger.info(f"Template email sent successfully to {recipients} using template {template}.")
157+
except Exception as e:
158+
logger.error("Failed to send template email", exc_info=e)
159+
raise

0 commit comments

Comments
 (0)