Tag Archives: twisted

Manhole service in Twisted Application.

What is Manhole?

Manhole is an in-process service, that will accept UNIX domain socket connections and present the stack traces for all threads and an interactive prompt.

Using it we can access and modify objects or definition in the running application, like change or add the method in any class, change the definition of any method of class or module.

This allows us to make modifications in running an application without restarting the application, it makes work easy like debugging the application, you are able to check the values of the object while the program is running.

How to configure it?

from twisted.internet import reactor
from twisted.conch import manhole, manhole_ssh
from twisted.conch.ssh.keys import Key
from twisted.cred import portal, checkers

DATA = {"Service": "Manhole"}


def get_manhole_factory(namespace, **passwords):

    def get_manhole(arg):
        return manhole.ColoredManhole(namespace)
            
    realm = manhole_ssh.TerminalRealm()
    realm.chainedProtocolFactory.protocolFactory = get_manhole
    p = portal.Portal(realm)
    p.registerChecker(checkers.InMemoryUsernamePasswordDatabaseDontUse(**passwords))
    f = manhole_ssh.ConchFactory(p)
    f.publicKeys = {"ssh-rsa": Key.fromFile("keys/manhole.pub")}
    f.privateKeys = {"ssh-rsa": Key.fromFile("keys/manhole")}
    return f


reactor.listenTCP(2222, get_manhole_factory(globals(), admin='admin'))
reactor.run()

Once you run above snippet, the service will start on TCP port 2222.

You need to use SSH command to get login into the service.

See below how it looks like.

[lalit : ~]₹ ssh admin@localhost -p 2222
admin@localhost's password:
>>> dir() 
['DATA', '__builtins__', '__doc__', '__file__', '__name__', '__package__', 'checkers', 'get_manhole_factory', 'manhole', 'manhole_ssh', 'portal', 'reactor'] 
>>> DATA 
{'Service': 'Manhole'}
>>> DATA['Service'] = "Edited" 
>>> DATA 
{'Service': 'Edited'}
[lalit : ~]₹ ssh admin@localhost -p 2222
admin@localhost's password: 
>>> dir() 
['DATA', '__builtins__', '__doc__', '__file__', '__name__', '__package__', 'checkers', 'get_manhole_factory', 'manhole', 'manhole_ssh', 'portal', 'reactor'] 
>>> DATA 
{'Service': 'Edited'} 

Here In the first login, we change the value in DATA dictionary in running application, as we can see we get the new value in the second login.

What is milter?

Every one gets tons of email these days. This includes emails about super duper offers from amazon to princess and wealthy businessmen trying to offer their money to you from some African country that you have never heard of. In all these emails in your inbox there lies one or two valuable emails either from your friends, bank alerts, work related stuff. Spam is a problem that email service providers are battling for ages. There are a few opensource spam fighting tools available like SpamAssasin or SpamBayes.

What is milter ?

Simply put – milter is mail filtering technology. Its designed by sendmail project. Now available in other MTAs also. People historically used all kinds of solutions for filtering mails on servers using procmail or MTA specific methods. The current scene seems to be moving forward to sieve. But there is a huge difference between milter and sieve. Sieve comes in to picture when mail is already accepted by MTA and had been handed over to MDA. On the other hand milter springs into action in the mail receiving part of MTA. When a new connection is made by remote server to your MTA, your MTA will give you an opportunity to accept of reject the mail every step of the way from new connection, reception of each header, and reception of body.

milter protocol various stages

The above picture depicts simplified version of milter protocol working. Full details of milter protocol can be found here https://github.com/avar/sendmail-pmilter/blob/master/doc/milter-protocol.txt  . Not only filtering; using milter, you can also modify message or change headers.

HOW DO I GET STARTED WITH CODING MILTER PROGRAM ?

If you want to get started in C you can use libmilter.  For Python you have couple of options:

  1. pymilter –  https://pythonhosted.org/milter/
  2. txmilter – https://github.com/flaviogrossi/txmilter

