Source code for email_handler

# labtools, Copyright (C) 2017 Jerry Fowler and Paul Scheet.
# This program comes with ABSOLUTELY NO WARRANTY. It is licensed under
# GNU GPL Version 3. License and warranty may be viewed in the manual.
'''
As simple an interface for email as seems viable.

>>> import email_handler
>>> bm = email_handler.BatchMailer('rgfowler@mdanderson.org', 'Package tester')
>>> response = bm.send_simple_mail('Batch mailer test', 'a better test')
>>> assert(const.EMPTY == response)
'''



import os
import sys


import smtplib
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart

from email import utils as mailutil

from labtools import const
from labtools import labexceptions
from labtools import misc


DOMAIN = 'mdanderson.org' #: The default domain attached to a bare unix userid
MAILHOST = 'mail.mdanderson.org' #:

def prepare_multipart_message(text, subject, from_addr, to_addr):
    msg = MIMEMultipart(None, None, [text])
    msg['Subject'] = subject
    msg['From'] = from_addr
    msg['To'] = const.COMMA.join(to_addr) if isinstance(to_addr, list) else to_addr
    return msg

def test_sender(sender, mailhost=MAILHOST):
    response = const.OK
    smtp = None
    try:
        smtp = smtplib.SMTP(mailhost)
    except Exception as connect:
        response = 'SMTP connection failed to %s: %s' % (mailhost, connect.strerror)
        return response

    try:
        code, verification = smtp.verify(sender)
    except smtplib.SMTPRecipientsRefused as recipients:
        # All recipients were refused. Nobody got the mail.
        response = 'SMTP failed to all recipients (%s)' % (msg['To'])
    if code >= 400:
        return verification
    return response

def send_message(msg, mailhost=MAILHOST):
    response = const.OK
    smtp = None
    try:
        smtp = smtplib.SMTP(mailhost)
    except Exception as connect:
        response = 'SMTP connection failed to %s: %s' % (mailhost, connect.strerror)
        return response

    try:
        smtp.sendmail(msg['From'], msg['To'], msg.as_string())
    except smtplib.SMTPRecipientsRefused as recipients:
        # All recipients were refused. Nobody got the mail.
        response = 'SMTP failed to all recipients (%s)' % (msg['To'])
    except smtplib.SMTPHeloError as helo:
        # target server rejected HELO
        response = 'Server (%s) rejected HELO' % (mailhost)
    except smtplib.SMTPSenderRefused as sender:
        # server rejected sender
        response = 'Server (%s) rejected sender (%s)' % (mailhost, msg['From'])
    except smtplib.SMTPDataError as err:
        response = 'SMTPDataError %r' % (err)
    finally:
        r = smtp.quit()
        if not response and 221 != r[0]:
            response = '%s (code %d)' % (r[1], r[0])

    return response


def send_simple_message(subject, text, from_addr, to_addr, mailhost=MAILHOST):
    if isinstance(text, list):
        text = const.NEWLINE.join(text)
    if not isinstance(text, str):
        raise labexceptions.ProgrammingFlawWarning('Message text is not a string (%s)' % 
                                                   (str(type(text))))
    if not isinstance(subject, str):
        raise labexceptions.ProgrammingFlawWarning('Message subject is not a string (%s)' % 
                                                   (str(type(subject))))
    msg = MIMEText(text)
    msg['Subject'] = subject
    msg['From'] = from_addr
    msg['To'] = const.COMMA.join(to_addr) if isinstance(to_addr, list) else to_addr
    return send_message(msg, mailhost=mailhost)


[docs]class BatchMailer(): ''' A very simple mailer that sets up a standard "mail chute" to drop simple messages in so that it can be referenced in arbitrary places in a package (specifically SyQADA) without having to track the sender or recipient list *sender* the address from which the mail will come, or current user if not specified *alias* the alias to be displayed for the sender if not None *recipients* a *list* of recipient email addresses. If None, then the *sender* duplicate addresses will be purged. address is used (common usage would be to notify the user of a process when it completes. *mailhost* the SMTP host through which the mail will be routed. ''' def __init__(self, alias=None, domain=DOMAIN, sender=None, recipients=None, mailhost=MAILHOST): if sender is None: sender = misc.username() if const.AT not in sender: sender = '{}@{}'.format(sender, domain) self.sender = mailutil.formataddr((alias, sender)) self.mailhost = mailhost temp = recipients if recipients is not None else list(self.sender) self.recipients = list(set(temp)) self.domain = DOMAIN
[docs] def send_simple_mail(self, subject, text): ''' Send a pure-text message using the *sender*, *recipients*, and *mailhost* defined upon creation of the BatchMailer. *subject* The message subject *text* The message text ''' return send_simple_message(subject, text, self.sender, self.recipients, self.mailhost)