mirror of https://github.com/zulip/zulip.git
email: Set an envelope-from which may be different from the From: field.
The envelope-from is used by the MTA if the destination address is not deliverable. Route all such mail to the noreply address.
This commit is contained in:
parent
173d2dec3d
commit
e53be6d043
|
@ -1422,7 +1422,8 @@ class StripeTest(StripeTestCase):
|
|||
self.assertEqual(message.to[0], "desdemona+admin@zulip.com")
|
||||
self.assertEqual(message.subject, "Sponsorship request (Open-source) for zulip")
|
||||
self.assertEqual(message.reply_to, ["hamlet@zulip.com"])
|
||||
self.assertIn("Zulip sponsorship <noreply-", message.from_email)
|
||||
self.assertEqual(self.email_envelope_from(message), settings.NOREPLY_EMAIL_ADDRESS)
|
||||
self.assertIn("Zulip sponsorship <noreply-", self.email_display_from(message))
|
||||
self.assertIn("Requested by: King Hamlet (Member)", message.body)
|
||||
self.assertIn(
|
||||
"Support URL: http://zulip.testserver/activity/support?q=zulip", message.body
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
{% if from_email != envelope_from %}
|
||||
<h4>Envelope-From: {{ envelope_from }}</h4>
|
||||
{% endif %}
|
||||
<h4>From: {{ from_email }}</h4>
|
||||
{% if reply_to %}
|
||||
<h4>Reply To:
|
||||
|
|
|
@ -148,7 +148,9 @@ def build_email(
|
|||
if from_address == FromAddress.support_placeholder:
|
||||
from_address = FromAddress.SUPPORT
|
||||
|
||||
from_email = str(Address(display_name=from_name, addr_spec=from_address))
|
||||
# Set the "From" that is displayed separately from the envelope-from
|
||||
extra_headers["From"] = str(Address(display_name=from_name, addr_spec=from_address))
|
||||
|
||||
reply_to = None
|
||||
if reply_to_email is not None:
|
||||
reply_to = [reply_to_email]
|
||||
|
@ -158,8 +160,9 @@ def build_email(
|
|||
elif from_address == FromAddress.NOREPLY:
|
||||
reply_to = [FromAddress.NOREPLY]
|
||||
|
||||
envelope_from = FromAddress.NOREPLY
|
||||
mail = EmailMultiAlternatives(
|
||||
email_subject, message, from_email, to_emails, reply_to=reply_to, headers=extra_headers
|
||||
email_subject, message, envelope_from, to_emails, reply_to=reply_to, headers=extra_headers
|
||||
)
|
||||
if html_message is not None:
|
||||
mail.attach_alternative(html_message, "text/html")
|
||||
|
|
|
@ -13,6 +13,7 @@ import lxml.html
|
|||
import orjson
|
||||
from django.apps import apps
|
||||
from django.conf import settings
|
||||
from django.core.mail import EmailMessage
|
||||
from django.db import connection
|
||||
from django.db.migrations.executor import MigrationExecutor
|
||||
from django.db.migrations.state import StateApps
|
||||
|
@ -1160,6 +1161,25 @@ Output:
|
|||
def ldap_password(self, uid: str) -> str:
|
||||
return f"{uid}_ldap_password"
|
||||
|
||||
def email_display_from(self, email_message: EmailMessage) -> str:
|
||||
"""
|
||||
Returns the email address that will show in email clients as the
|
||||
"From" field.
|
||||
"""
|
||||
# The extra_headers field may contain a "From" which is used
|
||||
# for display in email clients, and appears in the RFC822
|
||||
# header as `From`. The `.from_email` accessor is the
|
||||
# "envelope from" address, used by mail transfer agents if
|
||||
# the email bounces.
|
||||
return email_message.extra_headers.get("From", email_message.from_email)
|
||||
|
||||
def email_envelope_from(self, email_message: EmailMessage) -> str:
|
||||
"""
|
||||
Returns the email address that will be used if the email bounces.
|
||||
"""
|
||||
# See email_display_from, above.
|
||||
return email_message.from_email
|
||||
|
||||
|
||||
class WebhookTestCase(ZulipTestCase):
|
||||
"""
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import datetime
|
||||
|
||||
from django.conf import settings
|
||||
from django.core import mail
|
||||
from django.utils.timezone import now
|
||||
|
||||
|
@ -116,8 +117,9 @@ class EmailChangeTestCase(ZulipTestCase):
|
|||
)
|
||||
body = email_message.body
|
||||
self.assertIn("We received a request to change the email", body)
|
||||
self.assertEqual(self.email_envelope_from(email_message), settings.NOREPLY_EMAIL_ADDRESS)
|
||||
self.assertRegex(
|
||||
email_message.from_email,
|
||||
self.email_display_from(email_message),
|
||||
fr"^Zulip Account Security <{self.TOKENIZED_NOREPLY_REGEX}>\Z",
|
||||
)
|
||||
|
||||
|
|
|
@ -311,7 +311,8 @@ class TestMissedMessages(ZulipTestCase):
|
|||
self.assertEqual(len(mail.outbox), 1)
|
||||
if send_as_user:
|
||||
from_email = f'"{othello.full_name}" <{othello.email}>'
|
||||
self.assertEqual(msg.from_email, from_email)
|
||||
self.assertEqual(self.email_envelope_from(msg), settings.NOREPLY_EMAIL_ADDRESS)
|
||||
self.assertEqual(self.email_display_from(msg), from_email)
|
||||
self.assertEqual(msg.subject, email_subject)
|
||||
self.assertEqual(len(msg.reply_to), 1)
|
||||
self.assertIn(msg.reply_to[0], reply_to_emails)
|
||||
|
|
|
@ -377,8 +377,9 @@ class TestPasswordRestEmail(ZulipTestCase):
|
|||
call_command(self.COMMAND_NAME, users=self.example_email("iago"))
|
||||
from django.core.mail import outbox
|
||||
|
||||
self.assertEqual(self.email_envelope_from(outbox[0]), settings.NOREPLY_EMAIL_ADDRESS)
|
||||
self.assertRegex(
|
||||
outbox[0].from_email,
|
||||
self.email_display_from(outbox[0]),
|
||||
fr"^Zulip Account Security <{self.TOKENIZED_NOREPLY_REGEX}>\Z",
|
||||
)
|
||||
self.assertIn("reset your password", outbox[0].body)
|
||||
|
|
|
@ -282,8 +282,9 @@ class RealmTest(ZulipTestCase):
|
|||
from django.core.mail import outbox
|
||||
|
||||
self.assertEqual(len(outbox), 1)
|
||||
self.assertEqual(self.email_envelope_from(outbox[0]), settings.NOREPLY_EMAIL_ADDRESS)
|
||||
self.assertRegex(
|
||||
outbox[0].from_email,
|
||||
self.email_display_from(outbox[0]),
|
||||
fr"^Zulip Account Security <{self.TOKENIZED_NOREPLY_REGEX}>\Z",
|
||||
)
|
||||
self.assertIn("Reactivate your Zulip organization", outbox[0].subject)
|
||||
|
|
|
@ -349,8 +349,9 @@ class PasswordResetTest(ZulipTestCase):
|
|||
from django.core.mail import outbox
|
||||
|
||||
[message] = outbox
|
||||
self.assertEqual(self.email_envelope_from(message), settings.NOREPLY_EMAIL_ADDRESS)
|
||||
self.assertRegex(
|
||||
message.from_email,
|
||||
self.email_display_from(message),
|
||||
fr"^Zulip Account Security <{self.TOKENIZED_NOREPLY_REGEX}>\Z",
|
||||
)
|
||||
self.assertIn(f"{subdomain}.testserver", message.extra_headers["List-Id"])
|
||||
|
@ -911,9 +912,12 @@ class InviteUserBase(ZulipTestCase):
|
|||
return
|
||||
|
||||
if custom_from_name is not None:
|
||||
self.assertIn(custom_from_name, outbox[0].from_email)
|
||||
self.assertIn(custom_from_name, self.email_display_from(outbox[0]))
|
||||
|
||||
self.assertRegex(outbox[0].from_email, fr" <{self.TOKENIZED_NOREPLY_REGEX}>\Z")
|
||||
self.assertEqual(self.email_envelope_from(outbox[0]), settings.NOREPLY_EMAIL_ADDRESS)
|
||||
self.assertRegex(
|
||||
self.email_display_from(outbox[0]), fr" <{self.TOKENIZED_NOREPLY_REGEX}>\Z"
|
||||
)
|
||||
|
||||
self.assertEqual(outbox[0].extra_headers["List-Id"], "Zulip Dev <zulip.testserver>")
|
||||
|
||||
|
@ -1676,7 +1680,8 @@ so we didn't send them an invitation. We did send invitations to everyone else!"
|
|||
for job in email_jobs_to_deliver:
|
||||
deliver_email(job)
|
||||
self.assertEqual(len(outbox), email_count + 1)
|
||||
self.assertIn(FromAddress.NOREPLY, outbox[-1].from_email)
|
||||
self.assertEqual(self.email_envelope_from(outbox[-1]), settings.NOREPLY_EMAIL_ADDRESS)
|
||||
self.assertIn(FromAddress.NOREPLY, self.email_display_from(outbox[-1]))
|
||||
|
||||
# Now verify that signing up clears invite_reminder emails
|
||||
with self.settings(EMAIL_BACKEND="django.core.mail.backends.console.EmailBackend"):
|
||||
|
|
|
@ -39,7 +39,8 @@ class EmailLogBackEnd(EmailBackend):
|
|||
|
||||
context = {
|
||||
"subject": email.subject,
|
||||
"from_email": email.from_email,
|
||||
"envelope_from": email.from_email,
|
||||
"from_email": email.extra_headers.get("From", email.from_email),
|
||||
"reply_to": email.reply_to,
|
||||
"recipients": email.to,
|
||||
"body": email.body,
|
||||
|
|
Loading…
Reference in New Issue