This post talks about the basic messaging flow in mailman and then a little detail about what are rules and chains in Mailman. So a message can be injected in mailman system using a LMTP server via LMTP. LMTP(Local Mail Transfer Protocol) is a derivative of ESMTP(Extended SMTP), which is a extension of well known SMTP(Simple Mail Transfer Protocol). LMTP runner parses the message into a tuple of type (mailing_list, message, message_data) and stores it in a serialized form called python pickle(.pck) file, which is then queued in either one of incoming queue, bounce queue or command processing queue. If the message parsing fails then it is discarded. Queues are nothing but a set of directories managed by switchboards, where all the messages are stored till a runner wakes up and sends them one by one for processing according to the queue that they were queued in, e.g. IN runner sends to file to pipeline queue where i Below is a graph is decided if the message will be accepted, discarded or held for moderation. Below is a graph depicting the message flow in mailman.

How is it decided if a message is 'fit' for posting? This process is called Moderation where a message is tested against a set of rules. Rules are simple checks which return True if the rule hits and False in-case it misses. Moderation does not change anything in message, but it may record the processing information(like rule hits and misses) information in a metadata dictionary. Each rule has the following structure:

1@implementer(IRule)
2class New_Rule:
3
4    def check(self, mlist, msg, msgdata):
5        if foo == foobar:
6            return True
7        else:
8            return False

You can write your own rules to check whatever criteria you want mailman to check before accepting the message. All the rules reside inside /src/mailman/rules/ in mailman source code. All of the rules are arranged in a sequential manner to create a chain. Each list has two default start chains associated with it - first for normal postings and second one is for admin postings. The incoming runner checks who the message is addressed to and accordingly the moderation occurs for admin or normal postings.

The default-builtin-chain has a predefined list of rules to check before the message is discarded, held or accepted. In mailman3 the list is as follows in the order in which they are called:

 1    _link_descriptions = (
 2        ('approved', LinkAction.jump, 'accept'),
 3        ('emergency', LinkAction.jump, 'hold'),
 4        ('loop', LinkAction.jump, 'discard'),
 5        # Determine whether the member or nonmember has an action shortcut.
 6        ('member-moderation', LinkAction.jump, 'moderation'),
 7        # Do all of the following before deciding whether to hold the message.
 8        ('signature', LinkAction.jump, 'discard'),
 9        ('administrivia', LinkAction.defer, None),
10        ('implicit-dest', LinkAction.defer, None),
11        ('max-recipients', LinkAction.defer, None),
12        ('max-size', LinkAction.defer, None),
13        ('news-moderation', LinkAction.defer, None),
14        ('no-subject', LinkAction.defer, None),
15        ('suspicious-header', LinkAction.defer, None),
16        # Now if any of the above hit, jump to the hold chain.
17        ('any', LinkAction.jump, 'hold'),
18        # Take a detour through the header matching chain, which we'll create
19        # later.
20        ('truth', LinkAction.detour, 'header-match'),
21        # Check for nonmember moderation.
22        ('nonmember-moderation', LinkAction.jump, 'moderation'),
23        # Finally, the builtin chain jumps to acceptance.
24        ('truth', LinkAction.jump, 'accept'),
25     )

In the above list each entry consists of a tuple of the form ('rule_name', LinkAction.action, 'target'). The first element ,as evident from the name, is the name of the rule that is to be called, the second element tell about what action is taken if the rule hits(or return True). The last part is the target queue to which the message is copied to if the rule hits(or you may customise it of course for when rule misses), it is `None` incase the rule does not have action. According to your convenience you can create your own chains. In the above list the signature rule is the one I added to check for openpgp-signature.