From 96dded3ec8401e9832b3888338f37c846bd43583 Mon Sep 17 00:00:00 2001
From: Pierre de La Morinerie <pierre@capitainetrain.com>
Date: Mon, 17 Feb 2014 18:49:42 +0100
Subject: [PATCH] Send emails from the author

This changes the email "From" field from "gitlab@example.com" to either:

* "John Doe <gitlab@example.com>" if the author of the action is known,
* "GitLab <gitlab@example.com>" otherwise.

Rationale: this allow mails to appear as if they were sent by the
author. It appears in the mailbox more like a real discussion between
the sender and the receiver ("John sent: we should refactor this") and
less like a robot notifying about something.
---
 app/mailers/emails/issues.rb               | 14 ++--
 app/mailers/emails/merge_requests.rb       | 14 ++--
 app/mailers/emails/notes.rb                | 12 ++-
 app/mailers/emails/projects.rb             |  4 +-
 app/mailers/notify.rb                      | 19 ++++-
 app/services/notification_service.rb       |  2 +-
 config/initializers/devise.rb              |  2 +-
 spec/mailers/notify_spec.rb                | 90 ++++++++++++++++++----
 spec/services/notification_service_spec.rb |  8 +-
 9 files changed, 126 insertions(+), 39 deletions(-)

diff --git a/app/mailers/emails/issues.rb b/app/mailers/emails/issues.rb
index dece5112c9..3adb47dc5b 100644
--- a/app/mailers/emails/issues.rb
+++ b/app/mailers/emails/issues.rb
@@ -3,15 +3,17 @@ module Emails
     def new_issue_email(recipient_id, issue_id)
       @issue = Issue.find(issue_id)
       @project = @issue.project
