from email.message import EmailMessage from typing import Callable, Dict, Union import jinja2 from redmail.email.body import HTMLBody, TextBody from .address import EmailAddress from .envs import get_span, is_last_group_row import smtplib from pathlib import Path from platform import node from getpass import getuser import datetime class EmailSender: """Email sender Parameters ---------- server : str SMTP server address. port : int Port to the SMTP server. user : str, callable User name to authenticate on the server. password : str, callable User password to authenticate on the server. Examples -------- mymail = EmailSender(server="smtp.mymail.com", port=123) mymail.set_credentials( user=lambda: read_yaml("C:/config/email.yaml")["mymail"]["user"], password=lambda: read_yaml("C:/config/email.yaml")["mymail"]["password"] ) mymail.send_email( subject="Important email", html_body="

Important

", body_images={'nice_pic': 'path/to/pic.jpg'}, ) """ default_html_theme = "modest.html" default_text_theme = "pandas.txt" templates_html = jinja2.Environment(loader=jinja2.FileSystemLoader(Path(__file__).parent / "templates/html")) templates_html_table = jinja2.Environment(loader=jinja2.FileSystemLoader(Path(__file__).parent / "templates/html/table")) templates_text = jinja2.Environment(loader=jinja2.FileSystemLoader(Path(__file__).parent / "templates/text")) templates_text_table = jinja2.Environment(loader=jinja2.FileSystemLoader(Path(__file__).parent / "templates/text/table")) # Set globals templates_html_table.globals["get_span"] = get_span templates_text_table.globals["get_span"] = get_span templates_html_table.globals["is_last_group_row"] = is_last_group_row templates_text_table.globals["is_last_group_row"] = is_last_group_row def __init__(self, server:str, port:int, user_name:str=None, password:str=None): self.server = server self.port = port self.user_name = user_name self.password = password def send_email(self, **kwargs): """Send an email message. Parameters ---------- subject : str Subject of the email. receiver : list, optional Receivers of the email. sender : str, optional Sender of the email. cc : list, optional Cc or Carbon Copy of the email. Extra recipients of the email. bcc : list, optional Blind Carbon Copy of the email. Extra recipients of the email that don't see who else got the email. html_body : str, optional HTML body of the email. May contain Jinja templated variables of the tables, images and other variables. text_body : str, optional Text body of the email. body_images : dict of bytes, path-likes and figures, optional HTML images to embed with the html_body. The key should be as Jinja variables in the html_body and the values represent images (path to an image, bytes of an image or image object). body_tables : Dict[str, pd.DataFrame], optional HTML tables to embed with the html_body. The key should be as Jinja variables in the html_body and the values are Pandas DataFrames. html_params : dict, optional Extra parameters passed to html_table as Jinja parameters. Examples -------- >>> sender = EmailSender(server="myserver", port=1234) >>> sender.send_email( sender="me@gmail.com", receiver="you@gmail.com", subject="Some news", html_body='

Hi,

Nice to meet you. Look at this: ', body_images={"my_image": Path("C:/path/to/img.png")} ) >>> sender.send_email( sender="me@gmail.com", receiver="you@gmail.com", subject="Some news", html_body='

Hi {{ name }},

Nice to meet you. Look at this table: ', body_images={"my_image": Path("C:/path/to/img.png")}, html_params={"name": "Jack"}, ) Returns ------- EmailMessage Email message. """ msg = self.get_message(**kwargs) self.send_message(msg) return msg def get_message(self, subject:str, receiver:list=None, sender:str=None, cc:list=None, bcc:list=None, html_body:str=None, text_body:str=None, html_template=None, text_template=None, body_images:Dict[str, str]=None, body_tables:Dict[str, str]=None, body_params:dict=None) -> EmailMessage: """Get the email message.""" sender = sender or self.user_name msg = self._create_body( subject=subject, sender=sender, receiver=receiver, cc=cc, bcc=bcc, ) jinja_params = self.get_template_params(sender=sender) jinja_params.update(body_params if body_params is not None else {}) if text_body is not None or text_template is not None: body = TextBody( template=self.get_text_body_template(text_template), table_template=self.get_text_table_template(), ) body.attach( msg, text_body, tables=body_tables, jinja_params=jinja_params, ) if html_body is not None or html_template is not None: body = HTMLBody( template=self.get_html_body_template(html_template), table_template=self.get_html_table_template(), ) body.attach( msg, html=html_body, images=body_images, tables=body_tables, jinja_params=jinja_params ) return msg def _create_body(self, subject, sender, receiver=None, cc=None, bcc=None) -> EmailMessage: msg = EmailMessage() msg["from"] = sender msg["subject"] = subject # To whoom the email goes if receiver: msg["to"] = receiver if cc: msg['cc'] = cc if bcc: msg['bcc'] = bcc return msg def send_message(self, msg): "Send the created message" user = self.user_name password = self.password server = smtplib.SMTP(self.server, self.port) server.starttls() server.login(user, password) server.send_message(msg) server.quit() def get_template_params(self, sender:str): "Get Jinja parametes passed to template" # TODO: Add receivers to params return { "node": node(), "user": getuser(), "now": datetime.datetime.now(), "sender": EmailAddress(sender), } def get_html_table_template(self, layout=None) -> jinja2.Template: layout = self.default_html_theme if layout is None else layout if layout is None: return None return self.templates_html_table.get_template(layout) def get_html_body_template(self, layout=None) -> jinja2.Template: if layout is None: return None return self.templates_html.get_template(layout) def get_text_table_template(self, layout=None) -> jinja2.Template: layout = self.default_text_theme if layout is None else layout if layout is None: return None return self.templates_text_table.get_template(layout) def get_text_body_template(self, layout=None) -> jinja2.Template: if layout is None: return None return self.templates_text.get_template(layout) def set_template_paths(self, html=None, text=None, html_table=None, text_table=None): """Create Jinja envs for body templates using given paths This is a shortcut for manually setting them like: .. clode-block:: python sender.templates_html = jinja2.Environment(loader=jinja2.FileSystemLoader(...)) sender.templates_text = jinja2.Environment(loader=jinja2.FileSystemLoader(...)) ... """ if html is not None: self.templates_html = jinja2.Environment(loader=jinja2.FileSystemLoader(html)) if text is not None: self.templates_text = jinja2.Environment(loader=jinja2.FileSystemLoader(text)) if html_table is not None: self.templates_html_table = jinja2.Environment(loader=jinja2.FileSystemLoader(html_table)) if text_table is not None: self.templates_text_table = jinja2.Environment(loader=jinja2.FileSystemLoader(text_table))