Skip to content

Commit c458768

Browse files
author
epriestley
committedApr 12, 2012
Fix various threading issues, particularly in Gmail
Summary: - Add an explicit multiplexing option, and enable it by default. This is necessary for Mail.app to coexist with other clients ("Re:" breaks outlook at the very least, and generally sucks in the common case), and allows users with flexible clients to enable subject variance. - Add an option for subject line variance. Default to not varying the subject, so mail no longer says [Committed], [Closed], etc. This is so the defaults thread correctly in Gmail (not entirely sure this actually works). - Add a preference to enable subject line variance. - Unless all mail is multiplexed, don't enable or respect the "Re" or "vary subject" preferences. These are currently shown and respected in non-multiplex cases, which creates inconsistent results. NOTE: @jungejason @nh @vrana This changes the default behavior (from non-multiplexing to multiplexing), and might break Facebook's integration. You should be able to keep the same behavior by setting the options appropriately, although if you can get the new defaults working they're probably better. Test Plan: Send mail from Maniphest, Differential and Audit. Updated preferences. Enabled/disabled multiplexing. Things seem OK? NOTE: I haven't actually been able to repro the Gmail threading issue so I'm not totally sure what's going on there, maybe it started respecting "Re:" (or always has), but @cpiro and @20after4 both reported it independently. This fixes a bunch of bugs in any case and gives us more conservative set of defaults. I'll see if I can buff out the Gmail story a bit but every client is basically a giant black box of mystery. :/ Reviewers: btrahan, vrana, jungejason, nh Reviewed By: btrahan CC: cpiro, 20after4, aran Maniphest Tasks: T1097, T847 Differential Revision: https://secure.phabricator.com/D2206
1 parent f0e89b7 commit c458768

16 files changed

+229
-93
lines changed
 

‎conf/default.conf.php

+40-1
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,35 @@
145145
// Domain used to generate Message-IDs.
146146
'metamta.domain' => 'example.com',
147147

148+
// When a message is sent to multiple recipients (for example, several
149+
// reviewers on a code review), Phabricator can either deliver one email to
150+
// everyone (e.g., "To: alincoln, usgrant, htaft") or separate emails to each
151+
// user (e.g., "To: alincoln", "To: usgrant", "To: htaft"). The major
152+
// advantages and disadvantages of each approach are:
153+
//
154+
// - One mail to everyone:
155+
// - Recipients can see To/Cc at a glance.
156+
// - If you use mailing lists, you won't get duplicate mail if you're
157+
// a normal recipient and also Cc'd on a mailing list.
158+
// - Getting threading to work properly is harder, and probably requires
159+
// making mail less useful by turning off options.
160+
// - Sometimes people will "Reply All" and everyone will get two mails,
161+
// one from the user and one from Phabricator turning their mail into
162+
// a comment.
163+
// - Not supported with a private reply-to address.
164+
// - One mail to each user:
165+
// - Recipients need to look in the mail body to see To/Cc.
166+
// - If you use mailing lists, recipients may sometimes get duplicate
167+
// mail.
168+
// - Getting threading to work properly is easier, and threading settings
169+
// can be customzied by each user.
170+
// - "Reply All" no longer spams all other users.
171+
// - Required if private reply-to addresses are configured.
172+
//
173+
// In the code, splitting one outbound email into one-per-recipient is
174+
// sometimes referred to as "multiplexing".
175+
'metamta.one-mail-per-recipient' => true,
176+
148177
// When a user takes an action which generates an email notification (like
149178
// commenting on a Differential revision), Phabricator can either send that
150179
// mail "From" the user's email address (like "alincoln@logcabin.com") or
@@ -166,6 +195,7 @@
166195
// mostly never arrive.
167196
'metamta.can-send-as-user' => false,
168197

198+
169199
// Adapter class to use to transmit mail to the MTA. The default uses
170200
// PHPMailerLite, which will invoke "sendmail". This is appropriate
171201
// if sendmail actually works on your host, but if you haven't configured mail
@@ -329,9 +359,18 @@
329359

