It took all of my sunday, but I just finished porting Ben Gertzfield's excellent dupe removal patch to mailman cvs (I also had to learn some python in the process. I'm starting to believe that Mailman is a conspiracy to get people to learn python :-p)
In a nutshell, the patch does two things: 1) it does not send you your list copy if - your subscribed Email address is already in the headers - you already received the message through another list (Cc accross two lists or more on the same site) 2) The new "nodupes" setting is really something you probably want as a default on all lists. I also had lists were people wanted notmetoo as a default too. Ben's fix for that is to have a bitfield per list that you can set and that states which options newly added users get. As Ben said, this breaks the one patch one functionality rule, but when I ported his work to mailman-cvs, I realized that it didn't make sense to take them apart. However, Barry, if that would stop you from merging #1 in CVS, I could remove it, but I'm not sure why one would want to. I've done reasonable tests to make sure I didn't break all of mailman in the process, and the core logic hasn't changed, so the basic functionality is the same that Ben had written and that has been used for 6-9mo? on the debian lists now. In other words, it should work (it does for me, and I'm already running it on my production mailman-cvs list server), but there is always the chance that there might be a corner case buglet left somewhere. Considering this was a pain to port, and how this puts to rest many of the reply-to munging discussions (the only real argument for reply-to munging is that it "solves" the duplicate mails you other receive when people use reply to all), I'm hoping that this could make it in (wink, wink :-D) Thanks, Marc -- Microsoft is to operating systems & security .... .... what McDonalds is to gourmet cooking Home page: http://marc.merlins.org/ | Finger [EMAIL PROTECTED] for PGP key
diff -urN mailman-cvs/Mailman/Cgi/admin.py mailman-cvs-nodupes/Mailman/Cgi/admin.py --- mailman-cvs/Mailman/Cgi/admin.py Sun Mar 3 14:29:16 2002 +++ mailman-cvs-nodupes/Mailman/Cgi/admin.py Sun Mar 3 17:09:38 2002 @@ -843,7 +843,7 @@ usertable.AddRow([Center(Italic(_('%(allcnt)s members total')))]) usertable.AddCellInfo(usertable.GetCurrentRowIndex(), usertable.GetCurrentCellIndex(), - colspan=10, + colspan=11, bgcolor=mm_cfg.WEB_ADMINITEM_COLOR) # Add the alphabetical links if bucket: @@ -861,17 +861,18 @@ usertable.AddRow([Center(joiner.join(cells))]) usertable.AddCellInfo(usertable.GetCurrentRowIndex(), usertable.GetCurrentCellIndex(), - colspan=10, + colspan=11, bgcolor=mm_cfg.WEB_ADMINITEM_COLOR) usertable.AddRow([Center(h) for h in (_('unsub'), _('member address<br>member name'), _('mod'), _('hide'), _('nomail<br>[reason]'), _('ack'), _('not metoo'), + _('nodupes'), _('digest'), _('plain'), _('language'))]) rowindex = usertable.GetCurrentRowIndex() - for i in range(10): + for i in range(11): usertable.AddCellInfo(rowindex, i, bgcolor=mm_cfg.WEB_ADMINITEM_COLOR) # Find the longest name in the list longest = 0 @@ -907,7 +908,7 @@ checked = 0 box = CheckBox('%s_mod' % addr, value, checked) cells.append(Center(box).Format()) - for opt in ('hide', 'nomail', 'ack', 'notmetoo'): + for opt in ('hide', 'nomail', 'ack', 'notmetoo', 'nodupes'): extra = '' if opt == 'nomail': status = mlist.getDeliveryStatus(addr) @@ -984,6 +985,9 @@ _('''<b>not metoo</b> -- Does the member avoid copies of their own posts?''')) legend.AddItem( + _('''<b>nodupes</b> -- Does the member avoid duplicates of the same + message?''')) + legend.AddItem( _('''<b>digest</b> -- Does the member get messages in digests? (otherwise, individual messages)''')) legend.AddItem( @@ -1328,7 +1332,7 @@ mlist.setDeliveryStatus(user, MemberAdaptor.BYADMIN) else: mlist.setDeliveryStatus(user, MemberAdaptor.ENABLED) - for opt in ('hide', 'ack', 'notmetoo', 'plain'): + for opt in ('hide', 'ack', 'notmetoo', 'nodupes', 'plain'): opt_code = option_info[opt] if cgidata.has_key('%s_%s' % (user, opt)): mlist.setMemberOption(user, opt_code, 1) diff -urN mailman-cvs/Mailman/Cgi/options.py mailman-cvs-nodupes/Mailman/Cgi/options.py --- mailman-cvs/Mailman/Cgi/options.py Sun Mar 3 14:29:16 2002 +++ mailman-cvs-nodupes/Mailman/Cgi/options.py Sun Mar 3 17:50:46 2002 @@ -424,6 +424,7 @@ ('conceal', mm_cfg.ConcealSubscription), ('remind', mm_cfg.SuppressPasswordReminder), ('rcvtopic', mm_cfg.ReceiveNonmatchingTopics), + ('nodupes', mm_cfg.DontReceiveDuplicates), ): try: newval = int(cgidata.getvalue(item)) @@ -514,9 +515,18 @@ global_remind = newval break - if global_enable is not None or global_remind is not None: + global_nodupes = None + if cgidata.getvalue('nodupes-globally'): + for flag, newval in newvals: + if flag == mm_cfg.DontReceiveDuplicates: + global_nodupes = newval + break + + if (global_enable is not None or global_remind is not None + or global_nodupes is not None): for gmlist in lists_of_member(mlist, user): - global_options(gmlist, user, global_enable, global_remind) + global_options(gmlist, user, global_enable, global_remind, + global_nodupes) # Now print the results if cantdigest: @@ -591,6 +601,10 @@ mlist.FormatOptionButton(mm_cfg.ConcealSubscription, 0, user)) replacements['<mm-hide-subscription-button>'] = mlist.FormatOptionButton( mm_cfg.ConcealSubscription, 1, user) + replacements['<mm-dont-receive-duplicates-button>'] = ( + mlist.FormatOptionButton(mm_cfg.DontReceiveDuplicates, 1, user)) + replacements['<mm-receive-duplicates-button>'] = ( + mlist.FormatOptionButton(mm_cfg.DontReceiveDuplicates, 0, user)) replacements['<mm-unsubscribe-button>'] = ( mlist.FormatButton('unsub', _('Unsubscribe')) + '<br>' + CheckBox('unsubconfirm', 1, checked=0).Format() + @@ -620,6 +634,8 @@ CheckBox('deliver-globally', 1, checked=0).Format()) replacements['<mm-global-remind-button>'] = ( CheckBox('remind-globally', 1, checked=0).Format()) + replacements['<mm-global-nodupes-button>'] = ( + CheckBox('nodupes-globally', 1, checked=0).Format()) days = int(mm_cfg.PENDING_REQUEST_LIFE / mm_cfg.days(1)) if days > 1: @@ -806,7 +822,7 @@ -def global_options(mlist, user, global_enable, global_remind): +def global_options(mlist, user, global_enable, global_remind, global_nodupes): def sigterm_handler(signum, frame, mlist=mlist): # Make sure the list gets unlocked... mlist.Unlock() @@ -827,6 +843,10 @@ if global_remind is not None: mlist.setMemberOption(user, mm_cfg.SuppressPasswordReminder, global_remind) + + if global_nodupes is not None: + mlist.setMemberOption(user, mm_cfg.DontReceiveDuplicates, + global_nodupes) mlist.Save() finally: diff -urN mailman-cvs/Mailman/Defaults.py.in mailman-cvs-nodupes/Mailman/Defaults.py.in --- mailman-cvs/Mailman/Defaults.py.in Sun Mar 3 14:42:08 2002 +++ mailman-cvs-nodupes/Mailman/Defaults.py.in Sun Mar 3 17:08:19 2002 @@ -399,6 +399,7 @@ 'Hold', 'Tagger', 'CalcRecips', + 'AvoidDuplicates', 'Cleanse', 'CookHeaders', # And now we send the message to the digest mbox file, and to the arch and @@ -658,6 +659,11 @@ 'privacy', 'bounce', 'archive', 'gateway', 'autoreply', 'topics', ] +# See "Bitfield for user options" below; make this a sum of those +# options, to make all new members of lists start with those options +# flagged. +# We assume by default that people don't want to receive two copies of posts +DEFAULT_LIST_OPTIONS = 256 ##### @@ -979,6 +985,7 @@ TEXTFIELDWIDTH = 40 # Bitfield for user options +# See DEFAULT_LIST_OPTIONS above to set defaults for all new lists Digests = 0 # handled by other mechanism, doesn't need a flag. DisableDelivery = 1 # Obsolete; use set/getDeliveryStatus() DontReceiveOwnPosts = 2 # Non-digesters only @@ -987,7 +994,8 @@ ConcealSubscription = 16 SuppressPasswordReminder = 32 ReceiveNonmatchingTopics = 64 -Moderate = 128 +Moderate = 128 +DontReceiveDuplicates = 256 # Authentication contexts. # diff -urN mailman-cvs/Mailman/Gui/GUIBase.py mailman-cvs-nodupes/Mailman/Gui/GUIBase.py --- mailman-cvs/Mailman/Gui/GUIBase.py Wed Feb 27 20:00:42 2002 +++ mailman-cvs-nodupes/Mailman/Gui/GUIBase.py Mon Mar 4 00:55:04 2002 @@ -22,6 +22,7 @@ from Mailman import mm_cfg from Mailman import Utils from Mailman import Errors +from Mailman import MailCommandHandler from Mailman.i18n import _ NL = '\n' @@ -117,36 +118,48 @@ continue # Unpack the gui item description property, wtype, args, deps, desc = item[0:5] - # BAW: I know this code is a little crufty but I wanted to - # reproduce the semantics of the original code in admin.py as - # closely as possible, for now. We can clean it up later. - # - # The property may be uploadable... - uploadprop = property + '_upload' - if cgidata.has_key(uploadprop) and cgidata[uploadprop].value: - val = cgidata[uploadprop].value - elif not cgidata.has_key(property): - return - elif isinstance(cgidata[property], ListType): - val = [x.value for x in cgidata[property]] - else: - val = cgidata[property].value - # Coerce the value to the expected type, raising exceptions if the - # value is invalid - try: - val = self._getValidValue(mlist, property, wtype, val) - except ValueError: - doc.addError(_('Invalid value for variable: %(property)s'), - tag=_('Error: ')) - return - # This is the parent of MMBadEmailError and MMHostileAddress - except Errors.EmailAddressError: - doc.addError( - _('Bad email address for option %(property)s: %(val)s'), - tag=_('Error: ')) - return - # Set the attribute, which will normally delegate to the mlist - self._setValue(mlist, property, val, doc) + + if property == 'default_options': + checked_defaults = cgidata.getvalue("default_options") + i = 0 + new_defaults = 0 + for opt in ("hide", "ack", "notmetoo", "plain", "nodupes"): + opt_code = MailCommandHandler.option_info[opt] + if `i` in checked_defaults: + new_defaults = new_defaults | opt_code + i = i + 1 + mlist.default_options = new_defaults + else: + # BAW: I know this code is a little crufty but I wanted to + # reproduce the semantics of the original code in admin.py as + # closely as possible, for now. We can clean it up later. + # + # The property may be uploadable... + uploadprop = property + '_upload' + if cgidata.has_key(uploadprop) and cgidata[uploadprop].value: + val = cgidata[uploadprop].value + elif not cgidata.has_key(property): + return + elif isinstance(cgidata[property], ListType): + val = [x.value for x in cgidata[property]] + else: + val = cgidata[property].value + # Coerce the value to the expected type, raising exceptions + # if the value is invalid + try: + val = self._getValidValue(mlist, property, wtype, val) + except ValueError: + doc.addError(_('Invalid value for variable: %(property)s'), + tag=_('Error: ')) + return + # This is the parent of MMBadEmailError and MMHostileAddress + except Errors.EmailAddressError: + doc.addError( + _('Bad email address for option %(property)s: %(val)s'), + tag=_('Error: ')) + return + # Set the attribute, which will normally delegate to the mlist + self._setValue(mlist, property, val, doc) # Convenience method for handling $-string attributes def _convertString(self, mlist, property, alloweds, val, doc): diff -urN mailman-cvs/Mailman/Gui/General.py mailman-cvs-nodupes/Mailman/Gui/General.py --- mailman-cvs/Mailman/Gui/General.py Sun Mar 3 14:29:17 2002 +++ mailman-cvs-nodupes/Mailman/Gui/General.py Sun Mar 3 20:01:15 2002 @@ -33,6 +33,22 @@ return None WIDTH = mm_cfg.TEXTFIELDWIDTH + # These are for the default_options checkboxes below. + # this should be set in a module somewhere.. + option_info = {'hide' : mm_cfg.ConcealSubscription, + 'ack' : mm_cfg.AcknowledgePosts, + 'notmetoo' : mm_cfg.DontReceiveOwnPosts, + 'plain' : mm_cfg.DisableMime, + 'nodupes' : mm_cfg.DontReceiveDuplicates + } + + options = ['hide', 'ack', 'notmetoo', 'plain', 'nodupes'] + option_values = [] + + for o in options: + option_values.append(mlist.default_options & option_info[o]) + + rtn = [ _('''Fundamental list characteristics, including descriptive info and basic behaviors.'''), @@ -237,6 +253,12 @@ _('''Turn this on if you want password reminders to be sent once per month to your members. Note that members may disable their own individual password reminders.''')), + + ('default_options', mm_cfg.Checkbox, (options, option_values, 1), + 0, _('''Default options for all members that join this list.'''), + + _('''This value is a bitfield that lets you set default options + for list subscribers.''')), ('welcome_msg', mm_cfg.Text, (4, WIDTH), 0, _('''List-specific text prepended to new-subscriber welcome diff -urN mailman-cvs/Mailman/HTMLFormatter.py mailman-cvs-nodupes/Mailman/HTMLFormatter.py --- mailman-cvs/Mailman/HTMLFormatter.py Wed Feb 20 21:51:57 2002 +++ mailman-cvs-nodupes/Mailman/HTMLFormatter.py Sun Mar 3 17:55:45 2002 @@ -116,6 +116,7 @@ mm_cfg.ConcealSubscription : 'conceal', mm_cfg.SuppressPasswordReminder : 'remind', mm_cfg.ReceiveNonmatchingTopics : 'rcvtopic', + mm_cfg.DontReceiveDuplicates : 'nodupes', }[option] return '<input type=radio name="%s" value="%d"%s>' % ( name, value, checked) diff -urN mailman-cvs/Mailman/Handlers/AvoidDuplicates.py mailman-cvs-nodupes/Mailman/Handlers/AvoidDuplicates.py --- mailman-cvs/Mailman/Handlers/AvoidDuplicates.py Wed Dec 31 16:00:00 1969 +++ mailman-cvs-nodupes/Mailman/Handlers/AvoidDuplicates.py Sun Mar 3 22:31:02 +2002 @@ -0,0 +1,115 @@ +# Copyright (C) 1998,1999,2000,2001,2002 by the Free Software Foundation, Inc. +# +# This program 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 2 +# of the License, or (at your option) any later version. +# +# This program 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 this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +"""If the user wishes it, do not send duplicates of the same message. + +This module keeps an in-memory dictionary of Message-ID and recipient +pairs. If a message with an identical Message-ID is about to be sent +to someone who has already received a copy, we either drop the message, +add a duplicate warning header, or pass it through, depending on the +user's preferences. +""" + +import string + +from Mailman import mm_cfg +from Mailman import Utils +from Mailman import Message +from Mailman import Errors +from Mailman.i18n import _ +from email.Utils import getaddresses + + + +class DuplicateDetected(Errors.DiscardMessage): + """The message would have been sent multiple times to a user who + prefers not to receive duplicates.""" + +# A dictionary of dictionaries, used to store which recipients have received +# which message IDs. +recip_msgids = {} + + + +def process(mlist, msg, msgdata): + + recips = msgdata['recips'] + msgid = msg.get('message-id') + + if not recips or not msgid: + return + + # This dictionary will hold recips who want their mail to have + # the X-Mailman-Duplicate: yes header. + if not msgdata.has_key('add-dupe-header'): + msgdata['add-dupe-header'] = {} + + external_recips = [] + for header in ('to', 'cc', 'resent-to', 'resent-cc'): + external_recips.extend(getaddresses(msg.get_all(header, []))) + + # Anyone mentioned in the to/cc/resent-to/resent-cc headers should + # not get a duplicate of the message. + for (name, email) in external_recips: + + # If getaddresses fails, email could be null. Skip those. + if not email: + continue + + # Initialize the external recipient's msgid hash if this is the + # first email they've received with this message-id. + if not recip_msgids.has_key(email): + recip_msgids[email] = {} + + # We don't do anything except record that that address has + # gotten or will get a copy of this email externally. + recip_msgids[email][msgid] = 1 + + newrecips = [] + + for r in recips: + if not recip_msgids.has_key(r): + recip_msgids[r] = {} + + # If they have received a message with this message-id already, + # see if they don't want duplicates. + if recip_msgids[r].has_key(msgid): + send_duplicate = 1 + + # If the member wants to receive duplicates, or if the recipient + # is not a member at all, just flag the X-Mailman-Duplicate: yes + # header. + try: + if mlist.getMemberOption(r, mm_cfg.DontReceiveDuplicates): + send_duplicate = 0 + except Errors.NotAMemberError: + pass + + # We'll send a duplicate unless the user doesn't wish it. + # If personalization is enabled, the add-dupe-header flag will + # add a X-Mailman-Duplicate: yes header for this user's message. + if send_duplicate: + msgdata['add-dupe-header'][r] = 1 + newrecips.append(r) + + else: + # Otherwise, this is the first time they've been in the recips + # list. Add them to the newrecips list and flag them as having + # received this message. + recip_msgids[r][msgid] = 1 + newrecips.append(r) + + msgdata['recips'] = newrecips diff -urN mailman-cvs/Mailman/Handlers/Personalize.py mailman-cvs-nodupes/Mailman/Handlers/Personalize.py --- mailman-cvs/Mailman/Handlers/Personalize.py Tue Nov 20 08:39:38 2001 +++ mailman-cvs-nodupes/Mailman/Handlers/Personalize.py Sun Mar 3 17:07:42 2002 @@ -45,6 +45,13 @@ msg['To'] = '%s (%s)' % (member, name) else: msg['To'] = member + # We can flag the mail as a duplicate for each member, if + # they've already received that message. (See AvoidDuplicates.py) + if msgdata['add-dupe-header'].has_key(member): + msg['X-Mailman-Duplicate'] = 'yes' + elif msg.has_key('X-Mailman-Duplicate'): + del msg['X-Mailman-Duplicate'] + # See if we're taking the opportunity to VERP for more reliable bounce # processing. metadatacopy['verp'] = mm_cfg.VERP_PERSONALIZED_DELIVERIES @@ -52,6 +59,10 @@ # Restore the original To: line del msg['To'] msg['To'] = originalto + + # The original message is not a duplicate. + if msg.has_key('X-Mailman-Duplicate'): + del msg['X-Mailman-Duplicate'] # Don't let the normal ToOutgoing processing actually send the original # copy, otherwise we'll get duplicates. del msgdata['recips'] diff -urN mailman-cvs/Mailman/MailCommandHandler.py mailman-cvs-nodupes/Mailman/MailCommandHandler.py --- mailman-cvs/Mailman/MailCommandHandler.py Sun Mar 3 14:29:16 2002 +++ mailman-cvs-nodupes/Mailman/MailCommandHandler.py Sun Mar 3 16:28:01 2002 @@ -80,12 +80,20 @@ you get digests in MIME format, which are much better if you have a mail reader that supports MIME.""") -option_desc = {'hide' : HIDE, - 'nomail' : NOMAIL, - 'ack' : ACK, - 'notmetoo': NOTMETOO, - 'digest' : DIGEST, - 'plain' : PLAIN, +NODUPES = _("""When turned on, you do *not* receive duplicate mails if mail is +sent to multiple lists that you belong to. This option will let you avoid +duplicate mails; if you turn it on, you will never receive multiple copies +of the same message. Also, if you *and* the list are mentioned explicitly +in the To: or Cc: headers of a message, you will not receive duplicates if +this is turned on.""") + +option_desc = {'hide' : HIDE, + 'nomail' : NOMAIL, + 'ack' : ACK, + 'notmetoo' : NOTMETOO, + 'digest' : DIGEST, + 'plain' : PLAIN, + 'nodupes' : NODUPES, } # jcrey: and then the real one @@ -97,10 +105,11 @@ 'notmetoo': mm_cfg.DontReceiveOwnPosts, 'digest' : 0, 'plain' : mm_cfg.DisableMime, + 'nodupes' : mm_cfg.DontReceiveDuplicates } # ordered list -options = ('hide', 'nomail', 'ack', 'notmetoo', 'digest', 'plain') +options = ('hide', 'nomail', 'ack', 'notmetoo', 'digest', 'plain', 'nodupes') # strip just the outer layer of quotes quotecre = re.compile(r'["\'`](?P<cmd>.*)["\'`]') diff -urN mailman-cvs/Mailman/MailList.py mailman-cvs-nodupes/Mailman/MailList.py --- mailman-cvs/Mailman/MailList.py Sun Mar 3 14:29:16 2002 +++ mailman-cvs-nodupes/Mailman/MailList.py Sun Mar 3 17:08:03 2002 @@ -257,6 +257,7 @@ self.language = {} self.usernames = {} self.passwords = {} + self.default_options = mm_cfg.DEFAULT_LIST_OPTIONS # This stuff is configurable self.respond_to_post_requests = 1 diff -urN mailman-cvs/Mailman/OldStyleMemberships.py mailman-cvs-nodupes/Mailman/OldStyleMemberships.py --- mailman-cvs/Mailman/OldStyleMemberships.py Wed Feb 20 21:51:58 2002 +++ mailman-cvs-nodupes/Mailman/OldStyleMemberships.py Mon Mar 4 01:09:18 2002 @@ -207,6 +207,8 @@ self.setMemberLanguage(member, language) if realname: self.setMemberName(member, realname) + if self.__mlist.default_options: + self.__mlist.user_options[member] = self.__mlist.default_options def removeMember(self, member): assert self.__mlist.Locked() diff -urN mailman-cvs/Mailman/Version.py mailman-cvs-nodupes/Mailman/Version.py --- mailman-cvs/Mailman/Version.py Fri Jan 25 13:38:42 2002 +++ mailman-cvs-nodupes/Mailman/Version.py Sun Mar 3 16:44:50 2002 @@ -15,7 +15,7 @@ # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # Mailman version -VERSION = "2.1a4+" +VERSION = "2.1a4+-nodupes" # And as a hex number in the manner of PY_VERSION_HEX ALPHA = 0xa @@ -36,7 +36,7 @@ (REL_LEVEL << 4) | (REL_SERIAL << 0)) # config.pck schema version number -DATA_FILE_VERSION = 54 +DATA_FILE_VERSION = 55 # qfile/*.db schema version number QFILE_SCHEMA_VERSION = 3 diff -urN mailman-cvs/Mailman/versions.py mailman-cvs-nodupes/Mailman/versions.py --- mailman-cvs/Mailman/versions.py Fri Jan 25 13:38:42 2002 +++ mailman-cvs-nodupes/Mailman/versions.py Sun Mar 3 17:07:53 2002 @@ -286,6 +286,7 @@ add_only_if_missing('one_last_digest', {}) add_only_if_missing('usernames', {}) add_only_if_missing('personalize', 0) + add_only_if_missing('default_options', mm_cfg.DEFAULT_LIST_OPTIONS) add_only_if_missing('first_strip_reply_to', mm_cfg.DEFAULT_FIRST_STRIP_REPLY_TO) add_only_if_missing('unsubscribe_policy', diff -urN mailman-cvs/templates/en/help.txt mailman-cvs-nodupes/templates/en/help.txt --- mailman-cvs/templates/en/help.txt Fri May 18 14:28:54 2001 +++ mailman-cvs-nodupes/templates/en/help.txt Sun Mar 3 16:46:25 2002 @@ -79,6 +79,11 @@ Conceals your address when people look at who is on this list. + nodupes: + Turn this on if you do not want to receive duplicate mail + from the list, in case you are explicitly in the To: or Cc: + fields already or are included in multiple lists in one message. + options Show the current values of your list options. diff -urN mailman-cvs/templates/en/options.html mailman-cvs-nodupes/templates/en/options.html --- mailman-cvs/templates/en/options.html Sun Mar 3 14:29:21 2002 +++ mailman-cvs-nodupes/templates/en/options.html Sun Mar 3 17:53:01 2002 @@ -280,6 +280,26 @@ <mm-receive-nonmatching-topics>Yes </td></tr> + <tr><td bgcolor="#cccccc"> + <strong>Avoid duplicate copies of messages?</strong><p> + + When you are listed explicitly in the To: or Cc: headers + of a list message, or a message is sent to multiple lists + that you are a member of, you can opt to not receive another + copy from the mailing list. Select <em>Yes</em> to avoid + receiving duplicate copies from the mailing list; select + <em>No</em> to receive duplicate copies. + + <p>If the list has per-message personalization + enabled, every duplicate mail will have a + <tt>X-Mailman-Duplicate: yes</tt> header added to it. + + </td><td bgcolor="#cccccc"> + <mm-receive-duplicates-button>No<br> + <mm-dont-receive-duplicates-button>Yes<p> + <mm-global-nodupes-button><i>Set globally</i> + </td></tr> + <tr><TD colspan="2"> <center><MM-options-Submit-button></center> </td></tr>