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
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'
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 a few 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
and then run
bin/config_list -i input_file test-list
In order to remove the pipeline attribute from a list, either use bin/withlist to remove the pipeline attribute or run bin/config_list as above with an input file containing
Converted from the Mailman FAQ Wizard
This is one of many Frequently Asked Questions.