Colin Watson has proposed merging lp:~cjwatson/launchpad/message-for-header 
into lp:launchpad.

Commit message:
Add an X-Launchpad-Message-For header with just the name of the person 
subscribed to the notification.

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)
Related bugs:
  Bug #1493844 in Launchpad itself: "Can't use gmail+new-verbose-footers to 
filter by group rationale"
  https://bugs.launchpad.net/launchpad/+bug/1493844

For more details, see:
https://code.launchpad.net/~cjwatson/launchpad/message-for-header/+merge/270811

Add an X-Launchpad-Message-For header with just the name of the person 
subscribed to the notification.

There are several times more implementations of this than there ought to be, 
because not everything goes through BaseMailer yet; and the implementation for 
bug notifications is horrible because BugNotificationRecipient doesn't (I 
think) provide enough information.
-- 
Your team Launchpad code reviewers is requested to review the proposed merge of 
lp:~cjwatson/launchpad/message-for-header into lp:launchpad.
=== modified file 'lib/lp/answers/model/questionjob.py'
--- lib/lp/answers/model/questionjob.py	2015-08-25 16:24:06 +0000
+++ lib/lp/answers/model/questionjob.py	2015-09-11 12:44:20 +0000
@@ -236,6 +236,7 @@
         for email in recipients.getEmails():
             reason, header = recipients.getReason(email)
             headers['X-Launchpad-Message-Rationale'] = header
+            headers['X-Launchpad-Message-For'] = reason.subscriber.name
             formatted_body = self.buildBody(reason.getReason())
             simple_sendmail(
                 self.from_address, email, self.subject, formatted_body,

=== modified file 'lib/lp/bugs/doc/bugnotification-email.txt'
--- lib/lp/bugs/doc/bugnotification-email.txt	2015-07-31 14:46:31 +0000
+++ lib/lp/bugs/doc/bugnotification-email.txt	2015-09-11 12:44:20 +0000
@@ -585,6 +585,12 @@
     >>> print notification_email['X-Launchpad-Message-Rationale']
     Because-I-said-so
 
+The X-Launchpad-Message-For header is set from the to_person (since this
+notification is not for a team).
+
+    >>> print notification_email['X-Launchpad-Message-For']
+    name16
+
 The references parameter sets the References header of the email.
 
     >>> print notification_email['References']

=== modified file 'lib/lp/bugs/doc/bugnotification-sending.txt'
--- lib/lp/bugs/doc/bugnotification-sending.txt	2015-07-13 16:14:46 +0000
+++ lib/lp/bugs/doc/bugnotification-sending.txt	2015-09-11 12:44:20 +0000
@@ -22,6 +22,7 @@
     >>> def print_notification_headers(email_notification, extra_headers=[]):
     ...     for header in ['To', 'From', 'Subject',
     ...                    'X-Launchpad-Message-Rationale',
+    ...                    'X-Launchpad-Message-For',
     ...                    'X-Launchpad-Subscription'] + extra_headers:
     ...         if email_notification[header]:
     ...             print "%s: %s" % (header, email_notification[header])
@@ -73,6 +74,7 @@
     From: Sample Person <[email protected]>
     Subject: [Bug 1] subject
     X-Launchpad-Message-Rationale: Subscriber (mozilla-firefox in Ubuntu)
+    X-Launchpad-Message-For: name16
     <BLANKLINE>
     a comment.
     <BLANKLINE>
@@ -82,6 +84,7 @@
     From: Sample Person <[email protected]>
     Subject: [Bug 1] subject
     X-Launchpad-Message-Rationale: Assignee
+    X-Launchpad-Message-For: mark
     <BLANKLINE>
     a comment.
     <BLANKLINE>
@@ -91,6 +94,7 @@
     From: Sample Person <[email protected]>
     Subject: [Bug 1] subject
     X-Launchpad-Message-Rationale: Subscriber
+    X-Launchpad-Message-For: name12
     <BLANKLINE>
     a comment.
     <BLANKLINE>
@@ -154,6 +158,7 @@
     From: Sample Person <[email protected]>
     Subject: Re: [Bug 1] subject
     X-Launchpad-Message-Rationale: Assignee
+    X-Launchpad-Message-For: mark
     <BLANKLINE>
     a new comment.
     <BLANKLINE>
@@ -193,6 +198,7 @@
     From: Sample Person <[email protected]>
     Subject: [Bug 1] Re: Firefox does not support SVG
     X-Launchpad-Message-Rationale: Assignee
+    X-Launchpad-Message-For: mark
     <BLANKLINE>
     ** Summary changed:
     - Old summary
@@ -239,6 +245,7 @@
     From: Sample Person <[email protected]>
     Subject: [Bug 1] Re: Firefox does not support SVG
     X-Launchpad-Message-Rationale: Assignee
+    X-Launchpad-Message-For: mark
     <BLANKLINE>
     a new comment.
     <BLANKLINE>
@@ -366,6 +373,7 @@
     From: Sample Person <[email protected]>
     Subject: [Bug 16] [NEW] new bug
     X-Launchpad-Message-Rationale: Subscriber
+    X-Launchpad-Message-For: name12
     <BLANKLINE>
     Public bug reported:
     ...
@@ -396,6 +404,7 @@
     From: Sample Person <[email protected]>
     Subject: [Bug 16] subject
     X-Launchpad-Message-Rationale: Subscriber
+    X-Launchpad-Message-For: name12
     X-Launchpad-Bug-Duplicate: 1
     <BLANKLINE>
     *** This bug is a duplicate of bug 1 ***
@@ -432,6 +441,7 @@
     From: Sample Person <[email protected]>
     Subject: [Bug ...] [NEW] Zero-day on Frobulator
     X-Launchpad-Message-Rationale: Subscriber
+    X-Launchpad-Message-For: name12
     <BLANKLINE>
     *** This bug is a security vulnerability ***
     <BLANKLINE>
@@ -456,6 +466,7 @@
     From: Sample Person <[email protected]>
     Subject: [Bug ...] subject
     X-Launchpad-Message-Rationale: Subscriber
+    X-Launchpad-Message-For: name12
     <BLANKLINE>
     a comment.
     <BLANKLINE>
@@ -538,6 +549,7 @@
     References: [email protected]
     ...
     X-Launchpad-Message-Rationale: Assignee
+    X-Launchpad-Message-For: name12
     ...
     INFO    Notifying [email protected] about bug 1.
     ...
@@ -548,6 +560,7 @@
     References: sdsdfsfd
     ...
     X-Launchpad-Message-Rationale: Subscriber (mozilla-firefox in Ubuntu)
+    X-Launchpad-Message-For: name16
     ...
     INFO    Notifying [email protected] about bug 1.
     ...
@@ -564,6 +577,7 @@
     References: sdsdfsfd
     ...
     X-Launchpad-Message-Rationale: Subscriber (mozilla-firefox in Ubuntu)
+    X-Launchpad-Message-For: name16
     Errors-To: [email protected]
     Return-Path: [email protected]
     Precedence: bulk
@@ -840,12 +854,14 @@
     ...             "will be automatically wrapped by the BugNotification "
     ...             "machinery. Ain't technology great?")
     ...     verbose_person = factory.makePerson(
-    ...         displayname='Verbose Person', email='[email protected]',
+    ...         name='verbose-person', displayname='Verbose Person',
+    ...         email='[email protected]',
     ...         selfgenerated_bugnotifications=True)
     ...     verbose_person.verbose_bugnotifications = True
     ...     ignored = bug.subscribe(verbose_person, verbose_person)
     ...     concise_person = factory.makePerson(
-    ...         displayname='Concise Person', email='[email protected]')
+    ...         name='concise-person', displayname='Concise Person',
+    ...         email='[email protected]')
     ...     concise_person.verbose_bugnotifications = False
     ...     ignored = bug.subscribe(concise_person, concise_person)
 
