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))