April 9th, 2010

Asynchronous send_mail() in Django by Selwin Ong

Django, our web development framework of choice provides many useful functions, send_mail() being one of them which we frequently use.

Though convenient, send_mail() in Django is a synchronous operation – meaning Django will wait for the mail sending process to finish before continuing other tasks. This is not usually that big of a deal if the mail server Django is configured to use is located in the same site (and has low latency), but in cases where the mail server is not located in the same cloud, this could give the impression that your website is slow, especially in sites with relatively slow internet connectivity (like Indonesia).

Consider a really common scenario where an application automatically sends an email verification message to a newly registered user. If configured to use a third party SMTP server like Gmail, the email sending process takes close to half a second if our app is located in Jakarta. This means that we are adding close a half a second of latency before rendering a response to the user, making our app look sluggish (in reality the user creation process itself takes less than 50ms).

So one of our engineers Gilang wrote a simple wrapper around Django’s send_mail() to perform the task asynchronously using Python’s threading.

class EmailThread(threading.Thread):
    def __init__(self, subject, body, from_email, recipient_list, fail_silently, html):
        self.subject = subject
        self.body = body
        self.recipient_list = recipient_list
        self.from_email = from_email
        self.fail_silently = fail_silently
        self.html = html
        threading.Thread.__init__(self)

    def run (self):
        msg = EmailMultiAlternatives(self.subject, self.body, self.from_email, self.recipient_list)
        if self.html:
            msg.attach_alternative(self.html, "text/html")
        msg.send(self.fail_silently)

def send_mail(subject, body, from_email, recipient_list, fail_silently=False, html=None, *args, **kwargs):
    EmailThread(subject, body, from_email, recipient_list, fail_silently, html).start()

To use this small wrapper simply put django_asynchronous_send_mail to your Python path and import it as send_mail():

try:
    from django_asynchronous_send_mail import send_mail
except:
    from django.core.mail import send_mail

The syntax is the same as Django’s built in send_mail() so you don’t need to change anything. It also supports HTML email:

send_mail('Subject here', 'Here is the message.', 'from@example.com', ['to@example.com'], fail_silently=False, html = '')

The full source code is available at GitHub. Obviously, this is a very simple wrapper aimed only to make Django send_mail() behave asynchronously, if you want more advanced featuers like mail queuing or scheduling you should look at other alternatives such as Django Mailer.

Comments

  1. Matt Stevens
    25 October 2010
    7:49 pm

    Fantastic! thanks Gilang. This is just what I’ve been looking for.



Leave a Comment






Copyright © 2012 User Inspired Technology Services.