@@ -858,7 +874,7 @@
     ...         name='conciseteam', displayname='Concise Team')
     ...     concise_team.verbose_bugnotifications = False
     ...     concise_team_person = factory.makePerson(
-    ...         displayname='Concise Team Person',
+    ...         name='conciseteam-person', displayname='Concise Team Person',
     ...         email='[email protected]')
     ...     concise_team_person.verbose_bugnotifications = True
     ...     ignored = concise_team.addMember(
@@ -873,7 +889,7 @@
     ...         name='verboseteam', displayname='Verbose Team')
     ...     verbose_team.verbose_bugnotifications = True
     ...     verbose_team_person = factory.makePerson(
-    ...         displayname='Verbose Team Person',
+    ...         name='verboseteam-person', displayname='Verbose Team Person',
     ...         email='[email protected]')
     ...     verbose_team_person.verbose_bugnotifications = False
     ...     ignored = verbose_team.addMember(
@@ -934,6 +950,7 @@
     From: Verbose Person <[email protected]>
     Subject: [Bug ...] subject
     X-Launchpad-Message-Rationale: Subscriber
+    X-Launchpad-Message-For: concise-person
     <BLANKLINE>
     a really simple comment.
     <BLANKLINE>
@@ -961,6 +978,7 @@
     From: Verbose Person <[email protected]>
     Subject: [Bug ...] subject
     X-Launchpad-Message-Rationale: Subscriber @verboseteam
+    X-Launchpad-Message-For: verboseteam
     <BLANKLINE>
     a really simple comment.
     <BLANKLINE>
@@ -980,6 +998,7 @@
     From: Verbose Person <[email protected]>
     Subject: [Bug ...] subject
     X-Launchpad-Message-Rationale: Subscriber
+    X-Launchpad-Message-For: verbose-person
     <BLANKLINE>
     a really simple comment.
     <BLANKLINE>
@@ -1010,6 +1029,7 @@
     From: Verbose Person <[email protected]>
     Subject: [Bug ...] subject
     X-Launchpad-Message-Rationale: Subscriber @conciseteam
+    X-Launchpad-Message-For: conciseteam
     <BLANKLINE>
     a really simple comment.
     <BLANKLINE>
@@ -1134,6 +1154,7 @@
     From: Sample Person <[email protected]>
     Subject: [Bug 1] subject
     X-Launchpad-Message-Rationale: Subscriber (Mozilla Firefox)
+    X-Launchpad-Message-For: no-priv
     <BLANKLINE>
     another comment.
     <BLANKLINE>
@@ -1190,6 +1211,7 @@
     From: Sample Person <[email protected]>
     Subject: [Bug 1] subject
     X-Launchpad-Message-Rationale: Subscriber (Mozilla Firefox)
+    X-Launchpad-Message-For: no-priv
     X-Launchpad-Subscription: Allow-comments filter
     <BLANKLINE>
     another comment.
@@ -1245,6 +1267,7 @@
     From: Sample Person <[email protected]>
     Subject: [Bug 1] subject
     X-Launchpad-Message-Rationale: Subscriber @addressless
+    X-Launchpad-Message-For: addressless
     <BLANKLINE>
     no comment for no-priv.
     <BLANKLINE>
@@ -1291,6 +1314,7 @@
     From: Sample Person <[email protected]>
     Subject: [Bug 1] subject
     X-Launchpad-Message-Rationale: Subscriber (Mozilla Firefox)
+    X-Launchpad-Message-For: no-priv
     X-Launchpad-Subscription: Allow-comments filter
     <BLANKLINE>
     no comment for no-priv.
@@ -1350,6 +1374,7 @@
     From: Sample Person <[email protected]>
     Subject: [Bug 1] Re: Firefox does not support SVG
     X-Launchpad-Message-Rationale: Subscriber @addressless
+    X-Launchpad-Message-For: addressless
     <BLANKLINE>
     ** Summary changed:
     - Whatever
@@ -1405,6 +1430,7 @@
     From: Sample Person <[email protected]>
     Subject: [Bug 1] Re: Firefox does not support SVG
     X-Launchpad-Message-Rationale: Subscriber (Mozilla Firefox)
+    X-Launchpad-Message-For: no-priv
     <BLANKLINE>
     ** Summary changed:
     - I'm losing my

=== modified file 'lib/lp/bugs/doc/initial-bug-contacts.txt'
--- lib/lp/bugs/doc/initial-bug-contacts.txt	2012-08-16 07:02:41 +0000
+++ lib/lp/bugs/doc/initial-bug-contacts.txt	2015-09-11 12:44:20 +0000
@@ -191,6 +191,8 @@
 
     >>> msg['X-Launchpad-Message-Rationale']
     'Subscriber (pmount in Ubuntu)'
+    >>> msg['X-Launchpad-Message-For']
+    'daf'
 
     >>> msg['Subject']
     '[Bug 1] [NEW] Firefox does not support SVG'

=== modified file 'lib/lp/bugs/mail/bugnotificationbuilder.py'
--- lib/lp/bugs/mail/bugnotificationbuilder.py	2015-08-28 06:44:52 +0000
+++ lib/lp/bugs/mail/bugnotificationbuilder.py	2015-09-11 12:44:20 +0000
@@ -13,6 +13,7 @@
 
 from email.mime.text import MIMEText
 from email.utils import formatdate
+import re
 import rfc822
 
 from zope.component import getUtility
@@ -207,6 +208,16 @@
 
         if rationale is not None:
             headers.append(('X-Launchpad-Message-Rationale', rationale))
+            # XXX cjwatson 2015-09-11: The ridiculously complicated way that
+            # bug notifications are built means that we no longer have
+            # direct access to the subscriber name at this point.  As a
+            # stopgap, parse it out of the rationale.
+            match = re.search(r'@([^ ]*)', rationale)
+            if match is not None:
+                message_for = match.group(1)
+            else:
+                message_for = removeSecurityProxy(to_person).name
+            headers.append(('X-Launchpad-Message-For', message_for))
 
         if filters is not None:
             for filter in filters:

=== modified file 'lib/lp/bugs/stories/bugs/bug-add-subscriber.txt'
--- lib/lp/bugs/stories/bugs/bug-add-subscriber.txt	2012-07-27 01:15:04 +0000
+++ lib/lp/bugs/stories/bugs/bug-add-subscriber.txt	2015-09-11 12:44:20 +0000
@@ -82,6 +82,7 @@
     Reply-To: Bug ... <[email protected]>
     ...
     X-Launchpad-Message-Rationale: Subscriber
+    X-Launchpad-Message-For: ddaa
     ...
     You have been subscribed to a public bug by No Privileges Person (no-priv):
     ...

=== modified file 'lib/lp/code/doc/branch-merge-proposal-notifications.txt'
--- lib/lp/code/doc/branch-merge-proposal-notifications.txt	2015-09-02 16:54:24 +0000
+++ lib/lp/code/doc/branch-merge-proposal-notifications.txt	2015-09-11 12:44:20 +0000
@@ -29,13 +29,15 @@
     >>> previewdiff = factory.makePreviewDiff(merge_proposal=bmp)
     >>> transaction.commit()
     >>> source_subscriber = factory.makePerson(
-    ...     email='[email protected]', displayname='Source Subscriber')
+    ...     email='[email protected]', name='source-subscriber',
+    ...     displayname='Source Subscriber')
     >>> _unused = bmp.source_branch.subscribe(source_subscriber,
     ...     BranchSubscriptionNotificationLevel.NOEMAIL,
     ...     BranchSubscriptionDiffSize.NODIFF,
     ...     CodeReviewNotificationLevel.STATUS, source_subscriber)
     >>> target_subscriber = factory.makePerson(
-    ...     email='[email protected]', displayname='Target Subscriber')
+    ...     email='[email protected]', name='target-subscriber',
+    ...     displayname='Target Subscriber')
     >>> target_subscription = bmp.target_branch.subscribe(target_subscriber,
     ...     BranchSubscriptionNotificationLevel.NOEMAIL,
     ...     BranchSubscriptionDiffSize.NODIFF,
@@ -136,6 +138,8 @@
     ~person-name...
     >>> print notification['X-Launchpad-Message-Rationale']
     Subscriber
+    >>> print notification['X-Launchpad-Message-For']
+    source-subscriber
     >>> print notification.get_payload(decode=True)
     Eric has proposed merging
     lp://dev/~person-name...into lp://dev/~person-name...
@@ -176,11 +180,12 @@
     >>> for notification in notifications:
     ...     print "%s, %s" % (
     ...         notification['X-Envelope-To'],
-    ...         notification['X-Launchpad-Message-Rationale'])
-    [email protected], Reviewer
-    [email protected], Reviewer
-    [email protected], Subscriber
-    [email protected], Subscriber
+    ...         notification['X-Launchpad-Message-Rationale'],
+    ...         notification['X-Launchpad-Message-For'])
+    [email protected], Reviewer, bob
+    [email protected], Reviewer, mary
+    [email protected], Subscriber, source-subscriber
+    [email protected], Subscriber, target-subscriber
     >>> notification = notifications[0]
     >>> print notification.get_payload()[0].get_payload(decode=True)
     Eric has proposed merging