Postfix supports milter protocol. You can find every thing related to postfix’s milter support in here – http://www.postfix.org/MILTER_README.html

WHY NOT SIEVE WHY MILTER ?

I found sieve to be rather limited. It doesn’t offer too many options to implement complex logic. It was purposefully made like that. Also sieve starts at the end of mail reception process after mail is already accepted by MTA.

Coding milter program in your favorite programming language gives you full power and allows you to implement complex , creative stuff.

WATCHOUT!!!

When writing milter programs take proper care to return a reply to MTA quickly. Don’t do long running tasks in milter program when the MTA is waiting for reply. This will have crazy side effects like remote parties submitting same mail multiple time filling up your inbox.

InlineCallbacks

Twisted features a decorator named inlineCallbacks which allows you to work with deferreds without writing callback functions.

This is done by writing your code as generators, which yield deferreds instead of attaching callbacks.

Consider the following function written in the traditional deferred style:

import txredisapi as redis

from twisted.internet import defer
from twisted.internet import reactor

def main():
    rc = redis.Connection()
    print rc
    rc.addCallback(onSuccess)
    rc.addErrback(onFail)


def onSuccess(result):
    print "Success : "
    print result


def onFail(result):
    print "Fail : "
    print result


if __name__ == '__main__':
    main()
    reactor.run()

using inlineCallbacks, we can write this as:

from twisted.internet.defer import inlineCallbacks

import txredisapi as redis

from twisted.internet import defer
from twisted.internet import reactor


@defer.inlineCallbacks
def main():
    rc = yield redis.Connection()
    print rc

    yield rc.set("abc", "pqr")
    v = yield rc.get("abc")
    print "value : ", repr(v)

    yield rc.disconnect()


if __name__ == "__main__":
    main()
    reactor.run()

Instead of calling addCallback on the deferred returned by redis.Connection, we yield it. this causes Twisted to return the deferred‘s result to us.

Though the inlineCallbacks looks like synchronous code, which blocks while waiting for the request to finish, each yield statement allows other code to run while waiting for the deferred being yielded to fire.

inlineCallbacks become even more powerful when dealing with complex control flow and error handling.

txRedis

txredis is a non-blocking client for the redis database, written in Python. It uses twisted for the asynchronous communication with redis.

Install

pip install txredis

Now to check if txredis is properly installed or not goto python prompt and import txredis :

5520:~/test$ python
Python 2.7.12 (default, Nov 19 2016, 06:48:10)
[GCC 5.4.0 20160609] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import txredis

Example :

Using redis-cli, add list of user’s in redis-server with hmset :

127.0.0.1:6379> hmset user abc 123
OK
127.0.0.1:6379> hmset user pqr 456
OK
127.0.0.1:6379> hmset user xyz 789
OK

hmset, set key to value within hash name for each corresponding key and value from the mapping dict.

Now, using python twisted get user’s :

rediscreator = protocol.ClientCreator(reactor, RedisClient)
df = rediscreator.connectTCP("127.0.0.1", 6379)
df.addCallback(self.onRedisConnect)
df.addErrback(self.errRedisConnect)

Above code do connection with redis-server.

Here, rediscreator.connectTCP return defer and register appropriate method in addCallback, addErrback

def errRedisConnect(self, error):
    log.info("Error redis")
    print "error"
    log.error("Failed to connect redsi")
    log.error(error)


def onRedisConnect(self, result):
    log.info("result")
    res = result.ping()
    res.addCallback(self.onSuccess)
    res.addErrback(self.onFailer)
    self.rediscon = result

Here, define callback function onRedisConnect and errRedisConnect.

self.rdsdf = self.rdscon.hget("user", "abc")
self.rdsdf.addCallback(self.onSuccess)
self.rdsdf.addErrback(self.onFailur)

Here, hget return the value of key within the hase name and return defer

def onSuccess(self, result):
   log.info("success")
   log.debug(result)

def onFailur(self, result):
   log.info("fail")

