building out and preparing the EmailHandler's adaptive content. Using HTML formatting.

This commit is contained in:
Max Metz 2024-07-30 11:41:46 +02:00
parent ad1e0249d7
commit 63040c7f21

View File

@ -12,6 +12,12 @@ import email
from email.mime.multipart import MIMEMultipart
from email.mime.application import MIMEApplication
import subprocess
import sys
import time
from tempfile import NamedTemporaryFile
import json
from cryptography.fernet import Fernet
class EmailHandler():
"""
@ -36,7 +42,7 @@ class EmailHandler():
self.mail_port = mail_port
self.mail_address = mail_address
self.server = smtplib.SMTP_SSL(self.mail_server, self.mail_port) # alternatively, SMTP
self.server = smtplib.SMTP_SSL(self.mail_server, self.mail_port) # alternatively, use smtplib.SMTP
def check_state(self):
"""check, whether the server login took place and is open."""
@ -59,7 +65,7 @@ class EmailHandler():
user = self.server.__dict__.get("user",None)
return user is not None
def login(self, interactive:bool=True):
def login(self, interactive:bool=True, pwd=typing.Optional[bytes]):
"""
login on the determined mail server's mail address. By default, this function opens an interactive window to
type the password without echoing (printing '*******' instead of readable characters).
@ -71,19 +77,30 @@ class EmailHandler():
(status_code, status_msg) = self.server.login(self.mail_address, password=getpass())
else:
# fernet + password file
raise NotImplementedError()
assert pwd is not None, f"when non-interactive login is selected, one must provide a password"
assert isinstance(pwd, bytes), "please provide only byte-encrypted secure passwords. Those should be Fernet encoded."
fernet_key_path = os.path.join(os.path.expanduser("~"), "secure", "email_login_fernet_key.json")
assert os.path.exists(fernet_key_path), f"cannot find fernet key file at path: {fernet_key_path}"
with open(fernet_key_path, "r") as jr:
json_content = json.load(jr)
assert "fernet_key" in json_content
key = json_content.get("fernet_key").encode("utf8")
(status_code, status_msg) = self.server.login(self.mail_address, password=Fernet(key).decrypt(pwd).decode())
return (status_code, status_msg) # should be: (235, b'2.7.0 Authentication successful')
def create_email(self, subject:str, message_body:str)->EmailMessage:
def create_email(self, subject:str, message_body:str, subtype:typing.Optional[str]=None, sender_address:typing.Optional[str]=None)->EmailMessage:
"""
Create an EmailMessage object, which contains the Email's header ("Subject"), content ("Message Body") and the sender's address ("From").
The EmailMessage object does not contain the recipients yet, as these will be defined upon sending the Email.
"""
msg = EmailMessage()
msg["Subject"] = subject
msg["From"] = self.mail_address
msg["From"] = self.mail_address if sender_address is None else sender_address
#msg["To"] = email_tgts # will be defined in self.send_email
msg.set_content(message_body)
msg.set_content(message_body, subtype=subtype) if subtype is not None else msg.set_content(message_body, subtype=subtype)
return msg
def build_recipients(self, email_tgts:list[str]):
@ -172,3 +189,41 @@ class EmailHandler():
self.server.quit()
return
def preview_html_content(self, html:str, delete_after_s_seconds:typing.Optional[float]=None, file_path_dict:dict={}):
"""
Given an HTML-formatted text string, this method creates a temporary .html file and
spawns the local default webbrowser to preview the content.
This method is useful to design or debug HTML files before sending them via the EmailHandler.
When providing a floating point to the 'delete_after_s_seconds' argument, the temporary file will be
automatically removed after those seconds. The python script is blocked for the duration (using time.sleep)
args:
file_path_dict:
it is common to refer to images via 'cid:FILE_ID' within the HTML content. The preview cannot
display this, as the attached files are missing. To circumvent this, one can provide a dictionary, which
replaced the referred key
(e.g., 'cid:FILE_ID')
with the actual path, such as a logo or remote absolute path
(e.g., 'file:///C:/Users/User/brecal/misc/logo_bremen_calling.png')
Inspired by: https://stackoverflow.com/questions/53452322/is-there-a-way-that-i-can-preview-my-html-file
User: https://stackoverflow.com/users/355230/martineau
"""
for k, v in file_path_dict.items():
html = html.replace(k, v)
with NamedTemporaryFile(mode='wt', suffix='.html', delete=False, encoding="utf-8") as temp_file:
temp_file.write(html)
temp_filename = temp_file.name # Save temp file's name.
command = f"{sys.executable} -m webbrowser -n {temp_filename}"
browser = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
if delete_after_s_seconds is not None:
assert isinstance(delete_after_s_seconds, float)
time.sleep(delete_after_s_seconds)
if os.path.exists(temp_filename):
os.remove(temp_filename)
return