=== modified file 'lib/lp/code/doc/branch-notifications.txt'
--- lib/lp/code/doc/branch-notifications.txt	2015-09-02 16:54:24 +0000
+++ lib/lp/code/doc/branch-notifications.txt	2015-09-11 12:44:20 +0000
@@ -60,6 +60,8 @@
     ~name12/firefox/main
     >>> print branch_notification['X-Launchpad-Message-Rationale']
     Subscriber
+    >>> print branch_notification['X-Launchpad-Message-For']
+    name12
     >>> notification_body = branch_notification.get_payload(decode=True)
     >>> print notification_body #doctest: -NORMALIZE_WHITESPACE
     The contents.

=== modified file 'lib/lp/code/doc/codeimport.txt'
--- lib/lp/code/doc/codeimport.txt	2014-02-24 07:19:52 +0000
+++ lib/lp/code/doc/codeimport.txt	2015-09-11 12:44:20 +0000
@@ -100,6 +100,8 @@
     New code import: widget/trunk-cvs
     >>> print message['X-Launchpad-Message-Rationale']
     Operator @vcs-imports
+    >>> print message['X-Launchpad-Message-For']
+    vcs-imports
     >>> print message.get_payload(decode=True)
     A new CVS code import has been requested by Code Import Person:
         http://code.launchpad.dev/~import-person/widget/trunk-cvs

