Repository: zulip/zulip

Issue:
Improve missed message email notifications
As [discussed on CZO](https://chat.zulip.org/#narrow/stream/101-design/topic/notification.20emails/near/1428483), we should improve missed message notification emails to be more specific as to why the user is receiving the notification, as well as cleaning up some phrasing. Specifically, we should use the following "You are receiving this because..." lines, in priority order:

1. If the message(s) in the notification include a personal mention: "You are receiving this because you were personally mentioned."
2.  If the message(s) in the notification include a group mention: "You are receiving this because @{{ mentioned_user_group_name }} was mentioned." [probably doesn't matter too much which group is listed, if there are multiple ones]
3.  If the message(s) in the notification include a wildcard mention: "You are receiving this because everyone was mentioned."
4. Stream message notifications: "You are receiving this because you have email notifications enabled for `#<stream name>`."

In all the notifications emails, we should also replace the "Reply to this email directly..." sentence with: "Reply to this email directly, [view it in {{ realm_name }} Zulip](), or [manage email preferences]()."


Discussion:
Hello @zulip/server-development members, this issue was labeled with the "area: emails" label, so you may want to check it out!

<!-- areaLabelAddition -->

@zulipbot claim
@Rixant You have been unassigned from this issue because you have not made any updates for over 14 days. Please feel free to reclaim the issue if you decide to pick up again. Thanks!

@zulipbot claim
Welcome to Zulip, @dqkqd! We just sent you an invite to collaborate on this repository at https://github.com/zulip/zulip/invitations. Please accept this invite in order to claim this issue and begin a fun, rewarding experience contributing to Zulip!

Here's some tips to get you off to a good start:

- Join me on the [Zulip developers' server](https://chat.zulip.org), to get help, chat about this issue, and meet the other developers.
- [Unwatch this repository](https://help.github.com/articles/unwatching-repositories/), so that you don't get 100 emails a day.

As you work on this issue, you'll also want to refer to the [Zulip code contribution guide](https://zulip.readthedocs.io/en/latest/contributing/index.html), as well as the rest of the developer documentation on that site.

See you on the other side (that is, the pull request side)!


Resolving patch:
diff --git a/zerver/lib/email_notifications.py b/zerver/lib/email_notifications.py
--- a/zerver/lib/email_notifications.py
+++ b/zerver/lib/email_notifications.py
@@ -408,8 +408,15 @@ def do_send_missedmessage_events_reply_in_zulip(
     triggers = [message["trigger"] for message in missed_messages]
     unique_triggers = set(triggers)
 
+    personal_mentioned = any(
+        message["trigger"] == "mentioned" and message["mentioned_user_group_id"] is None
+        for message in missed_messages
+    )
+
     context.update(
         mention="mentioned" in unique_triggers or "wildcard_mentioned" in unique_triggers,
+        personal_mentioned=personal_mentioned,
+        wildcard_mentioned="wildcard_mentioned" in unique_triggers,
         stream_email_notify="stream_email_notify" in unique_triggers,
         mention_count=triggers.count("mentioned") + triggers.count("wildcard_mentioned"),
         mentioned_user_group_name=mentioned_user_group_name,
@@ -476,6 +483,7 @@ def do_send_missedmessage_events_reply_in_zulip(
         stream = Stream.objects.only("id", "name").get(id=message.recipient.type_id)
         stream_header = f"{stream.name} > {message.topic_name()}"
         context.update(
+            stream_name=stream.name,
             stream_header=stream_header,
         )
     else:


Test patch:
diff --git a/zerver/tests/test_email_notifications.py b/zerver/tests/test_email_notifications.py
--- a/zerver/tests/test_email_notifications.py
+++ b/zerver/tests/test_email_notifications.py
@@ -518,7 +518,7 @@ def _extra_context_in_missed_stream_messages_mention(
         if show_message_content:
             verify_body_include = [
                 "Othello, the Moor of Venice: > 1 > 2 > 3 > 4 > 5 > 6 > 7 > 8 > 9 > 10 > @**King Hamlet** -- ",
-                "You are receiving this because you were mentioned in Zulip Dev.",
+                "You are receiving this because you were personally mentioned.",
             ]
             email_subject = "#Denmark > test"
             verify_body_does_not_include: List[str] = []
@@ -527,7 +527,7 @@ def _extra_context_in_missed_stream_messages_mention(
             verify_body_include = [
                 "This email does not include message content because you have disabled message ",
                 "http://zulip.testserver/help/pm-mention-alert-notifications ",
-                "View or reply in Zulip",
+                "View or reply in Zulip Dev Zulip",
                 " Manage email preferences: http://zulip.testserver/#settings/notifications",
             ]
 
@@ -538,7 +538,7 @@ def _extra_context_in_missed_stream_messages_mention(
                 "1 2 3 4 5 6 7 8 9 10 @**King Hamlet**",
                 "private",
                 "group",
-                "Reply to this email directly, or view it in Zulip",
+                "Reply to this email directly, or view it in Zulip Dev Zulip",
             ]
         self._test_cases(
             msg_id,
@@ -561,7 +561,7 @@ def _extra_context_in_missed_stream_messages_wildcard_mention(
         if show_message_content:
             verify_body_include = [
                 "Othello, the Moor of Venice: > 1 > 2 > 3 > 4 > 5 > @**all** -- ",
-                "You are receiving this because you were mentioned in Zulip Dev.",
+                "You are receiving this because everyone was mentioned in #Denmark.",
             ]
             email_subject = "#Denmark > test"
             verify_body_does_not_include: List[str] = []
@@ -570,7 +570,7 @@ def _extra_context_in_missed_stream_messages_wildcard_mention(
             verify_body_include = [
                 "This email does not include message content because you have disabled message ",
                 "http://zulip.testserver/help/pm-mention-alert-notifications ",
-                "View or reply in Zulip",
+                "View or reply in Zulip Dev Zulip",
                 " Manage email preferences: http://zulip.testserver/#settings/notifications",
             ]
             email_subject = "New messages"
@@ -580,7 +580,7 @@ def _extra_context_in_missed_stream_messages_wildcard_mention(
                 "1 2 3 4 5 @**all**",
                 "private",
                 "group",
-                "Reply to this email directly, or view it in Zulip",
+                "Reply to this email directly, or view it in Zulip Dev Zulip",
             ]
         self._test_cases(
             msg_id,
@@ -599,7 +599,7 @@ def _extra_context_in_missed_stream_messages_email_notify(self, send_as_user: bo
         msg_id = self.send_stream_message(self.example_user("othello"), "denmark", "12")
         verify_body_include = [
             "Othello, the Moor of Venice: > 1 > 2 > 3 > 4 > 5 > 6 > 7 > 8 > 9 > 10 > 12 -- ",
-            "You are receiving this because you have email notifications enabled for this stream.",
+            "You are receiving this because you have email notifications enabled for #Denmark.",
         ]
         email_subject = "#Denmark > test"
         self._test_cases(
@@ -619,7 +619,7 @@ def _extra_context_in_missed_stream_messages_mention_two_senders(
         )
         verify_body_include = [
             "Cordelia, Lear's daughter: > 0 > 1 > 2 Othello, the Moor of Venice: > @**King Hamlet** -- ",
-            "You are receiving this because you were mentioned in Zulip Dev.",
+            "You are receiving this because you were personally mentioned.",
         ]
         email_subject = "#Denmark > test"
         self._test_cases(
@@ -648,14 +648,14 @@ def _extra_context_in_missed_personal_messages(
                 verify_body_include = [
                     "This email does not include message content because your organization has disabled",
                     "http://zulip.testserver/help/hide-message-content-in-emails",
-                    "View or reply in Zulip",
+                    "View or reply in Zulip Dev Zulip",
                     " Manage email preferences: http://zulip.testserver/#settings/notifications",
                 ]
             elif message_content_disabled_by_user:
                 verify_body_include = [
                     "This email does not include message content because you have disabled message ",
                     "http://zulip.testserver/help/pm-mention-alert-notifications ",
-                    "View or reply in Zulip",
+                    "View or reply in Zulip Dev Zulip",
                     " Manage email preferences: http://zulip.testserver/#settings/notifications",
                 ]
             email_subject = "New messages"
@@ -664,7 +664,7 @@ def _extra_context_in_missed_personal_messages(
                 "Extremely personal message!",
                 "mentioned",
                 "group",
-                "Reply to this email directly, or view it in Zulip",
+                "Reply to this email directly, or view it in Zulip Dev Zulip",
             ]
         self._test_cases(
             msg_id,
@@ -681,7 +681,7 @@ def _reply_to_email_in_missed_personal_messages(self, send_as_user: bool) -> Non
             self.example_user("hamlet"),
             "Extremely personal message!",
         )
-        verify_body_include = ["Reply to this email directly, or view it in Zulip"]
+        verify_body_include = ["Reply to this email directly, or view it in Zulip Dev Zulip"]
         email_subject = "PMs with Othello, the Moor of Venice"
         self._test_cases(msg_id, verify_body_include, email_subject, send_as_user)
 
@@ -717,7 +717,7 @@ def _extra_context_in_missed_huddle_messages_two_others(
             verify_body_include = [
                 "This email does not include message content because you have disabled message ",
                 "http://zulip.testserver/help/pm-mention-alert-notifications ",
-                "View or reply in Zulip",
+                "View or reply in Zulip Dev Zulip",
                 " Manage email preferences: http://zulip.testserver/#settings/notifications",
             ]
             email_subject = "New messages"
@@ -726,7 +726,7 @@ def _extra_context_in_missed_huddle_messages_two_others(
                 "Othello, the Moor of Venice Othello, the Moor of Venice",
                 "Group personal message!",
                 "mentioned",
-                "Reply to this email directly, or view it in Zulip",
+                "Reply to this email directly, or view it in Zulip Dev Zulip",
             ]
         self._test_cases(
             msg_id,
@@ -849,7 +849,7 @@ def test_smaller_user_group_mention_priority(self) -> None:
 
         expected_email_include = [
             "Othello, the Moor of Venice: > @*hamlet_only* > @*hamlet_and_cordelia* -- ",
-            "You are receiving this because @hamlet_only was mentioned in Zulip Dev.",
+            "You are receiving this because @hamlet_only was mentioned.",
         ]
 
         for text in expected_email_include:
@@ -889,7 +889,76 @@ def test_personal_over_user_group_mention_priority(self) -> None:
 
         expected_email_include = [
             "Othello, the Moor of Venice: > @*hamlet_and_cordelia* > @**King Hamlet** -- ",
-            "You are receiving this because you were mentioned in Zulip Dev.",
+            "You are receiving this because you were personally mentioned.",
+        ]
+
+        for text in expected_email_include:
+            self.assertIn(text, self.normalize_string(mail.outbox[0].body))
+
+    def test_user_group_over_wildcard_mention_priority(self) -> None:
+        hamlet = self.example_user("hamlet")
+        cordelia = self.example_user("cordelia")
+        othello = self.example_user("othello")
+
+        hamlet_and_cordelia = create_user_group(
+            "hamlet_and_cordelia", [hamlet, cordelia], get_realm("zulip")
+        )
+
+        wildcard_mentioned_message_id = self.send_stream_message(othello, "Denmark", "@**all**")
+        user_group_mentioned_message_id = self.send_stream_message(
+            othello, "Denmark", "@*hamlet_and_cordelia*"
+        )
+
+        handle_missedmessage_emails(
+            hamlet.id,
+            [
+                {
+                    "message_id": wildcard_mentioned_message_id,
+                    "trigger": "wildcard_mentioned",
+                    "mentioned_user_group_id": None,
+                },
+                {
+                    "message_id": user_group_mentioned_message_id,
+                    "trigger": "mentioned",
+                    "mentioned_user_group_id": hamlet_and_cordelia.id,
+                },
+            ],
+        )
+
+        expected_email_include = [
+            "Othello, the Moor of Venice: > @**all** > @*hamlet_and_cordelia* -- ",
+            "You are receiving this because @hamlet_and_cordelia was mentioned.",
+        ]
+
+        for text in expected_email_include:
+            self.assertIn(text, self.normalize_string(mail.outbox[0].body))
+
+    def test_wildcard_over_stream_mention_priority(self) -> None:
+        hamlet = self.example_user("hamlet")
+        othello = self.example_user("othello")
+
+        stream_mentioned_message_id = self.send_stream_message(othello, "Denmark", "1")
+        wildcard_mentioned_message_id = self.send_stream_message(othello, "Denmark", "@**all**")
+
+        handle_missedmessage_emails(
+            hamlet.id,
+            [
+                {
+                    "message_id": stream_mentioned_message_id,
+                    "trigger": "stream_email_notify",
+                    "mentioned_user_group_id": None,
+                },
+                {
+                    "message_id": wildcard_mentioned_message_id,
+                    "trigger": "wildcard_mentioned",
+                    "mentioned_user_group_id": None,
+                },
+            ],
+        )
+
+        expected_email_include = [
+            "Othello, the Moor of Venice: > 1 > @**all** -- ",
+            "You are receiving this because everyone was mentioned in #Denmark.",
         ]
 
         for text in expected_email_include:
@@ -1450,7 +1519,7 @@ def test_empty_backticks_in_missed_message(self) -> None:
             self.example_user("hamlet"),
             "```\n```",
         )
-        verify_body_include = ["view it in Zulip"]
+        verify_body_include = ["view it in Zulip Dev Zulip"]
         email_subject = "PMs with Othello, the Moor of Venice"
         self._test_cases(
             msg_id, verify_body_include, email_subject, send_as_user=False, verify_html_body=True