-      mail(to: recipient(recipient_id),
+      mail(from: sender(@issue.author_id),
+           to: recipient(recipient_id),
            subject: subject("#{@issue.title} (##{@issue.iid})"))
     end
 
-    def reassigned_issue_email(recipient_id, issue_id, previous_assignee_id)
+    def reassigned_issue_email(recipient_id, issue_id, previous_assignee_id, updated_by_user_id)
       @issue = Issue.find(issue_id)
       @previous_assignee = User.find_by(id: previous_assignee_id) if previous_assignee_id
       @project = @issue.project
-      mail(to: recipient(recipient_id),
+      mail(from: sender(updated_by_user_id),
+           to: recipient(recipient_id),
            subject: subject("#{@issue.title} (##{@issue.iid})"))
     end
 
@@ -19,7 +21,8 @@ module Emails
       @issue = Issue.find issue_id
       @project = @issue.project
       @updated_by = User.find updated_by_user_id
-      mail(to: recipient(recipient_id),
+      mail(from: sender(updated_by_user_id),
+           to: recipient(recipient_id),
            subject: subject("#{@issue.title} (##{@issue.iid})"))
     end
 
@@ -28,7 +31,8 @@ module Emails
       @issue_status = status
       @project = @issue.project
       @updated_by = User.find updated_by_user_id
-      mail(to: recipient(recipient_id),
+      mail(from: sender(updated_by_user_id),
+           to: recipient(recipient_id),
            subject: subject("#{@issue.title} (##{@issue.iid})"))
     end
   end
diff --git a/app/mailers/emails/merge_requests.rb b/app/mailers/emails/merge_requests.rb
index 39c02ca07c..0845e14edc 100644
--- a/app/mailers/emails/merge_requests.rb
+++ b/app/mailers/emails/merge_requests.rb
@@ -3,15 +3,17 @@ module Emails
     def new_merge_request_email(recipient_id, merge_request_id)
       @merge_request = MergeRequest.find(merge_request_id)
       @project = @merge_request.project
-      mail(to: recipient(recipient_id),
+      mail(from: sender(@merge_request.author_id),
+           to: recipient(recipient_id),
            subject: subject("#{@merge_request.title} (!#{@merge_request.iid})"))
     end
 
-    def reassigned_merge_request_email(recipient_id, merge_request_id, previous_assignee_id)
+    def reassigned_merge_request_email(recipient_id, merge_request_id, previous_assignee_id, updated_by_user_id)
       @merge_request = MergeRequest.find(merge_request_id)
       @previous_assignee = User.find_by(id: previous_assignee_id) if previous_assignee_id
       @project = @merge_request.project
-      mail(to: recipient(recipient_id),
+      mail(from: sender(updated_by_user_id),
+           to: recipient(recipient_id),
            subject: subject("#{@merge_request.title} (!#{@merge_request.iid})"))
     end
 
@@ -19,14 +21,16 @@ module Emails
       @merge_request = MergeRequest.find(merge_request_id)
       @updated_by = User.find updated_by_user_id
       @project = @merge_request.project
-      mail(to: recipient(recipient_id),
+      mail(from: sender(updated_by_user_id),
+           to: recipient(recipient_id),
            subject: subject("#{@merge_request.title} (!#{@merge_request.iid})"))
     end
 
     def merged_merge_request_email(recipient_id, merge_request_id)
       @merge_request = MergeRequest.find(merge_request_id)
       @project = @merge_request.project
-      mail(to: recipient(recipient_id),
+      mail(from: sender(@merge_request.author_id_of_changes),
+           to: recipient(recipient_id),
            subject: subject("#{@merge_request.title} (!#{@merge_request.iid})"))
     end
   end
diff --git a/app/mailers/emails/notes.rb b/app/mailers/emails/notes.rb
index b727301df5..00b127da42 100644
--- a/app/mailers/emails/notes.rb
+++ b/app/mailers/emails/notes.rb
@@ -4,7 +4,8 @@ module Emails
       @note = Note.find(note_id)
       @commit = @note.noteable
       @project = @note.project
-      mail(to: recipient(recipient_id),
+      mail(from: sender(@note.author_id),
+           to: recipient(recipient_id),
            subject: subject("#{@commit.title} (#{@commit.short_id})"))
     end
 
@@ -12,7 +13,8 @@ module Emails
       @note = Note.find(note_id)
       @issue = @note.noteable
       @project = @note.project
-      mail(to: recipient(recipient_id),
+      mail(from: sender(@note.author_id),
+           to: recipient(recipient_id),
            subject: subject("#{@issue.title} (##{@issue.iid})"))
     end
 
@@ -20,14 +22,16 @@ module Emails
       @note = Note.find(note_id)
       @merge_request = @note.noteable
       @project = @note.project
-      mail(to: recipient(recipient_id),
+      mail(from: sender(@note.author_id),
+           to: recipient(recipient_id),
            subject: subject("#{@merge_request.title} (!#{@merge_request.iid})"))
     end
 
     def note_wall_email(recipient_id, note_id)
       @note = Note.find(note_id)
       @project = @note.project
-      mail(to: recipient(recipient_id),
+      mail(from: sender(@note.author_id),
+           to: recipient(recipient_id),
            subject: subject("Note on wall"))
     end
   end
diff --git a/app/mailers/emails/projects.rb b/app/mailers/emails/projects.rb
index 428d74d83c..46f24e9fb7 100644
--- a/app/mailers/emails/projects.rb
+++ b/app/mailers/emails/projects.rb
@@ -22,7 +22,9 @@ module Emails
       @diffs   = compare.diffs
       @branch  = branch
 
-      mail(to: recipient, subject: subject("New push to repository"))
+      mail(from: sender(author_id),
+           to: recipient,
+           subject: subject("New push to repository"))
     end
   end
 end
diff --git a/app/mailers/notify.rb b/app/mailers/notify.rb
index 3a4c9cf73b..554f53cf14 100644
--- a/app/mailers/notify.rb
+++ b/app/mailers/notify.rb
@@ -15,7 +15,7 @@ class Notify < ActionMailer::Base
   default_url_options[:port]     = Gitlab.config.gitlab.port unless Gitlab.config.gitlab_on_standard_port?
   default_url_options[:script_name] = Gitlab.config.gitlab.relative_url_root
 
-  default from: Gitlab.config.gitlab.email_from
+  default from: Proc.new { default_sender_address.format }
   default reply_to: "noreply@#{Gitlab.config.gitlab.host}"
 
   # Just send email with 2 seconds delay
@@ -25,6 +25,23 @@ class Notify < ActionMailer::Base
 
   private
 
+  # The default email address to send emails from
+  def default_sender_address
+    address = Mail::Address.new(Gitlab.config.gitlab.email_from)
+    address.display_name = "GitLab"
+    address
+  end
+
+  # Return an email address that displays the name of the sender.
+  # Only the displayed name changes; the actual email address is always the same.
+  def sender(sender_id)
+    if sender = User.find(sender_id)
+      address = default_sender_address
+      address.display_name = sender.name
+      address.format
+    end
+  end
+
   # Look up a User by their ID and return their email address
   #
   # recipient_id - User ID
diff --git a/app/services/notification_service.rb b/app/services/notification_service.rb
index 9d7bb9639a..5daf573630 100644
--- a/app/services/notification_service.rb
+++ b/app/services/notification_service.rb
@@ -257,7 +257,7 @@ class NotificationService
     recipients.delete(current_user)
 
     recipients.each do |recipient|
-      mailer.send(method, recipient.id, target.id, target.assignee_id_was)
+      mailer.send(method, recipient.id, target.id, target.assignee_id_was, current_user.id)
     end
   end
 
diff --git a/config/initializers/devise.rb b/config/initializers/devise.rb
index a02bf9d4ae..50669ece7a 100644
--- a/config/initializers/devise.rb
+++ b/config/initializers/devise.rb
@@ -4,7 +4,7 @@ Devise.setup do |config|
   # ==> Mailer Configuration
   # Configure the e-mail address which will be shown in Devise::Mailer,
   # note that it will be overwritten if you use your own mailer class with default "from" parameter.
-  config.mailer_sender = Gitlab.config.gitlab.email_from
+  config.mailer_sender = "GitLab <#{Gitlab.config.gitlab.email_from}>"
 
 
   # Configure the class responsible to send e-mails.
diff --git a/spec/mailers/notify_spec.rb b/spec/mailers/notify_spec.rb
index fae726b965..26b7ca876f 100644
--- a/spec/mailers/notify_spec.rb
+++ b/spec/mailers/notify_spec.rb
@@ -4,6 +4,7 @@ describe Notify do
   include EmailSpec::Helpers
   include EmailSpec::Matchers
 
+  let(:gitlab_sender) { Gitlab.config.gitlab.email_from }
   let(:recipient) { create(:user, email: 'recipient@example.com') }
   let(:project) { create(:project) }
 
@@ -13,12 +14,22 @@ describe Notify do
     end
   end
 
+  shared_examples 'an email sent from GitLab' do
+    it 'is sent from GitLab' do
+      sender = subject.header[:from].addrs[0]
+      sender.display_name.should eq('GitLab')
+      sender.address.should eq(gitlab_sender)
+    end
+  end
+
   describe 'for new users, the email' do
     let(:example_site_path) { root_path }
     let(:new_user) { create(:user, email: 'newguy@example.com', created_by_id: 1) }
 
     subject { Notify.new_user_email(new_user.id, new_user.password) }
 
+    it_behaves_like 'an email sent from GitLab'
+
     it 'is sent to the new user' do
       should deliver_to new_user.email
     end
@@ -47,6 +58,8 @@ describe Notify do
 
     subject { Notify.new_user_email(new_user.id, new_user.password) }
 
+    it_behaves_like 'an email sent from GitLab'
+
     it 'is sent to the new user' do
       should deliver_to new_user.email
     end
@@ -73,6 +86,8 @@ describe Notify do
 
     subject { Notify.new_ssh_key_email(key.id) }
 
+    it_behaves_like 'an email sent from GitLab'
+
     it 'is sent to the new user' do
       should deliver_to key.user.email
     end
@@ -114,17 +129,24 @@ describe Notify do
 
   context 'for a project' do
     describe 'items that are assignable, the email' do
+      let(:current_user) { create(:user, email: "current@email.com") }
       let(:assignee) { create(:user, email: 'assignee@example.com') }
       let(:previous_assignee) { create(:user, name: 'Previous Assignee') }
 
       shared_examples 'an assignee email' do
+        it 'is sent as the author' do
+          sender = subject.header[:from].addrs[0]
+          sender.display_name.should eq(current_user.name)
+          sender.address.should eq(gitlab_sender)
+        end
+
         it 'is sent to the assignee' do
           should deliver_to assignee.email
         end
       end
 
       context 'for issues' do
-        let(:issue) { create(:issue, assignee: assignee, project: project ) }
+        let(:issue) { create(:issue, author: current_user, assignee: assignee, project: project ) }
 
         describe 'that are new' do
           subject { Notify.new_issue_email(issue.assignee_id, issue.id) }
@@ -132,7 +154,7 @@ describe Notify do
           it_behaves_like 'an assignee email'
 
           it 'has the correct subject' do
-            should have_subject /#{project.name} \| New issue ##{issue.iid} \| #{issue.title}/
+            should have_subject /#{project.name} \| #{issue.title} \(##{issue.iid}\)/
           end
 
           it 'contains a link to the new issue' do
@@ -141,14 +163,18 @@ describe Notify do
         end
 
         describe 'that have been reassigned' do
-          before(:each) { issue.stub(:assignee_id_was).and_return(previous_assignee.id) }
-
-          subject { Notify.reassigned_issue_email(recipient.id, issue.id, previous_assignee.id) }
+          subject { Notify.reassigned_issue_email(recipient.id, issue.id, previous_assignee.id, current_user) }
 
           it_behaves_like 'a multiple recipients email'
 
+          it 'is sent as the author' do
+            sender = subject.header[:from].addrs[0]
+            sender.display_name.should eq(current_user.name)
+            sender.address.should eq(gitlab_sender)
+          end
+
           it 'has the correct subject' do
-            should have_subject /Changed issue ##{issue.iid} \| #{issue.title}/
+            should have_subject /#{issue.title} \(##{issue.iid}\)/
           end
 
           it 'contains the name of the previous assignee' do
@@ -165,12 +191,17 @@ describe Notify do
         end
 
         describe 'status changed' do
-          let(:current_user) { create(:user, email: "current@email.com") }
           let(:status) { 'closed' }
           subject { Notify.issue_status_changed_email(recipient.id, issue.id, status, current_user) }
 
+          it 'is sent as the author' do
+            sender = subject.header[:from].addrs[0]
+            sender.display_name.should eq(current_user.name)
+            sender.address.should eq(gitlab_sender)
+          end
+
           it 'has the correct subject' do
-            should have_subject /Changed issue ##{issue.iid} \| #{issue.title}/i
+            should have_subject /#{issue.title} \(##{issue.iid}\)/i
           end
 
           it 'contains the new status' do
@@ -189,7 +220,7 @@ describe Notify do
       end
 
       context 'for merge requests' do
-        let(:merge_request) { create(:merge_request, assignee: assignee, source_project: project, target_project: project) }
+        let(:merge_request) { create(:merge_request, author: current_user, assignee: assignee, source_project: project, target_project: project) }
 
         describe 'that are new' do
           subject { Notify.new_merge_request_email(merge_request.assignee_id, merge_request.id) }
@@ -197,7 +228,7 @@ describe Notify do
           it_behaves_like 'an assignee email'
 
           it 'has the correct subject' do
-            should have_subject /New merge request ##{merge_request.iid}/
+            should have_subject /#{merge_request.title} \(!#{merge_request.iid}\)/
           end
 
           it 'contains a link to the new merge request' do
@@ -214,14 +245,18 @@ describe Notify do
         end
 
         describe 'that are reassigned' do
-          before(:each) { merge_request.stub(:assignee_id_was).and_return(previous_assignee.id) }
-
-          subject { Notify.reassigned_merge_request_email(recipient.id, merge_request.id, previous_assignee.id) }
+          subject { Notify.reassigned_merge_request_email(recipient.id, merge_request.id, previous_assignee.id, current_user.id) }
 
           it_behaves_like 'a multiple recipients email'
 
+          it 'is sent as the author' do
+            sender = subject.header[:from].addrs[0]
+            sender.display_name.should eq(current_user.name)
+            sender.address.should eq(gitlab_sender)
+          end
+
           it 'has the correct subject' do
-            should have_subject /Changed merge request ##{merge_request.iid}/
+            should have_subject /#{merge_request.title} \(!#{merge_request.iid}\)/
           end
 
           it 'contains the name of the previous assignee' do
@@ -245,6 +280,8 @@ describe Notify do
       let(:user) { create(:user) }
       subject { Notify.project_was_moved_email(project.id, user.id) }
 
+      it_behaves_like 'an email sent from GitLab'
+
       it 'has the correct subject' do
         should have_subject /Project was moved/
       end
@@ -265,6 +302,9 @@ describe Notify do
                                    project: project,
                                    user: user) }
       subject { Notify.project_access_granted_email(users_project.id) }
+
+      it_behaves_like 'an email sent from GitLab'
+
       it 'has the correct subject' do
         should have_subject /Access to project was granted/
       end
@@ -285,6 +325,12 @@ describe Notify do
       end
 
       shared_examples 'a note email' do
+        it 'is sent as the author' do
+          sender = subject.header[:from].addrs[0]
+          sender.display_name.should eq(note_author.name)
+          sender.address.should eq(gitlab_sender)
+        end
+
         it 'is sent to the given recipient' do
           should deliver_to recipient.email
         end
@@ -324,7 +370,7 @@ describe Notify do
         it_behaves_like 'a note email'
 
         it 'has the correct subject' do
-          should have_subject /Note for commit #{commit.short_id}/
+          should have_subject /#{commit.title} \(#{commit.short_id}\)/
         end
 
         it 'contains a link to the commit' do
@@ -342,7 +388,7 @@ describe Notify do
         it_behaves_like 'a note email'
 
         it 'has the correct subject' do
-          should have_subject /Note for merge request ##{merge_request.iid}/
+          should have_subject /#{merge_request.title} \(!#{merge_request.iid}\)/
         end
 
         it 'contains a link to the merge request note' do
@@ -360,7 +406,7 @@ describe Notify do
         it_behaves_like 'a note email'
 
         it 'has the correct subject' do
-          should have_subject /Note for issue ##{issue.iid}/
+          should have_subject /#{issue.title} \(##{issue.iid}\)/
         end
 
         it 'contains a link to the issue note' do
@@ -377,6 +423,8 @@ describe Notify do
 
     subject { Notify.group_access_granted_email(membership.id) }
 
+    it_behaves_like 'an email sent from GitLab'
+
     it 'has the correct subject' do
       should have_subject /Access to group was granted/
     end
@@ -401,6 +449,8 @@ describe Notify do
 
     subject { ActionMailer::Base.deliveries.last }
 
+    it_behaves_like 'an email sent from GitLab'
+
     it 'is sent to the new user' do
       should deliver_to 'new-email@mail.com'
     end
@@ -421,6 +471,12 @@ describe Notify do
 
     subject { Notify.repository_push_email(project.id, 'devs@company.name', user.id, 'master', compare) }
 
+    it 'is sent as the author' do
+      sender = subject.header[:from].addrs[0]
+      sender.display_name.should eq(user.name)
+      sender.address.should eq(gitlab_sender)
+    end
+
     it 'is sent to recipient' do
       should deliver_to 'devs@company.name'
     end
diff --git a/spec/services/notification_service_spec.rb b/spec/services/notification_service_spec.rb
index e378be0425..077ad8b6e1 100644
--- a/spec/services/notification_service_spec.rb
+++ b/spec/services/notification_service_spec.rb
@@ -137,11 +137,11 @@ describe NotificationService do
       end
 
       def should_email(user_id)
-        Notify.should_receive(:reassigned_issue_email).with(user_id, issue.id, issue.assignee_id)
+        Notify.should_receive(:reassigned_issue_email).with(user_id, issue.id, issue.assignee_id, @u_disabled.id)
       end
 
       def should_not_email(user_id)
-        Notify.should_not_receive(:reassigned_issue_email).with(user_id, issue.id, issue.assignee_id)
+        Notify.should_not_receive(:reassigned_issue_email).with(user_id, issue.id, issue.assignee_id, @u_disabled.id)
       end
     end
 
@@ -201,11 +201,11 @@ describe NotificationService do
       end
 
       def should_email(user_id)
-        Notify.should_receive(:reassigned_merge_request_email).with(user_id, merge_request.id, merge_request.assignee_id)
+        Notify.should_receive(:reassigned_merge_request_email).with(user_id, merge_request.id, merge_request.assignee_id, merge_request.author_id)
       end
 
       def should_not_email(user_id)
-        Notify.should_not_receive(:reassigned_merge_request_email).with(user_id, merge_request.id, merge_request.assignee_id)
+        Notify.should_not_receive(:reassigned_merge_request_email).with(user_id, merge_request.id, merge_request.assignee_id, merge_request.author_id)
       end
     end
 
-- 
2.30.9