=== modified file 'lib/lp/code/mail/codeimport.py'
--- lib/lp/code/mail/codeimport.py	2014-02-24 07:19:52 +0000
+++ lib/lp/code/mail/codeimport.py	2015-09-11 12:44:20 +0000
@@ -1,4 +1,4 @@
-# Copyright 2009-2011 Canonical Ltd.  This software is licensed under the
+# Copyright 2009-2015 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
 """Email notifications related to code imports."""
@@ -65,6 +65,7 @@
     headers = {'X-Launchpad-Branch': code_import.branch.unique_name,
                'X-Launchpad-Message-Rationale':
                    'Operator @%s' % vcs_imports.name,
+               'X-Launchpad-Message-For': vcs_imports.name,
                'X-Launchpad-Notification-Type': 'code-import',
                }
     for address in get_contact_email_addresses(vcs_imports):
@@ -185,6 +186,7 @@
             else:
                 template_params['rationale'] = rationale
             template_params['unsubscribe'] = ''
+            for_person = vcs_imports
         else:
             if subscription.notification_level in interested_levels:
                 template_params['rationale'] = (
@@ -197,10 +199,12 @@
                         "%s/+edit-subscription." % canonical_url(branch))
                 else:
                     template_params['unsubscribe'] = ''
+                for_person = subscription.person
             else:
                 # Don't send email to this subscriber.
                 continue
 
         headers['X-Launchpad-Message-Rationale'] = rationale
+        headers['X-Launchpad-Message-For'] = for_person.name
         body = email_template % template_params
         simple_sendmail(from_address, email_address, subject, body, headers)

