Files
zulip/zproject/email_backends.py
pletinckxc 184df7e656 emails: Fix outgoing email handling inside the dev environment.
Commit 9afde790c6 introduced a bug
concerning outgoing emails inside the development environment. These
emails are not supposed to use a real connection with a mail
server as the send_messages function is overwritten inside the
EmailLogBackEnd class.

The bug was happening inside the initialize_connection function that
was introduced in the above-mentioned commit. This function is used
to refresh the connection with an SMTP server that would have closed
it. As the socket used to communicate with the server is not
initialized inside the development environment this function was
wrongly trying to send no-op commands.

The fix just checks that the connection argument of the function is
an EmailLogBackEnd object before trying the no-op command.
Additionally as it is sometimes useful to be able to send outgoing
emails inside the development environment the get_forward_address
function is used to check if a real connection exists between Zulip
and the server. If it is the case, as EmailLogBackEnd is a subclass
of smtp.EmailBackend, the connection will be nicely refreshed.

This commit was tested manually by checking that the console prints
correctly that an email is sent to the user when it signs in inside
the development environment. It was also tested when a mail provider
is specified and the mails were correctly received.
2021-04-28 18:00:37 -07:00

93 lines
3.6 KiB
Python

# https://zulip.readthedocs.io/en/latest/subsystems/email.html#testing-in-a-real-email-client
import configparser
import logging
from typing import List
from django.conf import settings
from django.core.mail import EmailMultiAlternatives
from django.core.mail.backends.smtp import EmailBackend
from django.template import loader
def get_forward_address() -> str:
config = configparser.ConfigParser()
config.read(settings.FORWARD_ADDRESS_CONFIG_FILE)
try:
return config.get("DEV_EMAIL", "forward_address")
except (configparser.NoSectionError, configparser.NoOptionError):
return ""
def set_forward_address(forward_address: str) -> None:
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:
config.write(cfgfile)
class EmailLogBackEnd(EmailBackend):
@staticmethod
def log_email(email: EmailMultiAlternatives) -> None:
"""Used in development to record sent emails in a nice HTML log"""
html_message = "Missing HTML message"
if len(email.alternatives) > 0:
html_message = email.alternatives[0][0]
context = {
"subject": email.subject,
"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,
"html_message": html_message,
}
new_email = loader.render_to_string("zerver/email.html", context)
# Read in the pre-existing log, so that we can add the new entry
# at the top.
try:
with open(settings.EMAIL_CONTENT_LOG_PATH) as f:
previous_emails = f.read()
except FileNotFoundError:
previous_emails = ""
with open(settings.EMAIL_CONTENT_LOG_PATH, "w+") as f:
f.write(new_email + previous_emails)
@staticmethod
def prepare_email_messages_for_forwarding(email_messages: List[EmailMultiAlternatives]) -> None:
localhost_email_images_base_uri = settings.ROOT_DOMAIN_URI + "/static/images/emails"
czo_email_images_base_uri = "https://chat.zulip.org/static/images/emails"
for email_message in email_messages:
html_alternative = list(email_message.alternatives[0])
# 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.
html_alternative[0] = html_alternative[0].replace(
localhost_email_images_base_uri, czo_email_images_base_uri
)
email_message.alternatives[0] = tuple(html_alternative)
email_message.to = [get_forward_address()]
def send_messages(self, email_messages: List[EmailMultiAlternatives]) -> int:
num_sent = len(email_messages)
if get_forward_address():
self.prepare_email_messages_for_forwarding(email_messages)
num_sent = super().send_messages(email_messages)
if settings.DEVELOPMENT_LOG_EMAILS:
for email in email_messages:
self.log_email(email)
email_log_url = settings.ROOT_DOMAIN_URI + "/emails"
logging.info("Emails sent in development are available at %s", email_log_url)
return num_sent