From 95b0ab31bed2b09e55892207d5c715d71366db7a Mon Sep 17 00:00:00 2001 From: Alex Vandiver Date: Thu, 3 Aug 2023 21:22:21 +0000 Subject: [PATCH] send_custom_email: Only put the unsubscribe footer on marketing emails. --- .../zerver/emails/custom_email_base.pre.html | 2 +- zerver/lib/send_email.py | 3 +- .../management/commands/send_custom_email.py | 17 +++++++- .../email_base_headers_custom_test.md | 4 ++ zerver/tests/test_email_notifications.py | 43 +++++++++++++++++++ 5 files changed, 64 insertions(+), 5 deletions(-) create mode 100644 zerver/tests/fixtures/email/custom_emails/email_base_headers_custom_test.md diff --git a/templates/zerver/emails/custom_email_base.pre.html b/templates/zerver/emails/custom_email_base.pre.html index 9ff778eac1..d8ad3932c7 100644 --- a/templates/zerver/emails/custom_email_base.pre.html +++ b/templates/zerver/emails/custom_email_base.pre.html @@ -14,7 +14,7 @@ {% block manage_preferences %} {% if remote_server_email %}

You are receiving this email to update you about important changes to Zulip's Terms of Service.

- {% else %} + {% elif unsubscribe_link %}

{{ _("Manage email preferences") }} | {{ _("Unsubscribe from marketing emails") }}

{% endif %} {% endblock %} diff --git a/zerver/lib/send_email.py b/zerver/lib/send_email.py index 7e4122e37e..c904de3696 100644 --- a/zerver/lib/send_email.py +++ b/zerver/lib/send_email.py @@ -27,7 +27,7 @@ from django.utils.timezone import now as timezone_now from django.utils.translation import gettext as _ from django.utils.translation import override as override_language -from confirmation.models import generate_key, one_click_unsubscribe_link +from confirmation.models import generate_key from zerver.lib.logging_util import log_to_file from zerver.models import EMAIL_TYPES, Realm, ScheduledEmail, UserProfile, get_user_profile_by_id from zproject.email_backends import EmailLogBackEnd, get_forward_address @@ -565,7 +565,6 @@ def send_custom_email( context: Dict[str, Union[List[str], str]] = { "realm_uri": user_profile.realm.uri, "realm_name": user_profile.realm.name, - "unsubscribe_link": one_click_unsubscribe_link(user_profile, "marketing"), } if add_context is not None: add_context(context, user_profile) diff --git a/zerver/management/commands/send_custom_email.py b/zerver/management/commands/send_custom_email.py index b7a27d7e3f..5e0a116e38 100644 --- a/zerver/management/commands/send_custom_email.py +++ b/zerver/management/commands/send_custom_email.py @@ -1,9 +1,10 @@ from argparse import ArgumentParser -from typing import Any, List +from typing import Any, Callable, Dict, List, Optional, Union from django.conf import settings from django.db.models import Q, QuerySet +from confirmation.models import one_click_unsubscribe_link from zerver.lib.management import ZulipBaseCommand from zerver.lib.send_email import send_custom_email from zerver.models import Realm, UserProfile @@ -79,6 +80,9 @@ class Command(ZulipBaseCommand): def handle(self, *args: Any, **options: str) -> None: target_emails: List[str] = [] users: QuerySet[UserProfile] = UserProfile.objects.none() + add_context: Optional[ + Callable[[Dict[str, Union[List[str], str]], UserProfile], None] + ] = None if options["entire_server"]: users = UserProfile.objects.filter( @@ -95,6 +99,13 @@ class Command(ZulipBaseCommand): enable_marketing_emails=True, long_term_idle=False, ).distinct("delivery_email") + + def add_marketing_unsubscribe( + context: Dict[str, Union[List[str], str]], user: UserProfile + ) -> None: + context["unsubscribe_link"] = one_click_unsubscribe_link(user, "marketing") + + add_context = add_marketing_unsubscribe elif options["remote_servers"]: from zilencer.models import RemoteZulipServer @@ -129,7 +140,9 @@ class Command(ZulipBaseCommand): users = users.exclude( Q(tos_version=None) | Q(tos_version=UserProfile.TOS_VERSION_BEFORE_FIRST_LOGIN) ) - send_custom_email(users, target_emails=target_emails, options=options) + send_custom_email( + users, target_emails=target_emails, options=options, add_context=add_context + ) if options["dry_run"]: print("Would send the above email to:") diff --git a/zerver/tests/fixtures/email/custom_emails/email_base_headers_custom_test.md b/zerver/tests/fixtures/email/custom_emails/email_base_headers_custom_test.md new file mode 100644 index 0000000000..4cf4a5478d --- /dev/null +++ b/zerver/tests/fixtures/email/custom_emails/email_base_headers_custom_test.md @@ -0,0 +1,4 @@ +From: TestFrom +Subject: Test subject + +Test body with {{ custom }} value. diff --git a/zerver/tests/test_email_notifications.py b/zerver/tests/test_email_notifications.py index 662beec543..62b5a85f9a 100644 --- a/zerver/tests/test_email_notifications.py +++ b/zerver/tests/test_email_notifications.py @@ -1,5 +1,6 @@ import tempfile from datetime import datetime, timedelta, timezone +from typing import Dict, List, Union from unittest.mock import patch import ldap @@ -105,6 +106,48 @@ class TestCustomEmails(ZulipTestCase): self.assertFalse(msg.reply_to) self.assertEqual("Test body", msg.body) + def test_send_custom_email_context(self) -> None: + hamlet = self.example_user("hamlet") + markdown_template_path = ( + "zerver/tests/fixtures/email/custom_emails/email_base_headers_test.md" + ) + send_custom_email( + UserProfile.objects.filter(id=hamlet.id), + options={ + "markdown_template_path": markdown_template_path, + "dry_run": False, + }, + ) + self.assert_length(mail.outbox, 1) + msg = mail.outbox[0] + + # We default to not including an unsubscribe link in the headers + self.assertEqual(msg.extra_headers.get("X-Auto-Response-Suppress"), "All") + self.assertIsNone(msg.extra_headers.get("List-Unsubscribe")) + + mail.outbox = [] + markdown_template_path = ( + "zerver/tests/fixtures/email/custom_emails/email_base_headers_custom_test.md" + ) + + def add_context(context: Dict[str, Union[List[str], str]], user: UserProfile) -> None: + context["unsubscribe_link"] = "some@email" + context["custom"] = str(user.id) + + send_custom_email( + UserProfile.objects.filter(id=hamlet.id), + options={ + "markdown_template_path": markdown_template_path, + "dry_run": False, + }, + add_context=add_context, + ) + self.assert_length(mail.outbox, 1) + msg = mail.outbox[0] + self.assertEqual(msg.extra_headers.get("X-Auto-Response-Suppress"), "All") + self.assertEqual(msg.extra_headers.get("List-Unsubscribe"), "") + self.assertIn(f"Test body with {hamlet.id} value", msg.body) + def test_send_custom_email_no_argument(self) -> None: hamlet = self.example_user("hamlet") from_name = "from_name_test"