Cat Feeder  1.0.0
The Cat feeder project
Loading...
Searching...
No Matches
mail_management.py
Go to the documentation of this file.
1r"""
2# +==== BEGIN CatFeeder =================+
3# LOGO:
4# ..............(..../\
5# ...............)..(.')
6# ..............(../..)
7# ...............\‍(__)|
8# Inspired by Joan Stark
9# source https://www.asciiart.eu/
10# animals/cats
11# /STOP
12# PROJECT: CatFeeder
13# FILE: mail_management.py
14# CREATION DATE: 11-10-2025
15# LAST Modified: 14:47:48 19-12-2025
16# DESCRIPTION:
17# This is the backend server in charge of making the actual website work.
18# /STOP
19# COPYRIGHT: (c) Cat Feeder
20# PURPOSE: The file in charge of managing the emissions of e-mails.
21# // AR
22# +==== END CatFeeder =================+
23"""
24
25
26import ssl
27import smtplib
28from typing import List
29from email import encoders
30from email.message import EmailMessage
31from email.utils import make_msgid
32from email.mime.base import MIMEBase
33from display_tty import Disp, initialise_logger
34from . import mail_constants as CONST
35from ..core import FinalClass
36
37
38class MailManagement(metaclass=FinalClass):
39 """_summary_
40 """
41
42 disp: Disp = initialise_logger(__qualname__, False)
43
44 def __init__(self, error: int = 84, success: int = 0, debug: bool = False) -> None:
45 """_summary_
46 The class in charge of allowing the user to send e-mails.
47
48 Args:
49 error (int, optional): _description_. Defaults to 84.
50 success (int, optional): _description_. Defaults to 0.
51 debug (bool, optional): _description_. Defaults to False.
52 """
53 # ------------------------ The logging function ------------------------
54 self.disp.update_disp_debug(debug)
55 self.disp.log_debug("Initialising...")
56 # -------------------------- Inherited values --------------------------
57 self.success = success
58 self.error = error
59 self.debug = debug
60
61 # ------------------------- email related data -------------------------
62 self.sender = CONST.SENDER_ADDRESS
63 self.host = CONST.SENDER_HOST
64 self.api_key = CONST.SENDER_KEY
65 self.port = CONST.SENDER_PORT
66
67 self.disp.log_debug("Initialised")
68
69 def _send(self, em: EmailMessage) -> int:
70 """
71 Internal method to handle the actual sending of an email.
72
73 Args:
74 em (EmailMessage): The email message to be sent.
75
76 Returns:
77 int: The status of the email sending operation.
78 """
79 context = ssl.create_default_context()
80
81 try:
82 with smtplib.SMTP_SSL(self.host, self.port, context=context) as smtp:
83 smtp.login(self.sender, self.api_key)
84 smtp.send_message(em)
85 self.disp.log_debug("Email sent successfully")
86 return self.success
87 except (smtplib.SMTPException, OSError, ssl.SSLError) as e:
88 # SMTP/network/OS-level errors (authentication, connection, socket, SSL)
89 self.disp.log_critical(f"An error occurred sending email: {e}")
90 return self.error
91
92 def send_email(self, receiver: str, subject: str, body: str, body_type: str = "html") -> int:
93 """
94 Sends a simple email to a single receiver.
95
96 Args:
97 receiver (str): The recipient's email address.
98 subject (str): The subject of the email.
99 body (str): The content of the email.
100 body_type (str, optional): The MIME type of the email content ('html' or 'plain'). Defaults to 'html'.
101
102 Returns:
103 int: The status of the email sending operation.
104 """
105 em = EmailMessage()
106 em['From'] = self.sender
107 em['To'] = receiver
108 em['Subject'] = subject
109
110 if body_type.lower() == "html":
111 em.add_alternative(body, subtype='html')
112 else:
113 em.set_content(body)
114
115 return self._send(em)
116
117 def send_email_with_attachment(self, receiver: str, subject: str, body: str, attachments: List[str], body_type: str = "html") -> int:
118 """
119 Sends an email with one or more attachments.
120
121 Args:
122 receiver (str): The recipient's email address.
123 subject (str): The subject of the email.
124 body (str): The content of the email.
125 attachments (List[str]): List of file paths for attachments.
126 body_type (str, optional): The MIME type of the email content ('html' or 'plain'). Defaults to 'html'.
127
128 Returns:
129 int: The status of the email sending operation.
130 """
131 em = EmailMessage()
132 em['From'] = self.sender
133 em['To'] = receiver
134 em['Subject'] = subject
135
136 if body_type == "html":
137 em.add_alternative(body, subtype='html')
138 else:
139 em.set_content(body)
140
141 for file in attachments:
142 try:
143 with open(file, 'rb') as f:
144 file_data = f.read()
145 file_name = file.split('/')[-1]
146
147 part = MIMEBase('application', 'octet-stream')
148 part.set_payload(file_data)
149 encoders.encode_base64(part)
150 part.add_header(
151 'Content-Disposition',
152 f'attachment; filename={file_name}'
153 )
154 em.add_attachment(
155 part.get_payload(decode=True),
156 maintype='application',
157 subtype='octet-stream',
158 filename=file_name
159 )
160
161 except OSError as e:
162 # File I/O errors: missing file, permission denied, etc.
163 self.disp.log_critical(
164 f"Error reading attachment {file}: {e}",
165 "send_email_with_attachment"
166 )
167 return self.error
168
169 return self._send(em)
170
171 def send_email_to_multiple(self, receivers: List[str], subject: str, body: str, body_type: str = "html") -> int:
172 """
173 Sends an email to multiple recipients (To, Cc, or Bcc).
174
175 Args:
176 receivers (List[str]): A list of recipients' email addresses.
177 subject (str): The subject of the email.
178 body (str): The content of the email.
179 body_type (str, optional): The MIME type of the email content ('html' or 'plain'). Defaults to 'html'.
180
181 Returns:
182 int: The status of the email sending operation.
183 """
184 em = EmailMessage()
185 em['From'] = self.sender
186 em['To'] = ', '.join(receivers)
187 em['Subject'] = subject
188
189 if body_type == "html":
190 em.add_alternative(body, subtype='html')
191 else:
192 em.set_content(body)
193
194 return self._send(em)
195
196 def send_email_with_inline_image(self, receiver: str, subject: str, body: str, image_path: str, body_type: str = "html") -> int:
197 """
198 Sends an email with an inline image embedded in the body.
199
200 Args:
201 receiver (str): The recipient's email address.
202 subject (str): The subject of the email.
203 body (str): The content of the email, including a placeholder for the image.
204 image_path (str): The path to the image to be embedded.
205 body_type (str, optional): The MIME type of the email content ('html' or 'plain'). Defaults to 'html'.
206
207 Returns:
208 int: The status of the email sending operation.
209 """
210 em = EmailMessage()
211 em['From'] = self.sender
212 em['To'] = receiver
213 em['Subject'] = subject
214
215 if body_type == "html":
216 em.add_alternative(body, subtype='html')
217 else:
218 em.set_content(body)
219
220 try:
221 with open(image_path, 'rb') as img:
222 img_data = img.read()
223 img_cid = make_msgid()[1:-1]
224 em.add_related(
225 img_data,
226 maintype='image',
227 subtype='jpeg',
228 cid=img_cid
229 )
230 # Formatting the body may raise KeyError/ValueError if placeholders
231 # don't match; opening the file may raise OSError.
232 em.set_content(body.format(img_cid=img_cid))
233 except (OSError, KeyError, ValueError) as e:
234 self.disp.log_critical(
235 f"Error embedding inline image: {e}",
236 "send_email_with_inline_image"
237 )
238 return self.error
239
240 return self._send(em)
None __init__(self, int error=84, int success=0, bool debug=False)
int send_email(self, str receiver, str subject, str body, str body_type="html")
int send_email_with_attachment(self, str receiver, str subject, str body, List[str] attachments, str body_type="html")
int send_email_with_inline_image(self, str receiver, str subject, str body, str image_path, str body_type="html")
int send_email_to_multiple(self, List[str] receivers, str subject, str body, str body_type="html")