Here, define callback function onSuccess and onFailur and log the appropriate result.

1) "123"

It give output 123, it is correspond to user abc

How Implement Multiservice in Twisted.

Multiservice module is service collection provided by twisted, which is useful for creating a new service and combines with two or more existing services.

The major tools that manages Twisted application is a command line utility called twistd. twistd is a cross-platform, and is the recommended tool for running twisted applications.

The core component of the Twisted Application infrastructure is the

twisted.application.service.Application

object. which represents your application. Application acts as a container of any “Services” that your application provides. This will be done through Services.

Services manages application that can be started and stopped. In Application object can contain many services, or can even hierarchies of Services using “Multiservice” or your own custom IServiceCollection implementations.

Multiservice Implementaion:

To use multiserivce, which implements IService. For this, import internet and service module.

from twisted.application import internet, service

 

Example :

from twisted.application import internet,service
from serviceone import getServiceOne
from servicesecond import getServiceSecond
from twisted.web.server import Site

agentService = service.MultiService()
agentService.addService(getServiceOne())
agentService.addService(getServiceSecond())
application = service.Application("Receive Request")
agentService.setServiceParent(application)

To run, Save above code in a file as serviceexample.tac . Here, “tac ” file is regular python file. Twisted application infrastructure, protocol implementations live in a module, services, using those protocols are registered in a Twisted Application Configuration(TAC) file and the reactor and configuration are managed by an external utility.

Here, I use multiservice functionality from service. agentservice create object of multiservice. Then add services using add service method. In service, you can add web servers, FTP servers and SSH clients. After this, set application name and pass application to serviceparent method.

now, add service on port 8082 as :

from twisted.web.server import Site, NOT_DONE_YET
from twisted.web.resource import Resource
import logging
from twisted.application import internet

log = logging.getLogger("State Home")

class StateHome(Resource):
    isLeaf = True


    def reqComplete(self, req):
        req.write("Successfully Called.")
        req.finish()

    def render_GET(self, request):
        log.info("Run Render Get Method.")
        self.reqComplete(request)
        return NOT_DONE_YET

def getServiceOne():
    root = StateHome()
    factory = Site(root)
    return internet.TCPServer(8082, factory)

add another service same as above on port 8083 as:

from twisted.web.server import Site, NOT_DONE_YET
from twisted.web.resource import Resource
import logging
from twisted.application import internet

log = logging.getLogger("State Home")

class StateHome(Resource):
    isLeaf = True


    def reqComplete(self, req):
        req.write("Successfully Called second service.")
        req.finish()

    def render_GET(self, request):
        log.info("Run Render Get Method.")
        self.reqComplete(request)
        return NOT_DONE_YET

def getServiceSecond():
    root = StateHome()
    factory = Site(root)
    return internet.TCPServer(8083, factory)

To run serviceexample.tac file using twistd program, use command twistd -y serviceexample.tac -n. After this, open browser and enter url localhost:8082 and localhost:8083. You can see result on web page and both TCP servers are active.

Asynchronous DB Operations in Twisted

Twisted is an asynchronous networking framework. Other Database API Implementations have blocking interfaces.

For this reason, twisted.enterprise.adbapi was created. It is a non-blocking interface,which allows you to access a number of different RDBMSes.

General Method to access DB API.

1 ) Create a Connection with db.

 db = dbmodule.connect('dbname','user','password')

2) create a cursor.

 cursor = db.cursor()

3) do a query.

resultset = cursor.query('SELECT * FROM table WHERE condition=expression')

Cursor blocks to response in asynchronous framework. Those delays are unacceptable when using an asynchronous framework such as Twisted.
To Overcome blocking interface, twisted provides asynchronous wrapper for db module such as twisted.enterprise.adbapi

Database Connection using adbapi API.

To use adbapi, we import dependencies as below

 from twisted.enterprise import adbapi

1) Connect Database using adbapi.ConnectionPool

db = adbapi.ConnectionPool("MySQLdb",db="agentdata",user="root",passwd="<yourpassword>")