=== modified file 'lib/lp/code/mail/tests/test_branch.py'
--- lib/lp/code/mail/tests/test_branch.py	2015-09-02 16:54:24 +0000
+++ lib/lp/code/mail/tests/test_branch.py	2015-09-11 12:44:20 +0000
@@ -227,6 +227,7 @@
         self.assertEqual(
             {'X-Launchpad-Branch': branch.unique_name,
              'X-Launchpad-Message-Rationale': 'Subscriber',
+             'X-Launchpad-Message-For': bob.name,
              'X-Launchpad-Notification-Type': 'branch-updated',
              'X-Launchpad-Project': self.getBranchProjectName(branch),
              'Message-Id': '<foobar-example-com>'},
@@ -247,6 +248,7 @@
         self.assertEqual(
             {'X-Launchpad-Branch': branch.unique_name,
              'X-Launchpad-Message-Rationale': 'Subscriber',
+             'X-Launchpad-Message-For': bob.name,
              'X-Launchpad-Notification-Type': 'branch-revision',
              'X-Launchpad-Branch-Revision-Number': '1',
              'X-Launchpad-Project': self.getBranchProjectName(branch),

=== modified file 'lib/lp/code/mail/tests/test_branchmergeproposal.py'
--- lib/lp/code/mail/tests/test_branchmergeproposal.py	2015-09-02 16:54:24 +0000
+++ lib/lp/code/mail/tests/test_branchmergeproposal.py	2015-09-11 12:44:20 +0000
@@ -135,6 +135,7 @@
         self.assertEqual(
             {'X-Launchpad-Branch': bmp.source_branch.unique_name,
              'X-Launchpad-Message-Rationale': 'Subscriber',
+             'X-Launchpad-Message-For': subscriber.name,
              'X-Launchpad-Notification-Type': 'code-review',
              'X-Launchpad-Project': bmp.source_branch.product.name,
              'Reply-To': bmp.address,

=== modified file 'lib/lp/code/mail/tests/test_codereviewcomment.py'
--- lib/lp/code/mail/tests/test_codereviewcomment.py	2015-09-08 11:56:33 +0000
+++ lib/lp/code/mail/tests/test_codereviewcomment.py	2015-09-11 12:44:20 +0000
@@ -164,6 +164,7 @@
         rationale = mailer._recipients.getReason('[email protected]')[1]
         expected = {'X-Launchpad-Branch': source_branch.unique_name,
                     'X-Launchpad-Message-Rationale': rationale,
+                    'X-Launchpad-Message-For': subscriber.name,
                     'X-Launchpad-Notification-Type': 'code-review',
                     'X-Launchpad-Project': source_branch.product.name,
                     'Message-Id': message.rfc822msgid,
@@ -221,6 +222,7 @@
             'You are subscribed to branch %s.' % source_branch.bzr_identity,
             '',
             'Launchpad-Message-Rationale: %s' % rationale,
+            'Launchpad-Message-For: %s' % subscriber.name,
             'Launchpad-Notification-Type: code-review',
             'Launchpad-Branch: %s' % source_branch.unique_name,
             'Launchpad-Project: %s' % source_branch.product.name,

=== modified file 'lib/lp/code/mail/tests/test_sourcepackagerecipebuild.py'
--- lib/lp/code/mail/tests/test_sourcepackagerecipebuild.py	2015-09-02 16:54:24 +0000
+++ lib/lp/code/mail/tests/test_sourcepackagerecipebuild.py	2015-09-11 12:44:20 +0000
@@ -84,6 +84,8 @@
         self.assertEqual(
             'Requester', ctrl.headers['X-Launchpad-Message-Rationale'])
         self.assertEqual(
+            build.requester.name, ctrl.headers['X-Launchpad-Message-For'])
+        self.assertEqual(
             'recipe-build-status',
             ctrl.headers['X-Launchpad-Notification-Type'])
         self.assertEqual(
@@ -119,6 +121,8 @@
         self.assertEqual(
             'Requester', ctrl.headers['X-Launchpad-Message-Rationale'])
         self.assertEqual(
+            build.requester.name, ctrl.headers['X-Launchpad-Message-For'])
+        self.assertEqual(
             'recipe-build-status',
             ctrl.headers['X-Launchpad-Notification-Type'])
         self.assertEqual(

=== modified file 'lib/lp/registry/browser/person.py'
--- lib/lp/registry/browser/person.py	2015-08-06 16:48:48 +0000
+++ lib/lp/registry/browser/person.py	2015-09-11 12:44:20 +0000
@@ -4327,7 +4327,7 @@
             return
         try:
             send_direct_contact_email(
-                sender_email, self.recipients, subject, message)
+                sender_email, self.recipients, self.context, subject, message)
         except QuotaReachedError as error:
             fmt_date = DateTimeFormatterAPI(self.next_try)
             self.request.response.addErrorNotification(

=== modified file 'lib/lp/registry/doc/teammembership-email-notification.txt'
--- lib/lp/registry/doc/teammembership-email-notification.txt	2015-09-02 02:46:18 +0000
+++ lib/lp/registry/doc/teammembership-email-notification.txt	2015-09-11 12:44:20 +0000
@@ -1067,10 +1067,11 @@
     ...     name='team-two', email='[email protected]', owner=owner)
     >>> ignored = team_one.addMember(team_two, owner, force_team_add=True)
     >>> run_mail_jobs()
-    >>> print_distinct_emails()
+    >>> print_distinct_emails(include_for=True)
     From: Team One ...
     To: Team Two <team-two...>
     X-Launchpad-Message-Rationale: Member (team-one) @team-two
+    X-Launchpad-Message-For: team-two
     X-Launchpad-Notification-Type: team-membership-new
     Subject: team-two joined team-one
     <BLANKLINE>

=== modified file 'lib/lp/registry/mail/notification.py'
--- lib/lp/registry/mail/notification.py	2015-09-02 02:46:18 +0000
+++ lib/lp/registry/mail/notification.py	2015-09-11 12:44:20 +0000
@@ -157,13 +157,15 @@
 
 
 def send_direct_contact_email(
-    sender_email, recipients_set, subject, body):
+    sender_email, recipients_set, person_or_team, subject, body):
     """Send a direct user-to-user email.
 
     :param sender_email: The email address of the sender.
     :type sender_email: string
     :param recipients_set: The recipients.
-    :type recipients_set:' A ContactViaWebNotificationSet
+    :type recipients_set: `ContactViaWebNotificationSet`
+    :param person_or_team: The party that is the context of the email.
+    :type person_or_team: `IPerson`
     :param subject: The Subject header.
     :type subject: unicode
     :param body: The message body.
@@ -209,7 +211,7 @@
     message = None
     for recipient_email, recipient in recipients_set.getRecipientPersons():
         recipient_name = str(encode(recipient.displayname))
-        reason, rational_header = recipients_set.getReason(recipient_email)
+        reason, rationale_header = recipients_set.getReason(recipient_email)
         reason = str(encode(reason)).replace('\n ', '\n')
         formatted_body = mailwrapper.format(body, force_wrap=True)
         formatted_body += additions % reason
@@ -219,7 +221,8 @@
         message['To'] = formataddr((recipient_name, recipient_email))
         message['Subject'] = subject_header
         message['Message-ID'] = make_msgid('launchpad')
-        message['X-Launchpad-Message-Rationale'] = rational_header
+        message['X-Launchpad-Message-Rationale'] = rationale_header
+        message['X-Launchpad-Message-For'] = person_or_team.name
         # Send the message.
         sendmail(message, bulk=False)
     # Use the information from the last message sent to record the action

=== modified file 'lib/lp/registry/model/productjob.py'
--- lib/lp/registry/model/productjob.py	2015-07-09 20:06:17 +0000
+++ lib/lp/registry/model/productjob.py	2015-09-11 12:44:20 +0000
@@ -1,4 +1,4 @@
-# Copyright 2012 Canonical Ltd.  This software is licensed under the
+# Copyright 2012-2015 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
 """Jobs classes to update products and send notifications."""
@@ -321,6 +321,7 @@
             'X-Launchpad-Project':
                 '%(product_displayname)s (%(product_name)s)' % message_data,
             'X-Launchpad-Message-Rationale': rationale,
+            'X-Launchpad-Message-For': self.product.owner.name,
             }
         if reply_to is not None:
             headers['Reply-To'] = reply_to

=== modified file 'lib/lp/registry/tests/test_notification.py'
--- lib/lp/registry/tests/test_notification.py	2012-12-26 01:04:05 +0000
+++ lib/lp/registry/tests/test_notification.py	2015-09-11 12:44:20 +0000
@@ -1,4 +1,4 @@
-# Copyright 2012 Canonical Ltd.  This software is licensed under the
+# Copyright 2012-2015 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
 """Test notification classes and functions."""
@@ -28,7 +28,8 @@
         recipients_set = NotificationRecipientSet()
         recipients_set.add(user, 'test reason', 'test rationale')
         pop_notifications()
-        send_direct_contact_email('[email protected]', recipients_set, subject, body)
+        send_direct_contact_email(
+            '[email protected]', recipients_set, user, subject, body)
         notifications = pop_notifications()
         notification = notifications[0]
         self.assertEqual(1, len(notifications))
@@ -37,6 +38,7 @@
         self.assertEqual(subject, notification['Subject'])
         self.assertEqual(
             'test rationale', notification['X-Launchpad-Message-Rationale'])
+        self.assertEqual(user.name, notification['X-Launchpad-Message-For'])
         self.assertIs(None, notification['Precedence'])
         self.assertTrue('launchpad' in notification['Message-ID'])
         self.assertEqual(
@@ -61,7 +63,7 @@
             authorization.record(old_message)
         self.assertRaises(
             QuotaReachedError, send_direct_contact_email,
-            '[email protected]', recipients_set, 'subject', 'body')
+            '[email protected]', recipients_set, user, 'subject', 'body')
 
     def test_empty_recipient_set(self):
         # The recipient set can be empty. No messages are sent and the
@@ -75,7 +77,7 @@
             authorization.record(old_message)
         pop_notifications()
         send_direct_contact_email(
-            '[email protected]', recipients_set, 'subject', 'body')
+            '[email protected]', recipients_set, user, 'subject', 'body')
         notifications = pop_notifications()
         self.assertEqual(0, len(notifications))
         self.assertTrue(authorization.is_allowed)
@@ -87,7 +89,8 @@
         recipients_set.add(user, 'test reason', 'test rationale')
         pop_notifications()
         body = 'Can you help me? ' * 8
-        send_direct_contact_email('[email protected]', recipients_set, 'subject', body)
+        send_direct_contact_email(
+            '[email protected]', recipients_set, user, 'subject', body)
         notifications = pop_notifications()
         body, footer = notifications[0].get_payload().split('-- ')
         self.assertEqual(
@@ -105,7 +108,8 @@
         recipients_set = NotificationRecipientSet()
         recipients_set.add(user, 'test reason', 'test rationale')
         pop_notifications()
-        send_direct_contact_email('[email protected]', recipients_set, 'test', 'test')
+        send_direct_contact_email(
+            '[email protected]', recipients_set, user, 'test', 'test')
         notifications = pop_notifications()
         notification = notifications[0]
         self.assertEqual(

=== modified file 'lib/lp/registry/tests/test_productjob.py'
--- lib/lp/registry/tests/test_productjob.py	2015-07-08 16:05:11 +0000
+++ lib/lp/registry/tests/test_productjob.py	2015-09-11 12:44:20 +0000
@@ -1,4 +1,4 @@
-# Copyright 2010-2012 Canonical Ltd.  This software is licensed under the
+# Copyright 2010-2015 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
 """Tests for ProductJobs."""
@@ -442,6 +442,7 @@
             ('X-Launchpad-Project', '%s (%s)' %
               (product.displayname, product.name)),
             ('X-Launchpad-Message-Rationale', 'Maintainer'),
+            ('X-Launchpad-Message-For', product.owner.name),
             ('Reply-To', reply_to),
             ]
         self.assertContentEqual(expected_headers, headers.items())
@@ -458,6 +459,7 @@
             ('X-Launchpad-Project', '%s (%s)' %
               (product.displayname, product.name)),
             ('X-Launchpad-Message-Rationale', 'Maintainer'),
+            ('X-Launchpad-Message-For', product.owner.name),
             ]
         self.assertContentEqual(expected_headers, headers.items())
 

=== modified file 'lib/lp/services/mail/basemailer.py'
--- lib/lp/services/mail/basemailer.py	2015-08-27 14:34:21 +0000
+++ lib/lp/services/mail/basemailer.py	2015-09-11 12:44:20 +0000
@@ -123,6 +123,8 @@
         reason, rationale = self._recipients.getReason(email)
         headers = OrderedDict()
         headers['X-Launchpad-Message-Rationale'] = reason.mail_header
+        if reason.subscriber.name is not None:
+            headers['X-Launchpad-Message-For'] = reason.subscriber.name
         if self.notification_type is not None:
             headers['X-Launchpad-Notification-Type'] = self.notification_type
         reply_to = self._getReplyToAddress(email, recipient)

=== modified file 'lib/lp/services/mail/notificationrecipientset.py'
--- lib/lp/services/mail/notificationrecipientset.py	2015-08-26 13:41:21 +0000
+++ lib/lp/services/mail/notificationrecipientset.py	2015-09-11 12:44:20 +0000
@@ -29,6 +29,7 @@
     correspond to a real Person.
     """
 
+    name = None
     displayname = None
     is_team = False
     expanded_notification_footers = False

=== modified file 'lib/lp/services/mail/tests/test_basemailer.py'
--- lib/lp/services/mail/tests/test_basemailer.py	2015-08-23 22:53:55 +0000
+++ lib/lp/services/mail/tests/test_basemailer.py	2015-09-11 12:44:20 +0000
@@ -168,7 +168,8 @@
     def test_generateEmail_append_expanded_footer(self):
         # Recipients with expanded_notification_footers receive an expanded
         # footer on messages.
-        fake_to = self.factory.makePerson(email='[email protected]')
+        fake_to = self.factory.makePerson(
+            name='to-person', email='[email protected]')
         fake_to.expanded_notification_footers = True
         recipients = {fake_to: FakeSubscription()}
         mailer = BaseMailerSubclass(
@@ -179,6 +180,7 @@
             ctrl.body, EndsWith(
                 '\n-- \n'
                 'Launchpad-Message-Rationale: pete\n'
+                'Launchpad-Message-For: to-person\n'
                 'Launchpad-Notification-Type: test\n'))
 
     def test_sendall_single_failure_doesnt_kill_all(self):

=== modified file 'lib/lp/snappy/tests/test_snapbuild.py'
--- lib/lp/snappy/tests/test_snapbuild.py	2015-08-03 13:20:45 +0000
+++ lib/lp/snappy/tests/test_snapbuild.py	2015-09-11 12:44:20 +0000
@@ -255,6 +255,7 @@
             "unstable" % build.id, subject)
         self.assertEqual(
             "Requester", notification["X-Launchpad-Message-Rationale"])
+        self.assertEqual(person.name, notification["X-Launchpad-Message-For"])
         self.assertEqual(
             "snap-build-status",
             notification["X-Launchpad-Notification-Type"])

=== modified file 'lib/lp/soyuz/mail/tests/test_packageupload.py'
--- lib/lp/soyuz/mail/tests/test_packageupload.py	2015-08-25 23:27:09 +0000
+++ lib/lp/soyuz/mail/tests/test_packageupload.py	2015-09-11 12:44:20 +0000
@@ -132,6 +132,7 @@
                 "X-Launchpad-Message-Rationale": Equals("Announcement"),
                 "X-Launchpad-Notification-Type": Equals("package-upload"),
                 }))
+        self.assertNotIn("X-Launchpad-Message-For", dict(notifications[1]))
 
     def test_forAction_announce_from_person_override_with_unicode_names(self):
         # PackageUploadMailer.forAction() takes an optional
@@ -164,6 +165,7 @@
                 "X-Launchpad-Message-Rationale": Equals("Announcement"),
                 "X-Launchpad-Notification-Type": Equals("package-upload"),
                 }))
