Mailman can accept messages via LMTP (RFC 2033). Most modern mail servers support LMTP local delivery, so this is a very portable way to connect Mailman with your mail server.
Our LMTP server is fairly simple though; all it does is make sure that the message is destined for a valid endpoint, e.g. mylist-join@example.com.
Let’s start a testable LMTP queue runner.
>>> from mailman.testing import helpers
>>> master = helpers.TestableMaster()
>>> master.start('lmtp')
It also helps to have a nice LMTP client.
>>> lmtp = helpers.get_lmtp_client()
(220, '... Python LMTP queue runner 1.0')
>>> lmtp.lhlo('remote.example.org')
(250, ...)
If the mail server tries to send a message to a nonexistent mailing list, it will get a 550 error.
>>> lmtp.sendmail(
... 'anne.person@example.com',
... ['mylist@example.com'], """\
... From: anne.person@example.com
... To: mylist@example.com
... Subject: An interesting message
... Message-ID: <aardvark>
...
... This is an interesting message.
... """)
Traceback (most recent call last):
...
SMTPDataError: (550, 'Requested action not taken: mailbox unavailable')
Once the mailing list is created, the posting address is valid.
>>> create_list('mylist@example.com')
<mailing list "mylist@example.com" at ...>
>>> transaction.commit()
>>> lmtp.sendmail(
... 'anne.person@example.com',
... ['mylist@example.com'], """\
... From: anne.person@example.com
... To: mylist@example.com
... Subject: An interesting message
... Message-ID: <badger>
...
... This is an interesting message.
... """)
{}
>>> from mailman.testing.helpers import get_queue_messages
>>> messages = get_queue_messages('in')
>>> len(messages)
1
>>> print messages[0].msg.as_string()
From: anne.person@example.com
To: mylist@example.com
Subject: An interesting message
Message-ID: <badger>
X-MailFrom: anne.person@example.com
<BLANKLINE>
This is an interesting message.
>>> dump_msgdata(messages[0].msgdata)
_parsemsg : False
listname : mylist@example.com
original_size: ...
to_list : True
version : ...
The LMTP server understands each of the list’s sub-addreses, such as -join, -leave, -request and so on. If the message is posted to an invalid sub-address though, it is rejected.
>>> lmtp.sendmail(
... 'anne.person@example.com',
... ['mylist-bogus@example.com'], """\
... From: anne.person@example.com
... To: mylist-bogus@example.com
... Subject: Help
... Message-ID: <cow>
...
... Please help me.
... """)
Traceback (most recent call last):
...
SMTPDataError: (550, 'Requested action not taken: mailbox unavailable')
But the message is accepted if posted to a valid sub-address.
>>> lmtp.sendmail(
... 'anne.person@example.com',
... ['mylist-request@example.com'], """\
... From: anne.person@example.com
... To: mylist-request@example.com
... Subject: Help
... Message-ID: <dog>
...
... Please help me.
... """)
{}
Depending on the subaddress, there is a message in the appropriate queue for later processing. For example, all -request messages are put into the command queue for processing.
>>> messages = get_queue_messages('command')
>>> len(messages)
1
>>> print messages[0].msg.as_string()
From: anne.person@example.com
To: mylist-request@example.com
Subject: Help
Message-ID: <dog>
X-MailFrom: anne.person@example.com
<BLANKLINE>
Please help me.
>>> dump_msgdata(messages[0].msgdata)
_parsemsg : False
listname : mylist@example.com
original_size: ...
subaddress : request
version : ...
A message to the -bounces address goes to the bounce processor.
>>> lmtp.sendmail(
... 'mail-daemon@example.com',
... ['mylist-bounces@example.com'], """\
... From: mail-daemon@example.com
... To: mylist-bounces@example.com
... Subject: A bounce
... Message-ID: <elephant>
...
... Bouncy bouncy.
... """)
{}
>>> messages = get_queue_messages('bounces')
>>> len(messages)
1
>>> dump_msgdata(messages[0].msgdata)
_parsemsg : False
listname : mylist@example.com
original_size: ...
subaddress : bounces
version : ...
Confirmation messages go to the command processor...
>>> lmtp.sendmail(
... 'anne.person@example.com',
... ['mylist-confirm@example.com'], """\
... From: anne.person@example.com
... To: mylist-confirm@example.com
... Subject: A bounce
... Message-ID: <falcon>
...
... confirm 123
... """)
{}
>>> messages = get_queue_messages('command')
>>> len(messages)
1
>>> dump_msgdata(messages[0].msgdata)
_parsemsg : False
listname : mylist@example.com
original_size: ...
subaddress : confirm
version : ...
...as do join messages...
>>> lmtp.sendmail(
... 'anne.person@example.com',
... ['mylist-join@example.com'], """\
... From: anne.person@example.com
... To: mylist-join@example.com
... Message-ID: <giraffe>
...
... """)
{}
>>> messages = get_queue_messages('command')
>>> len(messages)
1
>>> dump_msgdata(messages[0].msgdata)
_parsemsg : False
listname : mylist@example.com
original_size: ...
subaddress : join
version : ...
>>> lmtp.sendmail(
... 'anne.person@example.com',
... ['mylist-subscribe@example.com'], """\
... From: anne.person@example.com
... To: mylist-subscribe@example.com
... Message-ID: <hippopotamus>
...
... """)
{}
>>> messages = get_queue_messages('command')
>>> len(messages)
1
>>> dump_msgdata(messages[0].msgdata)
_parsemsg : False
listname : mylist@example.com
original_size: ...
subaddress : join
version : ...
...and leave messages.
>>> lmtp.sendmail(
... 'anne.person@example.com',
... ['mylist-leave@example.com'], """\
... From: anne.person@example.com
... To: mylist-leave@example.com
... Message-ID: <iguana>
...
... """)
{}
>>> messages = get_queue_messages('command')
>>> len(messages)
1
>>> dump_msgdata(messages[0].msgdata)
_parsemsg : False
listname : mylist@example.com
original_size: ...
subaddress : leave
version : ...
>>> lmtp.sendmail(
... 'anne.person@example.com',
... ['mylist-unsubscribe@example.com'], """\
... From: anne.person@example.com
... To: mylist-unsubscribe@example.com
... Message-ID: <jackal>
...
... """)
{}
>>> messages = get_queue_messages('command')
>>> len(messages)
1
>>> dump_msgdata(messages[0].msgdata)
_parsemsg : False
listname : mylist@example.com
original_size: ...
subaddress : leave
version : ...
Messages to the -owner address go to the incoming processor.
>>> lmtp.sendmail(
... 'anne.person@example.com',
... ['mylist-owner@example.com'], """\
... From: anne.person@example.com
... To: mylist-owner@example.com
... Message-ID: <kangaroo>
...
... """)
{}
>>> messages = get_queue_messages('in')
>>> len(messages)
1
>>> dump_msgdata(messages[0].msgdata)
_parsemsg : False
envsender : changeme@example.com
listname : mylist@example.com
original_size: ...
subaddress : owner
to_owner : True
version : ...
>>> master.stop()