Skip to end of metadata
Go to start of metadata

4.67. How do I implement a custom handler in Mailman, e.g. to mung headers or filter posts based on regex searches of the message body?

People sometimes want to do things with posts to Mailman lists that aren't supported by standard configuration options. Whether you want to do this on a sitewide basis or for one or a few lists, you may be able to implement a custom handler to do it.

A custom handler can be preferable to modifying existing handlers or other Mailman code for a few reasons. A custom handler and its installation (if done well) will not be affected by a Mailman upgrade. A custom handler whose logic is to apply to only one or a few lists can be installed for just those lists so it doesn't have to be coded to know whether or not to apply its process to 'this' list. A custom handler isolates all the changes in one module which can be simply removed or disabled if necessary.

A custom handler is useful if you want to modify the headers or body of a post or approve, hold, reject or discard a post based on programmable logic beyond what's available in the standard configuration.

When a post is received by Mailman, it is processed by IncomingRunner by passing it through a pipeline of handlers until a handler raises an exception or the pipeline is exhausted. Exceptions are raised by handlers to indicate that the message should be discarded or rejected or that the handler has held the message for approval. See the details of this in the _dopipeline function in Mailman/Queue/IncomingRunner.py.

Handlers themselves have certain characteristics. Let's say we are implementing a handler we'll call MyHandler. We code the handler in a module named MyHandler.py which we save with the other handlers in the Mailman/Handlers/ directory.

The handler module must define a function process(mlist, msg, msgdata) which will be called with those three arguments (the list instance, the message instance and the message metadata). This function in turn can call list methods and message methods, can examine and add/change/delete message metadata which will affect other handlers and can make changes to the message itself. Coding the handler module itself requires some knowledge of Python and Mailman and won't be discussed further here except that there is a simple template/example at MyHandler.py.

There is a withlist script at http://www.msapiro.net/scripts/test_handler.py (mirrored at http://fog.ccsf.cc.ca.us/~msapiro/scripts/test_handler.py) which can be used to unit test your handler against a test list and message and optional metadata.

Once the handler has been coded, saved and tested as above, we need to arrange for it to be in the pipeline in order for it to actually be called to process the message. The default pipeline is defined in Defaults.py by a long statement that begins with

 GLOBAL_PIPELINE = [
     # These are the modules that do tasks common to all delivery paths.
     'SpamDetect',

and ends with

     'Acknowledge',
     'ToOutgoing',
     ]

If we want to invoke our handler globally, we can insert it in this list, but we don't want to do it in Defaults.py, and we don't have to copy the whole GLOBAL_PIPELINE list to mm_cfg.py either. Suppose we want MyHandler to be called just before the standard Moderate handler. We can put

 GLOBAL_PIPELINE.insert(GLOBAL_PIPELINE.index('Moderate'), 'MyHandler')

in mm_cfg.py. This will find 'Moderate' in the list and insert 'MyHandler' immediately before it.

If, on the other hand, we wanted to replace say 'Moderate' with 'MyHandler' globally, we could put

 GLOBAL_PIPELINE[GLOBAL_PIPELINE.index('Moderate')] = 'MyHandler'

in mm_cfg.py.

Suppose we don't want to invoke our handler globally. Maybe we just want it to mung a certain header in messages to a certain list, or maybe we want to test it first by installing it only for a list named 'test-list'. We can make use of the fact that if a list defines a 'pipeline' attribute, that overrides GLOBAL_PIPELINE for that list. There are at least three ways we can do this.

We can use bin/withlist to define the pipeline attribute for the list, but that may involve a lot of error-prone typing, so I don't recommend it.

We can use bin/config_list. To do this we make an input file which contains

 mlist.pipeline = [

followed by the complete pipeline definition - i.e., everything in GLOBAL_PIPELINE including (in our example)

     'Replybot',
     'MyHandler',
     'Moderate',

and then the rest ending with

     'ToOutgoing',
     ]

and then run

 bin/config_list -i input_file test-list

Another way to do it is to use the extend.py mechanism to create the pipeline attribute for the list on the fly. To do this, we create (in our example) the file lists/test-list/extend.py which contains

 def extend(mlist):
     mlist.pipeline = [

followed by the rest of the pipeline definition as in the config_list example. Note however, that the first time Mailman saves the list, the pipeline attribute will be saved along with it, so simply removing extend.py from lists/test-list/ won't remove the special pipeline.

If you are concerned about future changes to the default GLOBAL_PIPELINE, following the above examples, you could put something like the following in lists/test-list/extend.py

 import copy
 from Mailman import mm_cfg
 def extend(mlist):
     mlist.pipeline = copy.copy(mm_cfg.GLOBAL_PIPELINE)
     # The next line inserts MyHandler ahead of Moderate.
     mlist.pipeline.insert(mlist.pipeline.index('Moderate'), 'MyHandler')
     # Alternatively, the next line replaces Moderate with MyHandler
     mlist.pipeline[mlist.pipeline.index('Moderate')] = 'MyHandler'
     # Pick one of the two above example alternatives.

In order to remove the pipeline attribute from a list, first remove the extend.py file if you used one, and then either use bin/withlist to remove the pipeline attribute or run bin/config_list as above with an input file containing

 del mlist.pipeline

Last changed on Fri Oct 12 20:21:09 2007 by Mark Sapiro
Converted from the Mailman FAQ Wizard

This is one of many Frequently Asked Questions.

Labels
  • None