+        self.assertNotIn("X-Launchpad-Message-For", dict(notifications[1]))
 
     def test_forAction_bcc_to_derivatives_list(self):
         # PackageUploadMailer.forAction() will BCC the announcement email to
@@ -186,6 +188,7 @@
                 "X-Launchpad-Message-Rationale": Equals("Announcement"),
                 "X-Launchpad-Notification-Type": Equals("package-upload"),
                 }))
+        self.assertNotIn("X-Launchpad-Message-For", dict(notifications[1]))
 
     def test_fetch_information_spr_multiple_changelogs(self):
         # If previous_version is passed the "changelog" entry in the
@@ -454,15 +457,17 @@
             "accepted", blamer, spr, [], [], archive, distroseries,
             PackagePublishingPocket.RELEASE, changes=changes)
         recipients = dict(mailer._recipients.getRecipientPersons())
-        for email, rationale in (
-                (blamer.preferredemail.email, "Requester"),
-                ("[email protected]", "Maintainer"),
-                ("[email protected]", "Changed-By")):
+        for person, rationale in (
+                (blamer, "Requester"),
+                (maintainer, "Maintainer"),
+                (changer, "Changed-By")):
+            email = person.preferredemail.email
             headers = mailer._getHeaders(email, recipients[email])
             self.assertThat(
                 headers,
                 ContainsDict({
                     "X-Launchpad-Message-Rationale": Equals(rationale),
+                    "X-Launchpad-Message-For": Equals(person.name),
                     "X-Launchpad-Notification-Type": Equals("package-upload"),
                     "X-Katie": Equals("Launchpad actually"),
                     "X-Launchpad-Archive": Equals(archive.reference),
@@ -497,14 +502,16 @@
             "accepted", blamer, spr, [], [], archive, distroseries,
             PackagePublishingPocket.RELEASE, changes=changes)
         recipients = dict(mailer._recipients.getRecipientPersons())
-        for email, rationale in (
-                (blamer.preferredemail.email, "Requester"),
-                (uploader.preferredemail.email, "PPA Uploader")):
+        for person, rationale in (
+                (blamer, "Requester"),
+                (uploader, "PPA Uploader")):
+            email = person.preferredemail.email
             headers = mailer._getHeaders(email, recipients[email])
             self.assertThat(
                 headers,
                 ContainsDict({
                     "X-Launchpad-Message-Rationale": Equals(rationale),
+                    "X-Launchpad-Message-For": Equals(person.name),
                     "X-Launchpad-Notification-Type": Equals("package-upload"),
                     "X-Katie": Equals("Launchpad actually"),
                     "X-Launchpad-Archive": Equals(archive.reference),

=== modified file 'lib/lp/soyuz/tests/test_build_notify.py'
--- lib/lp/soyuz/tests/test_build_notify.py	2015-09-04 12:19:07 +0000
+++ lib/lp/soyuz/tests/test_build_notify.py	2015-09-11 12:44:20 +0000
@@ -105,10 +105,13 @@
             format_address_for_person(recipient), notification['To'])
         if reason == "buildd-admin":
             rationale = "Buildd-Admin @launchpad-buildd-admins"
+            expected_for = "launchpad-buildd-admins"
         else:
             rationale = reason.title()
+            expected_for = recipient.name
         self.assertEqual(
             rationale, notification['X-Launchpad-Message-Rationale'])
+        self.assertEqual(expected_for, notification['X-Launchpad-Message-For'])
         self.assertEqual(
             'package-build-status',
             notification['X-Launchpad-Notification-Type'])

=== modified file 'lib/lp/soyuz/tests/test_livefsbuild.py'
--- lib/lp/soyuz/tests/test_livefsbuild.py	2015-08-03 12:59:18 +0000
+++ lib/lp/soyuz/tests/test_livefsbuild.py	2015-09-11 12:44:20 +0000
@@ -1,4 +1,4 @@
-# Copyright 2014 Canonical Ltd.  This software is licensed under the
+# Copyright 2014-2015 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
 """Test live filesystem build features."""
@@ -257,6 +257,7 @@
             "unstable" % build.id, subject)
         self.assertEqual(
             "Requester", notification["X-Launchpad-Message-Rationale"])
+        self.assertEqual(person.name, notification["X-Launchpad-Message-For"])
         self.assertEqual(
             "livefs-build-status",
             notification["X-Launchpad-Notification-Type"])

=== modified file 'lib/lp/testing/mail_helpers.py'
--- lib/lp/testing/mail_helpers.py	2015-09-02 02:46:18 +0000
+++ lib/lp/testing/mail_helpers.py	2015-09-11 12:44:20 +0000
@@ -60,8 +60,8 @@
 
 
 def print_emails(include_reply_to=False, group_similar=False,
-                 include_rationale=False, notifications=None,
-                 include_notification_type=False):
+                 include_rationale=False, include_for=False,
+                 notifications=None, include_notification_type=False):
     """Pop all messages from stub.test_emails and print them with
      their recipients.
 
@@ -76,6 +76,7 @@
     :param group_similar: Group messages sent to multiple recipients if True.
     :param include_rationale: Include the X-Launchpad-Message-Rationale
         header.
+    :param include_for: Include the X-Launchpad-Message-For header.
     :param notifications: Use the provided list of notifications instead of
         the stack.
     :param include_notification_type: Include the
@@ -106,8 +107,10 @@
             print 'Reply-To:', message['Reply-To']
         rationale_header = 'X-Launchpad-Message-Rationale'
         if include_rationale and rationale_header in message:
-            print (
-                '%s: %s' % (rationale_header, message[rationale_header]))
+            print '%s: %s' % (rationale_header, message[rationale_header])
+        for_header = 'X-Launchpad-Message-For'
+        if include_for and for_header in message:
+            print '%s: %s' % (for_header, message[for_header])
         notification_type_header = 'X-Launchpad-Notification-Type'
         if include_notification_type and notification_type_header in message:
             print '%s: %s' % (
@@ -118,11 +121,12 @@
 
 
 def print_distinct_emails(include_reply_to=False, include_rationale=True,
-                          include_notification_type=True):
+                          include_for=False, include_notification_type=True):
     """A convenient shortcut for `print_emails`(group_similar=True)."""
     return print_emails(group_similar=True,
                         include_reply_to=include_reply_to,
                         include_rationale=include_rationale,
+                        include_for=include_for,
                         include_notification_type=include_notification_type)
 
 

_______________________________________________
Mailing list: https://launchpad.net/~launchpad-reviewers
Post to     : [email protected]
Unsubscribe : https://launchpad.net/~launchpad-reviewers
More help   : https://help.launchpad.net/ListHelp

Reply via email to