Here, We do not need to import dbmodule directly.
dbmodule.connect are passed as extra arguments to adbapi.ConnectionPool’s Constructor.

2) Run Database Query

query = ("SELECT * FROM agentdetails WHERE name = '%s'" % (name))
return dbpool.runQuery(query).addCallback(self.receiveResult).addErrback(self.errorquery)

Here, I used ‘%s’ paramstyle for mysql. if you use another database module, you need to use compatible paramstyle. for more, use DB-API specification.

Twisted doesn’t attempt to offer any sort of magic parameter munging – runQuery(query,params,…) maps directly onto cursor.execute(query,params,…).

This query returns Deferred, which allows arbitrary callbacks to be called upon completion (or failure).

Demo : Select, Insert and Update query in Database.

from twisted.enterprise import adbapi
import datetime,logging
from twisted.internet import reactor


"""
Test DB : This File do database connection and basic operation.
"""

log = logging.getLogger("Test DB")

dbpool = adbapi.ConnectionPool("MySQLdb",db="agentdata",user="root",passwd="<yourpassword>")

class AgentDB():

    def getTime(self):
        log.info("Get Current Time from System.")
        time = str(datetime.datetime.now()).split('.')[0]
        return time

    def doSelect(self,name):
        log.info("Select operation in Database.")
        query = ("SELECT * FROM agentdetails WHERE name = '%s'" % (name))
        return dbpool.runQuery(query).addCallback(self.receiveResult).addErrback(self.errorquery)

    def receiveResult(self,result):
        print "Receive Result"
        print result
        # general purpose method to receive result from defer.
        return result

    def errorquery(self,result):
        print "error received", result
        return result

    def doInsert(self,name,status,state):
        log.info("Insert operation in Database.")
        query = """INSERT INTO agentdetails (name,state,status,logtime) values('%s','%s','%s','%s')""" % (
        name,state,status,self.getTime())
        return dbpool.runQuery(query).addCallback(self.receiveResult)

    def doUpdate(self,name,status,state):
        log.info("Update operation in Database.")
        query = ("UPDATE agentdetails SET status = '%s', state = '%s',logtime = '%s' WHERE name = '%s'" % (
        status,state, self.getTime(),name))
        return dbpool.runQuery(query).addCallback(self.receiveResult)

    def checkDB(self):
        self.doSelect('1011')
        self.doInsert('Test','Test','Test')
        self.doUpdate('Test','SecondTest','SecondTest')



a= AgentDB()
a.checkDB()
reactor.run()

Here, I have used MySQLdb api, agentdata as a database name, root as a user, 123456 as a password.
Also, I have created select, insert and update query for select, insert and update operation respectively.
runQuery method returns deferred. For this, add callback and error back to handle success and failure respectively.

How to write port-forwarding program using Twisted

Recently I was faced with an issue where a long running process is listening on loop back IP (127.0.0.1) on port 8080 on one of our servers and client programs on other machines are trying to access it on server’s local IP 10.91.20.66.  We ended up at this situation when we have updated server configuration and restarted the server program and forgot to change IP binding info in config file from loop back to local IP. Server got busy with it’s work, with lots of customer’s connections already, by the time we have discovered that some services of  server are not accessible to client programs on other machines. So, the dummy’s guide to fixing it by changing config and restarting the server program is not an option as we can’t risk to disconnect existing customers. So, hot patching is the only option until we can restart the program at next scheduled down time.

I could have fixed this in couple of ways either by adding few lines to iptables configuration or by writing simple socket program in python. The task is to forward data coming in on local IP port 8080 to loop back IP (127.0.0.1) port 8080 and send replies back to source address. Forwarding one socket data to other socket is pretty trivial using Python’s socket library and Twisted made it even more trivial, so I went with the following solution using Twisted.

__author__ = 'godson'

from twisted.protocols.portforward import ProxyFactory
from twisted.application import internet,service

