~lfaraone/ubuntu/lucid/python-lamson/lp548998

« back to all changes in this revision

Viewing changes to doc/lamsonproject.org/output/docs/getting_started.html

  • Committer: Bazaar Package Importer
  • Author(s): David Watson
  • Date: 2009-08-21 14:45:16 UTC
  • Revision ID: james.westby@ubuntu.com-20090821144516-08tp9e4pyl4t493y
Tags: upstream-1.0pre2
ImportĀ upstreamĀ versionĀ 1.0pre2

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
<?xml version="1.0" encoding="utf-8"?>
 
2
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
 
3
        "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
 
4
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
 
5
        <head>
 
6
                <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />   
 
7
                <title>LamsonProject: Getting Started With Lamson</title>
 
8
        <link rel="stylesheet" href="/styles/global.css" type="text/css" charset="utf-8" />
 
9
        <link rel="stylesheet" href="/css/code.css" type="text/css" charset="utf-8" />
 
10
                <!--[if IE 7]>
 
11
                <style type="text/css" media="screen">
 
12
                        div#column_left ul.sidebar_menu li div.color{
 
13
                                display: none;
 
14
                        }
 
15
                </style>
 
16
        <![endif]-->
 
17
 
 
18
        <link href="/prettify.css" type="text/css" rel="stylesheet" />
 
19
        <script type="text/javascript" src="/prettify.js"></script>
 
20
                
 
21
        </head>
 
22
        <body onload="prettyPrint()">
 
23
                <div id="content_centered">                     
 
24
                        <div id="header">
 
25
                                <h1><img id="logo" src="/images/lamson.png" alt="Lamson Project(TM) - Pipes and aliases are so 1970." /></h1>
 
26
                                <ul id="header_menu">
 
27
                                        <li><a href="/">Home</a></li>
 
28
                                        <li><a href="/blog/">News</a></li>
 
29
                    <li><a href="/feed.xml">Feed</a></li>
 
30
                                        <li><a href="/download.html">Download</a></li>
 
31
                                        <li><a href="/docs/">Documentation</a></li>
 
32
                                        <li><a href="/docs/api/">API</a></li>
 
33
                                </ul>
 
34
                        </div>
 
35
 
 
36
 
 
37
            <div id="main_content">
 
38
                <h1>Getting Started With Lamson</h1>
 
39
                        <p>Lamson is designed to work like modern web application frameworks like Django, TurboGears,
 
40
<span class="caps">ASP</span>.<span class="caps">NET</span>, Ruby on Rails, and whatever <span class="caps">PHP</span> is using these days.  At every design decision 
 
41
Lamson tries to emulate terminology and features found in these frameworks.  This Getting Started
 
42
document will help you get through that terminology, get you started running your first
 
43
lamson application, and walk you through the code you should read.</p>
 
44
 
 
45
        <p>In total it should take you about 30 minutes to an hour to complete.  If you just want
 
46
to try Lamson, at least go through the 30 <strong>second</strong> introduction given first.</p>
 
47
 
 
48
        <h2>The 30 Second Introduction</h2>
 
49
 
 
50
        <p>If you have Python and <a href="http://peak.telecommunity.com/DevCenter/EasyInstall">easy_install</a> already, then try this out:</p>
 
51
 
 
52
<pre class="code">
 
53
$ easy_install lamson
 
54
$ lamson gen -project mymailserver
 
55
$ cd mymailserver
 
56
$ lamson start
 
57
$ lamson log
 
58
$ nosetests
 
59
$ lamson help -for send
 
60
$ lamson send -sender me@mydomain.com -to test@test.com \
 
61
        -subject "My test." -body "Hi there." -port 8823
 
62
$ less logs/lamson.log
 
63
$ mutt -F muttrc
 
64
</pre>
 
65
 
 
66
        <p>You now have a working base Lamson setup ready for you to work on with the following installed:</p>
 
67
 
 
68
        <ul>
 
69
                <li>Lamson and all dependencies (Jinja2, nosetests)</li>
 
70
                <li>Code for your project in mymailserver.  Look in app/handlers and config/settings.py.</li>
 
71
                <li>Two initial tests that verify your server is not an open relay and forwards mail in tests/handlers/open_relay_tests.py.</li>
 
72
                <li>A &#8220;logger&#8221; server running on port 8825 that dumps all of its mail into the run/queue maildir.</li>
 
73
                <li>A config script for mutt (muttrc) that you can use to inspect the run/queue <strong>and</strong> also send mail using Lamson&#8217;s <strong>send</strong> command.</li>
 
74
        </ul>
 
75
 
 
76
        <p>When you&#8217;re in mutt during the above test run, try sending an email.  The
 
77
included muttrc is configured to use the run/queue as the mail queue, and to
 
78
use the <code>lamson sendmail</code> command to deliver the mail.  This tricks mutt into
 
79
interacting directly with your running Lamson server, so you can test the thing
 
80
with a real mail client and see how it will work without having to actually
 
81
deploy the server.</p>
 
82
 
 
83
        <p>Finally, if you wanted to stop all of above you would do:</p>
 
84
 
 
85
<pre class="code">
 
86
$ lamson stop -ALL run
 
87
</pre>
 
88
 
 
89
        <p>Which tells Lamson to stop all processes that have a .pid file in the <code>run</code> directory.</p>
 
90
 
 
91
        <h2>Important Terminology</h2>
 
92
 
 
93
        <p>If you are an old <span class="caps">SMTP</span> guru and/or you&#8217;ve never written a web application with
 
94
a modern web framework, then some of the terminology used in Lamson may seem
 
95
confusing.  Other terms may just confuse you or scare you because they sound
 
96
complicated.  I tried my best to make the concepts used in Lamson
 
97
understandable and the code that implements them easy to read.  In fact, you
 
98
could probably read the code to Lamson in an evening and understand how
 
99
everything works.</p>
 
100
 
 
101
        <p>Experience has taught me that nobody reads the code, even if it is
 
102
small.  Therefore, here are the most important concepts you should know to get
 
103
a grasp of Lamson and how it works.</p>
 
104
 
 
105
        <ul>
 
106
                <li><acronym title="Model View Controller">MVC</acronym> &#8212; Model View Controller is a design methodology used in web application frameworks where the data (model), presentation (view), and logic (controller) layers of the application are strictly separated.</li>
 
107
                <li><acronym title="Finite State Machine">FSM</acronym> &#8212; Lamson uses the concept of a Finite State Machine to control how handlers execute.  Each time it runs it will perform an action based on what it is send <strong>and</strong> what it was doing last.  <span class="caps">FSM</span> in computer science class are overly complex, but in Lamson they are as easy to use as a <code>return</code> statement.</li>
 
108
                <li>Template &#8212; Lamson generates the bodies of its messages using Templates, which are text files that have parts that get replaced with variables you pass in.  Templates are converted to their final form with a process called <strong>rendering</strong>.</li>
 
109
                <li>Relay &#8212; The <strong>relay</strong> for a Lamson server is where Lamson delivers its messages.  Usually the Relay is a smart tougher server that&#8217;s not as smart, but very good at delivering mail.  Lamson can also be run as a Relay for testing purposes.</li>
 
110
                <li>Receiver &#8212; Lamson typically runs as the Receiver of email.  If you are familiar with a web application setup, then Lamson is the inverse.  Instead of Lamson runing &#8220;behind&#8221; an Apache or Nginx server, Lamson runs &#8220;in front&#8221; of an <span class="caps">SMTP</span> server like Postfix.  It listens on port 25, handles the mail it should, and forwards the rest to the Relay.  This makes Lamson much more of a Proxy or filter server.</li>
 
111
                <li>Queue &#8212; Lamson can also do all of its processing off a queue.  In this setup you would have your normal mail server dump all mail to a maildir queue, and then tell Lamson to process messages out of there.  This can be combined with the usual Receiver+Relay configuration for processing messages that might take a long time.</li>
 
112
                <li>Maildir &#8212; A standard created for the qmail project with stores mail in a directory such that you can access the mail atomically and store it on a shared disk without conflicts or locking.</li>
 
113
        </ul>
 
114
 
 
115
        <h2>Managing Your Server</h2>
 
116
 
 
117
        <p>Your Lamson application is now running inside the Lamson Python server.  This is a very simple server based on Python&#8217;s
 
118
<a href="http://docs.python.org/library/smtpd.html">smtpd</a> and <a href="http://docs.python.org/library/asyncore.html">asyncore</a> libraries.</p>
 
119
 
 
120
        <blockquote>
 
121
                <p>If you want to know more about how it operates, take a look at the <code>lamson/server.py</code> file in the source distribution.</p>
 
122
        </blockquote>
 
123
 
 
124
        <p>You&#8217;ll need to use a few Lamson commands to manage the server.  You already experienced them in the 
 
125
30 second introduction, and you can review <a href="/docs/lamson_commands.html">them all</a> or see them
 
126
by using the <code>lamson help</code> command.</p>
 
127
 
 
128
        <p>Right now you have Lamson running on port 8823 and a &#8220;Lamson logger&#8221; running on 8825.  This means that
 
129
your lamson server (port 8823) will forward its messages to the logger (port 8825) thinking it&#8217;s your
 
130
real relay server.  The truth is the logger just logs its messages to logs/logger.log and dumps it into
 
131
run/queue so you can inspect the results.</p>
 
132
 
 
133
        <p>Before we learn how to manage them and what they do, open up the
 
134
<code>config/settings.py</code> file and take a look:</p>
 
135
 
 
136
<pre class="code prettyprint">
 
137
from app.model import table
 
138
import logging
 
139
 
 
140
relay_config = {'host': 'localhost', 'port': 8825}
 
141
 
 
142
receiver_config = {'host': 'localhost', 'port': 8823}
 
143
 
 
144
database_config = {
 
145
    "metadata" : table.metadata,
 
146
    "url" : 'sqlite:///app/data/main.db',
 
147
    "log_level" : logging.DEBUG
 
148
}
 
149
 
 
150
handlers = ['app.handlers.sample']
 
151
 
 
152
router_defaults = {'host': 'test\\.com'}
 
153
 
 
154
template_config = {'dir': 'app', 'module': 'templates'}
 
155
</pre>
 
156
 
 
157
        <p>Your file probably has some comments telling you what these do, but it&#8217;s important to understand
 
158
how they work.</p>
 
159
 
 
160
        <p>First, this file is just plain old Python variables.  It is loaded by one of
 
161
two other files in your config directory:  <code>config/boot.py</code> or
 
162
<code>config/testing.py</code>.  The <code>config/boot.py</code> file is started whenever you use the
 
163
<code>lamson start</code> command and its job is to read the <code>config/settings.py</code> and
 
164
start all the services you need, then assign them as variables back to
 
165
<code>config.settings</code> so your handlers can get at them.  The <code>config/testing.py</code> is
 
166
almost the same, except it configures <code>config.settings</code> so that your unit tests
 
167
can run without any problems.  Typically this means setting the spell checker
 
168
and <strong>not</strong> starting the real server.</p>
 
169
 
 
170
        <blockquote>
 
171
                <p> Lamson can load any boot script you like, see <a href="/docs/deferred_processing_to_queues.html">Deferred Processing To
 
172
Queues</a> for an example of using this
 
173
to make a queue processor.</p>
 
174
        </blockquote>
 
175
 
 
176
        <p>The important thing to understand about this setup (where a boot file reads settings.py and
 
177
then configures <code>config.settings</code>) that it makes it easy for you to change Lamson&#8217;s operations
 
178
or start additional services you need and configure them.  For the most part you won&#8217;t need
 
179
to touch <code>boot.py</code> or <code>testing.py</code> until you need to add some new service, change the template
 
180
library you want to use, setup a different database <span class="caps">ORM</span>, etc.  Until then just ignore it.</p>
 
181
 
 
182
        <h2>settings.py Variables</h2>
 
183
 
 
184
        <p>The <code>receiver_config</code> variable is used by the <em>lamson start</em> command to figure out where to listen
 
185
for incoming <span class="caps">SMTP</span> connections.  In a real installation this would be port <strong>25</strong> on your external
 
186
IP address.  It&#8217;s where the internet talks to your server.</p>
 
187
 
 
188
        <p>The <code>relay_config</code> setting is used by Lamson to figure out where to forward message replies (responses)
 
189
for real delivery.  Normally this would be a &#8220;smart host&#8221; running a more established server
 
190
like <a href="http://www.postfix.org/">Postfix</a> or <a href="http://www.exim.org/">Exim</a> to do the grunt work
 
191
of delivering to the final recipients.</p>
 
192
 
 
193
        <p>The <code>handlers</code> variable lists the modules (not files) of the handlers you want to load.  
 
194
Simply put them here and they&#8217;ll be loaded, even the <a href="http://lamsonproject.org/docs/api/lamson.handlers-module.html">lamson.handlers</a> 
 
195
modules will work here too.</p>
 
196
 
 
197
        <p>The <code>router_defaults</code> are for the <a href="http://lamsonproject.org/docs/api/lamson.routing.RoutingBase-class.html">lamson.routing.Router</a> 
 
198
class and configure the default 
 
199
routing regular expressions you plan on using.  Typically you&#8217;ll at least configure the 
 
200
<code>host</code> regular expression since that is used in every route and shouldn&#8217;t change too often.</p>
 
201
 
 
202
        <p>Finally, <code>template_config</code> contains the configuration values for the templating system you&#8217;ll
 
203
be using.  Lamson supports either Mako or Jinja2, but defaults to Jinja2.</p>
 
204
 
 
205
        <h2>Looking At config/boot.py</h2>
 
206
 
 
207
        <p>Programmers need to know how everything works before they trust it, so let&#8217;s look at the <em>config/boot.py</em>
 
208
file and see how these variables are used:</p>
 
209
 
 
210
<pre class="code prettyprint">
 
211
from config import settings
 
212
from lamson.routing import Router
 
213
from lamson.server import Relay, SMTPReceiver
 
214
from lamson.utils import configure_database
 
215
from lamson import view
 
216
import logging
 
217
import logging.config
 
218
import jinja2
 
219
 
 
220
# configure logging to go to a log file
 
221
logging.config.fileConfig("config/logging.conf")
 
222
 
 
223
# the relay host to actually send the final message to
 
224
settings.relay = Relay(host=settings.relay_config['host'], 
 
225
                       port=settings.relay_config['port'], debug=1)
 
226
 
 
227
# where to listen for incoming messages
 
228
settings.receiver = SMTPReceiver(settings.receiver_config['host'],
 
229
                                 settings.receiver_config['port'])
 
230
 
 
231
settings.database = configure_database(settings.database_config, also_create=False)
 
232
 
 
233
Router.defaults(**settings.router_defaults)
 
234
Router.load(settings.handlers)
 
235
Router.RELOAD=True
 
236
 
 
237
view.LOADER = jinja2.Environment(
 
238
    loader=jinja2.PackageLoader(settings.template_config['dir'], 
 
239
                                settings.template_config['module']))
 
240
 
 
241
</pre>
 
242
 
 
243
        <blockquote>
 
244
                <p>Don&#8217;t be afraid that you see this much Python, you normally wouldn&#8217;t touch this file
 
245
unless it were to add your own services or to make a new version for a different configuration.
 
246
For the most part, you can just edit the <code>config/settings.py</code> and go.</p>
 
247
        </blockquote>
 
248
 
 
249
        <p>First you&#8217;ll see that <code>config/boot.py</code> sets up logging using the <code>config/logging.conf</code>
 
250
file, which you can change to reconfigure how you want logs to be created.</p>
 
251
 
 
252
        <p>Then it starts assigning variables to the config.settings module that it
 
253
has imported at the top.  This is important because after <code>config.boot</code> runs
 
254
your lamson code and handlers will have access to all these services.  You
 
255
can get directly to the relay, receiver, database and anything else you need
 
256
by simply doing:</p>
 
257
 
 
258
<pre class="code prettyprint">
 
259
from config import settings
 
260
</pre>
 
261
 
 
262
        <p>After that <code>config.boot</code> sets up the <code>settings.relay</code>, <code>settings.receiver</code>,
 
263
and <code>settings.database</code>.  These three are used heavily in your own Lamson code,
 
264
so knowing how to change them if you need to helps you later.</p>
 
265
 
 
266
        <p>After this we configure the <code>lamson.routing.Router</code> to have your defaults,
 
267
load up your handlers, and turn on <span class="caps">RELOAD</span>.  Setting <code>Router.RELOAD=True</code> 
 
268
tell the Router to reload all the handlers for each request.  Very handy when you
 
269
are doing development since you don&#8217;t need to reload the server so often.</p>
 
270
 
 
271
        <blockquote>
 
272
                <p> If you deploy to production, then you&#8217;ll want to set this to False since it&#8217;s
 
273
a performance hit.</p>
 
274
        </blockquote>
 
275
 
 
276
        <p>Finally, the <code>config.boot</code> does the job os loading the template system you&#8217;ll use,
 
277
in this case Jinja2.  Jinja2 and Mako use the same <span class="caps">API</span> so you can configure
 
278
Mako here as well, as long as the object assigned to view.<span class="caps">LOADER</span> has the same <span class="caps">API</span>
 
279
it will work.</p>
 
280
 
 
281
        <h1>Developing With Lamson</h1>
 
282
 
 
283
        <p>Now that you&#8217;ve received a thorough introduction to how to manage Lamson, and 
 
284
how it is configured, you can get into actually writing some code for it.</p>
 
285
 
 
286
        <p>Before you begin, you should know that writing an application for a mail server
 
287
can be a pain.  The clients and servers that handle <span class="caps">SMTP</span> make a large number of
 
288
assumptions based on how the world was back in 1975.  Everything is on defined ports
 
289
with defined command line parameters and the concept of someone pointing their
 
290
mail client at a different server arbitrarily just doesn&#8217;t exist.  
 
291
The world of email is not like the web where you just take any old &#8220;client&#8221; and point
 
292
it at any old server and start messing with it.</p>
 
293
 
 
294
        <p>Lucky for you, Lamson has solved most of these problems and provides you with a 
 
295
bunch of handy development tools and tricks so you can work with your Lamson server
 
296
without having to kill yourself in configuration hell.</p>
 
297
 
 
298
        <h2>Using Mutt</h2>
 
299
 
 
300
        <p>You probably don&#8217;t have another <span class="caps">SMTP</span> server running, and even if you did, it&#8217;d
 
301
be a pain to configure it for development purposes.  You&#8217;d have to setup aliases, new
 
302
mail boxes, restart it all the time, and other annoyances.</p>
 
303
 
 
304
        <p>For development, what we want is our own little private <span class="caps">SMTP</span> relay, and since Lamson can
 
305
also deliver mail, that is what we get with the command:</p>
 
306
 
 
307
<pre class="code">
 
308
$ lamson log
 
309
</pre>
 
310
 
 
311
        <p>This tells Lamson to run as a &#8220;logging server&#8221;, which doesn&#8217;t actually deliver
 
312
any mail.  With this one command you have a server running on 8825 that takes every
 
313
mail it receives and saves it to the <code>run/queue</code> Maildir and also logs it to
 
314
<code>logs/logger.log</code>.  It also logs the full protocol chat to <code>logs/lamson.err</code> so
 
315
you can inspect it.</p>
 
316
 
 
317
        <blockquote>
 
318
                <p>Lamson uses Maildir by default since it is the most reliable and fastest mail queue
 
319
format available.  It could also store mail messages to any queue supported by Python&#8217;s
 
320
<a href="http://docs.python.org/library/mailbox.html">mailbox</a> library.  If you were
 
321
adventurous you could also use a <span class="caps">RDBMS</span>, but that&#8217;s just silly.</p>
 
322
        </blockquote>
 
323
 
 
324
        <p>You also have the file <code>muttrc</code> which is configured to trick mutt into talking to <strong>your</strong>
 
325
running Lamson server, and then read mail out of the <code>run/queue</code> maildir that is filled
 
326
in by the <code>lamson log</code> server.  Let&#8217;s take a look:</p>
 
327
 
 
328
<pre class="code">
 
329
set mbox_type=Maildir
 
330
set folder="run/queue"
 
331
set mask="!^\\.[^.]"
 
332
set mbox="run/queue"
 
333
set record="+.Sent"
 
334
set postponed="+.Drafts"
 
335
set spoolfile="run/queue"
 
336
set sendmail="/usr/bin/env lamson sendmail -port 8823 -host 127.0.0.1"
 
337
</pre>
 
338
 
 
339
        <p>Notice that it&#8217;s configured sendmail to be &#8220;sendmail -port 8823 -host 127.0.0.1&#8221;
 
340
which is a special <code>lamson sendmail</code> command that knows how to talk to lamson and
 
341
read the arguments and input that mutt gives to deliver a mail.</p>
 
342
 
 
343
        <blockquote>
 
344
                <p> Why does Lamson need its own sendmail?  Because you actually have to configure most
 
345
mail server&#8217;s configuration files to change their ports before their <strong>sendmail command</strong>
 
346
will use a different port.  Yes, the average sendmail command line tool assumes that it
 
347
is always talking to one and only one server on one and only one port for ever and all
 
348
eternity.  Without <code>lamson sendmail</code> you wouldn&#8217;t be able to send to an arbitrary
 
349
server.</p>
 
350
        </blockquote>
 
351
 
 
352
        <p>With this setup (<code>lamson start</code> ; <code>lamson log</code> ; <code>mutt -F muttrc</code>) you can now
 
353
use your mutt client as a test tool for working with your application.</p>
 
354
 
 
355
        <h2>Stopping Lamson</h2>
 
356
 
 
357
        <p>The <acronym title="Process ID">PID</acronym> files are stored in the <code>run</code> directory.  Here&#8217;s a sample
 
358
session where I stop all the running servers:</p>
 
359
 
 
360
<pre class="code">
 
361
$ ls -l run/*.pid
 
362
-rw-r--r--  1 zedshaw  staff  5 May 16 16:41 run/log.pid
 
363
-rw-r--r--  1 zedshaw  staff  5 May 16 16:41 run/smtp.pid
 
364
 
 
365
$ lamson stop -ALL run
 
366
Stopping processes with the following PID files: ['run/log.pid', 'run/smtp.pid']
 
367
Attempting to stop lamson at pid 1693
 
368
Attempting to stop lamson at pid 1689
 
369
</pre>
 
370
 
 
371
        <p>You can also pass other options to the stop command to just stop one server.  Use
 
372
<em>lamson help -for stop</em> to see all the options.</p>
 
373
 
 
374
        <h2>Starting Lamson Again</h2>
 
375
 
 
376
        <p>Hopefully you&#8217;ve been paying attention and have figured out how to restart lamson and the
 
377
logging server.  Just in case, here it is again:</p>
 
378
 
 
379
<pre class="code">
 
380
$ lamson start
 
381
$ lamson log
 
382
</pre>
 
383
 
 
384
        <p>You should also look in the logs/lamson.log file to see that it actually started.  The
 
385
other files in the logs directory contain messages dumped to various output methods (like
 
386
Python&#8217;s stdout and stderr).  Periodically, if the information you want is not in 
 
387
logs/lamson.log then it is probably in the other files.</p>
 
388
 
 
389
        <blockquote>
 
390
                <p> You can change your logging configuration by editing the logging line your config/settings.py file.</p>
 
391
        </blockquote>
 
392
 
 
393
        <h2>Other Useful Commands</h2>
 
394
 
 
395
        <p>You should read the <a href="/docs/lamson_commands.html">available commands</a> documentation to get an
 
396
overview, and you can also use <em>lamson help</em> to see them at any time.</p>
 
397
 
 
398
        <h2>send</h2>
 
399
 
 
400
        <p>The first useful command is <em>lamson send</em>, which lets you send mail to <span class="caps">SMTP</span> servers (not
 
401
just Lamson) and watch the full <span class="caps">SMTP</span> protocol chatter.  Here&#8217;s a sample:</p>
 
402
 
 
403
<pre class="code">
 
404
$ lamson send -port 25 -host zedshaw.com -debug 1 \
 
405
    -sender tester@test.com -to zedshaw@zedshaw.com \
 
406
    -subject "Hi there" -body "Test body."
 
407
send: 'ehlo zedshawscomputer.local\r\n'
 
408
reply: '502 Error: command "EHLO" not implemented\r\n'
 
409
reply: retcode (502); Msg: Error: command "EHLO" not implemented
 
410
send: 'helo zedshawcomputer.local\r\n'
 
411
reply: '250 localhost.localdomain\r\n'
 
412
reply: retcode (250); Msg: localhost.localdomain
 
413
send: 'mail FROM:<tester@test.com>\r\n'
 
414
reply: '250 Ok\r\n'
 
415
reply: retcode (250); Msg: Ok
 
416
send: 'rcpt TO:<zedshaw@zedshaw.com>\r\n'
 
417
reply: '250 Ok\r\n'
 
418
reply: retcode (250); Msg: Ok
 
419
send: 'data\r\n'
 
420
reply: '354 End data with <CR><LF>.<CR><LF>\r\n'
 
421
reply: retcode (354); Msg: End data with <CR><LF>.<CR><LF>
 
422
data: (354, 'End data with <CR><LF>.<CR><LF>')
 
423
send: 'Content-Type: text/plain; charset="us-ascii"\r\nMIME-Version: 1.0\r\nContent-Transfer-Encoding: 7bit\r\nSubject: Hi there\r\nFrom: tester@test.com\r\nTo: zedshaw@zedshaw.com\r\n\r\n.\r\n'
 
424
reply: '250 Ok\r\n'
 
425
reply: retcode (250); Msg: Ok
 
426
data: (250, 'Ok')
 
427
send: 'quit\r\n'
 
428
reply: '221 Bye\r\n'
 
429
reply: retcode (221); Msg: Bye
 
430
</pre>
 
431
 
 
432
        <p>Using this helps you debug your Lamson server by showing you the exact protocol sent
 
433
between you and the server.  It is also a useful <span class="caps">SMTP</span> server debug command by itself.</p>
 
434
 
 
435
        <blockquote>
 
436
                <p> When you use the supplied muttrc you&#8217;ll be configured to use Lamson&#8217;s sendmail
 
437
(not *send) command as your delivery command.  This lets you use mutt as a complete development
 
438
tool with minimal configuration.</p>
 
439
        </blockquote>
 
440
 
 
441
        <h2>queue</h2>
 
442
 
 
443
        <p>The <em>lamson queue</em> command lets you investigate and manipulate the run/queue (or any maildir).
 
444
You can pop a message off, get a message by its key, remove a message by its key, count the messages,clear the queue, list keys in the queue.   It gives you a lower level view of the queue than
 
445
mutt would, and lets you manipulate it behind the scenes.</p>
 
446
 
 
447
        <h2>restart</h2>
 
448
 
 
449
        <p>Lamson does reload the code of your project when it receives a new request (probably too
 
450
frequently), but if you change the <code>config/settings.py</code> file then you need to restart.
 
451
Easiest way to do that is with the restart command.</p>
 
452
 
 
453
        <h2>Walking Through The Code</h2>
 
454
 
 
455
        <p>You should actually know quite a lot about how to run and mess with Lamson, so you&#8217;ll
 
456
want to start writing code.  Before you do, go check out the <a href="/docs/api/"><span class="caps">API</span> Documentation</a>
 
457
and take a look around.  This document will guide you through where everything is and how
 
458
to write your first handler, but when you start going out on your own you&#8217;ll need a good
 
459
set of reference material.</p>
 
460
 
 
461
        <p>At the top level of your newly minted project you have these directories:</p>
 
462
 
 
463
<pre class="code">
 
464
app -- Where the application code (handlers, templates, models) lives.
 
465
config -- You already saw everything in here.
 
466
logs -- Log files get put here.
 
467
run -- Stuff that would go in a /var/run like PID files and queues.
 
468
tests -- Unit tests for handlers, templates, and models.
 
469
</pre>
 
470
 
 
471
        <p>Lamson expects all of these directories to be right there, so don&#8217;t get 
 
472
fancy and think you can move them around.</p>
 
473
 
 
474
        <p>The first place to look is in the app directory, which has this:</p>
 
475
 
 
476
<pre class="code">
 
477
app/__init__.py
 
478
app/data -- Data you want to keep around goes here.
 
479
app/handlers -- Lamson handlers go here.
 
480
app/model -- Any type of backend ORM models or other non-handler code.
 
481
app/templates -- Email templates.
 
482
</pre>
 
483
 
 
484
        <p>You don&#8217;t technically <strong>have</strong> to store your data in app/data.  You are free
 
485
to put it anywhere you want, it&#8217;s just convenient for most situations to
 
486
have it there.</p>
 
487
 
 
488
        <p>Your <code>app/model</code> directory could have anything in it from simple modules for
 
489
working various Maildir queues, to full blown SQLAlchemy configurations for
 
490
your database.  The only restriction is that you load them in the modules
 
491
yourself (no magic here).</p>
 
492
 
 
493
        <p>The <code>app/templates</code> directory can have any structure you want, and as you
 
494
saw from the <code>config.boot</code> discussion it is just configured into the
 
495
Jinja2 configuration as the default.  If you have a lot of templates it might
 
496
help to have them match your <code>app/handlers</code> layout in some logical way.</p>
 
497
 
 
498
        <p>That only leaves your <code>app/handlers</code> directory:</p>
 
499
 
 
500
<pre class="code">
 
501
app/handlers/__init__.py
 
502
app/handlers/sample.py
 
503
</pre>
 
504
 
 
505
        <p>This is where the world gets started.  If you look at your <code>config.settings</code>
 
506
you&#8217;ll see this line:</p>
 
507
 
 
508
<pre class="code prettyprint">
 
509
handlers = ['app.handlers.sample']
 
510
</pre>
 
511
 
 
512
        <p>Yep, that&#8217;s telling the <a href="http://lamsonproject.org/docs/api/lamson.routing.RoutingBase-class.html">lamson.routing.Router</a> 
 
513
to load your <code>app.handlers.sample</code>
 
514
module to kick it into gear.  It really is as simple as just putting the file in
 
515
that directory (in in sub-modules there) and then adding them to the handlers
 
516
list.</p>
 
517
 
 
518
        <p>You can also add handlers from modules outside of your <code>app.handlers</code>:</p>
 
519
 
 
520
<pre class="code prettyprint">
 
521
handlers = ['app.handlers.sample', 'lamson.handlers.log']
 
522
</pre>
 
523
 
 
524
        <p>This installs the handler (<a href="http://lamsonproject.org/docs/api/lamson.handlers.log-module.html">lamson.handlers.log</a>) that lamson uses to log every email it receives.</p>
 
525
 
 
526
        <h2>Writing Your Handler</h2>
 
527
 
 
528
        <p>This document is for getting started quickly, so going into the depths of the cool stuff
 
529
you can do with Lamson handlers is outside the scope, but if you open the <em>app/handlers/sample.py</em>
 
530
file and take a look you&#8217;ll how a handler is structured.</p>
 
531
 
 
532
        <blockquote>
 
533
                <p>Since Lamson is changing so much the contents of the file aren&#8217;t included in this document.  You&#8217;ll have to open it and take a look.</p>
 
534
        </blockquote>
 
535
 
 
536
        <p>At the top of the file you should see your typical import statements:</p>
 
537
 
 
538
<pre class="code prettyprint">
 
539
import logging
 
540
from lamson.routing import route, route_like, stateless
 
541
from config.settings import relay, database
 
542
from lamson import view
 
543
</pre>
 
544
 
 
545
        <p>Notice that we include elements from the <code>lamson.routing</code> that are decorators
 
546
we use to configure a route.  Then you&#8217;ll see that we&#8217;re getting that <code>settings.relay</code>
 
547
and <code>settings.database</code> we configured in the previous sections.  Finally we bring
 
548
in the <code>lamson.view</code> module directory to make rendering templates into email messages
 
549
a lot easier.</p>
 
550
 
 
551
        <p>Now take a look at the rest of the file and you&#8217;ll how a handler is structured:</p>
 
552
 
 
553
        <ol>
 
554
                <li>Each state is a separate function in <span class="caps">CAPS</span>.  It doesn&#8217;t have to be, it just looks better.</li>
 
555
                <li>Above each state function is a <a href="http://lamsonproject.org/docs/api/lamson.routing.route-class.html">route</a>, <a href="http://lamsonproject.org/docs/api/lamson.routing.route_like-class.html">route_like</a>, or <a href="http://lamsonproject.org/docs/api/lamson.routing-module.html#stateless">stateless</a> decorator to configure how <code>lamson.routing.Router</code> uses it.</li>
 
556
                <li>The <a href="http://lamsonproject.org/docs/api/lamson.routing.route-class.html">route</a> decorator takes a pattern and then regex keyword arguments to fill it in.  The words in the pattern string are replaced in the final more complex routing regex by the keyword arguments after.  However, <strong>if you want to use regex directly you can</strong>, <a href="http://lamsonproject.org/docs/api/lamson.routing.route-class.html">route</a> just needs a string that eventually becomes a regex.</li>
 
557
                <li>A state function changes state by returning the next function to call.  You want to go to the <span class="caps">RUNNING</span> state, just <code>return RUNNING</code>.</li>
 
558
                <li>If any state function throws an error it will go into the <code>ERROR</code> state, so if you make a state handler named <span class="caps">ERROR</span> it will get called on the next event and can recover.</li>
 
559
                <li>If you want to run a state on this event rather than wait to have it run on the next, then simple call it and return what it returns.  So to have <span class="caps">RUNNING</span> go now, just do <code>return RUNNING(message, ...)</code> and it will work.</li>
 
560
                <li>If a state has the same regex as another state, just use <a href="http://lamsonproject.org/docs/api/lamson.routing.route_like-class.html">route_like</a> to say that.</li>
 
561
                <li>If you have a <a href="http://lamsonproject.org/docs/api/lamson.routing-module.html#stateless">stateless</a> decorator after a <a href="http://lamsonproject.org/docs/api/lamson.routing.route-class.html">route</a> or <a href="http://lamsonproject.org/docs/api/lamson.routing.route_like-class.html">route_like</a>, then that handler will run for <strong>all</strong> addresses that match, not just if this handler is in that state.</li>
 
562
        </ol>
 
563
 
 
564
        <p>That is pretty much the entire complexity of how you write a handler.  You
 
565
setup routes, and return the next step in your conversation as the next
 
566
function to run.   The <code>lamson.routing.Router</code> then takes each message it receives
 
567
and runs it through a processing loop handing it to your states and handlers.</p>
 
568
 
 
569
        <h2>How States Are Run</h2>
 
570
 
 
571
        <p>The best way to see how states are processed is to look at the <a href="http://lamsonproject.org/docs/api/lamson.routing.RoutingBase-class.html">Router</a> code that does it:</p>
 
572
 
 
573
<pre class="code prettyprint">
 
574
    def deliver(self, message):
 
575
        if self.RELOAD: self.reload()
 
576
 
 
577
        called_count = 0
 
578
 
 
579
        for functions, matchkw in self.match(message['to']):
 
580
            to_call = []
 
581
            in_state_found = False
 
582
 
 
583
            for func in functions:
 
584
                if lamson_setting(func, 'stateless'):
 
585
                    to_call.append(func)
 
586
                elif not in_state_found and self.in_state(func, message):
 
587
                    to_call.append(func)
 
588
                    in_state_found = True
 
589
 
 
590
            called_count += len(to_call)
 
591
 
 
592
            for func in to_call:
 
593
                if lamson_setting(func, 'nolocking'):
 
594
                    self.call_safely(func, message,  matchkw)
 
595
                else:
 
596
                    with self.call_lock:
 
597
                        self.call_safely(func, message, matchkw)
 
598
 
 
599
        if called_count == 0:
 
600
            if self.UNDELIVERABLE_QUEUE:
 
601
                LOG.debug("Message to %r from %r undeliverable, putting in undeliverable queue.",
 
602
                          message['to'], message['from'])
 
603
                self.UNDELIVERABLE_QUEUE.push(message)
 
604
            else:
 
605
                LOG.debug("Message to %r from %r didn't match any handlers.",
 
606
                          message['to'], message['from'])
 
607
</pre>
 
608
 
 
609
        <p>What this does is take all the handlers you&#8217;ve loaded, and then finds which handlers have a state function that
 
610
matches the current message.  It then goes through each potential match, and determines which of all the matching
 
611
state functions is &#8220;in that state&#8221;.  This means that, even though you have six state functions that answer to
 
612
&#8220;(list_name)-(action)@(host)&#8221; only the one that matches the users current state (say <span class="caps">PENDING</span>) will be called next.
 
613
As it goes through these functions it also loads up any that are marked &#8220;stateless&#8221; so they can be called as well.</p>
 
614
 
 
615
        <p>Finally, it just calls them in order.  If the message results in no methods to call, then it will take the message
 
616
and tell you this, or put it into an <code>UNDELIVERABLE_QUEUE</code> for you to review it later.</p>
 
617
 
 
618
        <blockquote>
 
619
                <p> Slight design criticism:  Currently the order of these calls is fairly deterministic, but you can&#8217;t rely on it.
 
620
It&#8217;s also not clear if <strong>all</strong> matching states should run, or just the first.  It currently only runs the first match,
 
621
but it might be better to run each match from each handler.  Suggestions welcome on this.</p>
 
622
        </blockquote>
 
623
 
 
624
        <h2>Debugging Routes</h2>
 
625
 
 
626
        <p>In the old way of doing routing you would edit a large table of &#8220;routes&#8221; in your <code>config/settings.py</code> file and
 
627
then that told Lamson how to run.  The problem with this is it was too hard to maintain and too hard to 
 
628
indicate that different states needed a different route.</p>
 
629
 
 
630
        <p>The new setup is great because all your routing for each handler module is right there, and it&#8217;s easy to see
 
631
what will cause a particular state function to go off.</p>
 
632
 
 
633
        <p>What sucks about the new setup is that you can&#8217;t find out what all the routes are doing <strong>globally</strong> in one 
 
634
place.  That&#8217;s where <code>lamson routes</code> comes in.  Simply run that command and you&#8217;ll get a debug dump of
 
635
all the full routing regex and the functions and modules they belong to:</p>
 
636
 
 
637
<pre class="code">
 
638
Routing ORDER:  ['^(?P&lt;address>.+)@(?P&lt;host>test\\.com)$']
 
639
Routing TABLE: 
 
640
---
 
641
'^(?P&lt;address>.+)@(?P&lt;host>test\\.com)$':  app.handlers.sample.START  app.handlers.sample.NEW_USER
 
642
   app.handlers.sample.END  app.handlers.sample.FORWARD  
 
643
---
 
644
</pre>
 
645
 
 
646
        <p>This is telling you which regex is matched first, then what those regex are mapped to.  This is very handy as you can copy-paste
 
647
that regex right into a python shell and then play with it to see if it would match what you want.</p>
 
648
 
 
649
        <p>You can also pass in an email address to the <code>-test</code> option and it will tell you what routes would match
 
650
and which functions that will call:</p>
 
651
 
 
652
<pre class="code">
 
653
osb $ lamson routes -test test.blog@oneshotblog.com
 
654
2009-06-07 02:33:31,678 - root - INFO - Database configured to use sqlite:///app/data/main.db URL.
 
655
Routing ORDER:  [... lots of regex here ...]
 
656
Routing TABLE: 
 
657
---
 
658
... each regex and what state functions it maps ..
 
659
---
 
660
'^post-confirm-(?P<id_number>[a-z0-9]+)@(?P<host>oneshotblog\\.com)$':  app.handlers.post.CONFIRMING  
 
661
---
 
662
 
 
663
TEST address 'test.blog@oneshotblog.com' matches:
 
664
  '^(?P<post_name>[a-zA-Z0-9][a-zA-Z0-9.]+)@(?P<host>oneshotblog\\.com)$' app.handlers.index.POSTING
 
665
  -  {'host': 'oneshotblog.com', 'post_name': 'test.blog'}
 
666
  '^(?P<post_name>[a-zA-Z0-9][a-zA-Z0-9.]+)@(?P<host>oneshotblog\\.com)$' app.handlers.post.START
 
667
  -  {'host': 'oneshotblog.com', 'post_name': 'test.blog'}
 
668
  '^(?P<post_name>[a-zA-Z0-9][a-zA-Z0-9.]+)@(?P<host>oneshotblog\\.com)$' app.handlers.post.POSTING
 
669
  -  {'host': 'oneshotblog.com', 'post_name': 'test.blog'}
 
670
osb $ 
 
671
</pre>
 
672
 
 
673
        <p>If you&#8217;re working with Lamson this is incredibly helpful, because it tells you what
 
674
routes you have, what functions they call, and then it&#8217;ll take an email address and
 
675
tell you all the routes that match it.</p>
 
676
 
 
677
        <h2>THREADING!</h2>
 
678
 
 
679
        <p>Lamson takes a lighter approach to how it runs.  It assumes that most of the
 
680
time you want lamson to keep itself sane with minimal locking, and that you
 
681
want each of your state functions to run in a thread lock that prevents others
 
682
from stepping on your operations.  In 95% of the cases, this is what you want.</p>
 
683
 
 
684
        <p>To accomplish this, Lamson&#8217;s router will acquire an internal lock for
 
685
operations that change its state, and a separate lock before it calls each
 
686
state function.  Since multiple state functions run inside each thread, but one
 
687
thread handles each message, you&#8217;ll get multiple processing, but each state
 
688
won&#8217;t step on other states in the system.</p>
 
689
 
 
690
        <p>However, it&#8217;s those 5% of the times that will kill your application, and if you
 
691
know what you&#8217;re doing, you should be able to turn this off.  In order to tell
 
692
the Router <strong>not</strong> to lock your state function, simply decorate it with
 
693
<a href="http://lamsonproject.org/docs/api/lamson.routing-module.html#nolocking">nolocking</a>
 
694
and Lamson will skip the locking and just run your state raw.  This means that
 
695
other threads will run potentially stepping on your execution, so you <strong>must</strong> do
 
696
your own locking.</p>
 
697
 
 
698
        <p>Now, don&#8217;t think that slapping a
 
699
<a href="http://lamsonproject.org/docs/api/lamson.routing-module.html#nolocking">nolocking</a>
 
700
on your state functions is some magic cure for performance issues.  You only
 
701
ever want to do this if you <strong>really</strong> know your stuff, and you know how to make
 
702
that operation faster with better controlled locking.</p>
 
703
 
 
704
        <p>The reality is, if you have an operation that takes so long it blocks
 
705
everything else, then you are doing it wrong by trying to do it all in your
 
706
state function.  You should change your design so that this handler drops the
 
707
message into a
 
708
<a href="http://lamsonproject.org/docs/api/lamson.queue.Queue-class.html">lamson.queue.Queue</a>
 
709
and that <strong>another</strong> Lamson server reads messages out of that to do the long
 
710
running processing.</p>
 
711
 
 
712
        <p>Using queues and separate Lamson servers you can solve most of your processing
 
713
issues without a lot of thread juggling and process locking.  In fact, since
 
714
Lamson uses maildir queues by default you can even spread these processors out
 
715
to multiple machines reading off a shared disk and everything will be just
 
716
fine.</p>
 
717
 
 
718
        <p>But, since programmers will always want to just try turning off the locking,
 
719
Lamson supports the <code>nolocking</code> decorator.  Use with care.</p>
 
720
 
 
721
        <h2>What&#8217;s In A Unit Test</h2>
 
722
 
 
723
        <p>Writing unit tests is way outside the scope of this document, but you should read up on using nosetests, testunit, and
 
724
you should look at <a href="http://lamsonproject.org/docs/api/lamson.testing-module.html">lamson.testing</a> for a bunch of helper functions.  Also look in the generated <code>tests</code> directory
 
725
to see some examples.</p>
 
726
 
 
727
        <h2>Spell Checking Your Email Templates</h2>
 
728
 
 
729
        <p>Another big help is that Lamson has support for <a href="http://www.rfk.id.au/software/pyenchant/">PyEnchant</a> so you
 
730
can spell check your templates.  You can use <a href="http://lamsonproject.org/docs/api/lamson.testing-module.html#spelling">lamson.testing.spelling</a> function in your unit tests, and you
 
731
can use the <code>lamson spell</code> command line tool to spell check your templates.</p>
 
732
 
 
733
        <p>Installing PyEnchant is kind of a pain, but the trick is to get the dictionary you want and put it in your
 
734
<code>~/.enchant/myspell</code> directory.  You&#8217;ll also want to open the <code>config/testing.py</code> file and uncomment the
 
735
lines at the bottom that tell PyEnchant where to find the enchant so (dylib).  Don&#8217;t worry, <code>lamson spell</code>
 
736
will bitch at you if it isn&#8217;t right.</p>
 
737
 
 
738
        <p>Once you have it all installed, you just have to do this:</p>
 
739
 
 
740
<pre class="code">
 
741
lamson spell -- app/templates/*
 
742
</pre>
 
743
 
 
744
        <p>When you do this lamson will run the PyEnchant spell checker and prompt you for corrections and 
 
745
additions.  Type help to get help for it, and use it from there.</p>
 
746
 
 
747
        <p>After you&#8217;ve spell checked your templates (making sure to add, not ignore
 
748
words) then you can write unit tests that spell check them with
 
749
<code>lamson.testing.spelling</code> from then on.  If they fail then you&#8217;ll get a
 
750
printout of the badly spelled words in your unit test dump and can fix it.</p>
 
751
 
 
752
        <p>If the unit test is failing because of a word it doesn&#8217;t know, then run <a href="http://lamsonproject.org/docs/api/lamson.commands-module.html#spell_command">lamson spell</a> and train
 
753
it again.</p>
 
754
 
 
755
        <h2>Spam Filtering For Free</h2>
 
756
 
 
757
        <p>Lamson comes with the
 
758
<a href="http://lamsonproject.org/docs/api/lamson.spam-module.html">lamson.spam</a> module
 
759
which supports <a href="http://spambayes.sourceforge.net/">SpamBayes</a> spam filtering
 
760
system.</p>
 
761
 
 
762
        <p>Read the document on <a href="/docs/filtering_spam.html">Filtering Spam With Lamson</a> to get
 
763
a full set of instructions on using the spam filtering features.</p>
 
764
 
 
765
        <h2>Other Examples</h2>
 
766
 
 
767
        <p>Next you&#8217;ll want to sink your teeth in a bigger example.  Go grab <a href="/releases/">the source distribution .tar.gz</a> and 
 
768
extract it so you can get at the examples:</p>
 
769
 
 
770
<pre class="code">
 
771
$ tar -xzvf lamson-VERSION.tar.gz
 
772
$ cd lamson-VERSION
 
773
$ cd examples/osb
 
774
</pre>
 
775
 
 
776
        <p>You are now in the osb example that is running on
 
777
<a href="http://oneshotblog.com/">oneshotblog.com</a>.  Using what you&#8217;ve learned so far
 
778
you can start reviewing the code and finding out how a working example
 
779
operates.</p>
 
780
 
 
781
        <h2>Getting Help</h2>
 
782
 
 
783
        <p>As you work through this documentation, send your questions <a href="/contact.html">to me</a> and I&#8217;ll try to help
 
784
you.</p>
 
785
 
 
786
 
 
787
                        </div>
 
788
 
 
789
                        <div id="column_left">
 
790
                                <ul class="sidebar_menu">
 
791
                                        <li>
 
792
                                                <div class="item">
 
793
                                                        <div class="color" style="background-color: #ff0000;">&nbsp;</div>
 
794
                            <a href="/blog/">Latest News</a>
 
795
                                                </div>
 
796
                                        </li>
 
797
                                        <li>
 
798
                                                <div class="item">
 
799
                                                        <div class="color" style="background-color: #ff9900;">&nbsp;</div>
 
800
                                                        <a href="/download.html">Download the Gear</a>
 
801
                                                </div>
 
802
                                        </li>
 
803
                                        <li>
 
804
                                                <div class="item">
 
805
                                                        <div class="color" style="background-color: #99cc00;">&nbsp;</div>
 
806
                                                        <a href="/docs/getting_started.html">Getting Started</a>
 
807
                                                </div>
 
808
                                        </li>
 
809
                                        <li>
 
810
                                                <div class="item">
 
811
                                                        <div class="color" style="background-color: #3399ff;">&nbsp;</div>
 
812
                                                        <a href="/docs/">Documentation</a>
 
813
                                                </div>
 
814
                                        </li>
 
815
                                        <li>
 
816
                                                <div class="item">
 
817
                                                        <div class="color" style="background-color: #ff3399;">&nbsp;</div>
 
818
                                                        <a href="/docs/faq.html">Frequently Asked Questions</a>
 
819
                                                </div>
 
820
                                        </li>
 
821
                                        <li>
 
822
                                                <div class="item">
 
823
                                                        <div class="color" style="background-color: #006699;">&nbsp;</div>
 
824
                                                        <a href="/about.html">About Lamson</a>
 
825
                                                </div>
 
826
                                        </li>
 
827
                                        <li>
 
828
                                                <div class="item">
 
829
                                                        <div class="color" style="background-color: #0099cc;">&nbsp;</div>
 
830
                                                        <a href="/contact.html">Getting Help with Lamson</a>
 
831
                                                </div>
 
832
                                        </li>
 
833
                                </ul>
 
834
                                
 
835
                                <div class="sidebar_item">
 
836
                                        <h3>Quick Start</h3>
 
837
                                        <p>See the download instructions for information on getting lamson, and read the getting started instructions to start your own application in less than 10 minutes.</p>
 
838
                </div>
 
839
 
 
840
                <br/>
 
841
 
 
842
                                <div class="sidebar_item">
 
843
                                        <h3>Mailing Lists</h3>
 
844
                    <p>Lamson hosts its own <a href="/lists/">mailing lists</a> as well as provides a free open mailing list 
 
845
                    service for anyone who needs one.  Simply send an email to the list you want @librelist.com and it will
 
846
                    get you started.</p>
 
847
                                </div>
 
848
                                
 
849
                        </div>
 
850
                        
 
851
                        <div id="footer">
 
852
                                <div class="footer_content">
 
853
                    Lamson Project(TM) and all material on this site Copyright &copy; 2009 <a href="http://zedshaw.com/" title="Zed Shaw's blog">Zed Shaw</a> unless otherwise stated.<br/>
 
854
                    
 
855
                    Website Designed by <a href="http://kenkeiter.com/">Kenneth Keitner</a> and donated to the LamsonProject.
 
856
                                </div>
 
857
                        </div>
 
858
                        
 
859
                        <!-- end:centered_content -->
 
860
                </div>
 
861
        </body>
 
862
</html>
 
863