330360
// Mail.app on OS X Lion won't respect threading headers unless the subject
331361
// is prefixed with "Re:". If you enable this option, Phabricator will add
332-
// "Re:" to the subject line of all mail which is expected to thread.
362+
// "Re:" to the subject line of all mail which is expected to thread. If
363+
// you've set 'metamta.one-mail-per-recipient', users can override this
364+
// setting in their preferences.
333365
'metamta.re-prefix' => false,
334366

367+
// If true, allow MetaMTA to change mail subjects to put text like
368+
// '[Accepted]' and '[Commented]' in them. This makes subjects more useful,
369+
// but might break threading on some clients. If you've set
370+
// 'metamta.one-mail-per-recipient', users can override this setting in their
371+
// preferences.
372+
'metamta.vary-subjects' => true,
373+
335374

336375
// -- Auth ------------------------------------------------------------------ //
337376

‎src/applications/audit/editor/comment/PhabricatorAuditCommentEditor.php

+8-3
Original file line numberDiff line numberDiff line change
@@ -283,12 +283,12 @@ private function sendMail(
283283
$reply_handler = self::newReplyHandlerForCommit($commit);
284284

285285
$prefix = PhabricatorEnv::getEnvConfig('metamta.diffusion.subject-prefix');
286-
$subject = "{$prefix} [{$verb}] {$name}: {$summary}";
286+
$subject = "{$prefix} {$name}: {$summary}";
287+
$vary_subject = "{$prefix} [{$verb}] {$name}: {$summary}";
287288

288289
$threading = self::getMailThreading($commit->getPHID());
289290
list($thread_id, $thread_topic) = $threading;
290291

291-
$is_new = !count($other_comments);
292292
$body = $this->renderMailBody(
293293
$comment,
294294
"{$name}: {$summary}",
@@ -311,8 +311,13 @@ private function sendMail(
311311
$phids = array_merge($email_to, $email_cc);
312312
$handles = id(new PhabricatorObjectHandleData($phids))->loadHandles();
313313

314+
// NOTE: Always set $is_new to false, because the "first" mail in the
315+
// thread is the Herald notification of the commit.
316+
$is_new = false;
317+
314318
$template = id(new PhabricatorMetaMTAMail())
315319
->setSubject($subject)
320+
->setVarySubject($subject)
316321
->setFrom($comment->getActorPHID())
317322
->setThreadID($thread_id, $is_new)
318323
->addHeader('Thread-Topic', $thread_topic)
@@ -332,7 +337,7 @@ private function sendMail(
332337

333338
public static function getMailThreading($phid) {
334339
return array(
335-
'<diffusion-audit-'.$phid.'>',
340+
'diffusion-audit-'.$phid,
336341
'Diffusion Audit '.$phid,
337342
);
338343
}

‎src/applications/differential/mail/base/DifferentialMail.php

+19-7
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,14 @@ abstract class DifferentialMail {
3434
protected $replyHandler;
3535
protected $parentMessageID;
3636

37-
abstract protected function renderSubject();
37+
protected function renderSubject() {
38+
$revision = $this->getRevision();
39+
$title = $revision->getTitle();
40+
$id = $revision->getID();
41+
return "D{$id}: {$title}";
42+
}
43+
44+
abstract protected function renderVarySubject();
3845
abstract protected function renderBody();
3946

4047
public function setActorHandle($actor_handle) {
@@ -70,10 +77,11 @@ public function send() {
7077
throw new Exception('No "To:" users provided!');
7178
}
7279

73-
$cc_phids = $this->getCCPHIDs();
74-
$subject = $this->buildSubject();
75-
$body = $this->buildBody();
76-
$attachments = $this->buildAttachments();
80+
$cc_phids = $this->getCCPHIDs();
81+
$subject = $this->buildSubject();
82+
$vary_subject = $this->buildVarySubject();
83+
$body = $this->buildBody();
84+
$attachments = $this->buildAttachments();
7785

7886
$template = new PhabricatorMetaMTAMail();
7987
$actor_handle = $this->getActorHandle();
@@ -85,6 +93,7 @@ public function send() {
8593

8694
$template
8795
->setSubject($subject)
96+
->setVarySubject($vary_subject)
8897
->setBody($body)
8998
->setIsHTML($this->shouldMarkMailAsHTML())
9099
->setParentMessageID($this->parentMessageID)
@@ -197,6 +206,10 @@ protected function buildSubject() {
197206
return trim($this->getSubjectPrefix().' '.$this->renderSubject());
198207
}
199208

209+
protected function buildVarySubject() {
210+
return trim($this->getSubjectPrefix().' '.$this->renderVarySubject());
211+
}
212+
200213
protected function shouldMarkMailAsHTML() {
201214
return false;
202215
}
@@ -320,8 +333,7 @@ public function getRevision() {
320333

321334
protected function getThreadID() {
322335
$phid = $this->getRevision()->getPHID();
323-
$domain = PhabricatorEnv::getEnvConfig('metamta.domain');
324-
return "<differential-rev-{$phid}-req@{$domain}>";
336+
return "differential-rev-{$phid}-req";
325337
}
326338

327339
public function setComment($comment) {

‎src/applications/differential/mail/ccwelcome/DifferentialCCWelcomeMail.php

+2-3
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,8 @@
1818

1919
final class DifferentialCCWelcomeMail extends DifferentialReviewRequestMail {
2020

21-
protected function renderSubject() {
22-
$revision = $this->getRevision();
23-
return 'Added to CC: '.$revision->getTitle();
21+
protected function renderVarySubject() {
22+
return '[Added to CC] '.$this->renderSubject();
2423
}
2524

2625
protected function renderBody() {

‎src/applications/differential/mail/comment/DifferentialCommentMail.php

+2-6
Original file line numberDiff line numberDiff line change
@@ -75,13 +75,9 @@ protected function getMailTags() {
7575
return $tags;
7676
}
7777

78-
protected function renderSubject() {
78+
protected function renderVarySubject() {
7979
$verb = ucwords($this->getVerb());
80-
$revision = $this->getRevision();
81-
$title = $revision->getTitle();
82-
$id = $revision->getID();
83-
$subject = "[{$verb}] D{$id}: {$title}";
84-
return $subject;
80+
return "[{$verb}] ".$this->renderSubject();
8581
}
8682

8783
protected function getVerb() {

‎src/applications/differential/mail/diffcontent/DifferentialDiffContentMail.php

+2-2
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,8 @@ public function __construct(DifferentialRevision $revision, $content) {
2525
$this->content = $content;
2626
}
2727

28-
protected function renderSubject() {
29-
return "Content: ".$this->getRevision()->getTitle();
28+
protected function renderVarySubject() {
29+
return '[Content] '.$this->renderSubject();
3030
}
3131

3232
protected function renderBody() {

‎src/applications/differential/mail/exception/DifferentialExceptionMail.php

+4
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,10 @@ protected function renderSubject() {
3636
return "Exception: unable to process your mail request.";
3737
}
3838

39+
protected function renderVarySubject() {
40+
return $this->renderSubject();
41+
}
42+
3943
protected function buildBody() {
4044
$exception = $this->exception;
4145
$original_body = $this->originalBody;

‎src/applications/differential/mail/newdiff/DifferentialNewDiffMail.php

+2-16
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818

1919
final class DifferentialNewDiffMail extends DifferentialReviewRequestMail {
2020

21-
protected function renderSubject() {
21+
protected function renderVarySubject() {
2222
$revision = $this->getRevision();
2323
$line_count = $revision->getLineCount();
2424
$lines = ($line_count == 1 ? "1 line" : "{$line_count} lines");
@@ -29,21 +29,7 @@ protected function renderSubject() {
2929
$verb = 'Updated';
3030
}
3131

32-
$revision_id = $revision->getID();
33-
$revision_title = $revision->getTitle();
34-
35-
return "[{$verb}, {$lines}] D{$revision_id}: {$revision_title}";
36-
}
37-
38-
protected function buildSubject() {
39-
if (!$this->isFirstMailToRecipients()) {
40-
return parent::buildSubject();
41-
}
42-
43-
$prefix = $this->getSubjectPrefix();
44-
$subject = $this->renderSubject();
45-
46-
return trim("{$prefix} {$subject}");
32+
return "[{$verb}, {$lines}] ".$this->renderSubject();
4733
}
4834

4935
protected function renderBody() {

‎src/applications/maniphest/editor/transaction/ManiphestTransactionEditor.php

+4-2
Original file line numberDiff line numberDiff line change
@@ -266,16 +266,18 @@ private function sendEmail($task, $transactions, $email_to, $email_cc) {
266266
" ".$reply_instructions."\n";
267267
}
268268

269-
$thread_id = '<maniphest-task-'.$task->getPHID().'>';
269+
$thread_id = 'maniphest-task-'.$task->getPHID();
270270
$task_id = $task->getID();
271271
$title = $task->getTitle();
272272
$prefix = $this->getSubjectPrefix();
273-
$subject = trim("{$prefix} [{$action}] T{$task_id}: {$title}");
273+
$subject = trim("{$prefix} T{$task_id}: {$title}");
274+
$vary_subject = trim("{$prefix} [{$action}] T{$task_id}: {$title}");
274275

275276
$mailtags = $this->getMailTags($transactions);
276277

277278
$template = id(new PhabricatorMetaMTAMail())
278279
->setSubject($subject)
280+
->setVarySubject($vary_subject)
279281
->setFrom($transaction->getAuthorPHID())
280282
->setParentMessageID($this->parentMessageID)
281283
->addHeader('Thread-Topic', 'Maniphest Task '.$task->getID())

‎src/applications/metamta/replyhandler/base/PhabricatorMailReplyHandler.php

+19-15
Original file line numberDiff line numberDiff line change
@@ -80,22 +80,26 @@ final public function multiplexMail(
8080

8181
$result = array();
8282

83-
// If private replies are not supported, simply send one email to all
84-
// recipients and CCs. This covers cases where we have no reply handler,
85-
// or we have a public reply handler.
86-
if (!$this->supportsPrivateReplies()) {
87-
$mail = clone $mail_template;
88-
$mail->addTos(mpull($to_handles, 'getPHID'));
89-
$mail->addCCs(mpull($cc_handles, 'getPHID'));
90-
91-
if ($this->supportsPublicReplies()) {
92-
$reply_to = $this->getPublicReplyHandlerEmailAddress();
93-
$mail->setReplyTo($reply_to);
83+
// If MetaMTA is configured to always multiplex, skip the single-email
84+
// case.
85+
if (!PhabricatorMetaMTAMail::shouldMultiplexAllMail()) {
86+
// If private replies are not supported, simply send one email to all
87+
// recipients and CCs. This covers cases where we have no reply handler,
88+
// or we have a public reply handler.
89+
if (!$this->supportsPrivateReplies()) {
90+
$mail = clone $mail_template;
91+
$mail->addTos(mpull($to_handles, 'getPHID'));
92+
$mail->addCCs(mpull($cc_handles, 'getPHID'));
93+
94+
if ($this->supportsPublicReplies()) {
95+
$reply_to = $this->getPublicReplyHandlerEmailAddress();
96+
$mail->setReplyTo($reply_to);
97+
}
98+
99+
$result[] = $mail;
100+
101+
return $result;
94102
}
95-
96-
$result[] = $mail;
97-
98-
return $result;
99103
}
100104

101105
// Merge all the recipients together. TODO: We could keep the CCs as real

‎src/applications/metamta/replyhandler/base/__init__.php

+1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77

88

9+
phutil_require_module('phabricator', 'applications/metamta/storage/mail');
910
phutil_require_module('phabricator', 'applications/metamta/storage/receivedmail');
1011
phutil_require_module('phabricator', 'applications/phid/constants');
1112
phutil_require_module('phabricator', 'infrastructure/env');

0 commit comments

Comments
 (0)
Failed to load comments.