src_ip = "10.91.20.66"
src_port = 8080
dst_ip = "127.0.0.1"
dst_port = 8080

application = service.Application("Proxy")
server = ProxyFactory(dst_ip, dst_port)
ps = internet.TCPServer(src_port,server,50,src_ip)

ps.setServiceParent(application)

That’s it. Now, all I needed to do is to run this program by the following command

twistd -y portforwarder.py

This simple program is made possible by the heavy lifting done by twisted library. Interested folks can look under hood at twisted’s portforward.py module.

Sending emails asynchronously using Twisted – Part 2

In Part 1 of article, we saw how to send blocking emails using ‘smtplib’ module & non-blocking emails using Twisted framework. In this part, we will see how to send asynchronous emails to multiple recipients using Twisted

  • Sending multiple emails

    Refer following script.This script sends emails to given recipients asynchronously. Here we have used twisted.internet.defer.DeferredList API. This API is very useful in some scenarios. Suppose you have to finish multiple task asynchronously and then you have to finish one final task. For examples, your program is connected to 4 different clients & and before shutting it down, you have to make sure that all connections are closed properly. In such cases, DeferredList API is used. Create deferrands of each task & make their list. Pass this list to ‘DeferredList‘ API which will return you another deferrand. This final deferrand will be fired when all deferrands in list will be fired.

        
    
            #!/usr/bin/env python2.7
            __author__ = 'Rohit Chormale'
    
            """
            In this tutorial, same email will be sent to multiple recipients using `twisted.internet.defer.DeferredList` API.
            For each recipient, one defer will be created and will be added in `DeferredList`.
            `DeferredList` API will be fired only when all deferrands in `DeferredList` will be fired.
            """
    
    
            from StringIO import StringIO
            from email.mime.text import MIMEText
            from email.mime.multipart import MIMEMultipart
            from email.utils import formatdate
            from email.header import Header
    
            from twisted.mail.smtp import ESMTPSenderFactory
            from twisted.internet import reactor, defer
            from twisted.internet.ssl import ClientContextFactory
    
    
            # enter email content
            CONTENT = """
            """
            # enter email subject
            SUBJECT = ""
            # enter sender email
            FROM_EMAIL = ""
            # list of multiple recipients
            RECIPIENTS = [['test1@example.com', 'test2@example.com'],
                          ['test3@example.com',],
                          ['test4@example.com', 'test4@example.com'],
                        ]
    
    
            # enter username of your email account
            USERNAME = "your-email"
            # enter password of your email account
            PASSWORD = "your-password"
            # enter smtp host of your email provider
            SMTP_HOST = "smtp-host"
            # enter smtp port of your email provider
            SMTP_PORT = 587
    
    
            def success(result, recipients):
                print 'Email sent successfully | recipients - %s' % recipients
                print result
    
    
            def failure(error, recipients):
                print 'Failed to send email | recipients - %s' % recipients
                print error
    
    
            def send_email(to_emails):
                mime_text = MIMEText(CONTENT, 'html', 'utf-8')
                mime_msg = MIMEMultipart('alternative')
                mime_msg['Subject'] = "%s" % Header(SUBJECT, 'utf-8')
                mime_msg['To'] = ",".join(to_emails)
                mime_msg['From'] = FROM_EMAIL
                mime_msg['Date'] = formatdate(localtime=True)
                mime_msg.attach(mime_text)
                mime_msg = StringIO(mime_msg.as_string())
                df = defer.Deferred()
                f = ESMTPSenderFactory(USERNAME, PASSWORD, FROM_EMAIL, to_emails, mime_msg,
                                       df, retries=2, contextFactory=ClientContextFactory(), requireTransportSecurity=True)
                reactor.connectTCP(SMTP_HOST, SMTP_PORT, f)
                return df
    
    
            def final_success(result):
                print 'All emails processed successfully'
                print result
                reactor.stop()
    
    
            def final_failure(error):
                print 'Failed to process all emails'
                print error
                reactor.stop()
    
    
            def send_multiple_emails(recipients):
                df_list = []
                for r in recipients:
                    df = send_email(r)
                    df.addCallback(success, recipients=r)
                    df.addErrback(failure, recipients=r)
                    df_list.append(df)
                final_df = defer.DeferredList(df_list, consumeErrors=0)
                return final_df
    
    
            def main():
                df = send_multiple_emails(RECIPIENTS)
                df.addCallback(final_success)
                df.addErrback(final_failure)
    
    
            reactor.callLater(1, main)
    
    
            if __name__ == '__main__':
                reactor.run()
    
    

     

  • Sending multiple emails using coiterator

    Though above script runs fine, there is one problem. Here, recipients number is very small. But suppose you have to send emails to millions recipients then will this code work ?. Refer function ‘send_multiple_emails’.

        
            def send_multiple_emails(recipients):
                df_list = []
                for r in recipients:
                    df = send_email(r)
                    df.addCallback(success, recipients=r)
                    df.addErrback(failure, recipients=r)
                    df_list.append(df)
                final_df = defer.DeferredList(df_list, consumeErrors=0)
                return final_df
    

    Here we have used ‘for’ loop which is blocking. So until this ‘for’ loop is iterated, program will not move to next line of code. For 3 recipients iteration will not take much time however for millions of recipients, it will not work.
    So lets modify our code to work like generators.

        
    #!/usr/bin/env python2.7
    __author__ = 'Rohit Chormale'
    
    
    """
    In this tutorial, same email will be sent to multiple recipients using `twisted.internet.task` API.
    Each email recipient will be yielded using `twisted.internet.task.coiterate` API.
    """
    
    
    from StringIO import StringIO
    from email.mime.text import MIMEText
    from email.mime.multipart import MIMEMultipart
    from email.utils import formatdate
    from email.header import Header
    
    from twisted.mail.smtp import ESMTPSenderFactory
    from twisted.internet import reactor, defer, task
    from twisted.internet.ssl import ClientContextFactory
    
    
    # enter email content
    CONTENT = """
    """
    # enter email subject
    SUBJECT = ""
    # enter sender email
    FROM_EMAIL = ""
    # list of multiple recipients
    RECIPIENTS = [['test1@example.com', 'test2@example.com'],
                  ['test3@example.com',],
                  ['test4@example.com', 'test4@example.com'],
                ]
    
    
    # enter username of your email account
    USERNAME = "your-email"
    # enter password of your email account
    PASSWORD = "your-password"
    # enter smtp host of your email provider
    SMTP_HOST = "your-mail-provider=host"
    # enter smtp port of your email provider
    SMTP_PORT = 587
    
    
    def success(result, recipients):
        print 'Email sent successfully | recipients - %s' % recipients
        print result
    
    
    def failure(error, recipients):
        print 'Failed to send email | recipients - %s' % recipients
        print error
    
    
    def send_email(to_emails):
        mime_text = MIMEText(CONTENT, 'html', 'utf-8')
        mime_msg = MIMEMultipart('alternative')
        mime_msg['Subject'] = "%s" % Header(SUBJECT, 'utf-8')
        mime_msg['To'] = ",".join(to_emails)
        mime_msg['From'] = FROM_EMAIL
        mime_msg['Date'] = formatdate(localtime=True)
        mime_msg.attach(mime_text)
        mime_msg = StringIO(mime_msg.as_string())
        df = defer.Deferred()
        f = ESMTPSenderFactory(USERNAME, PASSWORD, FROM_EMAIL, to_emails, mime_msg,
                               df, retries=2, contextFactory=ClientContextFactory(), requireTransportSecurity=True)
        reactor.connectTCP(SMTP_HOST, SMTP_PORT, f)
        return df
    
    
    def final_success(result):
        print 'All emails processed successfully'
        print result
        reactor.stop()
    
    
    def final_failure(error):
        print 'Failed to process all emails'
        print error
        reactor.stop()
    
    
    def yield_recipients(recipients):
        for r in recipients:
            df = send_email(r)
            df.addCallback(success, recipients=r)
            df.addErrback(failure, recipients=r)
            yield df
    
    
    def send_multiple_emails(recipients):
        final_df = task.coiterate(yield_recipients(recipients))
        return final_df
    
    
    def main():
        df = send_multiple_emails(RECIPIENTS)
        df.addCallback(final_success)
        df.addErrback(final_failure)
    
    
    reactor.callLater(1, main)
    
    
    if __name__ == '__main__':
        reactor.run()
    

    Here, we have used twisted.internet.task.coiterate API. This API iterates over iterator by dividing reactor runtime between all iterators. Thus we can send millions of emails asynchronously.

