send_email: Improve configurability for outgoing email sender name.

Currently, the sender names for outgoing emails sent by Zulip
are hardcoded. It should be configurable for self-hosted systems.

This commit makes the 'Zulip' part a variable in the following
email sender names: 'Zulip Account Security', 'Zulip Digest',
and 'Zulip Notifications' by introducing a settings variable
'SERVICE_NAME' with the default value as f"{EXTERNAL_HOST} Zulip".

Fixes: #23857
This commit is contained in:
Prakhar Pratyush 2022-12-24 22:01:48 +05:30 committed by Tim Abbott
parent 44d8dc66d2
commit d8cf12eaaa
10 changed files with 25 additions and 10 deletions

View File

@ -9,6 +9,7 @@ from django.conf import settings
from django.db import transaction from django.db import transaction
from django.db.models import Exists, OuterRef from django.db.models import Exists, OuterRef
from django.utils.timezone import now as timezone_now from django.utils.timezone import now as timezone_now
from django.utils.translation import gettext as _
from typing_extensions import TypeAlias from typing_extensions import TypeAlias
from confirmation.models import one_click_unsubscribe_link from confirmation.models import one_click_unsubscribe_link
@ -408,7 +409,7 @@ def bulk_handle_digest_email(user_ids: List[int], cutoff: float) -> None:
"zerver/emails/digest", "zerver/emails/digest",
user.realm, user.realm,
to_user_ids=[user.id], to_user_ids=[user.id],
from_name="Zulip Digest", from_name=_("{service_name} digest").format(service_name=settings.INSTALLATION_NAME),
from_address=FromAddress.no_reply_placeholder, from_address=FromAddress.no_reply_placeholder,
context=context, context=context,
) )

View File

