SMTP Client for Tornado (original) (raw)

from tornado import ioloop

from tornado import iostream

import socket

class Envelope(object):

def __init__(self, sender, rcpt, body, callback):

self.sender = sender

self.rcpt = rcpt[:]

self.body = body

self.callback = callback

class SMTPClient(object):

CLOSED = -2

CONNECTED = -1

IDLE = 0

EHLO = 1

MAIL = 2

RCPT = 3

DATA = 4

DATA_DONE = 5

QUIT = 6

def __init__(self, host='localhost', port=25):

self.host = host

self.port = port

self.msgs = []

self.stream = None

self.state = self.CLOSED

def send_message(self, msg, callback=None):

"""Message is a django style EmailMessage object"""

if not msg:

return

self.msgs.append(Envelope(msg.from_email, msg.recipients(), msg.message().as_string(), callback))

self.begin()

def send(self, sender=None, rcpt=[], body="", callback=None):

"""Very simple sender, just take the necessary parameters to create an envelope"""

self.msgs.append(Envelope(sender, rcpt, body, callback))

self.begin()

def begin(self):

"""Start the sending of a message, if we need a connection open it"""

if not self.stream:

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0)

self.stream = iostream.IOStream(s)

self.stream.connect((self.host, self.port), self.connected)

else:

self.work_or_quit(self.process)

def work_or_quit(self, callback=None):

"""

callback is provided, for the startup case where we're not in the main processing loop

"""

if self.state == self.IDLE:

if self.msgs:

self.state = self.MAIL

self.stream.write('MAIL FROM: <%s>\r\n' % self.msgs[0].sender)

else:

self.state = self.QUIT

self.stream.write('QUIT\r\n')

if callback:

self.stream.read_until('\r\n', callback)

def connected(self):

"""Socket connect callback"""

self.state = self.CONNECTED

self.stream.read_until('\r\n', self.process)

def process(self, data):

# print self.state, data,

code = int(data[0:3])

if data[3] not in (' ', '\r', '\n'):

self.stream.read_until('\r\n', self.process)

return

if self.state == self.CONNECTED:

if not 200 <= code < 300:

return self.error("Unexpected status %d from CONNECT: %s" % (code, data.strip()))

self.state = self.EHLO

self.stream.write('EHLO localhost\r\n')

elif self.state == self.EHLO:

if not 200 <= code < 300:

return self.error("Unexpected status %d from EHLO: %s" % (code, data.strip()))

self.state = self.IDLE

self.work_or_quit()

elif self.state == self.MAIL:

if not 200 <= code < 300:

return self.error("Unexpected status %d from MAIL: %s" % (code, data.strip()))

if self.msgs[0].rcpt:

self.stream.write('RCPT TO: <%s>\r\n' % self.msgs[0].rcpt.pop(0))

self.state = self.RCPT

elif self.state == self.RCPT:

if not 200 <= code < 300:

return self.error("Unexpected status %d from RCPT: %s" % (code, data.strip()))

if self.msgs[0].rcpt:

self.stream.write('RCPT TO: <%s>\r\n' % self.msgs[0].rcpt.pop(0))

else:

self.stream.write('DATA\r\n')

self.state = self.DATA

elif self.state == self.DATA:

if code not in (354,) :

return self.error("Unexpected status %d from DATA: %s" % (code, data.strip()))

self.stream.write(self.msgs[0].body)

if self.msgs[0].body[-2:] != '\r\n':

self.stream.write('\r\n')

self.stream.write('.\r\n')

self.state = self.DATA_DONE

elif self.state == self.DATA_DONE:

if not 200 <= code < 300:

return self.error("Unexpected status %d from DATA END: %s" % (code, data.strip()))

if self.msgs[0].callback:

self.msgs[0].callback(True)

self.msgs.pop(0)

self.state = self.IDLE

self.work_or_quit()

elif self.state == self.QUIT:

if not 200 <= code < 300:

return self.error("Unexpected status %d from QUIT: %s" % (code, data.strip()))

self.close()

if self.stream:

self.stream.read_until('\r\n', self.process)

def error(self, msg):

self.close()

def close(self):

for msg in self.msgs:

if msg.callback:

msg.callback(False)

self.stream.close()

self.stream = None

self.state = self.CLOSED

if __name__ == '__main__':

client = SMTPClient('localhost', 25)

body = """Subject: Testing

Just a test

"""

client.send('foo@example.com', ['recipient@example.com'], body)

ioloop.IOLoop.instance().start()