← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] lp:~cjwatson/launchpad/message-for-header into lp:launchpad

 

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 <1@xxxxxxxxxxxxxxxxxx>
     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 <1@xxxxxxxxxxxxxxxxxx>
     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 <1@xxxxxxxxxxxxxxxxxx>
     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 <1@xxxxxxxxxxxxxxxxxx>
     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 <1@xxxxxxxxxxxxxxxxxx>
     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 <1@xxxxxxxxxxxxxxxxxx>
     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 <16@xxxxxxxxxxxxxxxxxx>
     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 <16@xxxxxxxxxxxxxxxxxx>
     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 <...@bugs.launchpad.net>
     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 <...@bugs.launchpad.net>
     Subject: [Bug ...] subject
     X-Launchpad-Message-Rationale: Subscriber
+    X-Launchpad-Message-For: name12
     <BLANKLINE>
     a comment.
     <BLANKLINE>
@@ -538,6 +549,7 @@
     References: foo@xxxxxxxxxxx-332342--1231
     ...
     X-Launchpad-Message-Rationale: Assignee
+    X-Launchpad-Message-For: name12
     ...
     INFO    Notifying foo.bar@xxxxxxxxxxxxx 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 mark@xxxxxxxxxxx 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: bounces@xxxxxxxxxxxxx
     Return-Path: bounces@xxxxxxxxxxxxx
     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='verbose@xxxxxxxxxxx',
+    ...         name='verbose-person', displayname='Verbose Person',
+    ...         email='verbose@xxxxxxxxxxx',
     ...         selfgenerated_bugnotifications=True)
     ...     verbose_person.verbose_bugnotifications = True
     ...     ignored = bug.subscribe(verbose_person, verbose_person)
     ...     concise_person = factory.makePerson(
-    ...         displayname='Concise Person', email='concise@xxxxxxxxxxx')
+    ...         name='concise-person', displayname='Concise Person',
+    ...         email='concise@xxxxxxxxxxx')
     ...     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='conciseteam@xxxxxxxxxxx')
     ...     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='verboseteam@xxxxxxxxxxx')
     ...     verbose_team_person.verbose_bugnotifications = False
     ...     ignored = verbose_team.addMember(
@@ -934,6 +950,7 @@
     From: Verbose Person <verbose@xxxxxxxxxxx>
     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 <verbose@xxxxxxxxxxx>
     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 <verbose@xxxxxxxxxxx>
     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 <verbose@xxxxxxxxxxx>
     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 <...@bugs.launchpad.net>
     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 <...@bugs.launchpad.net>
     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 <...@bugs.launchpad.net>
     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 <...@bugs.launchpad.net>
     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 <...@bugs.launchpad.net>
     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 <...@bugs.launchpad.net>
     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 ... <...@bugs.launchpad.net>
     ...
     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='source@xxxxxxxxxxx', displayname='Source Subscriber')
+    ...     email='source@xxxxxxxxxxx', 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='target@xxxxxxxxxxx', displayname='Target Subscriber')
+    ...     email='target@xxxxxxxxxxx', 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'])
-    bob@xxxxxxxxxxx, Reviewer
-    mary@xxxxxxxxxxx, Reviewer
-    source@xxxxxxxxxxx, Subscriber
-    target@xxxxxxxxxxx, Subscriber
+    ...         notification['X-Launchpad-Message-Rationale'],
+    ...         notification['X-Launchpad-Message-For'])
+    bob@xxxxxxxxxxx, Reviewer, bob
+    mary@xxxxxxxxxxx, Reviewer, mary
+    source@xxxxxxxxxxx, Subscriber, source-subscriber
+    target@xxxxxxxxxxx, 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('subscriber@xxxxxxxxxxx')[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='team-two@xxxxxxxxxxx', 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('me@xxxxxx', recipients_set, subject, body)
+        send_direct_contact_email(
+            'me@xxxxxx', 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,
-            'me@xxxxxx', recipients_set, 'subject', 'body')
+            'me@xxxxxx', 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(
-            'me@xxxxxx', recipients_set, 'subject', 'body')
+            'me@xxxxxx', 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('me@xxxxxx', recipients_set, 'subject', body)
+        send_direct_contact_email(
+            'me@xxxxxx', 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('me@xxxxxx', recipients_set, 'test', 'test')
+        send_direct_contact_email(
+            'me@xxxxxx', 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='to@xxxxxxxxxxx')
+        fake_to = self.factory.makePerson(
+            name='to-person', email='to@xxxxxxxxxxx')
         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"),
-                ("maintainer@xxxxxxxxxxx", "Maintainer"),
-                ("changer@xxxxxxxxxxx", "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)
 
 


Follow ups