Skip to content

Instantly share code, notes, and snippets.

@SVilgelm
Last active April 21, 2020 00:21
Show Gist options
  • Save SVilgelm/2620a43bfff10376db7d to your computer and use it in GitHub Desktop.
Save SVilgelm/2620a43bfff10376db7d to your computer and use it in GitHub Desktop.
send email
#!/usr/bin/env python2
from email import header
from email.mime import audio
from email.mime import application
from email.mime import image
from email.mime import multipart
from email.mime import text
import logging
import mimetypes
import os
import re
import smtplib
import traceback
import unittest
LOG = logging.getLogger(__name__)
RE_EMAIL = re.compile(r'(?P<name>.+)<(?P<email>.+)>')
class EmailSender(object):
def __init__(self, host='', port=0, user=None, password=None,
use_tls=False):
self.host = host
self.port = port
self.user = user
self.password = password
self.use_tls = use_tls
self._smtp = None
def __enter__(self):
self._smtp = smtplib.SMTP(self.host, self.port)
self._smtp.ehlo()
if self.use_tls:
self._smtp.starttls()
self._smtp.ehlo()
if self.user is not None and self.password is not None:
self._smtp.login(self.user, self.password)
return self
def __exit__(self, exc_type, exc_val, _exc_tb):
if exc_type is not None and exc_val is not None:
msg = traceback.format_exception_only(exc_type, exc_val)
LOG.error(msg)
try:
self._smtp.quit()
except smtplib.SMTPException as e:
LOG.warning(e)
self._smtp = None
@staticmethod
def _files(msg, files):
for path in files:
ctype, encoding = mimetypes.guess_type(path)
if ctype is None or encoding is not None:
ctype = 'application/octet-stream'
maintype, subtype = ctype.split('/', 1)
types = {
'text': text.MIMEText,
'image': image.MIMEImage,
'audio': audio.MIMEAudio
}
with open(path, 'rb') as f:
part = types.get(maintype, application.MIMEApplication)(
f.read(), _subtype=subtype)
msg.add_header('Content-Disposition', 'attachment',
filename=os.path.basename(path))
msg.attach(part)
@staticmethod
def _body(msg, plain=None, html=None):
for body, subtype in ((plain, 'plain'), (html, 'html')):
if body:
part = text.MIMEText(body, _subtype=subtype, _charset='utf-8')
msg.attach(part)
@staticmethod
def _address(address):
m = RE_EMAIL.match(address)
if m:
address = '%s <%s>' % (header.Header(m.group('name'), 'utf-8'),
m.group('email'))
return address
def send(self, me, to, subject=None, plain=None, html=None, files=None):
if self._smtp is None:
with self:
self.send(me, to, subject, plain, html, files)
return
if isinstance(to, basestring):
to = [to]
msg = multipart.MIMEMultipart()
msg['From'] = self._address(me)
msg['To'] = ', '.join(map(self._address, to))
msg['Subject'] = header.Header(subject or '', 'utf-8')
self._body(msg, plain, html)
if files is not None:
self._files(msg, files)
self._smtp.sendmail(me, to, msg.as_string())
__call__ = send
class TestEmailSender(unittest.TestCase):
def test_init(self):
sender = EmailSender()
self.assertListEqual(['', 0, None, None, False],
[sender.host, sender.port, sender.user,
sender.password, sender.use_tls])
if __name__ == '__main__':
import argparse
import sys
parser = argparse.ArgumentParser(conflict_handler='resolve')
parser.add_argument('me')
parser.add_argument('to', nargs='+')
parser.add_argument('-s', '--subject')
parser.add_argument('-h', '--host', default='')
parser.add_argument('-p', '--port', default=0)
parser.add_argument('-u', '--user')
parser.add_argument('-w', '--password')
parser.add_argument('-u', '--use_tls', action='store_true')
parser.add_argument('-f', '--file', action='append')
parser.add_argument('-t', '--type', choices=('plain', 'html'),
default='plain')
args = parser.parse_args()
body = sys.stdin.read()
plain, html = (body, None) if args.type == 'plain' else (None, body)
EmailSender(args.host, args.port, args.user, args.password, args.use_tls)(
args.me, args.to, args.subject, plain, html, args.file)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment