lamson-1.0pre11/ 0000755 0000765 0000024 00000000000 11313464575 013055 5 ustar zedshaw staff lamson-1.0pre11/bin/ 0000755 0000765 0000024 00000000000 11313464573 013623 5 ustar zedshaw staff lamson-1.0pre11/bin/lamson 0000755 0000765 0000024 00000000222 11200524130 015013 0 ustar zedshaw staff #!/usr/bin/env python from lamson import args, commands import sys args.parse_and_run_command(sys.argv[1:], commands, default_command="help") lamson-1.0pre11/build.vel 0000644 0000765 0000024 00000005472 11313463463 014667 0 ustar zedshaw staff # Copyright (C) 2008 Zed A. Shaw. You're free to reuse this file # in your build scripts in anyway and remove the copyright # notice. imports [ recipe(from 'scripts/testing' as 'testing') recipe(from 'scripts/dist' as 'dist') recipe(from 'scripts/sample' as 'sample') ] options( project "lamson" default 'tests' sudo 'sudo' version '1.0pre11' website './doc/lamsonproject.org/output/releases/' version.file 'lamson/version.py' setup( name 'lamson' version '1.0pre11' author 'Zed A. Shaw' description 'Lamson is a modern Pythonic mail server built like a web application server.' url 'http://pypi.python.org/pypi/lamson' download_url 'http://pypi.python.org/pypi/lamson' author_email 'zedshaw@zedshaw.com' package_data ( lamson [ 'data/prototype.zip' ]) packages ['lamson' 'lamson.handlers'] scripts ['bin/lamson'] install_requires ['chardet' 'jinja2' 'mock' 'nose' 'python-daemon'] ) ) depends( build ['tests' 'prototype' 'examples' 'virus' 'version.gen' 'dist.install' 'dist.sdist'] commit ['dist.gen.setup' 'parser' 'dist.clean'] tests ['parser' 'testing.run'] release ['build' 'dist.release' 'book.release' ] clean ['dist.clean'] pypi ['build'] ) targets( commit [ $ bzr log --short > CHANGES $ bzr commit $ bzr push ] virus [ $ clamscan -i -r ] examples [ $ rm -rf run/queue examples/osb/run/* examples/librelist/run/* $ rm -rf examples/librelist/app/data/archive/* $ find . -name "main.db" -exec rm {} \; $ find . -name "*.log" -exec rm {} \; $ find . -name "*.err" -exec rm {} \; $ find . -name "*.out" -exec rm {} \; $ find . -name "*.pyc" -exec rm {} \; $ find . -name "*.sw*" -exec rm {} \; $ rm -f tests/test.db $ rm -f tests/sbdb $ rm -rf doc/lamsonproject.org/output/docs/api ] prototype [ $ rm -f lamson/data/prototype.zip $ cd lamson/data/prototype ; zip -r ../prototype.zip . ] version.gen [ $ fossil info | grep checkout > /tmp/lamson.rev py [ |rev = open("/tmp/lamson.rev").read().split()[1] |ver = {"version": version, "rev": [rev[:10], rev]} |open("%(version.file)s", 'w').write( | "VERSION=" + repr(ver)) ] ] coverage [ $ nosetests --quiet --with-coverage --cover-package lamson ] pypi [ $ python setup.py register sdist bdist_egg upload ] ) lamson-1.0pre11/doc/ 0000755 0000765 0000024 00000000000 11313464573 013620 5 ustar zedshaw staff lamson-1.0pre11/doc/lamsonproject.org/ 0000755 0000765 0000024 00000000000 11313464573 017266 5 ustar zedshaw staff lamson-1.0pre11/doc/lamsonproject.org/build.vel 0000644 0000765 0000024 00000001312 11217111635 021061 0 ustar zedshaw staff imports [ module(from "vellum.commands") ] options( default 'sync' sudo 'sudo' ) depends( sync ['gen'] ) targets( commit $ bzr commit gen [ $ python webgen.py $ cd ../.. && epydoc --graph classtree --url http://lamsonproject.org/ --name "Lamson Mail Server" --html --redundant-details lamson -o doc/lamsonproject.org/output/docs/api/ ] sync [ $ rsync -av input/* output/ $ rsync -avuz output/* zedshaw@www.zedshaw.com:/usr/local/nginx/html/lamsonproject/ $ rsync -av ../../dist/* zedshaw@www.zedshaw.com:/usr/local/nginx/html/lamsonproject/releases/ ] ) lamson-1.0pre11/doc/lamsonproject.org/ChangeLog 0000644 0000765 0000024 00000003747 11310773564 021053 0 ustar zedshaw staff === 2009-12-12 === 20:00:38 [42768ade2b] *CURRENT* 1.0pre8 release announced and done. (user: zedshaw tags: trunk, 1.0pre8) 19:43:53 [bb1b0a0dae] Updated MailBase.__nonzero__ to include parts as well. (user: zedshaw tags: trunk) 19:40:51 [2916b83307] Created ChangeLog from fossil timeline of commits. (user: zedshaw tags: trunk) 19:36:14 [41530d8a34] Version bump to 1.0pre8 for release. (user: zedshaw tags: trunk) 19:31:21 [dfe158b282] Initial support of using Delivered-To or similar more exact To header. (user: zedshaw tags: trunk) 19:14:13 [e9988679b7] Updated prototype.zip for upcoming release. (user: zedshaw tags: trunk) 19:13:51 [98e94b6103] Potentially fixed the unicode/ascii issues with shelve storage of confirmations and routing. (user: zedshaw tags: trunk) 18:56:35 [6224ae68e4] Updated docs about confirmation to fix a doc bug. (user: zedshaw tags: trunk) 18:56:14 [103454c43c] Make things clean up better on INT or TERM (not HUP). (user: zedshaw tags: trunk) 18:25:29 [009451cabc] Added meta tags to the site so it can be found easier. I didn't know search engines still use that crap. (user: zedshaw tags: trunk) 18:22:24 [8908d7852d] Applied starttls, username, password patch to server. (user: zedshaw tags: trunk) 18:16:01 [9a9f9f37b6] Applied patch for handling odd email addresses and white- listing what headers have email addresses. (user: zedshaw tags: trunk) 18:06:06 [7605ef0517] Patch for queue bugs applied [bug 9d330c8f80] (user: zedshaw tags: trunk) 18:02:50 [ef9808c4f7] Point at the right archive browser. (user: zedshaw tags: trunk) 18:01:39 [3b3dc9391a] Better logging message on failure to connect to relay. (user: zedshaw tags: trunk) === 2009-12-05 === 18:05:40 [ddc4369b1d] Initial commit to the new fossil based support site. (user: zedshaw tags: trunk) 17:48:22 [8140fcad03] initial empty check-in (user: lamson tags: trunk) lamson-1.0pre11/doc/lamsonproject.org/config.py 0000644 0000765 0000024 00000001227 11225636102 021076 0 ustar zedshaw staff import os author = 'Zed A. Shaw' # Default author name. Overridden in individual document THIS_DIR = os.path.abspath(os.path.dirname(__file__)) input_dir = os.path.join(THIS_DIR, 'input') output_dir = os.path.join(THIS_DIR, 'output') template_dir = THIS_DIR template = os.path.join(template_dir, 'template.html') ### Optional parameters options = { 'baseurl':"", # if not set, relative URLs will be generated 'sitename':"Lamson Project(TM)", 'slogan': "Lamson The Python SMTP Server and Framework", 'extensions':['.txt'], 'format': 'text/x-textile', 'siteurl': 'http://lamsonproject.org', } lamson-1.0pre11/doc/lamsonproject.org/input/ 0000755 0000765 0000024 00000000000 11313464573 020425 5 ustar zedshaw staff lamson-1.0pre11/doc/lamsonproject.org/input/about.txt 0000644 0000765 0000024 00000003537 11210043226 022270 0 ustar zedshaw staff From: Zed Title: About Lamson Lamson started more than a year ago as a fun side project of "mine":http://zedshaw.com/ after a few evil experiences with sendmail and various mailing list software. I realized that e-mail systems didn't have anything like a modern "framework" for building applications. Rather than replicate the mess of aliases, pipes, processes, and nasty m4 macros that currently existed, I decided to write something different. Lamson was originally called "Son Of Sam", but that name proved too difficult to work with as a project name. Apparently people don't like their software named after serial killers (actually, that was his dog's name). "Lamson Tubes" is a colloquial name for Pneumatic Tubes which were used last century to deliver mail, packages, and hazardous material to the corporate world. They are still in use today. Now Lamson is a fully functioning SMTP server, relay, proxy, and e-mail application framework. It supports most RDBMS, has templates, and is easy to manage and deploy. Lamson is smarter than most any e-mail processing system out there thanks to Python. However, as great as Lamson is for processing email intelligently, it isn't the best solution for delivering mail. There is 30+ years of SMTP lore and myth stored in the code of mail servers such as "Postfix":http://www.postfix.org/ and "Exim":http://www.exim.org/ that would take years to replicate and make efficient. Being a practical project, Lamson defers to much more capable SMTP servers for the grunt work of getting the mail to the final recipient. I currently use Lamson in my own work, but I'm always looking for people doing interesting things with e-mail. Even if you're a spammer, or someone trying to destroy the spammers, I want to hear from you. If you've got something interesting, feel free to "contact me":/contact.html and talk about it. lamson-1.0pre11/doc/lamsonproject.org/input/blog/ 0000755 0000765 0000024 00000000000 11313464573 021350 5 ustar zedshaw staff lamson-1.0pre11/doc/lamsonproject.org/input/blog/2009-05-16.txt 0000644 0000765 0000024 00000001460 11203540277 023144 0 ustar zedshaw staff title: Lamson Project Site Launched Today I launched the Lamson Project site at "lamsonproject.org":http://lamsonproject.org/ and started filling in the content. Lamson is really turning into a fun and useful project, and hopefully the site will get other people interested in it and using it. I took the design from one of the many free web design sites and reused the same Python blog script that I use on "my own site":http://zedshaw.com/ so getting this up and running was cake. Subscribe to the "RSS feed":/feed.xml and I'll soon be doing a blog post announcing mailing lists and other email services that I'll host on lamsonproject.org as well as new documentation and code drops. If you browse around you'll find some useful documentation, but nothing complete just yet. I'm still writing most of it. lamson-1.0pre11/doc/lamsonproject.org/input/blog/2009-05-18.txt 0000644 0000765 0000024 00000010144 11213057130 023135 0 ustar zedshaw staff Title: Bug Fix 0.8.4, Mailing Lists, Spam Blocking A few announcements from my work on Lamson the last few days. I managed to fix a bug, put Lamson to work doing Lamson's mailing lists, and use Lamson to do some spam blocking on my own email account. Hopefully eating my own dogfood won't be too painful. h2. Grab 0.8.4, Important Bug Fix I wasn't using the SMTPServer class in Python correctly, and was stopping the whole server when a single channel had an error. It's a one line fix and I've been running it for a few days with no problems. Please make sure that you upgrade if you installed Lamson: sudo easy_install --upgrade lamson Or you can "grab it from PyPI":http://pypi.python.org/pypi?name=lamson&version=0.8.4&:action=display h2. Mailing Lists Lamson is now running its very simple _examples/mailinglist_ sample on lists.lamsonproject.org. You can send an email to: lamson.users-subscribe@lists.lamsonproject.org And try it out. The software running that is also what's available in the _examples/mailinglist_ sample (minus a few tweaks) so if you want to help make it a real functioning mailing list then feel free to contribute. I'm subscribed there, and I'll be working on it over the next week to turn it into a more complete and robust mailing list. An important thing I need to implement is bounce and vacation detection. I'll probably also take my spam filter hacks and work them in as well. Also, the software at lists.lamsonproject.org is an open mailing list system. Feel free to use it at your own risk, and if it becomes popular then I may turn it into a permanent thing. As an open mailing list system, what it does is creates any list that doesn't exist when you subscribe. Think of it as lazy loading for mailing lists. No need to fill out forms, beg for permission, alter aliases, edit text files, or run a ton of shell commands. Just subscribe and go. Now, if that turns into a massive abuse vector then I'll turn it off, but I'd like to make it work for people since it demonstrates something simple that Lamson can do which other mailing list systems are bad at. h2. Spam Filtering And Graylists In The Works My personal email account (zedshaw@zedshaw.com) is now running "Spambayes":http://spambayes.sourceforge.net/ inside of Lamson to filter spam. Here's the code (stripped down for show):
class SpamHandler(server.MessageHandler): """Uses app.spam to mark all messages it receives as Spam.""" def process(self, session, message, args): filter = spam.Filter() filter.train_spam(message.msg) filter.close() class FilterHandler(server.MessageHandler): """ Uses the spam filter to either queue up a message into the run/queue for later review, or forward it on if it is not spam. """ def process(self, session, message, args): classifier = spam.Filter( ) classifier.filter(message.msg) if message.msg['X-Spambayes-Classification'].startswith('spam'): q = queue.Queue('run/spam') q.push(str(message)) else: self.relay.deliver(message)This shows Lamson's raw "handler style" of extension, which the Finite State Machine stuff is built on. These are wired into the config/settings.py file routing so that any mail that's sent to zedshaw@zedshaw.com goes through *FilterHandler* and any mail that sent to bogus addresses @zedshaw.com goes through *SpamHandler*. It turns out that a lot of spam is delivered to random addresses at zedshaw.com, so this works as an autotraining mechanism. Well, at least until some asshat figures out he can game my spam filter. The FilterHandler then takes messages that SpamBayes knows is spam and puts it into a queue for later review. I periodically check that folder and pull out any false positives for retraining. It's all very hacky right now, but seems to be working really well, so once I work out the full workflow I'll formalize this code and add it to Lamson as extra handlers to use. I'll also implement a simple graylisting system and include that too. Well, enjoy the work so far and shoot me any comments you have. lamson-1.0pre11/doc/lamsonproject.org/input/blog/2009-05-19.txt 0000644 0000765 0000024 00000002741 11204561532 023150 0 ustar zedshaw staff Title: New Site Look, Same Great Content This is just a quick update to say thanks to "Ken Keiter":http://kenkeiter.com/ for creating a new "lamsonproject.org":http://lamsonproject.org/ site layout and design. The new site should be easier to read, have more breathing room, and look easier on the eyes. It's even got a logo: !http://lamsonproject.org/images/lamson.png! Which I like quite a lot. Feel free to shoot your comments about the design to "me.":/contact.html h2. Two Days Of Spam Filtering So far the new Lamson based spam blocking and filtering is working pretty well. The majority of spam is blocked, however the success rate of "SpamBayes":http://spambayes.sourceforge.net/ needs to improve. Right now I've trained it with a large set of ham, and with a reasonable number of spam messages as I encounter them. I think it's received about a 20/1 (ham/spam) ratio for training. With that training it's sorted about 50 spam, no false positives, and missed about 15 spam as "unsure", with about 4 as "ham". The 4 that were classified as ham were just one liners with a link in them. The unsure spam probably could have just been treated as spam, but I'm being conservative while I test it out. Hopefully the success rate improves over the next few days. SpamBayes is great for being a Python library I can use easily, but if it's success rate doesn't get much higher then I may look at other options. Of course, there are more important things I could be doing right now too. lamson-1.0pre11/doc/lamsonproject.org/input/blog/2009-05-20.txt 0000644 0000765 0000024 00000000404 11205152004 023121 0 ustar zedshaw staff Title: Lamson Project Ideas I wrote a "blog post about project ideas for Lamson":http://zedshaw.com/blog/2009-05-20.html on my personal blog. Head on over if you're looking for something to hack on, or just want something to read that isn't about the web. lamson-1.0pre11/doc/lamsonproject.org/input/blog/2009-05-24.txt 0000644 0000765 0000024 00000012322 11213057130 023132 0 ustar zedshaw staff Title: Features For The 0.9 Release (Soon) Content-Type: text/html
I've been hard at work cooking up the very nice new routing system, and I must say it is rather tasty. I've gone and created a whole new routing and state management design that uses decorators right in your handler modules to indicate how each state will expect mail addresses.
For the 0.9 release happening tomorrow I've got a new Router setup, spam filtering baked in nice and clean, improved test coverage, and indirect state storage for those who don't like SQLAlchemy.
The new routing design is very nice if I do say so myself. It should reduce the amount of Python regex wizardry you need to learn, help set reasonable defaults, and put your handlers and routing in one spot so they are easier to maintain. In addition the new design eliminated many files and flaws from the previous design.
Here's a quick sample from the unit tests:
from lamson.routing import Router, route, route_like Router.defaults(host="test.com", action="[a-zA-Z0-9]+", list_name="[a-zA-Z.0-9]+") @route("(list_name)-(action)@(host)") def START(message, list_name=None, action=None, host=None): print "START", message, list_name, action, host if action == 'explode': print "EXPLODE!" raise RuntimeError("Exploded on purpose.") return UNKNOWN @route("(list_name)-(action)@(host)") def UNKNOWN(message, list_name=None, action=None, host=None): print "UNKNOWN", message, list_name, action, host return NEXT @route_like(UNKNOWN) def NEXT(message, list_name=None, action=None, host=None): print "NEXT", message, list_name, action, host return CONFIRM @route_like(UNKNOWN) def CONFIRM(message, list_name=None, action=None, host=None): print "CONFIRM", message, list_name, action, host return END @route("(anything)@(host)", anything=".*") def END(message, anything=None, host=None): print "END", anything, host return START
The formatting on the @route decorator will hopefully simplify the use
of regular expressions. Rather the above route for
"(list_name)-(action)@(host)" gets translated by Lamson into
'^(?P
This new setup also has some interesting implications in how you can use it. For example, you can register multiple address forms for each state (UNKNOWN, END, etc.) so that it can handle similar activity.
Now that the routing is nice and generic, I could implement a decorator for using SpamBayes to filter spam to your state functions. If you don't want a particular state to receive spam then you just use the @spam_filter decorator:
from lamson.routing import route, route_like from lamson.spam import spam_filter ham_db = "tests/sddb" @route("(anything)@(host)", anything=".+", host=".+") @spam_filter(ham_db, "tests/.hammierc", "run/queue") def START(message, **kw): print "Ham message received. Going to END." return END @route_like(START) def END(message, *kw): print "Done."
This simply attaches the @spam_filter decorator to START and then when START runs it transitions to END. It's still a little rough since you're having to configure the @spam_filter right there, rather than in a config/settings.py file, but it works. Later versions will be much cleaner.
I've also abstracted away the state storage, which will open the door to backends in any kind of storage you want, not just SQLAlchemy. The only thing you'll need to implement to use a different storage is a simple get/set/clear set of functionality and then attach it to the Router class to make it use the new storage.
Currently the code is using a simple MemoryStorage class to speed up the unit test runs, and then I'll implement the SQLAlchemy store and probably a Tokyo Tyrant store too.
I also worked on getting the unit test coverage up as high as I could. Since Lamson is a server with nasty OS level things like sockets and daemons, it is difficult to fully test in just simple unit tests. It's at about 85% right now with only the daemon and server commands not being tested.
Finally, this new design gets rid of the old class based handlers, the Conversation style of Finite State Machine handlers, and quite a bit of code that was design cruft from my previous iterations. It should be much cleaner and meaner, and will have a nicer experience for developers.
The code in the bzr branch is still in a state of flux, so feel free to grab it and look, but you probably don't want to actually do anything with it. I should have all the samples converted to the new style and then I'll do a 0.9 release. It should happen tomorrow (May 25th).
I'll have another blog post and some documentation for the new stuff tomorrow and all next week.
lamson-1.0pre11/doc/lamsonproject.org/input/blog/2009-05-28.txt 0000644 0000765 0000024 00000005643 11210151522 023143 0 ustar zedshaw staff Title: 0.9-pre2 Up For Testing, Docs Too First off, my apologies to everyone if your RSS reader went crazy today. I include documentation changes in the RSS feed so that people can easily track updates to the Lamson docs. However, that means when I'm writing a lot of documentation it hits the feed repeatedly. The good news though is that you can now get at the 0.9-pre2 release of Lamson from the "releases":/releases/ page and you can read the great new documentation I've got. Simply go to the "documentation":/docs/ list and go through. You'll want to check out "how to install Lamson in a virtualenv":/docs/lamson_virtual_env.html by Zachary Voase. You'll also want to read through the current draft of the "getting started":/docs/getting_started.html as it covers all the nitty gritty things you need. h2. Migrating To 0.9 I'm getting the 0.9 pre-releases out so that people can start migrating and feeding me problems they encounter. I'll have a full list of changes and some documentation on migrating to 0.9. The main things that changed are covered in the "getting started":/docs/getting_started.html documentation, notably how Lamson is configured and how routing works. Routing is the biggest change of all so read through it. I'll have documentation on exactly how Routing works and how to use it too coming soon. If you started using Lamson at the 0.8.x level and need help migrating "contact me":/contact.html and I'll help you out. Migration should involve just tagging your state functions with the right route decorators. h2. Mailing List Is Dead, Long Live OSB I really didn't like the way I did the mailinglist example, so rather than keep it alive I just killed it and will rewrite it after 0.9. In its place is the _examples/osb_ application which is a One-Shot-Blog. You send it an email and it makes a blog page and index for it. It's simple enough for everyone to comprehend and I'll be able to cover it in documentation easily. The OSB sample has all the right stuff for you to see how all of Lamson's features work, so check it out and use it as the basis for your own applications. You can get it by "downloading the source":/releases/ and looking in the examples/osb directory. h2. 0.9 Probably This Weekend I want to get all the documentation done for the 0.9 release and finish the *very* last feature that it needs. I'm hoping to have that in the next few days, at least by Monday. The only feature remaining for 0.9 is a SQLAlchemy based state storage, and maybe a shelf based one. Lamson now stores its state in an abstract "StateStorage":/docs/api/lamson.routing.StateStorage-class.html class that lets you store state however you need. Right now all that's implemented is a "MemoryStorage":/docs/api/lamson.routing.MemoryStorage-class.html which actually works well enough for most applications that you just start off, and is great for unit testing. But, there needs to be a permanent state store support. lamson-1.0pre11/doc/lamsonproject.org/input/blog/2009-05-31.txt 0000644 0000765 0000024 00000002037 11210533442 023134 0 ustar zedshaw staff Title: Lamson 0.9 Later Today I have been working hard on the documentation and scrubbing the code for Lamson and the 0.9 release coming out soon. The only things I feel I need to do before an official 0.9 release are: # Clean up the one-shot-blog demo application a bit more. # Write a small set of instructions on writing your own "StateStorage.":http://lamsonproject.org/docs/api/lamson.routing.StateStorage-class.html # Do a last final code review to check for any obvious problems. Once I do that I'll make 0.9 official. If you're using 0.8.x right now, or just playing with Lamson, please go grab the "0.9 pre-release":/releases/ and make sure it works. There is one bug in that 0.9-pre3 release where the router won't properly route of the form:To: "First Last"This is fairly rare, since someone would have to craft an email like this, and it would just be ignored anyway. *That bug is fixed in the current 0.9 source.* Please shoot me any bugs you find and I'll work them into the 0.9 release. lamson-1.0pre11/doc/lamsonproject.org/input/blog/2009-06-01.txt 0000644 0000765 0000024 00000007375 11210662772 023155 0 ustar zedshaw staff Title: Lamson 0.9 Is Out, Find My Bugs! I just pushed Lamson 0.9 up to PyPI for everyone to grab and break. This release features a complete redesign of the routing, state handling, templating, and a full set of very complete documentation. Everyone who was using 0.8.x series should be able to migrate to this version with some work, but it won't be terribly painful (assuming you have unit tests). h2. Getting 0.9 Easiest way to install is with "easy_install":http://peak.telecommunity.com/DevCenter/EasyInstall straight from PyPI:
sudo easy_install lamsonThere's also instructions on "installing to a virtualenv":/docs/lamson_virtual_env.html for those who like that. Best thing to do is read the "Getting Started":/docs/getting_started.html instructions and when you're done with at least the 30 second introduction you'll have your Lamson. Try to go through the whole document if you can and send me feedback. h2. Big Changes The biggest change is that the routing mechanism and internals are totally different. Now instead of putting all your routes into the config/settings.py as a big array, you just put them on each state function as a decorator. To help you debug these fancy routes there's also a new @lamson routes@ command that will dump them in their final state. The biggest impact of the new design is the old school "BlahHandler" classes are gone. Later versions of Lamson might bring back the ability to use a class as a handler. After that comes the removal of depending on SQLAlchemy for state storage. You can now "write your own state storage":/docs/writing_a_state_storage.html and store things however you need to whatever you need. There is currently a simple storage for storing in memory and for using a Python shelve dict. Next there was a change to how Lamson boots up and configures itself giving you more control over what it uses, and giving your handlers better access to your gear. This change also let's you use better unit tests that don't require a running lamson server, as well as giving you the old "integration test" style tests that hit your real server. There's also tentative support for PyEnchant spell checking of your templates, and a new @lamson spell@ command to help you use it. See the "getting started":/docs/getting_started.html document for how to configure that, but be warned that PyEnchant is kind of a mean install. Finally, I switched to Jinja2 templates by default, but you can use Mako fairly easily by altering a configuration variable in the boot.py. h2. Look At All The Shiny Docs In addition to redesigning the guts of Lamson so that it's easier to work on and use, I also spent a lot of time cranking out good documentation so people can get started using it. Take a look in the "documentation":/docs section and have a gander at the "API documentation":/docs/api/ that is generated from the source documentation. h2. New Example: One-Shot-Blog In the source distribution "you can grab here":/releases/ there's an example of doing a simple blog that uses just email to manage the blogger's posts. The example replaces the mailing list example (for now) as it is simpler and doesn't use a database. h2. Please Break It The 0.9 release is the result of a lot of work, but I want people to break it and criticize it to death. The goal is to have people beat up this basic functionality, producing a series of 0.9.x releases, and then when everything is tight doing a 1.0 release. h2. Thanks Everyone I'd like to thank everyone on Twitter, Hacker News, and yes, even Reddit for their comments both good and bad. It helped me make good decisions knowing what people wanted, so keep the feedback coming. Thanks also to those who donated documentation, praise, or other help directly to the project. lamson-1.0pre11/doc/lamsonproject.org/input/blog/2009-06-03-2.txt 0000644 0000765 0000024 00000005044 11211630350 023271 0 ustar zedshaw staff Title: Lamson 0.9.2, Test Coverage 97% The 0.9.2 release is out and ready for everyone to easy_install. I spent the day getting rid of my tech debt by boosting the Lamson test coverage to a whopping 97%. I also wrote new documentation on "Running A Queue Receiver":http://lamsonproject.org/docs/deferred_processing_to_queues.html in a separate process using Lamson. h2. Test Coverage 97% I used the very awesome "mock":http://www.voidspace.org.uk/python/mock/ library which let me mock out a bunch of the system calls that the "lamson.commands":http://lamsonproject.org/docs/api/lamson.commands-module.html module accesses to do its work.
Name Stmts Exec Cover Missing ----------------------------------------------------- lamson 0 0 100% lamson.args 120 120 100% lamson.commands 185 178 96% 199-200, 347-349, 367, 369 lamson.handlers 0 0 100% lamson.handlers.log 4 4 100% lamson.handlers.queue 6 6 100% lamson.mail 122 121 99% 234 lamson.queue 30 30 100% lamson.routing 185 184 99% 417 lamson.server 85 84 98% 182 lamson.spam 80 74 92% 38-40, 59, 65-66 lamson.utils 53 50 94% 57, 70-71 lamson.view 15 15 100% ----------------------------------------------------- TOTAL 885 866 97% ---------------------------------------------------------------------- Ran 84 tests in 7.056sBy mocking out things like @sys.exit@ and sockets I'm able to run the code and make sure those methods were called correctly. I could also throw exceptions to make sure that exception handlers ran correctly. h2. Two Bugs Fixed Interestingly enough, increasing the test coverage to 97% didn't find any more bugs. I did find one small bug and one obvious bug in the 0.9.1 release. # If you gave a trailing option that wasn't a string it would throw an exception. # If you tried to start with a different boot script then it wouldn't do it. These are now fixed in the 0.9.2 release, so make sure you "grab it":/download.html with the easiest method being:
$ sudo easy_install lamsonh2. IRC Channel on irc.freenode.org I'm now hanging out in the #lamson channel on irc.freenode.org. Anyone who wants to come and chat about lamson can stop by. I should be there all the time, but I may not respond immediately. lamson-1.0pre11/doc/lamsonproject.org/input/blog/2009-06-03.txt 0000644 0000765 0000024 00000003623 11211415751 023141 0 ustar zedshaw staff Title: Lamson 0.9.1 Out, New Docs I released Lamson 0.9.1 today so please "grab it and test it":/download.html and shoot me feedback. The little changes are easier handling of attachments in MailRequest, a small tweak to spam_filter, and then a bunch of test enhancements and extra gear. The big change is a redesign to lamson.args so that it infers the command line arguments from the lamson.commands function keyword arguments. It's mostly an internal change since others can't write their own commands (yet). h2. New Documentation I ran into a bunch of folks who didn't know that Lamson had built-in spam filtering, so I wrote a document on "Filtering Spam With Lamson":http://lamsonproject.org/docs/filtering_spam.html that tells you how to use it. Lamson's spam filtering support is fairly simplistic, but still useful for filtering crap out of your handlers. It's biggest problem is the current implementation requires you to use the (barely documented) "SpamBayes" command line tools for things like training. I've got some instructions to get you started with the most important SpamBayes command line tool you'll need for training. h2. ChangeLog Here's the log of what I did: * Big code review, cleanup, and test boosting. * Modified the MailRequest.body so that it works with attachments. * Updated the OSB in preparation for a redesign to make it more interesting. * Didn't like the name 'attachments' for getting what's really all the parts of a message. * Implemented a method on MailRequest to return all of the attachments in a message reliably. * Setup the muttrc to use env to find the lamson command. * Updated the commands and utils so that it works with the new lamson.args setup. * Refactored lamson.args to use inspect to figure out the required defaults of a command function, thus removing the need for a... * Contributed change to muttrc so it uses lamson wherever it is installed, even virtualenv. lamson-1.0pre11/doc/lamsonproject.org/input/blog/2009-06-04.txt 0000644 0000765 0000024 00000004626 11211760244 023146 0 ustar zedshaw staff Title: OneShotBlog Sample (Hack) Running I *finally* got off my ass and put the "OneShotBlog":http://oneshotblog.com/ sample up. This is the code (plus a few little tweaks) from the sample that is in the "Lamson source":/releases/ running on another server. It's using all the features of Lamson, include the new "Queue Receiver":/docs/deferred_processing_to_queues.html functionality. So far it's working great considering I've just been hacking on it on the side to try out the usability of the Lamson "APIs":/docs/api/ and not really taken it seriously. The concept of the oneshotblog is a little weird: # You send an email to SOMETAG@oneshotblog.com, it can have textile markup. ## SOMETAG can be anything that's a tag (chars, ints, periods, no leading periods), like: i.want.my.blog@oneshotblog.com # That is posted for you at http://oneshotblog.com/posts/YOURADDRESS/SOMETAG.html ## I *do* block attempts to use a bad path by whitelisting. If you send something messed up for the tag it's ignored. # Then your post is summarized and tossed on the "front page":http://oneshotblog.com/ with the most recent 50 shown. ## This is done by a deferred queue receiver that indexes messages as it receives them. # If you send to that same tag, it replaces it with the new contents (although the index will show the old stuff for a while). There's some dumb bugs in it currently, like delete doesn't remove your message from the index and other fun things, but so far it's running as expected given the work I put into it. Anyway, feel free to try it out and shoot me feedback. Try sending really broken email too so that I can see what Lamson does with it. h2. Future Features Of OSB Just for fun I'm gonna clean up OSB and make it official. I'll add features like commenting on a post via email, each post is a little mailing list, and handling image and code attachments. For the code attachments I'll colorize them with Pygments and put them up with the post. h2. Mailing Lists and Failmail I'll be bringing back the mailing list example soon, and I'll also make an address failmail@lamsonproject.org. More on that later. h2. Python 2.6 Support If you're running Python 2.6 then you'll want to wait for the 0.9.3 release later today. The deployed OSB sample at "oneshotblog.com":http://oneshotblog.com/ is running on Python 2.6, so I had to fix about 4 failing tests regarding unicode issues and weirdness with python-daemonize. lamson-1.0pre11/doc/lamsonproject.org/input/blog/2009-06-06.txt 0000644 0000765 0000024 00000020043 11213057130 023132 0 ustar zedshaw staff Title: Lamson 0.9.3 Is Out And Sexy As Hell This release is the result of me working on my little "oneshotblog.com":http://oneshotblog.com/ project while tweaking and refining Lamson as I go. The end result is 0.9.3 didn't have a lot of big code changes, but all the tiny little changes add up to a very nice release. The highlights of this release are more secure server runs, better character encoding handling for headers, various cleanups in how mail is queued, and fixes for Python 2.6 support. My plan is to keep using this release, improving what is there currently and only adding minimal features, and just making what is there tighter. The goal is to *have a 1.0 release out* once I've used it on "oneshotblog.com":http://oneshotblog.com/ and the new "lamsonproject.org mailing lists":/lists/ application. h2. Getting It (As Usual) All you need to do to get lamson is read the "download instruction":/download.html and then go through the "getting started":/docs/getting_started.html to try it out. The *docs may be out of date with this release*, but I'm going through docs tomorrow. h2. NYLUG Python Workshop Tuesday June 09 6:00PM-8:00PM I'll be presenting Lamson at the "NYLUG Python Workshop Tuesday June 09 6:00PM-8:00PM":http://nylug.org/pipermail/nylug-announce/2009-June/000795.html and helping people get started with it. If you wanted to try it but didn't want to waste the time then come down and I'll hook you up. h2. OneShotBlog Sample Is Live Lamson includes the source to the "oneshotblog.com":http://oneshotblog.com/ project in the @examples/osb@ directory. You can "grab the source tar.gz":/releases/ and look in there fore the source. You should also try out the sample site to help test out Lamson, especially if you use a weird character set (since that's the biggest weakness currently). This release of OSB features a bunch of bug fixes, tons of tests that improve the code coverage, the ability to comment on a post by sending an email, decent spam filtering of the comments, ability to flag a comment as spam, and lots of little tweaks. It uses *all* of the features Lamson has right now. It still has some work to do, and I want to add the ability to send pictures and source code (with syntax highlight). h2. The ChangeLog The change log for this release is (with the big ones bold): * Cleaned up some stray printing and logging. * Now prints a nicer message if args get a bad argument. * *Managed to get a file object out of the maildir which removes the need to reconvert from a message.* * A few minor test suite tweaks to get coverage on the most recent features. * *Implemented a reorg of how the servers are started so that they can bind port 25 and then drop priv to a safer user.* * *Initial support for an undeliverable queue and a forwarding handler that you can run in a queue receiver to deliver unhandled mail to the relay. Needs tests.* * Using the new Router.UNDELIVERABLE_QUEUE feature to have bad mail go to a queue. * Fixed the queue_command so that it prints what queue it is working with. * Added a setting for the Router to tell it to dump undeliverable mail to a queue. * *First cut at semi-automated header encoding conversion.* * Initial support for sort-of-automated header decoding/encoding on MailRequest. * Found a bug in clear_queue which made it not able to clear alternate queues. * Small changes to fix a non-idiomatic has_key usage. * Tweaked testing so that it resets the Router state when you do a client.begin() in unit tests. * Tweaked the boot vs. test logging setup to make it nicer for both situations. * *Since the Router is the primary code loading mechanism, it now uses its own separate logger that you can redirect to stderr to see loading errors during dev.* * *Created a disconnected set of config testing gear so that it can test without relying on any examples. Also configures the logger using a config file and cleans up test output.* * *Added a feature to the routes command to print out how each matching regex will match a given test address.* * Added an exception handler for loading and reloading handlers so that you can see which ones are broke during development. * Added a -test feature to lamson routes that let's you test an address to see what routes it matches. * *Updated for Python 2.6 so the tests will run, not sure why they pass in Python 2.5 actually.* * Modification to the mail parsing so that it forces 'ascii' encoding to keep email.message_from happy in Python 2.6 h2. Using The Drop Privilege Lamson can now drop privilege after binding port 25. Previously it was too difficult to make the python-daemonize library do it correctly, but after I reorganized some code I was able to do the privilege drop manually to implement it. What this means is you can run your email server and *stop being root* right after you bind port 25. To do this, you'll just run your server with:
$ sudo lamson start -uid 500 -gid 500 $ cd .. $ sudo chown -R someuser.someuser yourappYou technically don't have to fix the permissions, but if you use "queue receivers":/docs/deferred_processing_to_queues.html then you'll need to change the permissions back. Lamson still creates a few files as root before dropping to your given UID and GID, but hopefully it will stop doing this soon. Obviously, you might want to know what your UID and GID is, but it's important that you know what *Python* thinks it is:
Python 2.5.1 (r251:54863, Jan 13 2009, 10:26:13) [GCC 4.0.1 (Apple Inc. build 5465)] on darwin Type "help", "copyright", "credits" or "license" for more information. >>> import os >>> os.getgid() 500 >>> os.getuid() 500 >>>There's other ways to find it out, but that's the Python way. h2. Using lamson routes The @lamson routes@ command has improved a bit so that you can inspect your routing and test out an email address to see how it would be sorted. Here's a sample run:
osb $ lamson routes -test test.blog@oneshotblog.com 2009-06-07 02:33:31,678 - root - INFO - Database configured to use sqlite:///app/data/main.db URL. Routing ORDER: [... lots of regex here ...] Routing TABLE: --- ... each regex and what state functions it maps .. --- '^post-confirm-(?PIf you're working with Lamson this is incredibly helpful, because it tells you what routes you have, what functions they call, and then it'll take an email address and tell you all the routes that match it. The output needs to be cleaned up, but for debugging this is nice. h2. The lamson web Command While working on "oneshotblog.com":http://oneshotblog.com/ I kept having to open up the site that it generates, so I just threw a dirt simple HTTP server into Lamson. Here's a sample of running it:[a-z0-9]+)@(?P oneshotblog\\.com)$': app.handlers.post.CONFIRMING --- TEST address 'test.blog@oneshotblog.com' matches: '^(?P [a-zA-Z0-9][a-zA-Z0-9.]+)@(?P oneshotblog\\.com)$' app.handlers.index.POSTING - {'host': 'oneshotblog.com', 'post_name': 'test.blog'} '^(?P [a-zA-Z0-9][a-zA-Z0-9.]+)@(?P oneshotblog\\.com)$' app.handlers.post.START - {'host': 'oneshotblog.com', 'post_name': 'test.blog'} '^(?P [a-zA-Z0-9][a-zA-Z0-9.]+)@(?P oneshotblog\\.com)$' app.handlers.post.POSTING - {'host': 'oneshotblog.com', 'post_name': 'test.blog'} osb $
~ $ cd projects/lamson/examples/osb/ osb $ lamson web -basedir app/data/ Starting server on 127.0.0.1:8888 out of directory 'app/data/' localhost - - [06/Jun/2009 12:31:11] "GET / HTTP/1.1" 200 - localhost - - [06/Jun/2009 12:31:11] "GET /styles/reset.css HTTP/1.1" 200 - localhost - - [06/Jun/2009 12:31:11] "GET /styles/main.css HTTP/1.1" 200 - localhost - - [06/Jun/2009 12:31:18] code 404, message File not foundIt just stays there running and printing to the console, so I just use "GNU screen":http://www.gnu.org/software/screen/screen.html and leave it running in one of the windows. h2. As Usual Test And Report Test this release out, and shoot me feedback. I'm hanging on irc.freenode.org again in the #lamson room, so just stop in if you need help. lamson-1.0pre11/doc/lamsonproject.org/input/blog/2009-06-08.txt 0000644 0000765 0000024 00000003355 11213316150 023143 0 ustar zedshaw staff Title: A Screencast And Docs On Deploying Lamson And OneShotBlog I just finished writing some new documentation on "Deploying Lamson and OneShotBlog":/docs/deploying_lamson.html that shows you how to install Lamson and all required software into a completely clean Python 2.6 and virtualenv configuration. The instructions take you from nothing to a running "oneshotblog.com":http://oneshotblog.com/ installation that you can play with and hack on. The process of doing Lamson deployments is still a little too rough for me, but these instructions should get people started. I also created the first screencast "teaching you how to do the same deployment":/videos/ but stopping at installing "oneshotblog.com":http://oneshotblog.com to keep things short. It is basically me talking and typing into a terminal window to 12 minutes showing you how to do it. You can also "download the video":http://www.archive.org/details/Lamsonproject.orgDeployLamsonInAVirtualenv from "archive.org":http://archive.org/ to get a better viewing experience. Being my first screencast I can say that it is very tedious. I'll be working on various strategies to make the process more streamlined and easier since having to take a whole day for each one will drive me insane. If you have suggestions on making the video better, "let me know":/contact.html and also if you know of tools to help clean up video. One thing I know right away needs to improve is the sound quality. An apartment in NYC is just too damn noisy for this kind of thing. Enjoy, and don't forget that I'll be "presenting Lamson at the NYLUG Python workshop tomorrow June 9th at 6:00pm":http://nylug.org/pipermail/nylug-announce/2009-June/000795.html so come on down if you want to get your Lamson working. lamson-1.0pre11/doc/lamsonproject.org/input/blog/2009-06-09.txt 0000644 0000765 0000024 00000001020 11213542615 023136 0 ustar zedshaw staff Title: Lamson At NYLUG Python Workshop Today @ 6:00PM Just a quick reminder that I'll be presenting "Lamson":http://lamsonproject.org/ to the NYLUG Python Workshop today at 6:00PM. The event is at the NY Public Library Hudson Park Branch, 66 Leroy St., NY NY 10014 in NYC and you can "find out more here.":http://nylug.org/pipermail/nylug-announce/2009-June/000795.html I'll be showing people how to set up Lamson, get it installed, do cool stuff with it, and then just answering questions and helping people work with it. lamson-1.0pre11/doc/lamsonproject.org/input/blog/2009-06-14.txt 0000644 0000765 0000024 00000021570 11215276254 023153 0 ustar zedshaw staff Title: The Mailocalypse Is Upon Us! I'm currently polishing off the two final features before I start going for the Lamson 1.0 release. I've been using Lamson to make a few little cute applications and create one thing for a potential client, and so far I haven't had to change much since 0.9.3. It's great so far and I hope that Lamson 1.0 will be a fun release. The *big* thing that must improve though is handling character encodings in emails. After spending a week or more trying to come up with an automated conversion scheme that would honor all the encodings on the planet, I had a realization. h2. Why Isn't All Mail UTF-8? Remembering the purpose of Lamson as a *modern* mail server and framework, it finally struck me as dumb that I'm trying to keep around encodings that were invented in the pre-Unicode days. Every modern system understands and displays UTF-8, and apart from some pissy attitudes from the Japanese about the Han unification, there's really no reason that every email Lamson handles can't be "upgraded" to UTF-8. As I thought about this more I started to like the idea. Take all mail Lamson handles, and upgrade it to utf-8. Then inside Lamson you know that it's future proofed and ready to be used by Python no matter what. Finally when the mail goes out, the conversion for sending is simply to encode the email as proper UTF-8 encoding consistently. However, I'm not the type of person to think any random idea I have is instantly valid because I thought it up. Before I make a grand announcement that Lamson only deals with UTF-8 I better know that it's going to have minimal impact on people using it. I made a similar decision with Mongrel, where I chose *not* to accept clients that didn't follow the standard, and it turned out to block a ton of security attacks for free. What if Lamson's required conversion to UTF-8 could not only simplify Lamson mail processing, but also prevent spam and security flaws? I decided to do some basic analysis and find out. h2. This Is The Mailocalypse I've decided that Lamson will *NOT* try to maintain the encodings it is given by a client. It will consider all encodings suspect, and attempt to convert them to UTF-8 as a means of cleaning and simplifying the mail it receives before it talks to other systems. Every other system you'd most likely talk to is either UTF-8 capable or even requires it. I'm calling this "The Mailocalypse" because it's funny, but it will also separate Lamson from other systems, in potentially awesome or horrible ways. To validate this premise, I created a small Python script that takes a Maildir or mbox and tries to convert every email into a UTF-8 encoding. It's not trying to write the emails, just load them and convert every single piece reliably to UTF-8. It then reports any emails that have a problem with the conversion. After that I ran it on a bunch of mail I had and got other people to run it and report their findings. bq. "You can grab the mailocalypse.py script here.":/mailocalypse.py Improvements welcome. The data I got from a small sample of the mail (randomly selected) was the following:
all bad spam 5069 7 0 263 15 1 1806 231 1 2650 69 0 4509 20 0 2023 495 1 3723 74 0bq. all is the total number of messages in the mailbox (including bad). bad is the number that were bad. spam is whether that box was a spam box or not (meaning all messages were classified as spam). I also made sure that the characters showed up in web browsers and in various UTF-8 mail clients and terminal windows. Most of the conversion was reliable and very fast for a quick script. With the above data, there's two things you can notice right away, and which are confirmed with a quick R session: # Spam fails to convert more frequently than ham messages. It looks like failure to convert is a 0.03 significant indicator that the message is spam. I'd need more data to confirm that. # Legit mail (ham) only has a 1.2% chance of failing to convert, and a quick eye-ball sample says those failures mail are mostly spam that wasn't classified right. h2. Potential Problems This analysis shows initially that converting mail to UTF-8 could work, but there are two problems I see so far: # I may not be doing the best conversion in that script. # Encryption and signatures will screw this up. For the first problem I'm posting the script and asking people to check it out and let me know if there's problems. Run it on your stuff and shoot me the ALL/BAD numbers when it's done. Check the code and see if you can improve it. Whatever you can to show that converting to UTF-8 is a good *OR* bad idea. A key design decision though will be that you never lose the original email, only that it is converted for Lamson so you can work with it in your code without it blowing up. If the mail can't convert, then it's bad email and should be rejected anyway. No point trying to process it. For encrypted email or ones with signatures you would just run all the validation on the original, or forward the original on, depending on your application. For example, a mailing list would still want to convert all mail to UTF-8 for processing, posting to web sites, spam filtering, and rejecting potentially bad email. Signed and encrypted email would then just be forwarded on in original form, or decoded and validated depending on the how the mailing list works. h2. Advice And Criticism Welcome Take the mailocalypse script (presented below for review) and try it out. Confirm that it works on most of your mail, and send me the results. Simplest way is to send me a message on twitter like: bq. @zedshaw ALL 2340 BAD 17 spam yes #mailocalypse And I'll tally the results and do a better analysis. You are also encouraged to fill me in on what I'm missing in the idea. Realize that *part* of the idea is to eliminate uncommon corner cases and to prove that a situation is common using evidence. Try to follow the same model by running the script on your mail to prove that it won't convert reliably or devise your own script to demonstrate your thoughts. h2. The Codes "You can grab the mailocalypse.py script here.":/mailocalypse.py Improvements welcome.
import email from email.header import make_header, decode_header from string import capwords import sys import mailbox ALL_MAIL = 0 BAD_MAIL = 0 def all_parts(msg): parts = [m for m in msg.walk() if m != msg] if not parts: parts = [msg] return parts def collapse_header(header): if header.strip().startswith("=?"): decoded = decode_header(header) converted = (unicode( x[0], encoding=x[1] or 'ascii', errors='replace') for x in decoded) value = u"".join(converted) else: value = unicode(header, errors='replace') return value.encode("utf-8") def convert_header_insanity(header): if header is None: return header elif type(header) == list: return [collapse_header(h) for h in header] else: return collapse_header(header) def encode_header(name, val, charset='utf-8'): msg[name] = make_header([(val, charset)]).encode() def bless_headers(msg): # go through every header and convert it to utf-8 headers = {} for h in msg.keys(): headers[capwords(h, '-')] = convert_header_insanity(msg[h]) return headers def dump_headers(headers): for h in headers: print h, headers[h] def mail_load_cleanse(msg_file): global ALL_MAIL global BAD_MAIL msg = email.message_from_file(msg_file) headers = bless_headers(msg) # go through every body and convert it to utf-8 parts = all_parts(msg) bodies = [] for part in parts: guts = part.get_payload(decode=True) if part.get_content_maintype() == "text": charset = part.get_charsets()[0] try: if charset: uguts = unicode(guts, part.get_charsets()[0]) guts = uguts.encode("utf-8") else: guts = guts.encode("utf-8") except UnicodeDecodeError, exc: print >> sys.stderr, "CONFLICTED CHARSET:", exc, part.get_charsets() BAD_MAIL += 1 except LookupError, exc: print >> sys.stderr, "UNKNOWN CHARSET:", exc, part.get_charsets() BAD_MAIL += 1 except Exception, exc: print >> sys.stderr, "WEIRDO ERROR", exc, part.get_charsets() BAD_MAIL += 1 ALL_MAIL += 1 mb = None try: mb = mailbox.Maildir(sys.argv[1]) len(mb) # need this to make the maildir try to read the directory and fail except OSError: print "NOT A MAILDIR, TRYING MBOX" mb = mailbox.mbox(sys.argv[1]) if not mb: print "NOT A MAILDIR OR MBOX, SORRY" for key in mb.keys(): mail_load_cleanse(mb.get_file(key)) print >> sys.stderr, "ALL", ALL_MAIL print >> sys.stderr, "BAD", BAD_MAILlamson-1.0pre11/doc/lamsonproject.org/input/blog/2009-06-20.txt 0000644 0000765 0000024 00000010450 11217126716 023142 0 ustar zedshaw staff Title: Lamson 0.9.4 With Unicode Super Powers Lamson 0.9.4 is out and it's sporting a completely rewritten and meticulously crafted encoding system. With the new "lamson.encoding":http://lamsonproject.org/docs/api/lamson.encoding-module.html code Lamson can now decode nearly any nasty horrible encoded spam or mail you hand it, turn it into pristine nice Python unicode strings, and then output sweet clean ascii or utf-8 in a consistent way. The purpose of this new encoding system is to make sure that Lamson is giving your handlers the best input it can, based on the assumption that the world is evil and Lamson will be handed utter garbage. In order to pull this off, Lamson actually attempts to decode everything it gets, and uses "chardet":http://chardet.feedparser.org/ to guess whenever it can't. It goes far enough to make sure that 99.9% of legit mail gets through, and the rest is usually spam. However, Lamson is so good at this conversion now that it even does it properly on about 99% of spam, even more. It doesn't matter what character encoding is used, if there's a mix of encodings or even if something lies about the encoding or doesn't give one. Lamson can figure this out and convert it anyway, and the rest is junk. You can look at this "image of a spam inbox":/lamson_vs_spam.png and "this image of a spam inbox":/lamson_vs_spam_2.png where you can see a screen session showing this off. This is a split screen with a Mutt on top showing badly formatted spam, and then a Mutt on the bottom with the results of Lamson's cleansing. This is just running in iTerm, but looks the same in most unicode enabled terminals and clients. The interesting side effect of Lamson's decoding is that it undoes almost all of the spam obfuscation techniques quickly and consistently. h2. There Will Be Bugs I've thrashed the hell out of this code and made sure that I really took the time to get it right. I've ran it on thousands and thousands of real and spam mail and tested the crap out of it with unit tests and fuzzing. Still, this is new code handling email so there will be bugs in it. If you run into an email that you think should parse correctly, send it to me and I'll look at it. h2. How It Works All Lamson does is completely parse every header and figure out how to decode it, but it assumes that the client is a liar. If the string decodes without errors then Lamson is happy, but if it blows up then Lamson uses chardet to figure out what the contents really are encoded as. If chardet can't figure it out, or if it's still busted, then Lamson rejects that mail (which happens less than a fraction of a percent of the time to real email). Once this decoding process is finished Lamson has converted the email completely into Python unicode only. You can work with it in a modern way without worrying about the original codecs, and you can set your headers and attachments without worrying about setting the codecs because Lamson will use the same tech to get the encoding right. When Lamson writes an email, it assumes that your text can be encoded as either plain ASCII (as most headers are), or UTF-8. If it can't, then your text probably can't be processed by Lamson anyway. Lamson will favor ASCII first since it's easier, and then use UTF-8 for anything that can't be ASCII. bq. During this conversion process anything that's *not* text is treated as raw binary and just decoded as-is. By doing this Lamson produces nice clean email that can be easily processed and passed around, and which you can review and debug easier. It also turns Lamson into a "modernizing" agent since it is producing the email it wants to see. h2. Getting This Release I had some issues pushing this release to PyPI, so let me know if it barfs on you. Otherwise, you can hit the "download":/download.html section and grab the gear. h2. The New "Cleanse" Command In order to make it easier for people to try this new cleansing system on real email I've written a quick @lamson cleanse@ command. What this command will do is take a mbox or Maildir, and run it through the washing machine, writing the results to a different maildir. When it's done you can go look at the reasons why some mail failed, how many failures, and you can actually open that maildir mail and look at it with your client. Try it out and report any errors you find. lamson-1.0pre11/doc/lamsonproject.org/input/blog/2009-06-22.txt 0000644 0000765 0000024 00000007765 11220041004 023137 0 ustar zedshaw staff Title: 0.9.5 Almost There, But Stumped On Templates Since the 0.9.4 release I've rewritten the main part of the decoding parser so that it's much cleaner and handles more edge conditions. If there's one word that defines what makes MIME horrible it would be "edge". It's amazing the kind of stupid crap clients are allowed to send out in headers. I've actually got test cases from Mutt where it breaks headers up into completely different encodings across multiple lines for no damn reason at all. Here's a test I had to write just to cover the one worst case I found:
def test_dumb_shit(): # this is a sample of possibly the worst case Mutt can produce idiot = '=?iso-8859-1?B?SOlhdnkgTel05WwgVW7uY/hk?=\n\t=?iso-8859-1?Q?=E9?=' should_be = u'H\xe9avy M\xe9t\xe5l Un\xeec\xf8d\xe9' assert_equal(encoding.header_from_mime_encoding(idiot), should_be)If you can decode what that actually is and why it's retarded then you'll win a prize. I'll give you a hint: @B != Q@. The example of the above working is "on this page at oneshotblog.com":http://oneshotblog.com/posts/zedshaw@zedshaw.com/test.html so that's solved (for now). h2. Now About Those Templates I originally wanted to make writing templates as easy as writing an email or a web page, so I let you do things like this:
To: {{ message['from'] }} From: {{ confirm_address }}@{{host}} Subject: Please confirm your new blog {{ post_name }} Reply-To: {{ confirm_address }}@{{host}} Hi there, you requested that I create a blog with a post named: {{ post_name }} : {{ message['subject'] }} ...This is a "Jinja2":http://jinja.pocoo.org/2/ that "oneshotblog.com":oneshotblog.com sends out when it makes you confirm creating a blog. This seems all normal, since it's the headers you want and the body, like an email, but it is *a total fantasy*. The reason is you can't actually decode the above template if any part of it has non-ascii chars. I'll explain, and then hopefully I can get some suggestions on what people would like. First, Lamson does the right thing and in your templates you are getting perfectly usable and future proof @unicode@ objects to work with in your handlers. Also, Jinja2 has no problem with taking this unicode and processing the above template. It works fine, and Jinja2 or Mako aren't to blame for the problem with templates now. The problem is actually in how email has to be parsed, since the email library expects you to hand it a @ascii@ encoded string, no unicode allowed. Nothing else. Lamson's new code can handle the unicode fine, the problem is actually in this one function:
def respond(template, variables, html=False): results = render(template, variables) data = email.message_from_string(results.encode('ascii', 'replace')) if html: msg = mail.MailResponse(Html=data.get_payload()) else: msg = mail.MailResponse(Body=data.get_payload()) msg.update(data) return msgThe above function has to go, but where I'm stuck is that it'd be way more elegant to ditch this whole mess completely and remove headers from the template. It's too hard to write these headers by hand unless they are simple ones, and there needs to be a way to pass in more complex headers that you want to add. If I'm providing a way to pass in headers to @lamson.view.respond@ then why not take the headers out of the template and simplify the whole rendering process? I'd like to hear from people using Lamson or those who might have ideas on this. If taking your headers out of the template and moving them to the render call will totally screw up your day, then let me know. If it comes down to it, and I have to do it, I'll probably write a conversion tool to help. Also, if you have a suggestion for doing the above better but keeping the headers then let me know. Thanks for your help folks, and I'll be hanging out in the #lamson channel on irc.freenode.org if people want to hash out a design. lamson-1.0pre11/doc/lamsonproject.org/input/blog/2009-06-26.txt 0000644 0000765 0000024 00000007241 11221170627 023147 0 ustar zedshaw staff Title: Lamson 0.9.5, The Push To 1.0 I just released Lamson 0.9.5 with all the major Unicode refactoring done and working. This is an important release because 0.9.5 is where I declare that I'm pushing to a 1.0 release and the base Lamson "APIs":/docs/api/ won't change under penalty of death. In fact they can't change because I'm using them myself in a few applications. From now on, if an API has to change, then I'll keep the old one around and attach a deprecation warning, and then provide a new one. You'll then have by 1.0 to stop using it and start using the new one. Except for a situations where a bug must be fixed, I can't see the actual Lamson API needing such a drastic change by 1.0. What will happen between now and 1.0 is bug fixing, and adding any nice additional features people need within the current "API":/docs/api/ to help build applications. This would be things like more connectors to other mail protocols (LMTP, QPMP), commands for deployment features, and possibly a way to write your own command. So, if you've been waiting for the API to stabilize then 0.9.5 is the release to grab and start using. I'm currently using it on about 6 little projects and it's working great so far, but I need more people to try it out and send me feedback on how well it works for them. h2. Getting This Release As usual, you can read the "download":/download.html documentation to learn how to grab it. After you grab it, go read these documents: * "Getting Started With Lamson":http://lamsonproject.org/docs/getting_started.html * "A Painless Introduction To Finite State Machines":http://lamsonproject.org/docs/introduction_to_finite_state_machines.html * "Deploying Lamson And OneShotBlog":http://lamsonproject.org/docs/deploying_lamson.html Then you should hit the "API documentation":http://lamsonproject.org/docs/api/ and start writing your application. Keep your first one small and try deploying it in different ways. h2. Simple Virtualhost Deployment Instructions Coming Soon The "oneshotblog.com":http://oneshotblog.com/ sample application is now deployed in a new virtualhost configuration on a new VPS I purchased this week. This configuration is different from the ones given in the documentation above because it allows you to run mulitple applications for multiple domains on a single machine. I'll be writing up how to build your own version of this configuration today, but the quick description is this: # I have postfix running on port 25 now *in front* of Lamson. # Postfix is configured to take all mail for each domain and dump it into a maildir queue for each one. # The Lamson applications are then reconfigured in their @config/boot.py@ to use a "QueueReceiver":http://lamsonproject.org/docs/api/lamson.server.QueueReceiver-class.html to feed itself messages from its maildir. # Lamson then just uses the same postfix server as its relay host like before. The advantage of this configuration, apart from being able to easily host multiple domains, is that you can shutdown your Lamson application and when you start it back up it will catch up on the mail in the maildir. People will probably never know you did maintenance or stopped the server temporarily. The disadvantage of this configuration is that it's a royal pain to make postfix do this. The instructions will show you step-by-step how to pull it off, so hopefully it'll make it easier on people who need it. However, I'm gonna tell people to start small and just use the simpler configuration "Lamson in front" configuration and leave this for the big times. h2. Mailing List Will Go Down Finally, I'll be moving the mailing list shortly, so if you send a message and don't receive a reply then that's why. lamson-1.0pre11/doc/lamsonproject.org/input/blog/2009-07-03.txt 0000644 0000765 0000024 00000002330 11223705276 023143 0 ustar zedshaw staff Title: Article In The Reg About Lamson (By Ted Dziuba) Just a quick update to point people at an article in "The Register":http://www.theregister.co.uk/ by "Ted Dziuba":http://teddziuba.com/ entitled "Lamson - email app coding without the palm sweat: Doing what Java never did":http://www.theregister.co.uk/2009/07/03/lamson/. He interviewed me about Lamson, things I think you can use it for, and other fun stuffs. A snippet from the article: "As a development platform, e-mail has gone neglected for decades. Its esoteric implementation details and specifications are regarded by many in the IT business as voodoo, best left to old-granddad programs like Sendmail or Postfix. Zed Shaw hopes to change that with his new project, Lamson. (The name Lamson is a throwback to the early 20th century pneumatic tubes used to shuttle messages between offices. It was originally called Son of Sam, but Zed's dog convinced him to change it)." "Read the article.":http://www.theregister.co.uk/2009/07/03/lamson/ h2. 0.9.6 Coming Soon I've been cranking on various projects with Lamson and haven't ran into too many other bugs. I'll be doing a code review of what's in the Bazaar repo and then pusing out a 0.9.6 probably this weekend. lamson-1.0pre11/doc/lamsonproject.org/input/blog/2009-07-07.txt 0000644 0000765 0000024 00000000324 11224721637 023150 0 ustar zedshaw staff Title: Lamson Mailing Lists Down I'm making up the release of Lamson and doing some server maintenance so I took down the Lamson server running the mailing lists. I'll let everyone know when they're back up. lamson-1.0pre11/doc/lamsonproject.org/input/blog/2009-07-09.txt 0000644 0000765 0000024 00000022024 11225570307 023150 0 ustar zedshaw staff Title: Lamson's Bounce Detection Algorithm I just finished committing 0.9.6 code for doing bounce detection and analysis with Lamson. It's part of the new mailing list example I'm coding up for the 0.9.6 release which I'll be running on a free mailing list site I'm going to release soon. In this blog post I'd like to go through the bounce detection algorithm and get some feedback and samples from people. So far it works great for the samples I have, but I want it to be fairly bullet proof. What I have here is a quick description of how Lamson is doing bounce detection, and then how it gives you parsed information to do analysis beyond that. I'm hoping that people who have to deal with bounces from systems like MS Exchange can possibly either provide samples or advice so I can tune the algorithm better. h2. The State of RFC 3464 And 1893 The two big RFCs for bounce handling are "RFC3464 - An Extensible Message Format for Delivery Status Notifications":http://www.faqs.org/rfcs/rfc3464.html and "RFC1893 - Enhanced Mail System Status Codes":http://www.faqs.org/rfcs/rfc1893.html which cover the headers and error messages you'll encounter. In RFC3464 there's various headers that are given which a bounce message should have, as well as the double and sometimes triple multipart-mime wrapping you need to do to give back the bounced message. A major pain with this standard is that the headers are spread out across the different nested parts, some might be duplicated, and the format is *never* respected by the various MTAs out there. With RFC1893 you have a large number of status codes, but they are combined in weird ways such that you have a primary code with the first digit, a secondary code with the second, and then a "third" code that's the second and third digit combined. Unlike HTTP error codes where there's just a single number mapped to a single status code, in RFC1893 it's this strangely nested first+second+(second+third) thing. Adding to this is there's a few headers where you can put these status codes, and in various formats. Here's a sample of the *parsed* Diagnostic-Code from a gmail bounce message:
'Diagnostic-Code': [(u'smtp', u'550-5.1.1', u"The email account that you tried to reach does...")],The end result is that if you want to determine if a message has bounce (not even hard vs. soft) then you have to go trolling through nested attachments looking for headers and parsing their values on the chance they *might* be important to the bounce. h2. Soft and Hard The general consensus is that if a *primary status code* is greater than 4 then the message is a *hard* bounce, anything else is a soft bounce. Of course there's no way this is an absolute truth, and I'm sure there will end up being various complex things people will want to do with a soft vs. a hard bounce. Since there's no way Lamson could know what you want to base your decisions to honor a soft vs. hard bounce, it simply lets you figure out which type it is, and then analyze the information it's collected to determine the bounce. h2. Lamson's Simplification Lamson already converts the email into a normalized form, so the difficult part of parsing out the bounce information is simply trolling through the parts and finding all these randomly dispersed bounce headers. Then it's a matter of parsing the values into something that can be use for analysis in code. To simplify this, Lamson uses a dict of headers and matching regexes to search for in the email:
BOUNCE_MATCHERS = { 'Action': re.compile(r'(failed|delayed|delivered|relayed|expanded)', re.IGNORECASE | re.DOTALL), 'Content-Description': re.compile(r'(Notification|Undelivered Message|Delivery Report)', re.IGNORECASE | re.DOTALL), 'Diagnostic-Code': re.compile(r'(.+);\s*([0-9\-\.]+)?\s*(.*)', re.IGNORECASE | re.DOTALL), 'Final-Recipient': re.compile(r'(.+);\s*(.*)', re.IGNORECASE | re.DOTALL), 'Received': re.compile(r'(.+)', re.IGNORECASE | re.DOTALL), 'Remote-Mta': re.compile(r'(.+);\s*(.*)', re.IGNORECASE | re.DOTALL), 'Reporting-Mta': re.compile(r'(.+);\s*(.*)', re.IGNORECASE | re.DOTALL), 'Status': re.compile(r'([0-9]+)\.([0-9]+)\.([0-9]+)', re.IGNORECASE | re.DOTALL) }Each of these headers shows up about once, maybe more in the parts of a message that has bounced information, and they don't show up at all or rarely in regular messages. What Lamson does is just walk through each part, finds the matches, parses with the regex, and then produces something like this:
{'Action': [(u'failed',)], 'Content-Description': [(u'Notification',), (u'Undelivered Message',), (u'Delivery report',)], 'Diagnostic-Code': [(u'smtp', u'550-5.1.1', u"The email account that you tried to reach does...")], 'Final-Recipient': [(u'rfc822', u'asdfasdfasdfasdfasdfasdfewrqertrtyrthsfgdfgadfqeadvxzvz@gmail.com')], 'Received': [(u'by mail.zedshaw.com ...',)], 'Remote-Mta': [(u'dns', u'gmail-smtp-in.l.google.com')], 'Reporting-Mta': [(u'dns', u'mail.zedshaw.com')], 'Status': [(u'5', u'1', u'1')]}I've abbreviated some of the messages since they get stupidly long, but this shows you the information you get now. Lamson also provides a nice API through the lamson.bounce.BounceAnalyzer class that you'll see in a moment. bq. If you know of additional headers and formats that show up in bounces, let me know. h2. Bounce Probability Now, we've collected up all the different headers you might find in a bounce package, and we've attempted to parse the values into something meaningful. That means, the more of these headers that we find and successfully parse, the more likely the message is a bounce. Lamson keeps a score while it's finding the above headers, and for each one it finds and parses it adds a "point". It then produces a probability <= 1.0 that the message is a bounce or not. The more headers it finds, the closer to 1.0, the higher the chance. So far it looks like any message above 0.3 is most likely a bounce message, with only a few spams that are below that. Checking for a bounce then looks like this:
bm = mail.MailRequest(None,None,None, open("tests/bounce.msg").read()) assert bm.is_bounce() assert bm.bounce assert bm.bounce.score == 1.0 assert bm.bounce.probable()Which is from the test suite, and shows you various ways to get information about the probability of the message being a bounce. h2. Bounce Header Information Finally, you'll need to get at this parsed information to make any final decisions about the bounce. You might want to keep track of which MTAs consistently fail and include them in your black lists. You might want to tune your retries for different kind of soft bounces. Whatever you need to do, you have the original raw headers that Lamson found, but you also have a higher level API you can use:
if msg.is_bounce(): print "-------" print "hard", msg.bounce.is_hard() print "soft", msg.bounce.is_soft() print 'score', msg.bounce.score print 'primary', msg.bounce.primary_status print 'secondary', msg.bounce.secondary_status print 'combined', msg.bounce.combined_status print 'remote_mta', msg.bounce.remote_mta print 'reporting_mta', msg.bounce.reporting_mta print 'final_recipient', msg.bounce.final_recipient print 'diagnostic_codes', msg.bounce.diagnostic_codes print 'action', msg.bounce.actionThis is a simple sample that prints out the various bits of information available on a bounce message. Here's a sample of the output from the above:
hard True soft False score 1.0 primary (5, u'Permanent Failure') secondary (1, u'Addressing Status') combined (11, u'Bad destination mailbox address') remote_mta gmail-smtp-in.l.google.com reporting_mta mail.zedshaw.com final_recipient asdfasdfasdfasdfasdfasdfewrqertrtyrthsfgdfgadfqeadvxzvz@gmail.com diagnostic_codes (u'550-5.1.1', u"The email account that you tried...") action failedThis demonstrates how I've pulled out all of the error codes mentioned in RFC1893 and put them into Lamson so that you can get meaningful error messages for the various multi-level error codes you'll encounter. All of these error codes are available in the lamson.bounce module. h2. Comments Or Suggestions That's it for the review of Lamson's bounce detection. It's in the early stages but already showing a lot of promise. It turned out to be much easier than I anticipated, but only after doing the work in lamson.encoding to clean up emails. If you can think of situations that should be covered, and if you have samples of horrible bounce messages, then I'd love to have them. h2. Mailing Lists Coming Back Tomorrow I'll be using this to reimplement the mailing list sample and put it back into the bzr repository, then back online. I'll have a nice status update about that tomorrow as I finish it up. lamson-1.0pre11/doc/lamsonproject.org/input/blog/2009-07-14.txt 0000644 0000765 0000024 00000002006 11227105220 023130 0 ustar zedshaw staff Title: Lamson 0.9.6 Sometime Today I think I'm at a point where bounce detection and handling works really well and is ready for release. I've got it running on the latest Lamson demo "librelist.com":http://librelist.com/ and it works. I'll be pushing out a new release of Libre List in a little while that does some advanced bounce handling, and if that all works, then I'll push out 0.9.6. This release will include bounce handling, the ability to change how state is stored and maintained, the ability to route on the envelope rather than the headers, and the full code to the librelist.com site including "Django Integration":http://dpaste.de/3qGB/ which really isn't "integration". The plans for the 0.9.7 release are to remove all dependencies on SQLAlchemy to make it clear that you don't have to use it, and any bug fixes. After that release, assuming there's nothing major I need to add, I'll be doing the 1.0 pre-releases. Shoot me email if you have questions, or hop on #lamson on irc.freenode.org to chat. lamson-1.0pre11/doc/lamsonproject.org/input/blog/2009-07-19.txt 0000644 0000765 0000024 00000004462 11230540446 023154 0 ustar zedshaw staff Title: I Blame Bounces For The Delay I was going to release Lamson 0.9.6 much earlier than this, but as I worked on the bounce detection feature, I realized that if I just wrote that and a few other cleanups and features, I'd actually have a *1.0pre1* candidate on my hands instead of a 0.9.6 release. Instead of releasing 0.9.6, I continued to work on "librelist.com":http://librelist.com/ and got the software smart as hell. While working on librelist.com I managed to improve Lamson to the point where I'll be comfortable releasing a 1.0pre1 tomorrow. h2. What's librelist.com? If you go "librelist.com":http://librelist.com/ you can read about it, although the site is still rough and being written. It actually does function however, including basic archives. For people too lazy to click on the link: librelist.com is just a free mailing list service for open source projects, similar to freenode.org with IRC. The primary purpose is to give open source projects a mailing list they can use that has: * The code available (it's in the Lamson source). * No logins, signups, bundled services, captchas, tracking cookies, or other crap getting in your way. * No ads on your emails, ever. * Full access to your archives using rsync. * Easy and smart signup process. * Spam blocking and bounce protection galore (although spam is off right now). h2. Try The Lists I'll have an announcement tomorrow laying out the big things you'll get, but for now if you want to track Lamson and participate, as well as test the librelist.com code, then try subscribing to "lamson@librelist.com":mailto:lamson@librelist.com and saying hello. If you're interested in librelist.com development, then subscribe to "meta@librelist.com":mailto:meta@librelist.com and I'll be sending out announcements related to librelist there. h2. The Lamson 1.0pre1 Plan I haven't needed to touch Lamson much over the last few days, apart from some surgery on the Python Maildir class to add a safety feature. The code currently in bzr is also very stable and has been sped up a bit. I'm looking to basically spend tomorrow auditing the code and writing docs with the goal of pushing out a release in the evening. The goal then will be to make it solid and not add many more features so that I can get a 1.0 release out for real by the end of July. See you tomorrow! lamson-1.0pre11/doc/lamsonproject.org/input/blog/2009-07-20.txt 0000644 0000765 0000024 00000007546 11230764565 023164 0 ustar zedshaw staff Title: Lamson 1.0pre1 Released Tonight I'm releasing Lamson 1.0pre1 with all the latest improvements I've made while making "librelist.com":http://librelist.com/ and taking feedback from a few people using Lamson. The goal from now on will be to basically squash bugs and write docs, with only a rare feature or two as I find them needed on projects. h2. What's In This Release This release features really only four major enhancements, and a bunch of little fixes here and there. # There's now a working mailing list example in examples/librelist. # The router now uses the envelope header To (message.To) instead of the headers. # There is now very solid bounce detection and analysis. # Modules can specify anything as the state key (from, to+from, module+to, etc.) Here's the important changes from the change log by category: h3. Enhancements * 340 Added a version command finally, with some good information. * 330 There is now a safe=False option to lamson.queue.Queue to use a modified Maildir that md5 hashes the original hostname used. * 321 Routing it more deterministic and a bit quicker. It now uses the envelope To for routing. * 320 Refactored the routing to it is more deterministic and doesn't loop over the functions mulitple times. Still some work to do though. * 314 Bounce information now includes the original messages in easier to access forms. * 308 Got bounce handling working and tested. * 303 Slight reorg with the way @state_key_generator is implemented. * 302 Implemented a means to specify that a module will key off of different parts of a message. * 301 Got tired of not seeing errors during test runs so I made an option in the Router to explode on exceptions vs. log them. * 300 Bounce routing decorator for doing the actual routing of messages that are bounces. * 296 Implemented a bounce analysis method that is simple but seems to work on most bounce mail. h3. Bug Fixes * 329 Found an email that had a nasty header causing an edge case in the parser. * 325 Bounce detection was using the wrong email address. * 319 Removed the egg dependency on sqlalchemy. * 318 Moved the bounce stuff around in order to make it more advanced. * 307 Fixed up the @bounce_to decorator so that it returns the next state. * 294 Small bug in multipart handling where content types of 'message/' can be multipart too. h3. Librelist Related * 323 librelist.com sample is looking good, bounce handling is working, archives are started. * 322 Super bounce handling for librelist.com. * 306 Got the first cut of the whole app.handlers.admin working, including bounces, but Lamson bounce detection has a flaw. * 305 Got the basics of making mailing lists, managing subscribers. * 304 Initial commit of the librelist.com sample application. Also, librelist demonstrates how to integrate Lamson with "Django":http://www.djangoproject.com/ which turns out to just require a couple lines of code. I'll have some documentation on that soon. h3. Docs and Website Related * 317 Removed references to SQLAlchemy being needed in the docs. h2. Getting This Release As usual, try to install it using "easy_install":http://peak.telecommunity.com/DevCenter/EasyInstall and make sure you do the upgrade:
$ sudo easy_install -U lamsonThis release does not depend on SQLAlchemy anymore, since you can really use Lamson with any data source you can access with Python. As mentioned above, the librelist sample uses Django models. If you want to download the source you can visit the "releases":/releases/ page and grab it from there. h2. Mailing List Back Online Finally, the lamson mailing list is now back online, so you can subscribe to it and get help. Read "the lists page":/lists/ to find out more. h2. IRC Channel I'm also more active in the IRC channel #lamson on irc.freenode.org. Come by if you need help. Thanks again folks, and shoot me any bugs you find. lamson-1.0pre11/doc/lamsonproject.org/input/blog/2009-08-03.txt 0000644 0000765 0000024 00000035510 11235577723 023161 0 ustar zedshaw staff Title: Lamson 1.0pre2, HTML Email, Standalone Lamson 1.0pre2 features two features that might signal the end of the beginning or the beginning of the end, depending on your perspective: HTML Email and Lamson Standalone. HTML Email support comes from a new module "lamson.html":http://lamsonproject.org/docs/api/lamson.html-module.html that gives a nice template method to send out HTML to victims...uh...customers. Lamson Standalone will be a way to run Lamson as your customized email server instead of another server like Postfix. Both of these features will be done for Lamson 1.0, but are currently just getting started as of 1.0pre2. Once they're done that will be the last two requested features people had before 1.0. h2. Getting This Release As usual, you can get this "release from /releases":/releases/ or if you are too lazy to read that page then do this:
$ sudo easy_install -U lamsonIn addition, if you want play with the new features you'll need to install these (optional) Python libraries:
$ sudo easy_install -U pydns $ sudo easy_install -U beautifulsoup $ sudo easy_install -U markdown2 $ sudo easy_install -U clevercssI'll decide if I should make these mandatory or optional with the next release based on people's feedback. h2. Lamson Standalone Quite a few people have asked for a way to install Lamson as their primary email server. Usually they want it for just their own email on a machine just for them where installing something like Postfix is overkill or just too hard. This is entirely possible with Lamson, but it's kind of not the right use for it since simple delivery of mail is much better handled and implemented by an older server like Postfix. Rather than resist giving people a way to setup Lamson as their primary mail server, I've decided to start making it possible for them. By the next release (1.0pre3) you'll be able to run Lamson as your only email server for small installations handling mail for one person. I'll also include a "screen cast":/videos/ showing people how to use the standalone functionality to build a *Personal Mail Management Server* to filter and control their mail without any other mail server. I'm going to treat this as a first project for most people wanting to get started using Lamson for their email. As of 1.0pre2, the functionality that's available is code in the "lamson.server.Relay":http://lamsonproject.org/docs/api/lamson.server.Relay-class.html that will use "PyDNS":http://pydns.sourceforge.net/ to query up the MX host for recipient addresses. It's fairly primitive right now, but you use it by creating a Relay with the host explicitly None:
relay = server.Relay(host=None, port=25, debug=1)Once you do this the Relay will lookup the hosts rather than trying to send through a relay host. Don't go crazy with this yet, since it has to be tested with various kind of nasty email addressing out there, and it needs to have a way to generate a bounce when it has an error. h2. HTML Email Well, I broke down and implemented my idea for making HTML Email easy as hell to generate. In the past I didn't want to include simple HTML support because, well, HTML Email is annoying as hell and I didn't want to deal with the support headaches. I knew once I threw HTML generation into Lamson I'd have an army of marketing people using Lamson poorly to generate their marketing materials. I also think that HTML formatting in email doesn't work as a customer development strategy. Yet, every time I tell someone about Lamson, the very first, second, third, and 300th thing they ask is if it does HTML Email. Over and over and over this was the most important feature, above spam blocking, filtering, building applications, intelligent state management, or anything else that Lamson supports. Well, if the people want HTML email generation, then the people will get it. I introduce you to "lamson.html":http://lamsonproject.org/docs/api/lamson.html-module.html which makes it trivial to produce HTML in your email and doing it in a nice clean way using "CleverCSS":http://sandbox.pocoo.org/clevercss/ and "Jinja2":http://jinja.pocoo.org/2/ templates. Let's start with the simplest little example that will send out a disgusting html template:
import sys from lamson import html, server from config import testing relay = server.Relay(host=None, port=25, debug=1) hs = html.HtmlMail("style.css", "html_test.html") title = "Test Message HTML" msg = hs.respond(locals(), "content.markdown", From=sys.argv[1], To=sys.argv[2], Subject="Test %(title)s") relay.deliver(msg)You could run this right out of the Lamson source tree like this:
export PYTHONPATH=tests python sender.py thedude@thedude.com victim@gmail.comThe result would look like this in victim's email: !html_email_in_gmail.png! h2. How lamson.html.HtmlMail Works The rationale behind the HtmlMail class is that you'll typically have a template you want to send, some CSS, and then body content that you'll plug into the template as a slug for each person. What HtmlMail does is let you setup the template as HTML with Jinja2, and then specify your CSS using CleverCSS (which is way easier). When you go to send, you generate a "Markdown":http://daringfireball.net/projects/markdown/ template as the body. This lets you write the actual body of your HTML mail the same way you would a regular email, but still let's you get a good HTML output using the HTML/CSS templates. With markdown regular folks can write the marketing copy for your spam while keeping the design separated. HtmlMail then glues this all together by doing the following: # Parses your CleverCSS template (after running it through Jinja2). # Runs your content markdown through Jinja2 and "markdown2":http://code.google.com/p/python-markdown2/ just like all your other Lamson templates. # Injects your CSS *into* your HTML tags so that you get your styles even though many clients rip out the *style* tags from your HTML. See the output sample below. # Knits your generated content into your HTML template as the {{content}} variable. The end result is that these two lines of code from the above sample:
... hs = html.HtmlMail("style.css", "html_test.html") ... msg = hs.respond(locals(), "content.markdown", From=sys.argv[1], To=sys.argv[2], Subject="Test %(title)s")Will let you blast out well formatted HTML emails that have a high probability of displaying in most mail clients. h2. How The HTML And CSS Looks Here's what each of the files in the above sample look like. First the @style.css@:
body: margin: 10 padding: 20 background: green - 30 color: blue h1: font-size: 3em h2: font-size: 2em color: yellow h2: font-size: 1em p: padding: 0.3em background: red h2: color: yellow #bright: background: black color: white .dull: background: gray color: blackNotice that "CleverCSS":http://sandbox.pocoo.org/clevercss/ supports quite a few very cool features, like calling functions, nesting, variables, and calculations. Next you have the outer template @html_template.html@ that wraps your content markdown template. Notice that there's no *style* tag where the above CleverCSS is placed. That gets done later by lamson.html.HtmlMail.
<html> <head> <title>{{ title }}</title> </head> <body style="background: magenta"> <h1 class="bright">{{ title }}</h1> {{ content }} <h2 id="dull">All done.</h2> </body> </html>Finally, you have the @content.markdown@ file that has your basic markdown formatted email:
Hello ===== I would *love* for you to tell me what is going on here joe. NOW! Alright ------- This is the best I can come up with. ZedYou can use any format you want by changing a setting when you construct your HtmlMail object, but markdown seems to be the closest to what people would send as plain text for their email. In fact, I'd like to find a way to send the raw markdown as the plaintext version of each HTML email that goes out. With the above three components, you then get the following output:
<html> <head> <title>Test Message HTML</title> </head> <body style="margin: 10; padding: 20; background: #006200; color: blue"> <h1 class="bright" style="background: black; color: white">Test Message HTML</h1> <h1 style="font-size: 3em">Hello</h1> <p style="padding: 0.3em; background: red">I would <em>love</em> for you to tell me what is going on here joe. NOW!</p> <h2 style="color: yellow">Alright</h2> <p style="padding: 0.3em; background: red">This is the best I can come up with.</p> <p style="padding: 0.3em; background: red">Zed</p> <h2 id="dull" style="background: gray; color: black">All done.</h2> </body> </html>Like magic HtmlMail has taken your CleverCSS file and merged it into the tags of your HTML to style it, making it work in most clients without forcing you to do it manually. It does this by walking the CSS and generated HTML with "BeautifulSoup":http://www.crummy.com/software/BeautifulSoup/ and setting the *style* attribute as it goes. h2. Librelist JSON Archives There's been a ton of work on "librelist.com":http://librelist.com/ to make it work better, but the big feature is a complete JSON dump of the mail archives and a new "Archive Browser":http://librelist.com/browser/ for reading the archives. The browser is just a fast one day hack I did to prove that the JSON archive format was good. It uses "JQuery UI":http://jqueryui.com/ to build the interface, and has *no servers side software other than Nginx.* Yes, you read that right, there is no Django behind this, just a pure "Nginx":http://nginx.net/ setup serving files and directory indexes. The real meat of this setup is the Lamson server on librelist and an Nginx module. First, the librelist server generates a JSON version of each archived email which you "can see here":http://librelist.com/archives/lamson/2009/07/30/json/1248947932.M724307P15430Q6.09c5769d5b9f3d575cefc2ccb51877ec.json with your browser. This JSON actually loads as an object in JavaScript, so you have code like this:
function appendMessage(msg) { display = '<div id="message"><div id="header">' + '<div>' + msg.headers['Date'] + '</div>' + '<div id="addressing">' + msg.headers['From'] + '</div>' + '<div id="subject">' + msg.headers['Subject'] + '</div>' + '</div><div id="body"><pre>' + summarize(msg) + '</pre></div></div>' $("#messages").append(display); }Second, there's a tiny modification to Nginx's "autoindex":http://wiki.nginx.org/NginxHttpAutoindexModule I made so that it will generate a "JSON version of the index instead of HTML":http://librelist.com/archives/lamson/2009/07/ so that you can "browse" the stored JSON files via JavaScript:
function loadDay(date) { if(window.MAILING_LIST) { $.getJSON("/archives/" + window.MAILING_LIST + date + "/json/", {}, function(data) { $("#messages").empty(); base = data[0] msgs = data[1] for(i in msgs) { fetchMessage(base + msgs[i]) } }); } else { $("#messages").html("<h2>Now pick a list to look at for that day.</h2>"); } }With those two components I can export a JSON version of the mailing list archives without having to run a large web framework. h2. ChangeLog For those who are interested, here's the Bazaar logs from this release, separated into changes to Lamson and changes to the included librelist.com sample. h2. Lamson Changes * Cleanup and tuning of the html code. * Documenting the HtmlMail API. * New HTML email generation with CleverCSS support and merging the CSS into the HTML results. * Additional tests for the new MX query style of Relay. * Implemented a feature to let the relay do its own delivery, rather than needing a smart host. Requires PyDNS. * Cleaned up the build so that it removes junk from the examples. * Fixed up the lamson default help command so that it is more succinct. * Properly skips spam filtering if the spam databse doesn't exist. * Made the spam filter not barf if there's no spam db, which helps when testing. * Now don't bother storing the START state since it's the default. * Cleaned up mail some more and deprecated the MailRequest.msg and MailResponse.msg to be replaced with .base (since that's what it is). * Added a simple method to copy the attachments of a MailRequest to a MailResponse. * Got decent attachment copying implemented, mail api needs a bit of cleanup. * Return an empty string when trying to encode a none. * Minor bug in the safequeue, added logging so you can track oversize. * Getting the headers right for replies to work, removed the first message. * Added options to Queue and QueueReceiver to limit the size of incoming mail. * Added a version command finally, with some good information. h2. librelist.com Changes * Force the permissions to be correct for serving up the archives. * Make sure that bounces are always saved for later. * Json convert script can't go there. * Need to install simplejson to make the json work on librelist. * Prevent loop back of messages from lamson to itself, and stop messages that should not be considered lists like unbounce and noreply. * Implements a json version of the mailing list archives in addition to the regular MIME encoded ones. * Update of changes from the live server for setting up the spam system. * Migration to install the spam database on deployment. * Added spam filtering to the admin handler where it counts. * Confirmations are removed after they are verified. * Template for bad list name help. * Tweaked the formatting for the bad list name helper, and made sure that it returns to START. * Now giving out an error message for people who get the name format wrong. * Added additional headers needed for achives, and try to maintain the date and message-id headers. * Production and staging deployment scripts setup and a migration to make those work. * Implemented a simple bash based deployment setup for automating librelist deploys and migrations. * Nope, can't add the - yet without some regex wizardry. * Small change to allow - in the mailing list names. * Updated the messages librelist sends out for readability. lamson-1.0pre11/doc/lamsonproject.org/input/blog/2009-08-22.txt 0000644 0000765 0000024 00000012217 11243717140 023145 0 ustar zedshaw staff Title: Lamson 1.0pre4 Out, Lots Of Docs Done, 100% Coverage I happy to announce probably one of the last few releases before I officially put the 1.0 stamp on Lamson. This last 1% of the things I want to do takes a while, but it really puts a good shine on the project. What I've got in 1.0pre4 is "tons of docs":http://lamsonproject.org/docs/ and 100% test coverage. Most of the features that were added are nice-to-haves that I've found useful while developing my various sites. h2. Getting The Release As usual, @sudo easy_install lamson@ is your friend. If you want to use the HTML generation features, then you'll want to also install:
BeautifulSoup CleverCSS markdown2For completeness, here's the remaining packages I have installed in most of the virtualenvs for my applications:
ipython mock nose Jinja2 lockfile pydns spambayes chardet lxml python_daemon pytyrantYou can also grab "source releases":/releases/ and "download instructions":/download.html from this site if PyPI is failing you. h2. 100% Test Coverage First, I've managed to get the test coverage for Lamson up to 100%:
Name Stmts Exec Cover Missing ----------------------------------------------------- lamson 0 0 100% lamson.args 124 124 100% lamson.bounce 91 91 100% lamson.commands 176 176 100% lamson.confirm 57 57 100% lamson.encoding 238 238 100% lamson.handlers 0 0 100% lamson.handlers.log 4 4 100% lamson.handlers.queue 6 6 100% lamson.html 62 62 100% lamson.mail 140 140 100% lamson.queue 74 74 100% lamson.routing 218 218 100% lamson.server 80 80 100% lamson.spam 80 80 100% lamson.utils 56 56 100% lamson.version 1 1 100% lamson.view 22 22 100% ----------------------------------------------------- TOTAL 1429 1429 100% ----------------------------------------------------- Ran 156 tests in 16.305sIt's mostly a vanity thing, but doing this did uncover a couple of minor little bugs here and there, and it makes people feel better trusting Lamson. h2. Docs, Docs, Docs I firmly believe that a good software project has *both* "API":http://lamsonproject.org/docs/api/lamson-module.html style documentation and guided documentation. That's why I spent almost this whole time working on documenting all the parts of Lamson people need and making sure the generated docs were complete. What I've done is reorganized the documentation section so that you can find topic by their categories, and then documented most of the features people need to use daily. Here's some highlights: * "Unit Testing":http://lamsonproject.org/docs/unit_testing.html * "Confirmations":http://lamsonproject.org/docs/confirmations.html * "Filtering Spam":http://lamsonproject.org/docs/filtering_spam.html * "Bounce Detection":http://lamsonproject.org/docs/bounce_detection.html * "HTML Email Generation":http://lamsonproject.org/docs/html_email_generation.html * "Unicode Encoding/Decoding":http://lamsonproject.org/docs/unicode_encoding_and_decoding.html * "Hooking Into Django":http://lamsonproject.org/docs/hooking_into_django.html * "Tons more...":http://lamsonproject.org/docs/ I especially like how the HTML Email generation came out as a feature, but I really like the "Bounce Detection":http://lamsonproject.org/docs/bounce_detection.html documentation the most. h2. New Confirmation API This release has a few little bug fixes, but mostly it has a solidified feature for doing "Confirmations":http://lamsonproject.org/docs/confirmations.html easily. h2. HTML/Text Dual Email Previously the "HTML Email Generation":http://lamsonproject.org/docs/html_email_generation.html feature could only generate HTML email reliably, but I managed to track down a bug that lets you now craft dual email. I even went a step further and made it easy to simply use your markdown content templates as your text/plain alternative when you send out HTML Email. h2. Multiple Recipient Routing There was no way to avoid this, so I just implemented it. Now when a message is for multiple recipients (in the envelope) Lamson will route the message to each address. That means if it is destined for multiple people, then Lamson will effectively try to match each of those people and run them through your handlers separately. I was worried this would cause problems, but so far it's working fine. If you see that it can be used as a DOS attack, then I'll work on a way to limit the number of recipients that Lamson will allow (similar to how you can restrict the size of messages off a Queue). h2. Test And Report As usual, please test this out, review the documentation, and report back to me any problems you find. I'll be in #lamson on irc.freenode.org more, and will probably install a IRC bot to notify when I post new code to the repository. lamson-1.0pre11/doc/lamsonproject.org/input/blog/2009-09-07.txt 0000644 0000765 0000024 00000002417 11251323145 023147 0 ustar zedshaw staff Title: Lamson 1.0pre5 Out I just pushed Lamson 1.0pre5 to PyPI for your enjoyment: "PyPI Download":http://pypi.python.org/pypi?:action=files&name=lamson&version=1.0pre5 You can also get it from "releases":/releases/ or by doing the usual easy_install. This is a small bugfix release that only adds one small feature. If you're running with the SMTPReceiver, you can raise a server.SMTPError with an error code you want and the SMTPReceiver will return that as the error code to the client. For example, if you do this in your handler:
@route(".+") def START(message): raise server.SMTPError(550)Then it will generate a reasonable error message (using lamson.bounce messages) and give that to the client. You can also give a second parameter to SMTPError that will be the error message if you don't like the automatic ones. Only warning with this is use it sparingly, as it then ties you always running Lamson as an SMTPReceiver, instead of off the QueueReceiver. It also leaks out to malicious clients more information about your server than you might want them to know. In general, it's better to just ignore bad messages and save them in an undeliverable queue for later inspection. Alright, try it out and report bugs to me. Thanks. lamson-1.0pre11/doc/lamsonproject.org/input/blog/2009-09-26.txt 0000644 0000765 0000024 00000002264 11257445456 023170 0 ustar zedshaw staff Title: Quick Lamson 1.0pre6 Release, More This Weekend I put up a quick release of Lamson that fixes a bug in the gen project prototype. I recently switched all the domain names being used in the test suite to "localhost" or a similar nonexistant domain name. Why? Well, because some people liked to run the Lamson test suite or their fresh project test suite against a *live* SMTP server. The test suite would "send" mail to test@test.com and my own address, under the assumption that people would use the @lamson log@ test server. End result: Periodically I'd get bounce messages from test.com and other test suite mail. Well, I made that change, but forgot to update a couple places in the prototype, so newbies were seeing the tests for their fresh project fail and wondering if Lamson was broken. Sorry about that. You can grab this 1.0pre6 release from the "releases":/releases/ area, and you can also do the usual @easy_install -U lamson@ to get it. I'll be doing some more work to fix a few bugs this weekend and working on a fun new site for guitarists, so stay tuned. And if there's something in particular you want in Lamson just email the lamson@librelist.com mailing list. lamson-1.0pre11/doc/lamsonproject.org/input/blog/2009-12-12.txt 0000644 0000765 0000024 00000007011 11311017422 023123 0 ustar zedshaw staff Title: Lamson 1.0pre9 Is Out *UPDATE*: This used to be an announce for 1.0pre8, but python-daemon apparently has a bug that makes it not honor the TERM signal (still) so I had to go back to HUP. I just got done knocking down all the bugs in the new "Fossil":http://fossil-scm.org/ based "Lamson Support Site":http://support.lamsonproject.org/ and cooked up 1.0pre9. This has a ton of fixes, but I'll let the ChangeLog speak for itself:
=== 2009-12-12 === 22:49:43 [c2be9dedc3] *CURRENT* Version bump to 1.0pre9 thanks to python- daemon. (user: zedshaw tags: trunk, 1.0pre9) 22:43:04 [1718d014a5] Stupid fucking python-daemon doesn't recognize TERM yet. (user: zedshaw tags: trunk) 20:24:20 [b5796e14a1] Pointed at the tag instead of just the leaves. (user: zedshaw tags: trunk) 20:00:38 [42768ade2b] 1.0pre8 release announced and done. (user: zedshaw tags: trunk, 1.0pre8) 19:43:53 [bb1b0a0dae] Updated MailBase.__nonzero__ to include parts as well. (user: zedshaw tags: trunk) 19:40:51 [2916b83307] Created ChangeLog from fossil timeline of commits. (user: zedshaw tags: trunk) 19:36:14 [41530d8a34] Version bump to 1.0pre8 for release. (user: zedshaw tags: trunk) 19:31:21 [dfe158b282] Initial support of using Delivered-To or similar more exact To header. (user: zedshaw tags: trunk) 19:14:13 [e9988679b7] Updated prototype.zip for upcoming release. (user: zedshaw tags: trunk) 19:13:51 [98e94b6103] Potentially fixed the unicode/ascii issues with shelve storage of confirmations and routing. (user: zedshaw tags: trunk) 18:56:35 [6224ae68e4] Updated docs about confirmation to fix a doc bug. (user: zedshaw tags: trunk) 18:56:14 [103454c43c] Make things clean up better on INT or TERM (not HUP). (user: zedshaw tags: trunk) 18:25:29 [009451cabc] Added meta tags to the site so it can be found easier. I didn't know search engines still use that crap. (user: zedshaw tags: trunk) 18:22:24 [8908d7852d] Applied starttls, username, password patch to server. (user: zedshaw tags: trunk) 18:16:01 [9a9f9f37b6] Applied patch for handling odd email addresses and white- listing what headers have email addresses. (user: zedshaw tags: trunk) 18:06:06 [7605ef0517] Patch for queue bugs applied [bug 9d330c8f80] (user: zedshaw tags: trunk) 18:02:50 [ef9808c4f7] Point at the right archive browser. (user: zedshaw tags: trunk) 18:01:39 [3b3dc9391a] Better logging message on failure to connect to relay. (user: zedshaw tags: trunk) === 2009-12-05 === 18:05:40 [ddc4369b1d] Initial commit to the new fossil based support site. (user: zedshaw tags: trunk) 17:48:22 [8140fcad03] initial empty check-in (user: lamson tags: trunk)This closed out a bunch of bugs, so if you entered a bug in then go make sure that it's actually working for you. h2. Getting This Release As usual, you should be able to install it with PIP or easy_install, and you can get the raw downloads from the "releases":/releases/ section of the site. h2. Submitting Bugs Keep in mind that this isn't as heavily tested as other releases, so if you hit a bug, "report it":http://support.lamsonproject.org/ and I'll fix it. Even better, if you supply a patch then I'll consider giving you commit rights. h2. Potential Contributors Most of the changes were from supplied patches, and if you supplied a patch and want to contribute to Lamson, then contact me and I'll give you an account on the site so you can commit directly. lamson-1.0pre11/doc/lamsonproject.org/input/blog/2009-12-13.txt 0000644 0000765 0000024 00000000405 11311261207 023126 0 ustar zedshaw staff Title: Small Lamson 1.0pre10 Update There was a very minor one line fix that corrects a fairly obnoxious bug in Lamson due to my not understanding how __nonzero__ works. You'll want to update to the latest "release":/releases/ if you are following Lamson. lamson-1.0pre11/doc/lamsonproject.org/input/blog/._html_email_in_gmail.png 0000644 0000765 0000024 00000000505 11235004025 026226 0 ustar zedshaw staff Mac OS X 2 E ATTR } E ] * *com.apple.metadata:kMDItemIsScreenCapture 3 ,com.apple.metadata:kMDItemScreenCaptureType bplist00 bplist00Yselection lamson-1.0pre11/doc/lamsonproject.org/input/blog/html_email_in_gmail.png 0000644 0000765 0000024 00000127520 11235004025 026020 0 ustar zedshaw staff PNG IHDR 3 Bb $iCCPICC Profile xX 8?3c0jY6uZ2ƒRH("JZD=$Z*HH!d?C}}]߹ywy}~H ~ti- @͍K"#zĘaؒ.q |㟌y?(s @҈k0e0z x30G,M!"8^7p#o lyZ`22LCY `=? >KD.I!e# euαdA S #S`a :ج)/nbipmmFyԵܵ<