1 <?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
3 <title>Twisted Documentation: Twisted Mail Tutorial: Building an SMTP Client from Scratch</title>
4 <link href="../../howto/stylesheet.css" rel="stylesheet" type="text/css"/>
8 <h1 class="title">Twisted Mail Tutorial: Building an SMTP Client from Scratch</h1>
9 <div class="toc"><ol><li><a href="#auto0">Introduction</a></li><ul><li><a href="#auto1">SMTP Client 1</a></li><li><a href="#auto2">SMTP Client 2</a></li><li><a href="#auto3">SMTP Client 3</a></li><li><a href="#auto4">SMTP Client 4</a></li><li><a href="#auto5">SMTP Client 5</a></li><li><a href="#auto6">SMTP Client 6</a></li><li><a href="#auto7">SMTP Client 7</a></li><li><a href="#auto8">SMTP Client 8</a></li><li><a href="#auto9">SMTP Client 9</a></li><li><a href="#auto10">SMTP Client 10</a></li><li><a href="#auto11">SMTP Client 11</a></li></ul></ol></div>
14 <h2>Introduction<a name="auto0"/></h2>
16 <p>This tutorial will walk you through the creation of an extremely
17 simple SMTP client application. By the time the tutorial is complete,
18 you will understand how to create and start a TCP client speaking the
19 SMTP protocol, have it connect to an appropriate mail exchange server,
20 and transmit a message for delivery.</p>
22 <p>For the majority of this tutorial, <code>twistd</code> will be used
23 to launch the application. Near the end we will explore other
24 possibilities for starting a Twisted application. Until then, make
25 sure that you have <code>twistd</code> installed and conveniently
26 accessible for use in running each of the example <code>.tac</code>
29 <h3>SMTP Client 1<a name="auto1"/></h3>
31 <p>The first step is to create <a href="smtpclient-1.tac" shape="rect">the most
32 minimal <code>.tac</code> file</a> possible for use by <code>twistd</code> .</p>
34 <pre class="python"><p class="py-linenumber">1
35 </p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">application</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">service</span>
38 <p>The first line of the <code>.tac</code> file
39 imports <code>twisted.application.service</code>, a module which
40 contains many of the basic <em>service</em> classes and helper
41 functions available in Twisted. In particular, we will be using
42 the <code>Application</code> function to create a new <em>application
43 service</em>. An <em>application service</em> simply acts as a
44 central object on which to store certain kinds of deployment
47 <pre class="python"><p class="py-linenumber">1
48 </p><span class="py-src-variable">application</span> = <span class="py-src-variable">service</span>.<span class="py-src-variable">Application</span>(<span class="py-src-string">"SMTP Client Tutorial"</span>)
51 <p>The second line of the <code>.tac</code> file creates a
52 new <em>application service</em> and binds it to the local
53 name <code>application</code>. <code>twistd</code> requires this
54 local name in each <code>.tac</code> file it runs. It uses various
55 pieces of configuration on the object to determine its behavior. For
56 example, <code>"SMTP Client Tutorial"</code> will be used as the name
57 of the <code>.tap</code> file into which to serialize application
58 state, should it be necessary to do so.</p>
60 <p>That does it for the first example. We now have enough of
61 a <code>.tac</code> file to pass to <code>twistd</code>. If we
62 run <a href="smtpclient-1.tac" shape="rect">smtpclient-1.tac</a> using
63 the <code>twistd</code> command line:</p>
65 <pre class="python"><p class="py-linenumber">1
66 </p><span class="py-src-variable">twistd</span> -<span class="py-src-variable">ny</span> <span class="py-src-variable">smtpclient</span>-<span class="py-src-number">1.</span><span class="py-src-variable">tac</span>
69 <p>we are rewarded with the following output:</p>
71 <pre class="shell" xml:space="preserve">
72 exarkun@boson:~/mail/tutorial/smtpclient$ twistd -ny smtpclient-1.tac
73 18:31 EST [-] Log opened.
74 18:31 EST [-] twistd 2.0.0 (/usr/bin/python2.4 2.4.1) starting up
75 18:31 EST [-] reactor class: twisted.internet.selectreactor.SelectReactor
76 18:31 EST [-] Loading smtpclient-1.tac...
80 <p>As we expected, not much is going on. We can shutdown this server
81 by issuing <code>^C</code>:</p>
83 <pre class="shell" xml:space="preserve">
84 18:34 EST [-] Received SIGINT, shutting down.
85 18:34 EST [-] Main loop terminated.
86 18:34 EST [-] Server Shut Down.
87 exarkun@boson:~/mail/tutorial/smtpclient$
90 <h3>SMTP Client 2<a name="auto2"/></h3>
92 <p>The first version of our SMTP client wasn't very interesting. It
93 didn't even establish any TCP connections! The <a href="smtpclient-2.tac" shape="rect">second version</a> will come a little bit
94 closer to that level of complexity. First, we need to import a few
97 <pre class="python"><p class="py-linenumber">1
99 </p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">application</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">internet</span>
100 <span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">protocol</span>
103 <p><code>twisted.application.internet</code> is
104 another <em>application service</em> module. It provides services for
105 establishing outgoing connections (as well as creating network
106 servers, though we are not interested in those parts for the
107 moment). <code>twisted.internet.protocol</code> provides base
108 implementations of many of the core Twisted concepts, such
109 as <em>factories</em> and <em>protocols</em>.</p>
111 <p>The next line of <a href="smtpclient-2.tac" shape="rect">smtpclient-2.tac</a>
112 instantiates a new <em>client factory</em>.</p>
114 <pre class="python"><p class="py-linenumber">1
115 </p><span class="py-src-variable">smtpClientFactory</span> = <span class="py-src-variable">protocol</span>.<span class="py-src-variable">ClientFactory</span>()
118 <p><em>Client factories</em> are responsible for
119 constructing <em>protocol instances</em> whenever connections are
120 established. They may be required to create just one instance, or
121 many instances if many different connections are established, or they
122 may never be required to create one at all, if no connection ever
123 manages to be established.</p>
125 <p>Now that we have a client factory, we'll need to hook it up to the
126 network somehow. The next line of <code>smtpclient-2.tac</code> does
129 <pre class="python"><p class="py-linenumber">1
130 </p><span class="py-src-variable">smtpClientService</span> = <span class="py-src-variable">internet</span>.<span class="py-src-variable">TCPClient</span>(<span class="py-src-variable">None</span>, <span class="py-src-variable">None</span>, <span class="py-src-variable">smtpClientFactory</span>)
133 <p>We'll ignore the first two arguments
134 to <code>internet.TCPClient</code> for the moment and instead focus on
135 the third. <code>TCPClient</code> is one of those <em>application
136 service</em> classes. It creates TCP connections to a specified
137 address and then uses its third argument, a <em>client factory</em>,
138 to get a <em>protocol instance</em>. It then associates the TCP
139 connection with the protocol instance and gets out of the way.</p>
141 <p>We can try to run <code>smtpclient-2.tac</code> the same way we
142 ran <code>smtpclient-1.tac</code>, but the results might be a little
145 <pre class="shell" xml:space="preserve">
146 exarkun@boson:~/mail/tutorial/smtpclient$ twistd -ny smtpclient-2.tac
147 18:55 EST [-] Log opened.
148 18:55 EST [-] twistd SVN-Trunk (/usr/bin/python2.4 2.4.1) starting up
149 18:55 EST [-] reactor class: twisted.internet.selectreactor.SelectReactor
150 18:55 EST [-] Loading smtpclient-2.tac...
151 18:55 EST [-] Loaded.
152 18:55 EST [-] Starting factory <twisted.internet.protocol.ClientFactory
153 instance at 0xb791e46c>
154 18:55 EST [-] Traceback (most recent call last):
155 File "twisted/scripts/twistd.py", line 187, in runApp
156 app.runReactorWithLogging(config, oldstdout, oldstderr)
157 File "twisted/application/app.py", line 128, in runReactorWithLogging
159 File "twisted/internet/posixbase.py", line 200, in run
161 File "twisted/internet/posixbase.py", line 208, in mainLoop
162 self.runUntilCurrent()
163 --- <exception caught here> ---
164 File "twisted/internet/base.py", line 533, in runUntilCurrent
165 call.func(*call.args, **call.kw)
166 File "twisted/internet/tcp.py", line 489, in resolveAddress
167 if abstract.isIPAddress(self.addr[0]):
168 File "twisted/internet/abstract.py", line 315, in isIPAddress
169 parts = string.split(addr, '.')
170 File "/usr/lib/python2.4/string.py", line 292, in split
171 return s.split(sep, maxsplit)
172 exceptions.AttributeError: 'NoneType' object has no attribute 'split'
174 18:55 EST [-] Received SIGINT, shutting down.
175 18:55 EST [-] Main loop terminated.
176 18:55 EST [-] Server Shut Down.
177 exarkun@boson:~/mail/tutorial/smtpclient$
180 <p>What happened? Those first two arguments to <code>TCPClient</code>
181 turned out to be important after all. We'll get to them in the next
184 <h3>SMTP Client 3<a name="auto3"/></h3>
186 <p>Version three of our SMTP client only changes one thing. The line
187 from version two:</p>
189 <pre class="python"><p class="py-linenumber">1
190 </p><span class="py-src-variable">smtpClientService</span> = <span class="py-src-variable">internet</span>.<span class="py-src-variable">TCPClient</span>(<span class="py-src-variable">None</span>, <span class="py-src-variable">None</span>, <span class="py-src-variable">smtpClientFactory</span>)
193 <p>has its first two arguments changed from <code>None</code> to
194 something with a bit more meaning:</p>
196 <pre class="python"><p class="py-linenumber">1
197 </p><span class="py-src-variable">smtpClientService</span> = <span class="py-src-variable">internet</span>.<span class="py-src-variable">TCPClient</span>(<span class="py-src-string">'localhost'</span>, <span class="py-src-number">25</span>, <span class="py-src-variable">smtpClientFactory</span>)
200 <p>This directs the client to connect to <em>localhost</em> on
201 port <em>25</em>. This isn't the address we want ultimately, but it's
202 a good place-holder for the time being. We can
203 run <a href="smtpclient-3.tac" shape="rect">smtpclient-3.tac</a> and see what this
206 <pre class="shell" xml:space="preserve">
207 exarkun@boson:~/mail/tutorial/smtpclient$ twistd -ny smtpclient-3.tac
208 19:10 EST [-] Log opened.
209 19:10 EST [-] twistd SVN-Trunk (/usr/bin/python2.4 2.4.1) starting up
210 19:10 EST [-] reactor class: twisted.internet.selectreactor.SelectReactor
211 19:10 EST [-] Loading smtpclient-3.tac...
212 19:10 EST [-] Loaded.
213 19:10 EST [-] Starting factory <twisted.internet.protocol.ClientFactory
214 instance at 0xb791e48c>
215 19:10 EST [-] Enabling Multithreading.
216 19:10 EST [Uninitialized] Traceback (most recent call last):
217 File "twisted/python/log.py", line 56, in callWithLogger
218 return callWithContext({"system": lp}, func, *args, **kw)
219 File "twisted/python/log.py", line 41, in callWithContext
220 return context.call({ILogContext: newCtx}, func, *args, **kw)
221 File "twisted/python/context.py", line 52, in callWithContext
222 return self.currentContext().callWithContext(ctx, func, *args, **kw)
223 File "twisted/python/context.py", line 31, in callWithContext
224 return func(*args,**kw)
225 --- <exception caught here> ---
226 File "twisted/internet/selectreactor.py", line 139, in _doReadOrWrite
227 why = getattr(selectable, method)()
228 File "twisted/internet/tcp.py", line 543, in doConnect
230 File "twisted/internet/tcp.py", line 546, in _connectDone
231 self.protocol = self.connector.buildProtocol(self.getPeer())
232 File "twisted/internet/base.py", line 641, in buildProtocol
233 return self.factory.buildProtocol(addr)
234 File "twisted/internet/protocol.py", line 99, in buildProtocol
236 exceptions.TypeError: 'NoneType' object is not callable
238 19:10 EST [Uninitialized] Stopping factory
239 <twisted.internet.protocol.ClientFactory instance at
241 19:10 EST [-] Received SIGINT, shutting down.
242 19:10 EST [-] Main loop terminated.
243 19:10 EST [-] Server Shut Down.
244 exarkun@boson:~/mail/tutorial/smtpclient$
247 <p>A meagre amount of progress, but the service still raises an
248 exception. This time, it's because we haven't specified
249 a <em>protocol class</em> for the factory to use. We'll do that in
250 the next example.</p>
252 <h3>SMTP Client 4<a name="auto4"/></h3>
254 <p>In the previous example, we ran into a problem because we hadn't
255 set up our <em>client factory's</em> <em>protocol</em> attribute
256 correctly (or at all). <code>ClientFactory.buildProtocol</code> is
257 the method responsible for creating a <em>protocol instance</em>. The
258 default implementation calls the factory's <code>protocol</code> attribute,
259 adds itself as an attribute named <code>factory</code> to the
260 resulting instance, and returns it. In <a href="smtpclient-4.tac" shape="rect">smtpclient-4.tac</a>, we'll correct the
261 oversight that caused the traceback in smtpclient-3.tac:</p>
263 <pre class="python"><p class="py-linenumber">1
264 </p><span class="py-src-variable">smtpClientFactory</span>.<span class="py-src-variable">protocol</span> = <span class="py-src-variable">protocol</span>.<span class="py-src-variable">Protocol</span>
267 <p>Running this version of the client, we can see the output is once
268 again traceback free:</p>
270 <pre class="shell" xml:space="preserve">
271 exarkun@boson:~/doc/mail/tutorial/smtpclient$ twistd -ny smtpclient-4.tac
272 19:29 EST [-] Log opened.
273 19:29 EST [-] twistd SVN-Trunk (/usr/bin/python2.4 2.4.1) starting up
274 19:29 EST [-] reactor class: twisted.internet.selectreactor.SelectReactor
275 19:29 EST [-] Loading smtpclient-4.tac...
276 19:29 EST [-] Loaded.
277 19:29 EST [-] Starting factory <twisted.internet.protocol.ClientFactory
278 instance at 0xb791e4ac>
279 19:29 EST [-] Enabling Multithreading.
280 19:29 EST [-] Received SIGINT, shutting down.
281 19:29 EST [Protocol,client] Stopping factory
282 <twisted.internet.protocol.ClientFactory instance at
284 19:29 EST [-] Main loop terminated.
285 19:29 EST [-] Server Shut Down.
286 exarkun@boson:~/doc/mail/tutorial/smtpclient$
289 <p>But what does this
290 mean? <code>twisted.internet.protocol.Protocol</code> is the
291 base <em>protocol</em> implementation. For those familiar with the
292 classic UNIX network services, it is equivalent to
293 the <em>discard</em> service. It never produces any output and it
294 discards all its input. Not terribly useful, and certainly nothing
295 like an SMTP client. Let's see how we can improve this in the next
298 <h3>SMTP Client 5<a name="auto5"/></h3>
300 <p>In <a href="smtpclient-5.tac" shape="rect">smtpclient-5.tac</a>, we will begin
301 to use Twisted's SMTP protocol implementation for the first time.
302 We'll make the obvious change, simply swapping
303 out <code>twisted.internet.protocol.Protocol</code> in favor
304 of <code>twisted.mail.smtp.ESMTPClient</code>. Don't worry about
305 the <em>E</em> in <em>ESMTP</em>. It indicates we're actually using a
306 newer version of the SMTP protocol. There is
307 an <code>SMTPClient</code> in Twisted, but there's essentially no
308 reason to ever use it.</p>
310 <p>smtpclient-5.tac adds a new import:</p>
312 <pre class="python"><p class="py-linenumber">1
313 </p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">mail</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">smtp</span>
316 <p>All of the mail related code in Twisted exists beneath
317 the <code>twisted.mail</code> package. More specifically, everything
318 having to do with the SMTP protocol implementation is defined in
319 the <code>twisted.mail.smtp</code> module.</p>
321 <p>Next we remove a line we added in smtpclient-4.tac:</p>
323 <pre class="python"><p class="py-linenumber">1
324 </p><span class="py-src-variable">smtpClientFactory</span>.<span class="py-src-variable">protocol</span> = <span class="py-src-variable">protocol</span>.<span class="py-src-variable">Protocol</span>
327 <p>And add a similar one in its place:</p>
329 <pre class="python"><p class="py-linenumber">1
330 </p><span class="py-src-variable">smtpClientFactory</span>.<span class="py-src-variable">protocol</span> = <span class="py-src-variable">smtp</span>.<span class="py-src-variable">ESMTPClient</span>
333 <p>Our client factory is now using a protocol implementation which
334 behaves as an SMTP client. What happens when we try to run this
337 <pre class="shell" xml:space="preserve">
338 exarkun@boson:~/doc/mail/tutorial/smtpclient$ twistd -ny smtpclient-5.tac
339 19:42 EST [-] Log opened.
340 19:42 EST [-] twistd SVN-Trunk (/usr/bin/python2.4 2.4.1) starting up
341 19:42 EST [-] reactor class: twisted.internet.selectreactor.SelectReactor
342 19:42 EST [-] Loading smtpclient-5.tac...
343 19:42 EST [-] Loaded.
344 19:42 EST [-] Starting factory <twisted.internet.protocol.ClientFactory
345 instance at 0xb791e54c>
346 19:42 EST [-] Enabling Multithreading.
347 19:42 EST [Uninitialized] Traceback (most recent call last):
348 File "twisted/python/log.py", line 56, in callWithLogger
349 return callWithContext({"system": lp}, func, *args, **kw)
350 File "twisted/python/log.py", line 41, in callWithContext
351 return context.call({ILogContext: newCtx}, func, *args, **kw)
352 File "twisted/python/context.py", line 52, in callWithContext
353 return self.currentContext().callWithContext(ctx, func, *args, **kw)
354 File "twisted/python/context.py", line 31, in callWithContext
355 return func(*args,**kw)
356 --- <exception caught here> ---
357 File "twisted/internet/selectreactor.py", line 139, in _doReadOrWrite
358 why = getattr(selectable, method)()
359 File "twisted/internet/tcp.py", line 543, in doConnect
361 File "twisted/internet/tcp.py", line 546, in _connectDone
362 self.protocol = self.connector.buildProtocol(self.getPeer())
363 File "twisted/internet/base.py", line 641, in buildProtocol
364 return self.factory.buildProtocol(addr)
365 File "twisted/internet/protocol.py", line 99, in buildProtocol
367 exceptions.TypeError: __init__() takes at least 2 arguments (1 given)
369 19:42 EST [Uninitialized] Stopping factory
370 <twisted.internet.protocol.ClientFactory instance at
372 19:43 EST [-] Received SIGINT, shutting down.
373 19:43 EST [-] Main loop terminated.
374 19:43 EST [-] Server Shut Down.
375 exarkun@boson:~/doc/mail/tutorial/smtpclient$
379 <p>Oops, back to getting a traceback. This time, the default
380 implementation of <code>buildProtocol</code> seems no longer to be
381 sufficient. It instantiates the protocol with no arguments,
382 but <code>ESMTPClient</code> wants at least one argument. In the next
383 version of the client, we'll override <code>buildProtocol</code> to
384 fix this problem.</p>
386 <h3>SMTP Client 6<a name="auto6"/></h3>
388 <p><a href="smtpclient-6.tac" shape="rect">smtpclient-6.tac</a> introduces
389 a <code>twisted.internet.protocol.ClientFactory</code> subclass with
390 an overridden <code>buildProtocol</code> method to overcome the
391 problem encountered in the previous example.</p>
393 <pre class="python"><p class="py-linenumber">1
398 </p><span class="py-src-keyword">class</span> <span class="py-src-identifier">SMTPClientFactory</span>(<span class="py-src-parameter">protocol</span>.<span class="py-src-parameter">ClientFactory</span>):
399 <span class="py-src-variable">protocol</span> = <span class="py-src-variable">smtp</span>.<span class="py-src-variable">ESMTPClient</span>
401 <span class="py-src-keyword">def</span> <span class="py-src-identifier">buildProtocol</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">addr</span>):
402 <span class="py-src-keyword">return</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">protocol</span>(<span class="py-src-variable">secret</span>=<span class="py-src-variable">None</span>, <span class="py-src-variable">identity</span>=<span class="py-src-string">'example.com'</span>)
405 <p>The overridden method does almost the same thing as the base
406 implementation: the only change is that it passes values for two
407 arguments to <code>twisted.mail.smtp.ESMTPClient</code>'s initializer.
408 The <code>secret</code> argument is used for SMTP authentication
409 (which we will not attempt yet). The <code>identity</code> argument
410 is used as a to identify ourselves Another minor change to note is
411 that the <code>protocol</code> attribute is now defined in the class
412 definition, rather than tacked onto an instance after one is created.
413 This means it is a class attribute, rather than an instance attribute,
414 now, which makes no difference as far as this example is concerned.
415 There are circumstances in which the difference is important: be sure
416 you understand the implications of each approach when creating your
419 <p>One other change is required: instead of
420 instantiating <code>twisted.internet.protocol.ClientFactory</code>, we
421 will now instantiate <code>SMTPClientFactory</code>:</p>
423 <pre class="python"><p class="py-linenumber">1
424 </p><span class="py-src-variable">smtpClientFactory</span> = <span class="py-src-variable">SMTPClientFactory</span>()
427 <p>Running this version of the code, we observe that the
428 code <strong>still</strong> isn't quite traceback-free.</p>
430 <pre class="shell" xml:space="preserve">
431 exarkun@boson:~/doc/mail/tutorial/smtpclient$ twistd -ny smtpclient-6.tac
432 21:17 EST [-] Log opened.
433 21:17 EST [-] twistd SVN-Trunk (/usr/bin/python2.4 2.4.1) starting up
434 21:17 EST [-] reactor class: twisted.internet.selectreactor.SelectReactor
435 21:17 EST [-] Loading smtpclient-6.tac...
436 21:17 EST [-] Loaded.
437 21:17 EST [-] Starting factory <__builtin__.SMTPClientFactory instance
439 21:17 EST [-] Enabling Multithreading.
440 21:17 EST [ESMTPClient,client] Traceback (most recent call last):
441 File "twisted/python/log.py", line 56, in callWithLogger
442 return callWithContext({"system": lp}, func, *args, **kw)
443 File "twisted/python/log.py", line 41, in callWithContext
444 return context.call({ILogContext: newCtx}, func, *args, **kw)
445 File "twisted/python/context.py", line 52, in callWithContext
446 return self.currentContext().callWithContext(ctx, func, *args, **kw)
447 File "twisted/python/context.py", line 31, in callWithContext
448 return func(*args,**kw)
449 --- <exception caught here> ---
450 File "twisted/internet/selectreactor.py", line 139, in _doReadOrWrite
451 why = getattr(selectable, method)()
452 File "twisted/internet/tcp.py", line 351, in doRead
453 return self.protocol.dataReceived(data)
454 File "twisted/protocols/basic.py", line 221, in dataReceived
455 why = self.lineReceived(line)
456 File "twisted/mail/smtp.py", line 1039, in lineReceived
457 why = self._okresponse(self.code,'\n'.join(self.resp))
458 File "twisted/mail/smtp.py", line 1281, in esmtpState_serverConfig
459 self.tryTLS(code, resp, items)
460 File "twisted/mail/smtp.py", line 1294, in tryTLS
461 self.authenticate(code, resp, items)
462 File "twisted/mail/smtp.py", line 1343, in authenticate
463 self.smtpState_from(code, resp)
464 File "twisted/mail/smtp.py", line 1062, in smtpState_from
465 self._from = self.getMailFrom()
466 File "twisted/mail/smtp.py", line 1137, in getMailFrom
467 raise NotImplementedError
468 exceptions.NotImplementedError:
470 21:17 EST [ESMTPClient,client] Stopping factory
471 <__builtin__.SMTPClientFactory instance at 0xb77fd68c>
472 21:17 EST [-] Received SIGINT, shutting down.
473 21:17 EST [-] Main loop terminated.
474 21:17 EST [-] Server Shut Down.
475 exarkun@boson:~/doc/mail/tutorial/smtpclient$
478 <p>What we have accomplished with this iteration of the example is to
479 navigate far enough into an SMTP transaction that Twisted is now
480 interested in calling back to application-level code to determine what
481 its next step should be. In the next example, we'll see how to
482 provide that information to it.</p>
484 <h3>SMTP Client 7<a name="auto7"/></h3>
486 <p>SMTP Client 7 is the first version of our SMTP client which
487 actually includes message data to transmit. For simplicity's sake,
488 the message is defined as part of a new class. In a useful program
489 which sent email, message data might be pulled in from the filesystem,
490 a database, or be generated based on
491 user-input. <a href="smtpclient-7.tac" shape="rect">smtpclient-7.tac</a>, however,
492 defines a new class, <code>SMTPTutorialClient</code>, with three class
493 attributes (<code>mailFrom</code>, <code>mailTo</code>,
494 and <code>mailData</code>):</p>
496 <pre class="python"><p class="py-linenumber"> 1
507 </p><span class="py-src-keyword">class</span> <span class="py-src-identifier">SMTPTutorialClient</span>(<span class="py-src-parameter">smtp</span>.<span class="py-src-parameter">ESMTPClient</span>):
508 <span class="py-src-variable">mailFrom</span> = <span class="py-src-string">"tutorial_sender@example.com"</span>
509 <span class="py-src-variable">mailTo</span> = <span class="py-src-string">"tutorial_recipient@example.net"</span>
510 <span class="py-src-variable">mailData</span> = <span class="py-src-string">'''\
511 Date: Fri, 6 Feb 2004 10:14:39 -0800
512 From: Tutorial Guy <tutorial_sender@example.com>
513 To: Tutorial Gal <tutorial_recipient@example.net>
516 Hello, how are you, goodbye.
520 <p>This statically defined data is accessed later in the class
521 definition by three of the methods which are part of the
522 <em>SMTPClient callback API</em>. Twisted expects each of the three
523 methods below to be defined and to return an object with a particular
524 meaning. First, <code>getMailFrom</code>:</p>
526 <pre class="python"><p class="py-linenumber">1
530 </p><span class="py-src-keyword">def</span> <span class="py-src-identifier">getMailFrom</span>(<span class="py-src-parameter">self</span>):
531 <span class="py-src-variable">result</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">mailFrom</span>
532 <span class="py-src-variable">self</span>.<span class="py-src-variable">mailFrom</span> = <span class="py-src-variable">None</span>
533 <span class="py-src-keyword">return</span> <span class="py-src-variable">result</span>
536 <p>This method is called to determine the <em>reverse-path</em>,
537 otherwise known as the <em>envelope from</em>, of the message. This
538 value will be used when sending the <code>MAIL FROM</code> SMTP
539 command. The method must return a string which conforms to the <a href="http://www.faqs.org/rfcs/rfc2821.html" shape="rect">RFC 2821</a> definition
540 of a <em>reverse-path</em>. In simpler terms, it should be a string
541 like <code>"alice@example.com"</code>. Only one <em>envelope
542 from</em> is allowed by the SMTP protocol, so it cannot be a list of
543 strings or a comma separated list of addresses. Our implementation
544 of <code>getMailFrom</code> does a little bit more than just return a
545 string; we'll get back to this in a little bit.</p>
547 <p>The next method is <code>getMailTo</code>:</p>
549 <pre class="python"><p class="py-linenumber">1
551 </p><span class="py-src-keyword">def</span> <span class="py-src-identifier">getMailTo</span>(<span class="py-src-parameter">self</span>):
552 <span class="py-src-keyword">return</span> [<span class="py-src-variable">self</span>.<span class="py-src-variable">mailTo</span>]
555 <p><code>getMailTo</code> is similar to <code>getMailFrom</code>. It
556 returns one or more RFC 2821 addresses (this time a
557 <em>forward-path</em>, or <em>envelope to</em>). Since SMTP allows
558 multiple recipients, <code>getMailTo</code> returns a list of these
559 addresses. The list must contain at least one address, and even if
560 there is exactly one recipient, it must still be in a list.</p>
562 <p>The final callback we will define to provide information to
563 Twisted is <code>getMailData</code>:</p>
565 <pre class="python"><p class="py-linenumber">1
567 </p><span class="py-src-keyword">def</span> <span class="py-src-identifier">getMailData</span>(<span class="py-src-parameter">self</span>):
568 <span class="py-src-keyword">return</span> <span class="py-src-variable">StringIO</span>.<span class="py-src-variable">StringIO</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">mailData</span>)
571 <p>This one is quite simple as well: it returns a file or a file-like
572 object which contains the message contents. In our case, we return
573 a <code>StringIO</code> since we already have a string containing our
574 message. If the contents of the file returned
575 by <code>getMailData</code> span multiple lines (as email messages
576 often do), the lines should be <code>\n</code> delimited (as they
577 would be when opening a text file in the <code>"rt"</code> mode):
578 necessary newline translation will be performed
579 by <code>SMTPClient</code> automatically.</p>
581 <p>There is one more new callback method defined in smtpclient-7.tac.
582 This one isn't for providing information about the messages to
583 Twisted, but for Twisted to provide information about the success or
584 failure of the message transmission to the application:</p>
586 <pre class="python"><p class="py-linenumber">1
588 </p><span class="py-src-keyword">def</span> <span class="py-src-identifier">sentMail</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">code</span>, <span class="py-src-parameter">resp</span>, <span class="py-src-parameter">numOk</span>, <span class="py-src-parameter">addresses</span>, <span class="py-src-parameter">log</span>):
589 <span class="py-src-keyword">print</span> <span class="py-src-string">'Sent'</span>, <span class="py-src-variable">numOk</span>, <span class="py-src-string">'messages'</span>
592 <p>Each of the arguments to <code>sentMail</code> provides some
593 information about the success or failure of the message transmission
594 transaction. <code>code</code> is the response code from the ultimate
595 command. For successful transactions, it will be 250. For transient
596 failures (those which should be retried), it will be between 400 and
597 499, inclusive. For permanent failures (this which will never work,
598 no matter how many times you retry them), it will be between 500 and
601 <h3>SMTP Client 8<a name="auto8"/></h3>
603 <p>Thus far we have succeeded in creating a Twisted client application
604 which starts up, connects to a (possibly) remote host, transmits some
605 data, and disconnects. Notably missing, however, is application
606 shutdown. Hitting ^C is fine during development, but it's not exactly
607 a long-term solution. Fortunately, programmatic shutdown is extremely
608 simple. <a href="smtpclient-8.tac" shape="rect">smtpclient-8.tac</a>
609 extends <code>sentMail</code> with these two lines:</p>
611 <pre class="python"><p class="py-linenumber">1
613 </p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
614 <span class="py-src-variable">reactor</span>.<span class="py-src-variable">stop</span>()
617 <p>The <code>stop</code> method of the reactor causes the main event
618 loop to exit, allowing a Twisted server to shut down. With this
619 version of the example, we see that the program actually terminates
620 after sending the message, without user-intervention:</p>
622 <pre class="shell" xml:space="preserve">
623 exarkun@boson:~/doc/mail/tutorial/smtpclient$ twistd -ny smtpclient-8.tac
624 19:52 EST [-] Log opened.
625 19:52 EST [-] twistd SVN-Trunk (/usr/bin/python2.4 2.4.1) starting up
626 19:52 EST [-] reactor class: twisted.internet.selectreactor.SelectReactor
627 19:52 EST [-] Loading smtpclient-8.tac...
628 19:52 EST [-] Loaded.
629 19:52 EST [-] Starting factory <__builtin__.SMTPClientFactory instance
631 19:52 EST [-] Enabling Multithreading.
632 19:52 EST [SMTPTutorialClient,client] Sent 1 messages
633 19:52 EST [SMTPTutorialClient,client] Stopping factory
634 <__builtin__.SMTPClientFactory instance at 0xb791beec>
635 19:52 EST [-] Main loop terminated.
636 19:52 EST [-] Server Shut Down.
637 exarkun@boson:~/doc/mail/tutorial/smtpclient$
640 <h3>SMTP Client 9<a name="auto9"/></h3>
642 <p>One task remains to be completed in this tutorial SMTP client:
643 instead of always sending mail through a well-known host, we will look
644 up the mail exchange server for the recipient address and try to
645 deliver the message to that host.</p>
647 <p>In <a href="smtpclient-9.tac" shape="rect">smtpclient-9.tac</a>, we'll take the
648 first step towards this feature by defining a function which returns
649 the mail exchange host for a particular domain:</p>
651 <pre class="python"><p class="py-linenumber">1
653 </p><span class="py-src-keyword">def</span> <span class="py-src-identifier">getMailExchange</span>(<span class="py-src-parameter">host</span>):
654 <span class="py-src-keyword">return</span> <span class="py-src-string">'localhost'</span>
657 <p>Obviously this doesn't return the correct mail exchange host yet
658 (in fact, it returns the exact same host we have been using all
659 along), but pulling out the logic for determining which host to
660 connect to into a function like this is the first step towards our
661 ultimate goal. Now that we have <code>getMailExchange</code>, we'll
662 call it when constructing our <code>TCPClient</code> service:</p>
664 <pre class="python"><p class="py-linenumber">1
666 </p><span class="py-src-variable">smtpClientService</span> = <span class="py-src-variable">internet</span>.<span class="py-src-variable">TCPClient</span>(
667 <span class="py-src-variable">getMailExchange</span>(<span class="py-src-string">'example.net'</span>), <span class="py-src-number">25</span>, <span class="py-src-variable">smtpClientFactory</span>)
670 <p>We'll expand on the definition of <code>getMailExchange</code> in
671 the next example.</p>
673 <h3>SMTP Client 10<a name="auto10"/></h3>
675 <p>In the previous example we defined <code>getMailExchange</code> to
676 return a string representing the mail exchange host for a particular
677 domain. While this was a step in the right direction, it turns out
678 not to be a very big one. Determining the mail exchange host for a
679 particular domain is going to involve network traffic (specifically,
680 some DNS requests). These might take an arbitrarily large amount of
681 time, so we need to introduce a <code>Deferred</code> to represent the
682 result of <code>getMailExchange</code>. <a href="smtpclient-10.tac" shape="rect">smtpclient-10.tac</a> redefines it
685 <pre class="python"><p class="py-linenumber">1
687 </p><span class="py-src-keyword">def</span> <span class="py-src-identifier">getMailExchange</span>(<span class="py-src-parameter">host</span>):
688 <span class="py-src-keyword">return</span> <span class="py-src-variable">defer</span>.<span class="py-src-variable">succeed</span>(<span class="py-src-string">'localhost'</span>)
691 <p><code>defer.succeed</code> is a function which creates a
692 new <code>Deferred</code> which already has a result, in this
693 case <code>'localhost'</code>. Now we need to adjust
694 our <code>TCPClient</code>-constructing code to expect and properly
695 handle this <code>Deferred</code>:</p>
697 <pre class="python"><p class="py-linenumber">1
704 </p><span class="py-src-keyword">def</span> <span class="py-src-identifier">cbMailExchange</span>(<span class="py-src-parameter">exchange</span>):
705 <span class="py-src-variable">smtpClientFactory</span> = <span class="py-src-variable">SMTPClientFactory</span>()
707 <span class="py-src-variable">smtpClientService</span> = <span class="py-src-variable">internet</span>.<span class="py-src-variable">TCPClient</span>(<span class="py-src-variable">exchange</span>, <span class="py-src-number">25</span>, <span class="py-src-variable">smtpClientFactory</span>)
708 <span class="py-src-variable">smtpClientService</span>.<span class="py-src-variable">setServiceParent</span>(<span class="py-src-variable">application</span>)
710 <span class="py-src-variable">getMailExchange</span>(<span class="py-src-string">'example.net'</span>).<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">cbMailExchange</span>)
713 <p>An in-depth exploration of <code>Deferred</code>s is beyond the
714 scope of this document. For such a look, see
715 the <a href="../../../core/howto/defer.html" shape="rect">Deferred Reference</a>.
716 However, in brief, what this version of the code does is to delay the
717 creation of the <code>TCPClient</code> until the <code>Deferred</code>
718 returned by <code>getMailExchange</code> fires. Once it does, we
719 proceed normally through the creation of
720 our <code>SMTPClientFactory</code> and <code>TCPClient</code>, as well
721 as set the <code>TCPClient</code>'s service parent, just as we did in
722 the previous examples.</p>
724 <h3>SMTP Client 11<a name="auto11"/></h3>
726 <p>At last we're ready to perform the mail exchange lookup. We do
727 this by calling on an object provided specifically for this
728 task, <code>twisted.mail.relaymanager.MXCalculator</code>:</p>
730 <pre class="python"><p class="py-linenumber">1
734 </p><span class="py-src-keyword">def</span> <span class="py-src-identifier">getMailExchange</span>(<span class="py-src-parameter">host</span>):
735 <span class="py-src-keyword">def</span> <span class="py-src-identifier">cbMX</span>(<span class="py-src-parameter">mxRecord</span>):
736 <span class="py-src-keyword">return</span> <span class="py-src-variable">str</span>(<span class="py-src-variable">mxRecord</span>.<span class="py-src-variable">name</span>)
737 <span class="py-src-keyword">return</span> <span class="py-src-variable">relaymanager</span>.<span class="py-src-variable">MXCalculator</span>().<span class="py-src-variable">getMX</span>(<span class="py-src-variable">host</span>).<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">cbMX</span>)
740 <p>Because <code>getMX</code> returns a <code>Record_MX</code> object
741 rather than a string, we do a little bit of post-processing to get the
742 results we want. We have already converted the rest of the tutorial
743 application to expect a <code>Deferred</code>
744 from <code>getMailExchange</code>, so no further changes are
745 required. <a href="smtpclient-11.tac" shape="rect">smtpclient-11.tac</a> completes
746 this tutorial by being able to both look up the mail exchange host for
747 the recipient domain, connect to it, complete an SMTP transaction,
748 report its results, and finally shut down the reactor.</p>
754 <p><a href="../../howto/index.html">Index</a></p>
755 <span class="version">Version: 12.1.0</span>