@ -574,7 +574,9 @@ def do_send_missedmessage_events_reply_in_zulip(
) )
with override_language(user_profile.default_language): with override_language(user_profile.default_language):
from_name: str = _("Zulip notifications") from_name: str = _("{service_name} notifications").format(
service_name=settings.INSTALLATION_NAME
)
from_address = FromAddress.NOREPLY from_address = FromAddress.NOREPLY
email_dict = { email_dict = {

View File

@ -70,7 +70,9 @@ class FromAddress:
language = user_profile.default_language language = user_profile.default_language
with override_language(language): with override_language(language):
return _("Zulip Account Security") return _("{service_name} account security").format(
service_name=settings.INSTALLATION_NAME
)
def build_email( def build_email(

View File

@ -194,7 +194,7 @@ class EmailChangeTestCase(ZulipTestCase):
self.assertEqual(self.email_envelope_from(email_message), settings.NOREPLY_EMAIL_ADDRESS) self.assertEqual(self.email_envelope_from(email_message), settings.NOREPLY_EMAIL_ADDRESS)
self.assertRegex( self.assertRegex(
self.email_display_from(email_message), self.email_display_from(email_message),
rf"^Zulip Account Security <{self.TOKENIZED_NOREPLY_REGEX}>\Z", rf"^testserver account security <{self.TOKENIZED_NOREPLY_REGEX}>\Z",
) )
self.assertEqual(email_message.extra_headers["List-Id"], "Zulip Dev <zulip.testserver>") self.assertEqual(email_message.extra_headers["List-Id"], "Zulip Dev <zulip.testserver>")
@ -392,7 +392,7 @@ class EmailChangeTestCase(ZulipTestCase):
self.assertEqual(self.email_envelope_from(email_message), settings.NOREPLY_EMAIL_ADDRESS) self.assertEqual(self.email_envelope_from(email_message), settings.NOREPLY_EMAIL_ADDRESS)
self.assertRegex( self.assertRegex(
self.email_display_from(email_message), self.email_display_from(email_message),
rf"^Zulip Account Security <{self.TOKENIZED_NOREPLY_REGEX}>\Z", rf"^testserver account security <{self.TOKENIZED_NOREPLY_REGEX}>\Z",
) )
self.assertEqual(email_message.extra_headers["List-Id"], "Zulip Dev <zulip.testserver>") self.assertEqual(email_message.extra_headers["List-Id"], "Zulip Dev <zulip.testserver>")

View File

@ -402,7 +402,7 @@ class TestPasswordRestEmail(ZulipTestCase):
self.assertEqual(self.email_envelope_from(outbox[0]), settings.NOREPLY_EMAIL_ADDRESS) self.assertEqual(self.email_envelope_from(outbox[0]), settings.NOREPLY_EMAIL_ADDRESS)
self.assertRegex( self.assertRegex(
self.email_display_from(outbox[0]), self.email_display_from(outbox[0]),
rf"^Zulip Account Security <{self.TOKENIZED_NOREPLY_REGEX}>\Z", rf"^testserver account security <{self.TOKENIZED_NOREPLY_REGEX}>\Z",
) )
self.assertIn("reset your password", outbox[0].body) self.assertIn("reset your password", outbox[0].body)

View File

@ -131,7 +131,9 @@ class TestMessageNotificationEmails(ZulipTestCase):
reply_to_emails = ["noreply@testserver"] reply_to_emails = ["noreply@testserver"]
msg = mail.outbox[0] msg = mail.outbox[0]
assert isinstance(msg, EmailMultiAlternatives) assert isinstance(msg, EmailMultiAlternatives)
from_email = str(Address(display_name="Zulip notifications", addr_spec=FromAddress.NOREPLY)) from_email = str(
Address(display_name="testserver notifications", addr_spec=FromAddress.NOREPLY)
)
self.assert_length(mail.outbox, 1) self.assert_length(mail.outbox, 1)
self.assertEqual(self.email_envelope_from(msg), settings.NOREPLY_EMAIL_ADDRESS) self.assertEqual(self.email_envelope_from(msg), settings.NOREPLY_EMAIL_ADDRESS)
self.assertEqual(self.email_display_from(msg), from_email) self.assertEqual(self.email_display_from(msg), from_email)

View File

@ -452,7 +452,7 @@ class RealmTest(ZulipTestCase):
self.assertEqual(self.email_envelope_from(outbox[0]), settings.NOREPLY_EMAIL_ADDRESS) self.assertEqual(self.email_envelope_from(outbox[0]), settings.NOREPLY_EMAIL_ADDRESS)
self.assertRegex( self.assertRegex(
self.email_display_from(outbox[0]), self.email_display_from(outbox[0]),
rf"^Zulip Account Security <{self.TOKENIZED_NOREPLY_REGEX}>\Z", rf"^testserver account security <{self.TOKENIZED_NOREPLY_REGEX}>\Z",
) )
self.assertIn("Reactivate your Zulip organization", outbox[0].subject) self.assertIn("Reactivate your Zulip organization", outbox[0].subject)
self.assertIn("Dear former administrators", outbox[0].body) self.assertIn("Dear former administrators", outbox[0].body)

View File

@ -16,7 +16,6 @@ from django.http import HttpResponse, HttpResponseBase
from django.template.response import TemplateResponse from django.template.response import TemplateResponse
from django.test import Client, override_settings from django.test import Client, override_settings
from django.utils import translation from django.utils import translation
from django.utils.translation import gettext as _
from confirmation import settings as confirmation_settings from confirmation import settings as confirmation_settings
from confirmation.models import Confirmation, one_click_unsubscribe_link from confirmation.models import Confirmation, one_click_unsubscribe_link
@ -360,7 +359,7 @@ class PasswordResetTest(ZulipTestCase):
# The email might be sent in different languages for i18n testing # The email might be sent in different languages for i18n testing
self.assertRegex( self.assertRegex(
self.email_display_from(message), self.email_display_from(message),
rf'^{_("Zulip Account Security")} <{self.TOKENIZED_NOREPLY_REGEX}>\Z', rf"^testserver account security <{self.TOKENIZED_NOREPLY_REGEX}>\Z",
) )
self.assertIn(f"{subdomain}.testserver", message.extra_headers["List-Id"]) self.assertIn(f"{subdomain}.testserver", message.extra_headers["List-Id"])

View File

@ -485,6 +485,8 @@ WELCOME_EMAIL_SENDER: Optional[Dict[str, str]] = None
# Whether to send periodic digests of activity. # Whether to send periodic digests of activity.
SEND_DIGEST_EMAILS = True SEND_DIGEST_EMAILS = True
# The variable part of email sender names to be used for outgoing emails.
INSTALLATION_NAME = EXTERNAL_HOST
# Used to change the Zulip logo in portico pages. # Used to change the Zulip logo in portico pages.
CUSTOM_LOGO_URL: Optional[str] = None CUSTOM_LOGO_URL: Optional[str] = None

View File

@ -101,6 +101,13 @@ EXTERNAL_HOST = "zulip.example.com"
## confirmation emails when ADD_TOKENS_TO_NOREPLY_ADDRESS=False. ## confirmation emails when ADD_TOKENS_TO_NOREPLY_ADDRESS=False.
# NOREPLY_EMAIL_ADDRESS = "noreply@example.com" # NOREPLY_EMAIL_ADDRESS = "noreply@example.com"
## Emails sent by the Zulip server will use a sender name starting
## with INSTALLATION_NAME. The default is EXTERNAL_HOST. If INSTALLATION_NAME is
## "zulip.example.com", email senders names will include:
## * "zulip.example.com notifications" (message notification emails).
## * "zulip.example.com account security" (account security emails).
# INSTALLATION_NAME = "My Zulip Server"
## Many countries and bulk mailers require certain types of email to display ## Many countries and bulk mailers require certain types of email to display
## a physical mailing address to comply with anti-spam legislation. ## a physical mailing address to comply with anti-spam legislation.
## Non-commercial and non-public-facing installations are unlikely to need ## Non-commercial and non-public-facing installations are unlikely to need