Sending emails asynchronously using Twisted – Part 1

  • Using ‘smtplib‘ module

It is very easy to send emails using ‘smtplib‘ module of python. Check following recipe.

#!/usr/bin/env python2.7
__author__ = 'Rohit Chormale'


from smtplib import SMTP


# enter email content
CONTENT = """
"""
# enter email subject
SUBJECT = """
"""
# enter recipients
TO_EMAILS = ["", ""]
# enter sender's emails
FROM_EMAIL = ""
# enter username of your email account
USERNAME = ""
# enter password of your email account
PASSWORD = ""
# enter smtp host of your email provider
SMTP_HOST = ""
# enter smtp host of your email provider
SMTP_PORT = 0


mailer = SMTP(SMTP_HOST, SMTP_PORT)
mailer.starttls()
mailer.login(USERNAME, PASSWORD)
mailer.sendmail(FROM_EMAIL, TO_EMAILS, CONTENT)
mailer.quit()

But ‘smtplib’ module sends emails synchronously. So code execution is blocked until email is sent. To overcome this, lets try to send email asynchornously.

  • Using Twisted

For this tutorial we are going to use Twisted framework. Twisted is event-driven networking engine. It uses reactor-pattern. Twisted uses deferred objects to address waiting IOs. Deferred is more like subset of promises. Check following recipe to send asynchronously MIME message using Twisted.

