MarkdownMerge

API details
from fastcore.test import test_eq

Utility functions


get_addr


def get_addr(
    email, name:NoneType=None
):

Convert email and optional name into an email Address object

Specify from_addr and to_addrs as either a string, or an Address object (created with get_addr). Note the to_addrs is a list.

from_addr = get_addr('from@example.com', 'Jeremy Howard')
to_addrs = [get_addr('to@example.com', 'Jeręmy Hòwärd')]
print(from_addr)
print(to_addrs[0])
Jeremy Howard <from@example.com>
Jeręmy Hòwärd <to@example.com>

attach_file


def attach_file(
    msg, f
):

Attach file f to message msg

msg = MIMEMultipart(policy=EmailPolicy())
attach_file(msg, '../settings.ini')
part = msg.get_payload()[0]
test_eq(part.get_content_type(), 'application/octet-stream')
test_eq(part['Content-Disposition'], 'attachment; filename=settings.ini')
assert len(part.get_payload())>20

create_multipart_msg


def create_multipart_msg(
    subj, from_addr, to_addrs, md:NoneType=None, html:NoneType=None, attach:NoneType=None, hdrs:NoneType=None
):

Create a multipart email with markdown text and HTML

msg = create_multipart_msg('Test Subject', from_addr, to_addrs, hdrs={'atest':'foo'},
                           md='**Bold text**', html='<b>Bold text</b>')
test_eq(msg['Subject'], 'Test Subject')
test_eq(msg['From'], str(from_addr))
test_eq(msg['atest'], 'foo')
test_eq(len(msg.get_payload()), 2)
test_eq(msg.get_payload()[0].get_content_type(), 'text/plain')
test_eq(msg.get_payload()[1].get_content_type(), 'text/html')
assert '@' in msg['To']

md2email


def md2email(
    subj, from_addr, to_addrs, md, attach:NoneType=None, hdrs:NoneType=None
):

Create a multipart email from markdown

test_msg2 = md2email('Test md2email', 'support@answer.ai', 'j@answer.ai', '**Markdown** test with _attachment_', attach='../settings.ini')
test_eq(test_msg2['Subject'], 'Test md2email')
payload = test_msg2.get_payload()
test_eq(len(payload), 3)
test_eq(payload[2]['Content-Disposition'], 'attachment; filename=settings.ini')

The basic email body is the plain text message (note that the template variables in {} will be filled in by MarkdownMerge):

print(payload[0].get_payload())
**Markdown** test with _attachment_

Most email software is set up to display the HTML version:

from IPython.display import HTML
html = payload[1].get_payload()
HTML(html)

Markdown test with attachment

att = payload[2].get_payload()
import base64
decoded = base64.b64decode(payload[2].get_payload())
print(decoded.decode('utf-8')[:35])
[DEFAULT]
repo = markdown_merge
lib

smtp_connection


def smtp_connection(
    host, port, user:NoneType=None, password:NoneType=None, use_ssl:bool=True, use_tls:bool=False
):

Create and return an SMTP connection

servernm='email-smtp.us-west-2.amazonaws.com'
username=os.getenv('SES_SMTP_USER')
password=os.getenv('SES_SMTP_PASS')
smtp_cfg = dict(host=servernm, port=587, user=username, password=password, use_ssl=False, use_tls=True)
test_msg = create_multipart_msg('Test from stdlib', 'support@answer.ai', 'j@answer.ai', md='**Test message**', html='<b>Test message</b>')
# try:
#     conn = smtp_connection(**smtp_cfg)
#     conn.send_message(test_msg)
# finally: conn.quit()

MarkdownMerge


def MarkdownMerge(
    addrs, from_addr, subj, msg, smtp_cfg:NoneType=None, inserts:NoneType=None, test:bool=False, hdrs:NoneType=None,
    env_from:NoneType=None, attach:NoneType=None
):

Send templated email merge messages formatted with Markdown

Your message should be in markdown format. It will be converted into a two part email, containing both a plain text and an HTML part, so recipients will see whatever format they’re set as their preference for viewing mail. Anything in curly brackets {} will be replaced with the contents of the inserts dictionary for that address. If there are no bracketed variables to replace, then you don’t need to pass any inserts.

msg = "**Hello {name}!**\n\nYour special number is: *{num}*"

inserts is a list of dictionaries. For each dictionary, the keys should match the bracketed names in your email template, and the values will be filled in to those sections.

inserts = [{'name': 'Jeremy', 'num': 42}, {'name': 'Rachel', 'num': 7}]
mm = MarkdownMerge(['aaa@answer.ai', 'bbb@answer.ai'], 'from@answer.ai', 'Test merge',
                   msg, smtp_cfg=smtp_cfg, inserts=inserts, test=True)
mm.send_msgs()
To: aaa@answer.ai
----------------------------------------
**Hello Jeremy!**

Your special number is: *42*
========================================

To: bbb@answer.ai
----------------------------------------
**Hello Rachel!**

Your special number is: *7*
========================================

Use pause to avoid sending too many messages too quickly; many SMTP servers restrict sending speed to avoid abuse. If you get an error during sending (e.g. “too many messages”), then wait an hour or so, then continue sending, using a larger pause value.

NB: You can just call send_msgs again when resending, since the successfully sent message count is saved, and those messages are not re-sent (unless you call reset). This includes test sends, therefore you should run reset after a test send.

To reset the counter to 0, call reset:

mm.reset()

Attachments

Pass attach to include file attachments with your email. It can be a single file path or a list of paths:

mm = MarkdownMerge(addrs, from_addr, 'Subject', msg, smtp_cfg=smtp_cfg, attach='report.pdf')

For multiple attachments:

mm = MarkdownMerge(addrs, from_addr, 'Subject', msg, smtp_cfg=smtp_cfg, attach=['report.pdf', 'data.csv'])

The MIME type is automatically detected from the file extension. If the type cannot be determined, it defaults to application/octet-stream.