2021-04-28 20:23:14 +02:00
|
|
|
# https://zulip.readthedocs.io/en/latest/subsystems/email.html#testing-in-a-real-email-client
|
2018-05-09 20:57:01 +02:00
|
|
|
import configparser
|
2020-06-11 00:54:34 +02:00
|
|
|
import logging
|
2024-07-12 02:30:25 +02:00
|
|
|
from collections.abc import MutableSequence, Sequence
|
2021-08-15 18:32:51 +02:00
|
|
|
from email.message import Message
|
2017-10-25 01:54:43 +02:00
|
|
|
|
|
|
|
from django.conf import settings
|
|
|
|
from django.core.mail import EmailMultiAlternatives
|
2021-01-28 19:28:34 +01:00
|
|
|
from django.core.mail.backends.smtp import EmailBackend
|
2021-08-15 18:32:51 +02:00
|
|
|
from django.core.mail.message import EmailMessage
|
2017-10-25 01:54:43 +02:00
|
|
|
from django.template import loader
|
2023-10-12 19:43:45 +02:00
|
|
|
from typing_extensions import override
|
2017-10-25 01:54:43 +02:00
|
|
|
|
2020-06-11 00:54:34 +02:00
|
|
|
|
2017-11-27 14:35:36 +01:00
|
|
|
def get_forward_address() -> str:
|
2017-10-25 01:58:05 +02:00
|
|
|
config = configparser.ConfigParser()
|
|
|
|
config.read(settings.FORWARD_ADDRESS_CONFIG_FILE)
|
|
|
|
try:
|
|
|
|
return config.get("DEV_EMAIL", "forward_address")
|
2018-05-24 16:41:34 +02:00
|
|
|
except (configparser.NoSectionError, configparser.NoOptionError):
|
2017-10-25 01:58:05 +02:00
|
|
|
return ""
|
|
|
|
|
2021-02-12 08:19:30 +01:00
|
|
|
|
2017-11-27 14:35:36 +01:00
|
|
|
def set_forward_address(forward_address: str) -> None:
|
2017-10-25 01:58:05 +02:00
|
|
|
config = configparser.ConfigParser()
|
|
|
|
config.read(settings.FORWARD_ADDRESS_CONFIG_FILE)
|
|
|
|
|
|
|
|
if not config.has_section("DEV_EMAIL"):
|
|
|
|
config.add_section("DEV_EMAIL")
|
|
|
|
config.set("DEV_EMAIL", "forward_address", forward_address)
|
|
|
|
|
|
|
|
with open(settings.FORWARD_ADDRESS_CONFIG_FILE, "w") as cfgfile:
|
2019-01-31 14:32:37 +01:00
|
|
|
config.write(cfgfile)
|
2017-10-25 01:58:05 +02:00
|
|
|
|
2021-02-12 08:19:30 +01:00
|
|
|
|
2021-01-28 19:28:34 +01:00
|
|
|
class EmailLogBackEnd(EmailBackend):
|
2021-01-28 20:17:13 +01:00
|
|
|
@staticmethod
|
2022-07-28 17:25:58 +02:00
|
|
|
def log_email(email: EmailMessage) -> None:
|
2017-10-25 01:54:43 +02:00
|
|
|
"""Used in development to record sent emails in a nice HTML log"""
|
2024-07-12 02:30:23 +02:00
|
|
|
html_message: bytes | EmailMessage | Message | str = "Missing HTML message"
|
2022-07-28 17:25:58 +02:00
|
|
|
assert isinstance(email, EmailMultiAlternatives)
|
2017-10-25 01:54:43 +02:00
|
|
|
if len(email.alternatives) > 0:
|
|
|
|
html_message = email.alternatives[0][0]
|
|
|
|
|
|
|
|
context = {
|
2021-02-12 08:20:45 +01:00
|
|
|
"subject": email.subject,
|
2021-01-26 04:20:36 +01:00
|
|
|
"envelope_from": email.from_email,
|
|
|
|
"from_email": email.extra_headers.get("From", email.from_email),
|
2021-02-12 08:20:45 +01:00
|
|
|
"reply_to": email.reply_to,
|
|
|
|
"recipients": email.to,
|
|
|
|
"body": email.body,
|
|
|
|
"html_message": html_message,
|
2017-10-25 01:54:43 +02:00
|
|
|
}
|
|
|
|
|
2021-02-12 08:20:45 +01:00
|
|
|
new_email = loader.render_to_string("zerver/email.html", context)
|
2017-10-25 01:54:43 +02:00
|
|
|
|
|
|
|
# Read in the pre-existing log, so that we can add the new entry
|
|
|
|
# at the top.
|
|
|
|
try:
|
2020-04-09 21:51:58 +02:00
|
|
|
with open(settings.EMAIL_CONTENT_LOG_PATH) as f:
|
2017-10-25 01:54:43 +02:00
|
|
|
previous_emails = f.read()
|
|
|
|
except FileNotFoundError:
|
|
|
|
previous_emails = ""
|
|
|
|
|
|
|
|
with open(settings.EMAIL_CONTENT_LOG_PATH, "w+") as f:
|
|
|
|
f.write(new_email + previous_emails)
|
|
|
|
|
2021-01-28 19:28:34 +01:00
|
|
|
@staticmethod
|
2022-07-28 17:25:58 +02:00
|
|
|
def prepare_email_messages_for_forwarding(email_messages: Sequence[EmailMessage]) -> None:
|
2023-04-26 01:47:00 +02:00
|
|
|
localhost_email_images_base_url = settings.ROOT_DOMAIN_URI + "/static/images/emails"
|
|
|
|
czo_email_images_base_url = "https://chat.zulip.org/static/images/emails"
|
2021-01-28 19:28:34 +01:00
|
|
|
|
|
|
|
for email_message in email_messages:
|
2022-07-28 17:25:58 +02:00
|
|
|
assert isinstance(email_message, EmailMultiAlternatives)
|
2022-07-28 17:17:18 +02:00
|
|
|
assert isinstance(email_message.alternatives[0][0], str)
|
2021-01-28 19:28:34 +01:00
|
|
|
# Here, we replace the email addresses used in development
|
|
|
|
# with chat.zulip.org, so that web email providers like Gmail
|
|
|
|
# will be able to fetch the illustrations used in the emails.
|
2022-07-28 17:17:18 +02:00
|
|
|
html_alternative = (
|
|
|
|
email_message.alternatives[0][0].replace(
|
2023-04-26 01:47:00 +02:00
|
|
|
localhost_email_images_base_url, czo_email_images_base_url
|
2022-07-28 17:17:18 +02:00
|
|
|
),
|
|
|
|
email_message.alternatives[0][1],
|
2021-02-12 08:19:30 +01:00
|
|
|
)
|
2021-08-15 18:32:51 +02:00
|
|
|
assert isinstance(email_message.alternatives, MutableSequence)
|
2022-07-28 17:17:18 +02:00
|
|
|
email_message.alternatives[0] = html_alternative
|
2021-01-28 19:28:34 +01:00
|
|
|
|
|
|
|
email_message.to = [get_forward_address()]
|
|
|
|
|
2022-05-03 03:46:20 +02:00
|
|
|
# This wrapper function exists to allow tests easily to mock the
|
|
|
|
# step of trying to send the emails. Previously, we had mocked
|
|
|
|
# Django's connection.send_messages(), which caused unexplained
|
|
|
|
# test failures when running test-backend at very high
|
|
|
|
# concurrency.
|
2022-07-28 17:25:58 +02:00
|
|
|
def _do_send_messages(self, email_messages: Sequence[EmailMessage]) -> int:
|
2022-05-03 03:46:20 +02:00
|
|
|
return super().send_messages(email_messages) # nocoverage
|
|
|
|
|
2023-10-12 19:43:45 +02:00
|
|
|
@override
|
2022-07-28 17:25:58 +02:00
|
|
|
def send_messages(self, email_messages: Sequence[EmailMessage]) -> int:
|
2021-01-28 19:28:34 +01:00
|
|
|
num_sent = len(email_messages)
|
|
|
|
if get_forward_address():
|
|
|
|
self.prepare_email_messages_for_forwarding(email_messages)
|
2022-05-03 03:46:20 +02:00
|
|
|
num_sent = self._do_send_messages(email_messages)
|
2021-01-28 19:28:34 +01:00
|
|
|
|
|
|
|
if settings.DEVELOPMENT_LOG_EMAILS:
|
|
|
|
for email in email_messages:
|
2018-08-06 11:40:31 +02:00
|
|
|
self.log_email(email)
|
|
|
|
email_log_url = settings.ROOT_DOMAIN_URI + "/emails"
|
2020-05-02 08:44:14 +02:00
|
|
|
logging.info("Emails sent in development are available at %s", email_log_url)
|
2021-01-28 19:28:34 +01:00
|
|
|
return num_sent
|