diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/mailman/app/inject.py | 8 | ||||
| -rw-r--r-- | src/mailman/commands/docs/info.rst | 29 | ||||
| -rw-r--r-- | src/mailman/config/config.py | 1 | ||||
| -rw-r--r-- | src/mailman/config/mailman.cfg | 4 | ||||
| -rw-r--r-- | src/mailman/config/schema.cfg | 2 | ||||
| -rw-r--r-- | src/mailman/config/tests/test_configuration.py | 6 | ||||
| -rw-r--r-- | src/mailman/docs/NEWS.rst | 8 | ||||
| -rw-r--r-- | src/mailman/rest/docs/queues.rst | 174 | ||||
| -rw-r--r-- | src/mailman/rest/queues.py | 129 | ||||
| -rw-r--r-- | src/mailman/rest/root.py | 13 | ||||
| -rw-r--r-- | src/mailman/rest/tests/test_queues.py | 107 | ||||
| -rw-r--r-- | src/mailman/rest/tests/test_systemconf.py | 1 | ||||
| -rw-r--r-- | src/mailman/runners/docs/command.rst | 17 | ||||
| -rw-r--r-- | src/mailman/runners/docs/incoming.rst | 10 |
14 files changed, 477 insertions, 32 deletions
diff --git a/src/mailman/app/inject.py b/src/mailman/app/inject.py index 77ad8dedb..7e8c359ea 100644 --- a/src/mailman/app/inject.py +++ b/src/mailman/app/inject.py @@ -49,6 +49,8 @@ def inject_message(mlist, msg, recipients=None, switchboard=None, **kws): :type switchboard: string :param kws: Additional values for the message metadata. :type kws: dictionary + :return: filebase of enqueued message + :rtype: string """ if switchboard is None: switchboard = 'in' @@ -68,7 +70,7 @@ def inject_message(mlist, msg, recipients=None, switchboard=None, **kws): msgdata.update(kws) if recipients is not None: msgdata['recipients'] = recipients - config.switchboards[switchboard].enqueue(msg, **msgdata) + return config.switchboards[switchboard].enqueue(msg, **msgdata) @@ -91,6 +93,8 @@ def inject_text(mlist, text, recipients=None, switchboard=None, **kws): :type switchboard: string :param kws: Additional values for the message metadata. :type kws: dictionary + :return: filebase of enqueued message + :rtype: string """ message = message_from_string(text, Message) - inject_message(mlist, message, recipients, switchboard, **kws) + return inject_message(mlist, message, recipients, switchboard, **kws) diff --git a/src/mailman/commands/docs/info.rst b/src/mailman/commands/docs/info.rst index 8bc7579e6..6ce223403 100644 --- a/src/mailman/commands/docs/info.rst +++ b/src/mailman/commands/docs/info.rst @@ -62,20 +62,21 @@ definition. Python ... ... File system paths: - ARCHIVE_DIR = /var/lib/mailman/archives - BIN_DIR = /sbin - DATA_DIR = /var/lib/mailman/data - ETC_DIR = /etc - EXT_DIR = /etc/mailman.d - LIST_DATA_DIR = /var/lib/mailman/lists - LOCK_DIR = /var/lock/mailman - LOCK_FILE = /var/lock/mailman/master.lck - LOG_DIR = /var/log/mailman - MESSAGES_DIR = /var/lib/mailman/messages - PID_FILE = /var/run/mailman/master.pid - QUEUE_DIR = /var/spool/mailman - TEMPLATE_DIR = .../mailman/templates - VAR_DIR = /var/lib/mailman + ARCHIVE_DIR = /var/lib/mailman/archives + BIN_DIR = /sbin + CFG_FILE = .../test.cfg + DATA_DIR = /var/lib/mailman/data + ETC_DIR = /etc + EXT_DIR = /etc/mailman.d + LIST_DATA_DIR = /var/lib/mailman/lists + LOCK_DIR = /var/lock/mailman + LOCK_FILE = /var/lock/mailman/master.lck + LOG_DIR = /var/log/mailman + MESSAGES_DIR = /var/lib/mailman/messages + PID_FILE = /var/run/mailman/master.pid + QUEUE_DIR = /var/spool/mailman + TEMPLATE_DIR = .../mailman/templates + VAR_DIR = /var/lib/mailman .. _`Filesystem Hierarchy Standard`: http://www.pathname.com/fhs/ diff --git a/src/mailman/config/config.py b/src/mailman/config/config.py index 1aab4a82a..779fa27e5 100644 --- a/src/mailman/config/config.py +++ b/src/mailman/config/config.py @@ -166,6 +166,7 @@ class Configuration: # path is relative. var_dir = os.environ.get('MAILMAN_VAR_DIR', category.var_dir) substitutions = dict( + cwd = os.getcwd(), argv = bin_dir, # Directories. bin_dir = category.bin_dir, diff --git a/src/mailman/config/mailman.cfg b/src/mailman/config/mailman.cfg index ab2853e42..aea420280 100644 --- a/src/mailman/config/mailman.cfg +++ b/src/mailman/config/mailman.cfg @@ -27,6 +27,10 @@ # where the mailman.cfg file lives. var_dir: $cfg_file/../.. +[paths.here] +# Layout where the var directory is put in the current working directory. +var_dir: $cwd/var + [paths.fhs] # Filesystem Hiearchy Standard 2.3 # http://www.pathname.com/fhs/pub/fhs-2.3.html diff --git a/src/mailman/config/schema.cfg b/src/mailman/config/schema.cfg index c7a63e794..4a896eec5 100644 --- a/src/mailman/config/schema.cfg +++ b/src/mailman/config/schema.cfg @@ -59,7 +59,7 @@ pre_hook: post_hook: # Which paths.* file system layout to use. -layout: dev +layout: here # Can MIME filtered messages be preserved by list owners? filtered_messages_are_preservable: no diff --git a/src/mailman/config/tests/test_configuration.py b/src/mailman/config/tests/test_configuration.py index b4b2145c0..253b63239 100644 --- a/src/mailman/config/tests/test_configuration.py +++ b/src/mailman/config/tests/test_configuration.py @@ -116,14 +116,14 @@ layout: nonesuch self.assertEqual(cm.exception.args, (1,)) def test_path_expansion_infloop(self): - # A path expansion never completes because it references a - # non-existent substitution variable. + # A path expansion never completes because it references a non-existent + # substitution variable. fd, filename = tempfile.mkstemp() self.addCleanup(os.remove, filename) os.close(fd) with open(filename, 'w') as fp: print("""\ -[paths.dev] +[paths.here] log_dir: $nopath/log_dir """, file=fp) config = Configuration() diff --git a/src/mailman/docs/NEWS.rst b/src/mailman/docs/NEWS.rst index 342ce75eb..9115c9bdb 100644 --- a/src/mailman/docs/NEWS.rst +++ b/src/mailman/docs/NEWS.rst @@ -19,6 +19,14 @@ Configuration default ``[paths.dev]`` section, ``$var_dir`` is now specified relative to ``$cfg_file`` so that it won't accidentally be relative to the current working directory, if ``-C`` is given. + * ``$cwd`` is now an additional substitution variable for the ``mailman.cfg`` + file's ``[paths.*]`` sections. A new ``[paths.here]`` section is added, + which puts the ``var_dir`` in ``$cwd``. It is made the default layout. + +REST +---- + * You can now view the contents of, inject messages into, and delete messages + from the various queue directories via the ``<api>/queues`` resource. 3.0 beta 5 -- "Carve Away The Stone" diff --git a/src/mailman/rest/docs/queues.rst b/src/mailman/rest/docs/queues.rst new file mode 100644 index 000000000..861b6806f --- /dev/null +++ b/src/mailman/rest/docs/queues.rst @@ -0,0 +1,174 @@ +====== +Queues +====== + +You can get information about what messages are currently in the Mailman +queues by querying the top-level ``queues`` resource. Of course, this +information may be out-of-date by the time you receive a response, since queue +management is asynchronous, but the information will be as current as +possible. + +You can get the list of all queue names. + + >>> dump_json('http://localhost:9001/3.0/queues') + entry 0: + count: 0 + directory: .../queue/archive + files: [] + http_etag: ... + name: archive + self_link: http://localhost:9001/3.0/queues/archive + entry 1: + count: 0 + directory: .../queue/bad + files: [] + http_etag: ... + name: bad + self_link: http://localhost:9001/3.0/queues/bad + entry 2: + count: 0 + directory: .../queue/bounces + files: [] + http_etag: ... + name: bounces + self_link: http://localhost:9001/3.0/queues/bounces + entry 3: + count: 0 + directory: .../queue/command + files: [] + http_etag: ... + name: command + self_link: http://localhost:9001/3.0/queues/command + entry 4: + count: 0 + directory: .../queue/digest + files: [] + http_etag: ... + name: digest + self_link: http://localhost:9001/3.0/queues/digest + entry 5: + count: 0 + directory: .../queue/in + files: [] + http_etag: ... + name: in + self_link: http://localhost:9001/3.0/queues/in + entry 6: + count: 0 + directory: .../queue/nntp + files: [] + http_etag: ... + name: nntp + self_link: http://localhost:9001/3.0/queues/nntp + entry 7: + count: 0 + directory: .../queue/out + files: [] + http_etag: ... + name: out + self_link: http://localhost:9001/3.0/queues/out + entry 8: + count: 0 + directory: .../queue/pipeline + files: [] + http_etag: ... + name: pipeline + self_link: http://localhost:9001/3.0/queues/pipeline + entry 9: + count: 0 + directory: .../queue/retry + files: [] + http_etag: ... + name: retry + self_link: http://localhost:9001/3.0/queues/retry + entry 10: + count: 0 + directory: .../queue/shunt + files: [] + http_etag: ... + name: shunt + self_link: http://localhost:9001/3.0/queues/shunt + entry 11: + count: 0 + directory: .../queue/virgin + files: [] + http_etag: ... + name: virgin + self_link: http://localhost:9001/3.0/queues/virgin + http_etag: ... + self_link: http://localhost:9001/3.0/queues + start: 0 + total_size: 12 + +Query an individual queue to get a count of, and the list of file base names +in the queue. There are currently no files in the ``bad`` queue. + + >>> dump_json('http://localhost:9001/3.0/queues/bad') + count: 0 + directory: .../queue/bad + files: [] + http_etag: ... + name: bad + self_link: http://localhost:9001/3.0/queues/bad + +We can inject a message into the ``bad`` queue. It must be destined for an +existing mailing list. + + >>> dump_json('http://localhost:9001/3.0/lists', { + ... 'fqdn_listname': 'ant@example.com', + ... }) + content-length: 0 + date: ... + location: http://localhost:9001/3.0/lists/ant.example.com + server: WSGIServer/0.2 CPython/3.4.2 + status: 201 + +While list creation takes an FQDN list name, injecting a message to the queue +requires a List ID. + + >>> dump_json('http://localhost:9001/3.0/queues/bad', { + ... 'list_id': 'ant.example.com', + ... 'text': """\ + ... From: anne@example.com + ... To: ant@example.com + ... Subject: Testing + ... + ... """}) + content-length: 0 + date: ... + location: http://localhost:9001/3.0/queues/bad/... + server: ... + status: 201 + +And now the ``bad`` queue has at least one message in it. + + >>> dump_json('http://localhost:9001/3.0/queues/bad') + count: 1 + directory: .../queue/bad + files: ['...'] + http_etag: ... + name: bad + self_link: http://localhost:9001/3.0/queues/bad + +We can delete the injected message. + + >>> json = call_http('http://localhost:9001/3.0/queues/bad') + >>> len(json['files']) + 1 + >>> dump_json('http://localhost:9001/3.0/queues/bad/{}'.format( + ... json['files'][0]), + ... method='DELETE') + content-length: 0 + date: ... + server: ... + status: 204 + +And now the queue has no files. + + >>> dump_json('http://localhost:9001/3.0/queues/bad') + count: 0 + directory: .../queue/bad + files: [] + http_etag: ... + name: bad + self_link: http://localhost:9001/3.0/queues/bad diff --git a/src/mailman/rest/queues.py b/src/mailman/rest/queues.py new file mode 100644 index 000000000..f1007052e --- /dev/null +++ b/src/mailman/rest/queues.py @@ -0,0 +1,129 @@ +# Copyright (C) 2015 by the Free Software Foundation, Inc. +# +# This file is part of GNU Mailman. +# +# GNU Mailman is free software: you can redistribute it and/or modify it under +# the terms of the GNU General Public License as published by the Free +# Software Foundation, either version 3 of the License, or (at your option) +# any later version. +# +# GNU Mailman is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +# more details. +# +# You should have received a copy of the GNU General Public License along with +# GNU Mailman. If not, see <http://www.gnu.org/licenses/>. + +"""<api>/queues.""" + +__all__ = [ + 'AQueue', + 'AQueueFile', + 'AllQueues', + ] + + +import six + +from mailman.config import config +from mailman.app.inject import inject_text +from mailman.interfaces.listmanager import IListManager +from mailman.rest.helpers import ( + CollectionMixin, bad_request, created, etag, no_content, not_found, okay, + paginate, path_to) +from mailman.rest.validator import Validator +from zope.component import getUtility + + + +class _QueuesBase(CollectionMixin): + """Shared base class for queues.""" + + def _resource_as_dict(self, name): + """See `CollectionMixin`.""" + switchboard = config.switchboards[name] + files = switchboard.files + return dict( + name=switchboard.name, + directory=switchboard.queue_directory, + count=len(files), + files=files, + self_link=path_to('queues/{}'.format(name)), + ) + + @paginate + def _get_collection(self, request): + """See `CollectionMixin`.""" + return sorted(config.switchboards) + + + +class AQueue(_QueuesBase): + """A single queue.""" + + def __init__(self, name): + self._name = name + + def on_get(self, request, response): + """Return a single queue resource.""" + if self._name not in config.switchboards: + not_found(response) + else: + okay(response, self._resource_as_json(self._name)) + + def on_post(self, request, response): + """Inject a message into the queue.""" + try: + validator = Validator(list_id=six.text_type, + text=six.text_type) + values = validator(request) + except ValueError as error: + bad_request(response, str(error)) + return + list_id = values['list_id'] + mlist = getUtility(IListManager).get_by_list_id(list_id) + if mlist is None: + bad_request(response, 'No such list: {}'.format(list_id)) + return + try: + filebase = inject_text( + mlist, values['text'], switchboard=self._name) + except Exception as error: + bad_request(response, str(error)) + return + else: + location = path_to('queues/{}/{}'.format(self._name, filebase)) + created(response, location) + + + +class AQueueFile: + def __init__(self, name, filebase): + self._name = name + self._filebase = filebase + + def on_delete(self, request, response): + """Delete the queue file.""" + switchboard = config.switchboards.get(self._name) + if switchboard is None: + not_found(response, 'No such queue: {}'.format(self._name)) + return + try: + switchboard.dequeue(self._filebase) + except FileNotFoundError: + not_found(response, + 'No such queue file: {}'.format(self._filebase)) + else: + no_content(response) + + + +class AllQueues(_QueuesBase): + """All queues.""" + + def on_get(self, request, response): + """<api>/queues""" + resource = self._make_collection(request) + resource['self_link'] = path_to('queues') + okay(response, etag(resource)) diff --git a/src/mailman/rest/root.py b/src/mailman/rest/root.py index 34f058bf2..381bec751 100644 --- a/src/mailman/rest/root.py +++ b/src/mailman/rest/root.py @@ -36,6 +36,7 @@ from mailman.rest.helpers import ( from mailman.rest.lists import AList, AllLists, Styles from mailman.rest.members import AMember, AllMembers, FindMembers from mailman.rest.preferences import ReadOnlyPreferences +from mailman.rest.queues import AQueue, AQueueFile, AllQueues from mailman.rest.templates import TemplateFinder from mailman.rest.users import AUser, AllUsers from zope.component import getUtility @@ -213,3 +214,15 @@ class TopLevel: content_type = None return TemplateFinder( fqdn_listname, template, language, content_type) + + @child() + def queues(self, request, segments): + """/<api>/queues[/<name>[/file]]""" + if len(segments) == 0: + return AllQueues() + elif len(segments) == 1: + return AQueue(segments[0]), [] + elif len(segments) == 2: + return AQueueFile(segments[0], segments[1]), [] + else: + return BadRequest(), [] diff --git a/src/mailman/rest/tests/test_queues.py b/src/mailman/rest/tests/test_queues.py new file mode 100644 index 000000000..43659a2e4 --- /dev/null +++ b/src/mailman/rest/tests/test_queues.py @@ -0,0 +1,107 @@ +# Copyright (C) 2015 by the Free Software Foundation, Inc. +# +# This file is part of GNU Mailman. +# +# GNU Mailman is free software: you can redistribute it and/or modify it under +# the terms of the GNU General Public License as published by the Free +# Software Foundation, either version 3 of the License, or (at your option) +# any later version. +# +# GNU Mailman is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +# more details. +# +# You should have received a copy of the GNU General Public License along with +# GNU Mailman. If not, see <http://www.gnu.org/licenses/>. + +"""Test the `queues` resource.""" + +__all__ = [ + 'TestQueues', + ] + + +import unittest + +from mailman.app.lifecycle import create_list +from mailman.config import config +from mailman.database.transaction import transaction +from mailman.testing.helpers import call_api, get_queue_messages +from mailman.testing.layers import RESTLayer +from six.moves.urllib_error import HTTPError + + +TEXT = """\ +From: anne@example.com +To: test@example.com +Subject: A test +Message-ID: <ant> + +""" + + + +class TestQueues(unittest.TestCase): + layer = RESTLayer + + def setUp(self): + with transaction(): + self._mlist = create_list('test@example.com') + + def test_missing_queue(self): + # Trying to print a missing queue gives a 404. + with self.assertRaises(HTTPError) as cm: + call_api('http://localhost:9001/3.0/queues/notaq') + self.assertEqual(cm.exception.code, 404) + + def test_no_such_list(self): + # POSTing to a queue with a bad list-id gives a 400. + with self.assertRaises(HTTPError) as cm: + call_api('http://localhost:9001/3.0/queues/bad', { + 'list_id': 'nosuchlist.example.com', + 'text': TEXT, + }) + self.assertEqual(cm.exception.code, 400) + + def test_inject(self): + # Injecting a message leaves the message in the queue. + starting_messages = get_queue_messages('bad') + self.assertEqual(len(starting_messages), 0) + content, response = call_api('http://localhost:9001/3.0/queues/bad', { + 'list_id': 'test.example.com', + 'text': TEXT}) + self.assertEqual(response.status, 201) + location = response['location'] + filebase = location.split('/')[-1] + # The message is in the 'bad' queue. + content, response = call_api('http://localhost:9001/3.0/queues/bad') + files = content['files'] + self.assertEqual(len(files), 1) + self.assertEqual(files[0], filebase) + # Verify the files directly. + files = list(config.switchboards['bad'].files) + self.assertEqual(len(files), 1) + self.assertEqual(files[0], filebase) + # Verify the content. + items = get_queue_messages('bad') + self.assertEqual(len(items), 1) + msg = items[0].msg + # Remove some headers that get added by Mailman. + del msg['date'] + self.assertEqual(msg['x-message-id-hash'], + 'MS6QLWERIJLGCRF44J7USBFDELMNT2BW') + del msg['x-message-id-hash'] + self.assertMultiLineEqual(msg.as_string(), TEXT) + + def test_delete_file(self): + # Inject a file, then delete it. + content, response = call_api('http://localhost:9001/3.0/queues/bad', { + 'list_id': 'test.example.com', + 'text': TEXT}) + location = response['location'] + self.assertEqual(len(config.switchboards['bad'].files), 1) + # Delete the file through REST. + content, response = call_api(location, method='DELETE') + self.assertEqual(response.status, 204) + self.assertEqual(len(config.switchboards['bad'].files), 0) diff --git a/src/mailman/rest/tests/test_systemconf.py b/src/mailman/rest/tests/test_systemconf.py index 2eb4fa251..2158a024a 100644 --- a/src/mailman/rest/tests/test_systemconf.py +++ b/src/mailman/rest/tests/test_systemconf.py @@ -128,6 +128,7 @@ class TestSystemConfiguration(unittest.TestCase): 'passwords', 'paths.dev', 'paths.fhs', + 'paths.here', 'paths.local', 'paths.testing', 'runner.archive', diff --git a/src/mailman/runners/docs/command.rst b/src/mailman/runners/docs/command.rst index fe3311d87..82ee33fbc 100644 --- a/src/mailman/runners/docs/command.rst +++ b/src/mailman/runners/docs/command.rst @@ -27,7 +27,7 @@ the sender. The command can be in the ``Subject`` header. ... """) >>> from mailman.app.inject import inject_message - >>> inject_message(mlist, msg, switchboard='command') + >>> filebase = inject_message(mlist, msg, switchboard='command') >>> from mailman.runners.command import CommandRunner >>> from mailman.testing.helpers import make_testable_runner >>> command = make_testable_runner(CommandRunner) @@ -85,7 +85,7 @@ message is plain text. ... echo foo bar ... """) - >>> inject_message(mlist, msg, switchboard='command') + >>> filebase = inject_message(mlist, msg, switchboard='command') >>> command.run() >>> messages = get_queue_messages('virgin') >>> len(messages) @@ -133,7 +133,8 @@ address, and the other is the results of his email command. ... ... """) - >>> inject_message(mlist, msg, switchboard='command', subaddress='join') + >>> filebase = inject_message( + ... mlist, msg, switchboard='command', subaddress='join') >>> command.run() >>> messages = get_queue_messages('virgin', sort_on='subject') >>> len(messages) @@ -165,7 +166,8 @@ Similarly, to leave a mailing list, the user need only email the ``-leave`` or ... ... """) - >>> inject_message(mlist, msg, switchboard='command', subaddress='leave') + >>> filebase = inject_message( + ... mlist, msg, switchboard='command', subaddress='leave') >>> command.run() >>> messages = get_queue_messages('virgin') >>> len(messages) @@ -200,7 +202,8 @@ The ``-confirm`` address is also available as an implicit command. ... ... """) - >>> inject_message(mlist, msg, switchboard='command', subaddress='confirm') + >>> filebase = inject_message( + ... mlist, msg, switchboard='command', subaddress='confirm') >>> command.run() >>> messages = get_queue_messages('virgin') >>> len(messages) @@ -244,7 +247,7 @@ looked at by the command queue. ... echo baz qux ... """) - >>> inject_message(mlist, msg, switchboard='command') + >>> filebase = inject_message(mlist, msg, switchboard='command') >>> command.run() >>> messages = get_queue_messages('virgin') >>> len(messages) @@ -276,7 +279,7 @@ The ``stop`` command is an alias for ``end``. ... echo baz qux ... """) - >>> inject_message(mlist, msg, switchboard='command') + >>> filebase = inject_message(mlist, msg, switchboard='command') >>> command.run() >>> messages = get_queue_messages('virgin') >>> len(messages) diff --git a/src/mailman/runners/docs/incoming.rst b/src/mailman/runners/docs/incoming.rst index 0ae3336ca..d4fb65c85 100644 --- a/src/mailman/runners/docs/incoming.rst +++ b/src/mailman/runners/docs/incoming.rst @@ -54,7 +54,7 @@ Inject the message into the incoming queue, similar to the way the upstream mail server normally would. >>> from mailman.app.inject import inject_message - >>> inject_message(mlist, msg) + >>> filebase = inject_message(mlist, msg) The incoming runner runs until it is empty. @@ -103,7 +103,7 @@ that it will be accepted and forward to the pipeline queue. Inject the message into the incoming queue and run until the queue is empty. - >>> inject_message(mlist, msg) + >>> filebase = inject_message(mlist, msg) >>> incoming.run() There are no messages left in the incoming queue. @@ -156,7 +156,7 @@ pipeline queue. >>> from mailman.testing.helpers import event_subscribers >>> with event_subscribers(on_chain): - ... inject_message(mlist, msg) + ... filebase = inject_message(mlist, msg) ... incoming.run() <mailman.interfaces.chain.HoldEvent ...> <mailman.chains.hold.HoldChain ...> @@ -191,7 +191,7 @@ new chain and set it as the mailing list's start chain. >>> msg.replace_header('message-id', '<second>') >>> with event_subscribers(on_chain): - ... inject_message(mlist, msg) + ... filebase = inject_message(mlist, msg) ... incoming.run() <mailman.interfaces.chain.DiscardEvent ...> <mailman.chains.discard.DiscardChain ...> @@ -220,7 +220,7 @@ just create a new chain that does. >>> msg.replace_header('message-id', '<third>') >>> with event_subscribers(on_chain): - ... inject_message(mlist, msg) + ... filebase = inject_message(mlist, msg) ... incoming.run() <mailman.interfaces.chain.RejectEvent ...> <mailman.chains.reject.RejectChain ...> |
