Mail utility class for use with Flask from application import app, mail
from threading import Thread
from flask.ext.mail import Message

__all__ = ['send_email', 'send_bulk_email', 'create_email']


def send_async_email(app, msg):
    with app.app_context():
        mail.send(msg)


def send_bulk_async_email(app, emails):
    with app.app_context():
        with mail.connect() as conn:
            for email in emails:
                conn.send(email)


def create_email(subject, recipients, text_body, html_body, sender=('My Company', 'info@mycompany.com.au')):
    email = Message(subject, sender=sender, recipients=recipients)
    email.body = text_body
    email.html = html_body

    return email


def send_email(email):
    thr = Thread(target=send_async_email, args=[app, email])
    thr.start()


def send_bulk_email(emails):
    thr = Thread(target=send_bulk_async_email, args=[app, emails])
    thr.start()


The first two functions are used internally as the threaded function calls. The create_email function is used to generate Message objects, which are then passed back to the send_email or send_bulk_email function dependent on whether there is a singular or multiple emails, respectively.

I currently think merging the singular and multiple send functions would be overkill, and remove some of the readability, however I am also open to feedback on that opinion.
I'd unify both functions and only have the bulk/multiple-emails version
because the single case is basically a specialisation of the general
case and the slight bit more work is unlikely to be noticeable (unless I
missed some other technical reason why this isn't feasible).

Btw. the with can be nested on a single line, i.e.:

def send_bulk_async_email(app, emails):
    with app.app_context(), mail.connect() as conn:
        for email in emails:
            conn.send(email)


The default value for the sender on create_email sounds like it
doesn't belong there and should rather be passed in like the rest,
perhaps store it in a configuration file anyway.

For spawning the threads also consider making it a bit more flexible by
allowing an optional parameter for the thread creation, e.g. to use a
thread pool instead of spawning an unlimited number of threads like done
here.