diff --git a/src/mailman/app/bounces.py b/src/mailman/app/bounces.py
index e3d923946..2ebb6dd4b 100644
--- a/src/mailman/app/bounces.py
+++ b/src/mailman/app/bounces.py
@@ -77,7 +77,7 @@ def bounce_message(mlist, msg, error=None):
else str(error))
# Currently we always craft bounces as MIME messages.
bmsg = UserNotification(msg.sender, mlist.owner_address, subject,
- lang=mlist.preferred_language)
+ lang=mlist.preferred_language, role='poster')
# BAW: Be sure you set the type before trying to attach, or you'll get
# a MultipartConversionError.
bmsg.set_type('multipart/mixed')
@@ -222,8 +222,9 @@ def send_probe(member, msg):
# Craft the probe message. This will be a multipart where the first part
# is the probe text and the second part is the message that caused this
# probe to be sent.
- probe = UserNotification(member.address.email, probe_sender,
- subject, lang=member.preferred_language)
+ probe = UserNotification(
+ member.address.email, probe_sender, subject,
+ lang=member.preferred_language, role='subscriber')
probe.set_type('multipart/mixed')
notice = MIMEText(text, _charset=mlist.preferred_language.charset)
probe.attach(notice)
diff --git a/src/mailman/app/docs/bounces.rst b/src/mailman/app/docs/bounces.rst
index b25381031..fadee9e92 100644
--- a/src/mailman/app/docs/bounces.rst
+++ b/src/mailman/app/docs/bounces.rst
@@ -38,10 +38,12 @@ to the original message author.
Subject: Something important
From: ant-owner@example.com
To: aperson@example.com
+ List-Administrivia: poster
MIME-Version: 1.0
Content-Type: multipart/mixed; boundary="..."
Message-ID: ...
Date: ...
+ List-Id: <ant.example.com>
Precedence: bulk
<BLANKLINE>
--...
@@ -74,10 +76,12 @@ passed in as an instance of a ``RejectMessage`` exception.
Subject: Something important
From: ant-owner@example.com
To: aperson@example.com
+ List-Administrivia: poster
MIME-Version: 1.0
Content-Type: multipart/mixed; boundary="..."
Message-ID: ...
Date: ...
+ List-Id: <ant.example.com>
Precedence: bulk
<BLANKLINE>
--...
@@ -114,10 +118,12 @@ be interpolated into the message using the ``{reasons}`` placeholder.
Subject: Something important
From: ant-owner@example.com
To: aperson@example.com
+ List-Administrivia: poster
MIME-Version: 1.0
Content-Type: multipart/mixed; boundary="..."
Message-ID: ...
Date: ...
+ List-Id: <ant.example.com>
Precedence: bulk
<BLANKLINE>
--...
diff --git a/src/mailman/app/docs/message.rst b/src/mailman/app/docs/message.rst
index 658bf4e30..e87facb79 100644
--- a/src/mailman/app/docs/message.rst
+++ b/src/mailman/app/docs/message.rst
@@ -39,8 +39,10 @@ The message will end up in the `virgin` queue.
Subject: Something you need to know
From: test@example.com
To: aperson@example.com
+ List-Administrivia: subscriber
Message-ID: ...
Date: ...
+ List-Id: <test.example.com>
Precedence: bulk
<BLANKLINE>
I needed to tell you this.
diff --git a/src/mailman/app/docs/moderator.rst b/src/mailman/app/docs/moderator.rst
index ce25a4711..a1f4171cb 100644
--- a/src/mailman/app/docs/moderator.rst
+++ b/src/mailman/app/docs/moderator.rst
@@ -329,7 +329,8 @@ There's now a message in the virgin queue, destined for the list owner.
MIME-Version: 1.0
...
Subject: New subscription request to A Test List from gwen@example.com
- From: ant-owner@example.com
+ From: noreply@example.com
+ List-Administrivia: list-owner
To: ant-owner@example.com
...
Your authorization is required for a mailing list subscription request
@@ -349,7 +350,8 @@ Jeff is a member of the mailing list, and chooses to unsubscribe.
MIME-Version: 1.0
...
Subject: New unsubscription request from A Test List by jeff@example.org
- From: ant-owner@example.com
+ From: noreply@example.com
+ List-Administrivia: list-owner
To: ant-owner@example.com
...
Your authorization is required for a mailing list unsubscription
@@ -380,6 +382,7 @@ receive a membership change notice.
...
Subject: A Test List subscription notification
From: noreply@example.com
+ List-Administrivia: list-owner
To: ant-owner@example.com
...
Gwen Person <gwen@example.com> has been successfully subscribed to A
@@ -398,6 +401,7 @@ get a notification.
...
Subject: A Test List unsubscription notification
From: noreply@example.com
+ List-Administrivia: list-owner
To: ant-owner@example.com
...
Gwen Person <gwen@example.com> has been removed from A Test List.
diff --git a/src/mailman/app/moderator.py b/src/mailman/app/moderator.py
index 43763e248..9a809d620 100644
--- a/src/mailman/app/moderator.py
+++ b/src/mailman/app/moderator.py
@@ -24,7 +24,7 @@ from email.utils import formatdate, getaddresses, make_msgid
from mailman.app.membership import delete_member
from mailman.config import config
from mailman.core.i18n import _
-from mailman.email.message import UserNotification
+from mailman.email.message import OwnerNotification, UserNotification
from mailman.interfaces.action import Action
from mailman.interfaces.listmanager import ListDeletingEvent
from mailman.interfaces.member import NotAMemberError
@@ -163,7 +163,7 @@ def handle_message(mlist, id, action, comment=None, forward=None):
fmsg = UserNotification(
addresses, mlist.bounces_address,
_('Forward of moderated message'),
- lang=language)
+ lang=language, role='moderator')
fmsg.set_type('message/rfc822')
fmsg.attach(msg)
fmsg.send(mlist)
@@ -202,9 +202,7 @@ def hold_unsubscription(mlist, email):
)))
# This message should appear to come from the <list>-owner so as
# to avoid any useless bounce processing.
- msg = UserNotification(
- mlist.owner_address, mlist.owner_address,
- subject, text, mlist.preferred_language)
+ msg = OwnerNotification(mlist, subject, text, mlist.owners)
msg.send(mlist)
return request_id
@@ -268,7 +266,8 @@ def send_rejection(mlist, request, recip, comment, origmsg=None, lang=None):
str(origmsg)
])
subject = _('Request to mailing list "$display_name" rejected')
- msg = UserNotification(recip, mlist.bounces_address, subject, text, lang)
+ msg = UserNotification(
+ recip, mlist.bounces_address, subject, text, lang, 'subscriber')
msg.send(mlist)
diff --git a/src/mailman/app/notifications.py b/src/mailman/app/notifications.py
index 4a7bd16fb..1a3b82b46 100644
--- a/src/mailman/app/notifications.py
+++ b/src/mailman/app/notifications.py
@@ -68,7 +68,7 @@ def send_welcome_message(mlist, member, language, text=''):
formataddr((display_name, member.address.email)),
mlist.request_address,
_('Welcome to the "$mlist.display_name" mailing list${digmode}'),
- text, language)
+ text, language, 'subscriber')
msg['X-No-Archive'] = 'yes'
msg.send(mlist, verp=as_boolean(config.mta.verp_personalized_deliveries))
@@ -93,7 +93,7 @@ def send_goodbye_message(mlist, address, language):
address, mlist.bounces_address,
_('You have been unsubscribed from the $mlist.display_name '
'mailing list'),
- goodbye_message, language)
+ goodbye_message, language, 'subscriber')
msg.send(mlist, verp=as_boolean(config.mta.verp_personalized_deliveries))
diff --git a/src/mailman/app/subscriptions.py b/src/mailman/app/subscriptions.py
index 6353eee70..e192485ad 100644
--- a/src/mailman/app/subscriptions.py
+++ b/src/mailman/app/subscriptions.py
@@ -27,7 +27,7 @@ from mailman.app.membership import delete_member
from mailman.app.workflow import Workflow
from mailman.core.i18n import _
from mailman.database.transaction import flush
-from mailman.email.message import UserNotification
+from mailman.email.message import OwnerNotification, UserNotification
from mailman.interfaces.address import IAddress
from mailman.interfaces.bans import IBanManager
from mailman.interfaces.listmanager import ListDeletingEvent
@@ -301,9 +301,8 @@ class SubscriptionWorkflow(_SubscriptionWorkflowCommon):
)))
# This message should appear to come from the <list>-owner so as
# to avoid any useless bounce processing.
- msg = UserNotification(
- self.mlist.owner_address, self.mlist.owner_address,
- subject, text, self.mlist.preferred_language)
+ msg = OwnerNotification(
+ self.mlist, subject, text, self.mlist.owners)
msg.send(self.mlist)
# The workflow must stop running here.
raise StopIteration
@@ -449,9 +448,8 @@ class UnSubscriptionWorkflow(_SubscriptionWorkflowCommon):
)))
# This message should appear to come from the <list>-owner so as
# to avoid any useless bounce processing.
- msg = UserNotification(
- self.mlist.owner_address, self.mlist.owner_address,
- subject, text, self.mlist.preferred_language)
+ msg = OwnerNotification(
+ self.mlist, subject, text, self.mlist.owners)
msg.send(self.mlist)
# The workflow must stop running here
raise StopIteration
@@ -575,7 +573,7 @@ def _handle_confirmation_needed_events(event, template_name):
))
msg = UserNotification(
email_address, confirm_address, subject, text,
- event.mlist.preferred_language)
+ event.mlist.preferred_language, role='subscriber')
msg.send(event.mlist, add_precedence=False)
diff --git a/src/mailman/app/tests/test_moderation.py b/src/mailman/app/tests/test_moderation.py
index c95f5ae9d..8fb216861 100644
--- a/src/mailman/app/tests/test_moderation.py
+++ b/src/mailman/app/tests/test_moderation.py
@@ -160,6 +160,8 @@ class TestUnsubscription(unittest.TestCase):
# When unsubscriptions must be approved by the moderator, but the
# moderator defers this decision.
user_manager = getUtility(IUserManager)
+ owner = user_manager.create_address('owner@example.com')
+ self._mlist.subscribe(owner, MemberRole.owner)
anne = user_manager.create_address('anne@example.org', 'Anne Person')
token, token_owner, member = self._manager.register(
anne, pre_verified=True, pre_confirmed=True, pre_approved=True)
@@ -179,7 +181,7 @@ class TestUnsubscription(unittest.TestCase):
else:
raise AssertionError('No moderator email found')
self.assertEqual(
- item.msgdata['recipients'], {'test-owner@example.com'})
+ item.msgdata['recipients'], {'owner@example.com'})
self.assertEqual(
item.msg['subject'],
'New unsubscription request from Test by anne@example.org')
diff --git a/src/mailman/app/tests/test_subscriptions.py b/src/mailman/app/tests/test_subscriptions.py
index b75c68542..468b5c487 100644
--- a/src/mailman/app/tests/test_subscriptions.py
+++ b/src/mailman/app/tests/test_subscriptions.py
@@ -439,6 +439,8 @@ class TestSubscriptionWorkflow(unittest.TestCase):
bart = self._user_manager.create_user('bart@example.com', 'Bart User')
address = set_preferred(bart)
self._mlist.subscribe(address, MemberRole.moderator)
+ owner = self._user_manager.create_address('owner@example.com')
+ self._mlist.subscribe(owner, MemberRole.owner)
workflow = SubscriptionWorkflow(self._mlist, anne,
pre_verified=True,
pre_confirmed=True)
@@ -452,9 +454,9 @@ class TestSubscriptionWorkflow(unittest.TestCase):
else:
raise AssertionError('No moderator email found')
self.assertEqual(
- item.msgdata['recipients'], {'test-owner@example.com'})
+ item.msgdata['recipients'], {'owner@example.com'})
message = items[0].msg
- self.assertEqual(message['From'], 'test-owner@example.com')
+ self.assertEqual(message['From'], 'noreply@example.com')
self.assertEqual(message['To'], 'test-owner@example.com')
self.assertEqual(
message['Subject'],
diff --git a/src/mailman/app/tests/test_unsubscriptions.py b/src/mailman/app/tests/test_unsubscriptions.py
index dbd4cbe14..92175b090 100644
--- a/src/mailman/app/tests/test_unsubscriptions.py
+++ b/src/mailman/app/tests/test_unsubscriptions.py
@@ -23,6 +23,7 @@ from contextlib import suppress
from mailman.app.lifecycle import create_list
from mailman.app.subscriptions import UnSubscriptionWorkflow
from mailman.interfaces.mailinglist import SubscriptionPolicy
+from mailman.interfaces.member import MemberRole
from mailman.interfaces.pending import IPendings
from mailman.interfaces.subscriptions import TokenOwner
from mailman.interfaces.usermanager import IUserManager
@@ -278,13 +279,17 @@ class TestUnSubscriptionWorkflow(unittest.TestCase):
# is so configured, a notification is sent to the list moderators.
self._mlist.admin_immed_notify = True
self._mlist.unsubscription_policy = SubscriptionPolicy.moderate
+ owner = self._user_manager.create_address('owner@example.com')
+ self._mlist.subscribe(owner, MemberRole.owner)
workflow = UnSubscriptionWorkflow(
self._mlist, self.anne, pre_confirmed=True)
# Consume the entire state machine.
list(workflow)
items = get_queue_messages('virgin', expected_count=1)
message = items[0].msg
- self.assertEqual(message['From'], 'test-owner@example.com')
+ msgdata = items[0].msgdata
+ self.assertEqual(msgdata['recipients'], {'owner@example.com'})
+ self.assertEqual(message['From'], 'noreply@example.com')
self.assertEqual(message['To'], 'test-owner@example.com')
self.assertEqual(
message['Subject'],
diff --git a/src/mailman/chains/hold.py b/src/mailman/chains/hold.py
index fec0303b0..6d1f89a98 100644
--- a/src/mailman/chains/hold.py
+++ b/src/mailman/chains/hold.py
@@ -27,7 +27,7 @@ from mailman.app.replybot import can_acknowledge
from mailman.chains.base import TerminalChainBase
from mailman.config import config
from mailman.core.i18n import _, format_reasons
-from mailman.email.message import UserNotification
+from mailman.email.message import OwnerNotification, UserNotification
from mailman.interfaces.autorespond import IAutoResponseSet, Response
from mailman.interfaces.chain import HoldEvent
from mailman.interfaces.languages import ILanguageManager
@@ -115,7 +115,7 @@ def autorespond_to_sender(mlist, sender, language=None):
msg = UserNotification(
sender, mlist.owner_address,
_('Last autoresponse notification for today'),
- text, lang=language)
+ text, lang=language, role='poster')
msg.send(mlist)
return False
else:
@@ -206,7 +206,7 @@ class HoldChain(TerminalChainBase):
adminaddr = mlist.bounces_address
nmsg = UserNotification(
msg.sender, adminaddr, subject, text,
- getUtility(ILanguageManager)[send_language_code])
+ getUtility(ILanguageManager)[send_language_code], 'poster')
nmsg.send(mlist)
# Now the message for the list moderators. This one should appear to
# come from <list>-owner since we really don't need to do bounce
@@ -225,9 +225,8 @@ class HoldChain(TerminalChainBase):
subject = _(
'$mlist.fqdn_listname post from $msg.sender requires '
'approval')
- nmsg = UserNotification(mlist.owner_address,
- mlist.owner_address,
- subject, lang=language)
+ nmsg = OwnerNotification(
+ mlist, subject, roster=mlist.owners)
nmsg.set_type('multipart/mixed')
template = getUtility(ITemplateLoader).get(
'list:admin:action:post', mlist)
diff --git a/src/mailman/chains/tests/test_hold.py b/src/mailman/chains/tests/test_hold.py
index a780585a3..900c24e52 100644
--- a/src/mailman/chains/tests/test_hold.py
+++ b/src/mailman/chains/tests/test_hold.py
@@ -79,6 +79,8 @@ Content-Transfer-Encoding: 7bit
Subject: Last autoresponse notification for today
From: test-owner@example.com
To: anne@example.com
+List-Administrivia: poster
+List-Id: <test.example.com>
Precedence: bulk
We have received a message from your address <anne@example.com>
@@ -125,6 +127,8 @@ A message body.
if item.msg['to'] == 'test-owner@example.com':
part = item.msg.get_payload(0)
payloads['owner'] = part.get_payload().splitlines()
+ self.assertIn('List-Id', item.msg)
+ self.assertIn('List-Administrivia', item.msg)
elif item.msg['To'] == 'anne@example.com':
payloads['sender'] = item.msg.get_payload().splitlines()
else:
@@ -173,6 +177,8 @@ A message body.
bart = self._user_manager.create_user('bart@example.com', 'Bart User')
address = set_preferred(bart)
self._mlist.subscribe(address, MemberRole.moderator)
+ owner = getUtility(IUserManager).create_address('owner@example.com')
+ self._mlist.subscribe(owner, MemberRole.owner)
path = resource_filename('mailman.chains.tests', 'issue144.eml')
with open(path, 'rb') as fp:
msg = mfb(fp.read())
@@ -182,10 +188,11 @@ A message body.
# delivery to the moderators.
items = get_queue_messages('virgin', expected_count=1)
msgdata = items[0].msgdata
+ msg = items[0].msg
# Should get sent to -owner address.
- self.assertEqual(msgdata['recipients'], {'test-owner@example.com'})
+ self.assertEqual(msg['to'], 'test-owner@example.com')
+ self.assertEqual(msgdata['recipients'], {'owner@example.com'})
# Ensure that the subject looks correct in the postauth.txt.
- msg = items[0].msg
value = None
for line in msg.get_payload(0).get_payload().splitlines():
if line.strip().startswith('Subject:'):
diff --git a/src/mailman/commands/cli_lists.py b/src/mailman/commands/cli_lists.py
index f8fa8d800..9ba3c85e9 100644
--- a/src/mailman/commands/cli_lists.py
+++ b/src/mailman/commands/cli_lists.py
@@ -24,7 +24,7 @@ from mailman.app.lifecycle import create_list, remove_list
from mailman.core.constants import system_preferences
from mailman.core.i18n import _
from mailman.database.transaction import transaction
-from mailman.email.message import UserNotification
+from mailman.email.message import OwnerNotification
from mailman.interfaces.address import (
IEmailValidator, InvalidEmailAddressError)
from mailman.interfaces.command import ICLISubCommand
@@ -208,10 +208,9 @@ def create(ctx, language, owners, notify, quiet, create_domain, fqdn_listname):
# will match the template language. Stashing and restoring the old
# translation context is just (healthy? :) paranoia.
with _.using(mlist.preferred_language.code):
- msg = UserNotification(
- owners, mlist.no_reply_address,
- _('Your new mailing list: $fqdn_listname'),
- text, mlist.preferred_language)
+ msg = OwnerNotification(
+ mlist, _('Your new mailing list: $fqdn_listname'),
+ text, mlist.owners)
msg.send(mlist)
diff --git a/src/mailman/commands/docs/create.rst b/src/mailman/commands/docs/create.rst
index c80ff7a3d..a7855a557 100644
--- a/src/mailman/commands/docs/create.rst
+++ b/src/mailman/commands/docs/create.rst
@@ -128,7 +128,8 @@ The notification message is in the virgin queue.
...
Subject: Your new mailing list: fly@example.com
From: noreply@example.com
- To: anne@example.com, bart@example.com, cate@example.com
+ List-Administrivia: list-owner
+ To: fly-owner@example.com
...
<BLANKLINE>
The mailing list 'fly@example.com' has just been created for you.
diff --git a/src/mailman/core/docs/chains.rst b/src/mailman/core/docs/chains.rst
index 84b340775..9c81cf5fc 100644
--- a/src/mailman/core/docs/chains.rst
+++ b/src/mailman/core/docs/chains.rst
@@ -122,7 +122,8 @@ This one is addressed to the list moderators.
>>> print(messages[0].as_string())
Subject: test@example.com post from aperson@example.com requires approval
- From: test-owner@example.com
+ From: noreply@example.com
+ List-Administrivia: list-owner
To: test-owner@example.com
MIME-Version: 1.0
...
diff --git a/src/mailman/email/message.py b/src/mailman/email/message.py
index cdf5869c8..6b26ac749 100644
--- a/src/mailman/email/message.py
+++ b/src/mailman/email/message.py
@@ -31,6 +31,7 @@ from email.header import Header
from email.mime.multipart import MIMEMultipart
from mailman.config import config
from mailman.interfaces.address import IEmailValidator
+from mailman.utilities.email import make_listid
from public import public
from zope.component import getUtility
@@ -132,7 +133,11 @@ class MultipartDigestMessage(MIMEMultipart, Message):
class UserNotification(Message):
"""Class for internally crafted messages."""
- def __init__(self, recipients, sender, subject=None, text=None, lang=None):
+ ROLES = ('poster', 'subscriber', 'moderator', 'list-owner',
+ 'domain-owner', 'hostmaster')
+
+ def __init__(self, recipients, sender, subject=None, text=None, lang=None,
+ role='subscriber'):
Message.__init__(self)
charset = (lang.charset if lang is not None else 'us-ascii')
subject = ('(no subject)' if subject is None else subject)
@@ -147,6 +152,9 @@ class UserNotification(Message):
else:
self['To'] = recipients
self.recipients = set([recipients])
+ if role not in self.ROLES:
+ raise ValueError('Unknown role: {}'.format(role))
+ self['List-Administrivia'] = role
def send(self, mlist, *, add_precedence=True, **_kws):
"""Sends the message by enqueuing it to the 'virgin' queue.
@@ -170,6 +178,9 @@ class UserNotification(Message):
# Ditto for Date: as required by RFC 2822.
if 'date' not in self:
self['Date'] = email.utils.formatdate(localtime=True)
+ # Also set the List-Id header
+ if 'list-id' not in self:
+ self['List-Id'] = make_listid(mlist)
# UserNotifications are typically for admin messages, and for messages
# other than list explosions. Send these out as Precedence: bulk, but
# don't override an existing Precedence: header.
@@ -205,7 +216,7 @@ class OwnerNotification(UserNotification):
to = mlist.owner_address
sender = config.mailman.site_owner
UserNotification.__init__(self, recipients, sender, subject,
- text, mlist.preferred_language)
+ text, mlist.preferred_language, 'list-owner')
# Hack the To header to look like it's going to the -owner address
del self['to']
self['To'] = to
diff --git a/src/mailman/handlers/acknowledge.py b/src/mailman/handlers/acknowledge.py
index a8d8781fd..3e7032dcf 100644
--- a/src/mailman/handlers/acknowledge.py
+++ b/src/mailman/handlers/acknowledge.py
@@ -74,5 +74,5 @@ class Acknowledge:
# queue.
subject = _('$display_name post acknowledgment')
usermsg = UserNotification(sender, mlist.bounces_address,
- subject, text, language)
+ subject, text, language, 'poster')
usermsg.send(mlist)
diff --git a/src/mailman/handlers/docs/replybot.rst b/src/mailman/handlers/docs/replybot.rst
index 9e18ce911..1053a9dfb 100644
--- a/src/mailman/handlers/docs/replybot.rst
+++ b/src/mailman/handlers/docs/replybot.rst
@@ -62,6 +62,7 @@ response.
Subject: Auto-response for your message to the "XTest" mailing list
From: _xtest-bounces@example.com
To: aperson@example.com
+ List-Administrivia: poster
X-Mailer: The Mailman Replybot
X-Ack: No
Message-ID: <...>
@@ -154,6 +155,7 @@ Unless the ``X-Ack:`` header has a value of ``yes``, in which case, the
Subject: Auto-response for your message to the "XTest" mailing list
From: _xtest-bounces@example.com
To: asystem@example.com
+ List-Administrivia: poster
X-Mailer: The Mailman Replybot
X-Ack: No
Message-ID: <...>
@@ -193,6 +195,7 @@ will get auto-responses: those sent to the ``-request`` address...
Subject: Auto-response for your message to the "XTest" mailing list
From: _xtest-bounces@example.com
To: aperson@example.com
+ List-Administrivia: poster
X-Mailer: The Mailman Replybot
X-Ack: No
Message-ID: <...>
@@ -226,6 +229,7 @@ will get auto-responses: those sent to the ``-request`` address...
Subject: Auto-response for your message to the "XTest" mailing list
From: _xtest-bounces@example.com
To: aperson@example.com
+ List-Administrivia: poster
X-Mailer: The Mailman Replybot
X-Ack: No
Message-ID: <...>
diff --git a/src/mailman/handlers/replybot.py b/src/mailman/handlers/replybot.py
index 62ceed8f5..5f918a2d9 100644
--- a/src/mailman/handlers/replybot.py
+++ b/src/mailman/handlers/replybot.py
@@ -105,8 +105,9 @@ class Replybot:
)
# Interpolation and Wrap the response text.
text = wrap(expand(response_text, mlist, d))
- outmsg = UserNotification(msg.sender, mlist.bounces_address,
- subject, text, mlist.preferred_language)
+ outmsg = UserNotification(
+ msg.sender, mlist.bounces_address, subject, text,
+ mlist.preferred_language, 'poster')
outmsg['X-Mailer'] = _('The Mailman Replybot')
# prevent recursions and mail loops!
outmsg['X-Ack'] = 'No'
diff --git a/src/mailman/handlers/rfc_2369.py b/src/mailman/handlers/rfc_2369.py
index 63daab2f2..9ce0f51ba 100644
--- a/src/mailman/handlers/rfc_2369.py
+++ b/src/mailman/handlers/rfc_2369.py
@@ -19,12 +19,11 @@
import logging
-from email.utils import formataddr
from mailman.core.i18n import _
-from mailman.handlers.cook_headers import uheader
from mailman.interfaces.archiver import ArchivePolicy
from mailman.interfaces.handler import IHandler
from mailman.interfaces.mailinglist import IListArchiverSet
+from mailman.utilities.email import make_listid
from public import public
from zope.interface import implementer
@@ -41,18 +40,9 @@ def process(mlist, msg, msgdata):
# headers by default, pissing off their users. Too bad. Fix the MUAs.
if not mlist.include_rfc2369_headers:
return
- list_id = '{0.list_name}.{0.mail_host}'.format(mlist)
- if mlist.description:
- # Don't wrap the header since here we just want to get it properly RFC
- # 2047 encoded.
- i18ndesc = uheader(mlist, mlist.description, 'List-Id', maxlinelen=998)
- listid_h = formataddr((str(i18ndesc), list_id))
- else:
- # Without a description, we need to ensure the MUST brackets.
- listid_h = '<{}>'.format(list_id)
# No other agent should add a List-ID header except Mailman.
del msg['list-id']
- msg['List-Id'] = listid_h
+ msg['List-Id'] = make_listid(mlist)
# For internally crafted messages, we also add a (nonstandard),
# "X-List-Administrivia: yes" header. For all others (i.e. those coming
# from list posts), we add a bunch of other RFC 2369 headers.
diff --git a/src/mailman/runners/command.py b/src/mailman/runners/command.py
index e885b217d..4a4e71113 100644
--- a/src/mailman/runners/command.py
+++ b/src/mailman/runners/command.py
@@ -223,7 +223,7 @@ class CommandRunner(Runner):
language = getUtility(ILanguageManager)[msgdata['lang']]
reply = UserNotification(msg.sender, mlist.bounces_address,
_('The results of your email commands'),
- lang=language)
+ lang=language, role='poster')
cte = msg.get('content-transfer-encoding')
if cte is not None:
reply['Content-Transfer-Encoding'] = cte
diff --git a/src/mailman/runners/digest.py b/src/mailman/runners/digest.py
index dc5f65cbe..9bd636fc8 100644
--- a/src/mailman/runners/digest.py
+++ b/src/mailman/runners/digest.py
@@ -34,6 +34,7 @@ from mailman.email.message import Message, MultipartDigestMessage
from mailman.handlers.decorate import decorate
from mailman.interfaces.member import DeliveryMode, DeliveryStatus
from mailman.interfaces.template import ITemplateLoader
+from mailman.utilities.email import make_listid
from mailman.utilities.mailbox import Mailbox
from mailman.utilities.string import expand, oneline, wrap
from public import public
@@ -63,6 +64,7 @@ class Digester:
self._message['Reply-To'] = mlist.posting_address
self._message['Date'] = formatdate(localtime=True)
self._message['Message-ID'] = make_msgid()
+ self._message['List-Id'] = make_listid(self._mlist)
# In the rfc1153 digest, the masthead contains the digest boilerplate
# plus any digest header. In the MIME digests, the masthead and
# digest header are separate MIME subobjects. In either case, it's
diff --git a/src/mailman/runners/tests/test_digest.py b/src/mailman/runners/tests/test_digest.py
index 251e9cb02..8c1e0ebf1 100644
--- a/src/mailman/runners/tests/test_digest.py
+++ b/src/mailman/runners/tests/test_digest.py
@@ -66,6 +66,7 @@ class TestDigest(unittest.TestCase):
for item in items:
self.assertEqual(item.msg['subject'],
'Test Digest, Vol 1, Issue 1')
+ self.assertEqual(item.msg['list-id'], '<test.example.com>')
def test_simple_message(self):
# Subscribe some users receiving digests.
diff --git a/src/mailman/utilities/email.py b/src/mailman/utilities/email.py
index 81c2cb1fb..c8da62269 100644
--- a/src/mailman/utilities/email.py
+++ b/src/mailman/utilities/email.py
@@ -18,7 +18,9 @@
"""Email helpers."""
from base64 import b32encode
+from email.utils import formataddr
from hashlib import sha1
+from mailman.handlers.cook_headers import uheader
from public import public
@@ -75,3 +77,16 @@ def add_message_hash(msg):
del msg['x-message-id-hash']
msg['X-Message-ID-Hash'] = hash32
return hash32
+
+
+def make_listid(mlist):
+ list_id = '{0.list_name}.{0.mail_host}'.format(mlist)
+ if mlist.description:
+ # Don't wrap the header since here we just want to get it properly RFC
+ # 2047 encoded.
+ i18ndesc = uheader(mlist, mlist.description, 'List-Id', maxlinelen=998)
+ listid_h = formataddr((str(i18ndesc), list_id))
+ else:
+ # Without a description, we need to ensure the MUST brackets.
+ listid_h = '<{}>'.format(list_id)
+ return listid_h
diff --git a/src/mailman/utilities/tests/test_email.py b/src/mailman/utilities/tests/test_email.py
index b14b23306..3e3afde40 100644
--- a/src/mailman/utilities/tests/test_email.py
+++ b/src/mailman/utilities/tests/test_email.py
@@ -19,11 +19,12 @@
import unittest
+from mailman.app.lifecycle import create_list
from mailman.interfaces.messages import IMessageStore
from mailman.testing.helpers import (
specialized_message_from_string as mfs)
from mailman.testing.layers import ConfigLayer
-from mailman.utilities.email import add_message_hash, split_email
+from mailman.utilities.email import add_message_hash, make_listid, split_email
from zope.component import getUtility
@@ -149,3 +150,26 @@ X-Message-ID-Hash: abc
self.assertEqual(len(x_message_id_hashes), 1)
self.assertEqual(x_message_id_hashes[0],
'MS6QLWERIJLGCRF44J7USBFDELMNT2BW')
+
+
+class TestListID(unittest.TestCase):
+
+ layer = ConfigLayer
+
+ def setUp(self):
+ self._mlist = create_list('list@example.com')
+
+ def test_basic(self):
+ self.assertEqual(make_listid(self._mlist), '<list.example.com>')
+
+ def test_description(self):
+ self._mlist.description = 'List description'
+ self.assertEqual(make_listid(self._mlist),
+ 'List description <list.example.com>')
+
+ def test_i18n_description(self):
+ self._mlist.description = 'Description with non-ascii chars é ç à'
+ self.assertEqual(
+ make_listid(self._mlist),
+ '=?utf-8?q?Description_with_non-ascii_chars_=C3=A9_=C3=A7_=C3=A0?='
+ ' <list.example.com>')