|
| 1 | +"""API details""" |
| 2 | + |
| 3 | +# AUTOGENERATED! DO NOT EDIT! File to edit: ../00_core.ipynb. |
| 4 | + |
| 5 | +# %% auto 0 |
| 6 | +__all__ = ['get_addr', 'attach_file', 'create_multipart_msg', 'md2email', 'smtp_connection', 'MarkdownMerge'] |
| 7 | + |
| 8 | +# %% ../00_core.ipynb 2 |
| 9 | +import os,mimetypes,smtplib |
| 10 | + |
| 11 | +from fastcore.utils import * |
| 12 | +from email.mime.multipart import MIMEMultipart |
| 13 | +from email.mime.text import MIMEText |
| 14 | +from email.mime.base import MIMEBase |
| 15 | +from email import encoders |
| 16 | + |
| 17 | +from contextlib import contextmanager |
| 18 | +from markdown import markdown |
| 19 | +from email.headerregistry import Address |
| 20 | +from time import sleep |
| 21 | + |
| 22 | +# %% ../00_core.ipynb 6 |
| 23 | +def get_addr(email, name=None): |
| 24 | + "Convert `email` and optional `name` into an email `Address` object" |
| 25 | + return Address(email if name is None else name, addr_spec=email) |
| 26 | + |
| 27 | +# %% ../00_core.ipynb 9 |
| 28 | +def attach_file(msg, f): |
| 29 | + "Attach file `f` to message `msg`" |
| 30 | + mtype,_ = mimetypes.guess_type(f) |
| 31 | + main,sub = (mtype or 'application/octet-stream').split('/', 1) |
| 32 | + part = MIMEBase(main, sub) |
| 33 | + with open(f, 'rb') as fp: part.set_payload(fp.read()) |
| 34 | + encoders.encode_base64(part) |
| 35 | + part['Content-Disposition'] = f'attachment; filename={Path(f).name}' |
| 36 | + msg.attach(part) |
| 37 | + |
| 38 | +# %% ../00_core.ipynb 11 |
| 39 | +def create_multipart_msg(subj, from_addr, to_addrs, md=None, html=None, attach=None): |
| 40 | + "Create a multipart email with markdown text and HTML" |
| 41 | + msg = MIMEMultipart('alternative') |
| 42 | + msg['Subject'],msg['From'] = subj,str(from_addr) |
| 43 | + msg['To'] = ', '.join([str(a) for a in listify(to_addrs)]) |
| 44 | + if md: msg.attach(MIMEText(md, 'plain')) |
| 45 | + if html: msg.attach(MIMEText(html, 'html')) |
| 46 | + for f in listify(attach): attach_file(msg, f) |
| 47 | + return msg |
| 48 | + |
| 49 | +# %% ../00_core.ipynb 13 |
| 50 | +def md2email(subj, from_addr, to_addrs, md, attach=None): |
| 51 | + "Create a multipart email from markdown" |
| 52 | + html = markdown(md) |
| 53 | + return create_multipart_msg(subj, from_addr, to_addrs, md=md, html=html, attach=attach) |
| 54 | + |
| 55 | +# %% ../00_core.ipynb 23 |
| 56 | +@contextmanager |
| 57 | +def smtp_connection(host, port, user=None, password=None, use_ssl=True, use_tls=False): |
| 58 | + "Context manager for SMTP connection" |
| 59 | + conn = smtplib.SMTP_SSL(host, port) if use_ssl else smtplib.SMTP(host, port) |
| 60 | + if use_tls and not use_ssl: conn.starttls() |
| 61 | + if user and password: conn.login(user, password) |
| 62 | + try: yield conn |
| 63 | + finally: conn.quit() |
| 64 | + |
| 65 | +# %% ../00_core.ipynb 27 |
| 66 | +class MarkdownMerge: |
| 67 | + "Send templated email merge messages formatted with Markdown" |
| 68 | + def __init__(self, addrs, from_addr, subj, msg, smtp_cfg=None, inserts=None, test=False): |
| 69 | + self.addrs,self.from_addr,self.subj,self.msg,self.i = addrs,from_addr,subj,msg,0 |
| 70 | + self.inserts = [{}]*len(addrs) if inserts is None else inserts |
| 71 | + self.smtp_cfg,self.test = smtp_cfg,test |
| 72 | + |
| 73 | + def send_msgs(self, pause=0.5): |
| 74 | + "Send all unsent messages to `addrs` with `pause` secs between each send" |
| 75 | + with smtp_connection(**self.smtp_cfg) as conn: |
| 76 | + while self.i < len(self.addrs): |
| 77 | + addr,insert = self.addrs[self.i],self.inserts[self.i] |
| 78 | + msg = self.msg.format(**insert) |
| 79 | + eml = md2email(self.subj, self.from_addr, addr, md=msg) |
| 80 | + if self.test: print(f"To: {addr}\n{'-'*40}\n{msg}\n{'='*40}\n") |
| 81 | + else: conn.send_message(eml); sleep(pause) |
| 82 | + self.i += 1 |
| 83 | + if self.i%100==0: print(self.i) |
| 84 | + |
| 85 | + def reset(self): self.i=0 |
0 commit comments