#!/usr/bin/env python2.7
__author__ = 'Rohit Chormale'


from StringIO import StringIO
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from email.utils import formatdate
from email.header import Header

from twisted.mail.smtp import ESMTPSenderFactory
from twisted.internet import reactor, defer
from twisted.internet.ssl import ClientContextFactory

# enter email content
CONTENT = """
"""
# enter email subject
SUBJECT = ""
# enter recipients
TO_EMAILS = ["", ""]
# enter sender email
FROM_EMAIL = ""
# enter username of your email account
USERNAME = ""
# enter password of your email account
PASSWORD = ""
# enter smtp host of your email provider
SMTP_HOST = ""
# enter smtp port of your email provider
SMTP_PORT = 0


def success(result):
    print 'Email sent successfully'
    print result
    reactor.stop()


def failure(error):
    print 'Failed to send email'
    print error
    reactor.stop()


def send_email():
    mime_text = MIMEText(CONTENT, 'html', 'utf-8')
    mime_msg = MIMEMultipart('alternative')
    mime_msg['Subject'] = "%s" % Header(SUBJECT, 'utf-8')
    mime_msg['To'] = ",".join(TO_EMAILS)
    mime_msg['From'] = FROM_EMAIL
    mime_msg['Date'] = formatdate(localtime=True)
    mime_msg.attach(mime_text)
    mime_msg = StringIO(mime_msg.as_string())
    df = defer.Deferred()
    f = ESMTPSenderFactory(USERNAME, PASSWORD, FROM_EMAIL, TO_EMAILS, mime_msg,
                           df, retries=2, contextFactory=ClientContextFactory(), requireTransportSecurity=True)
    reactor.connectTCP(SMTP_HOST, SMTP_PORT, f)
    return df


def main():
    df = send_email()
    df.addCallback(success)
    df.addErrback(failure)


reactor.callLater(1, main)


if __name__ == '__main__':
    reactor.run()