What is pymalist
Pymalist is a minimal and modular mail list software for Python
How do I get source?
Grab it from http://github.com/gabrys/pymalist
How does pymalist work
pymalist works like this:
- using fetcher it fetches mail
- if NothingMore exception is raised program ends
- if other exception is raised, it is being passed to reactor and program ends
- mail is processed with processor
- processed mail is distributed with distributor
- go to #1
In component raises an exception (other than NothingMore) it gets handled by reactor.
You have to supply fetcher, processor, distributor and reactor objects the the play method. Basically you need to write a few lines of code to do it, but you can use built-in fetchers, distributors, processors and reactors.
pymalist flow diagram --------------- --------------- | | exception | | | fetcher |--------------->| reactor | | | | | --------------- --------------- | ^ ^ | | | --------------- | | | | exception | | | processor |--------------------- | | | | --------------- | | | | | --------------- | | | exception | | distributor |--------------------------- | | ---------------
The basic components
The basic fetchers are:
- Pop3Fetcher - to fetch mails from dedicated POP3 account
- StdInFetcher - to get one mail from stdin (from procmail)
The basic distributors are:
- SmtpDistributor - to distribute mails with SMTP server
- MultipleDistributor - distribute mails with many distributors
- DeliverToDistributor - distribute to given list of recipients using given distributor
The basic processors are:
- SingleListProcessor - used for a sigle list list (does not check To: header)
- ManyListsProcessor - container of many SingleListProcessors that directs the mail to be processed by right one
The basic reactor is:
- StdErrLogger - loggs each exception to standard error
Example mail list server
Example script, that fetches all mail from POP3 account processes two mail lists and distributes mails with SMTP server and send each copy of mail to archive mail address using the same SMTP distributor as for distributing the emails to real recipients:
from pymalist import play from fetchers import * from processors import * from distributors import * from reactors import * pop3 = Pop3Fetcher( host = 'pop3.example.com', user = 'example', password = 'hackyou', ssl = True, ) smtp = SmtpDistributor( host = 'smtp.example.com', user = 'example', password = 'hackyou', tls = True, ) play( fetcher = pop3, distributor = MultipleDistributor( smtp, DeliverToDistributor(['firstname.lastname@example.org'], smtp), ) processor = MoreListsProcessor( SingleListProcessor( list_mail = 'Red mail list <email@example.com>', subject_prefix = '[Red] ', subscribers = ['firstname.lastname@example.org', 'email@example.com'] ), SingleListProcessor( list_mail = 'Green mail list <firstname.lastname@example.org>', subject_prefix = '[Green] ', subscribers = ['email@example.com', 'firstname.lastname@example.org'] ), ), reactor=StdErrLogger(), )
You'll want to run the script periodically (for example in cron).
Customization or adding new features
If you need another logic to fetch, distribute, process or react to exceptions, you need to create a custom fetcher, distributor, processor or reactor.
Every fetcher object must have "fetch" method, that returns the next mail as a email.Message object. Convert it from message string by email.message_from_string(message_string). If there is no new mail, raise fetchers.NothingMore exception.
Every distributor object must have "distribute" method, that gets mail message (email.Message) with has additional properties set:
- ml_sender - the original sender
- ml_send_to - addresses to send message to
- ml_list - "Mail List Name" <address@somewhere>
Distributor should distribute the mails (but of course it can do anything - store, archive, send, play music, eject CD tray and play 8-bit music on PC speaker).
Every processor must have "process" method, that takes one argument - the mail message fetched and returns mail message enhanced with at least ml_sender, ml_send_to and ml_list properties (see above for explanation). An exception should be raised if the message should not be distributed because of some violation (person can't post to list or something). If message should be delivered, but there is zero recipients, just set ml_send_to property to empty list: .
Every reactor implements "react" method that takes two arguments: type of error (currently string "fetcher", "processor" or "distributor") and the exception that was raised. NothingMore exception raised by fetcher is not passed to reactor.
Why did I write this
I wrote this software in a few hours, because I didn't find any good mail list software that was flexible enough to
- read email from a remote mailbox (with POP3 or IMAP)
- distribute emails with a remote SMTP server (possibly using the same mailbox)
- require no root permissions
- is modular enough
I know my mail list is not perfect, because it has no input/output queues, no (un)subscribe features nor mail archive, but using the concepts of fetchers, distributors, processors and reactors they can be easily plugged-in.
Author: Piotr Gabryjeluk
Started: 22 May 2009