mirror of
https://github.com/zulip/zulip.git
synced 2025-11-03 13:33:24 +00:00
emails: Add support for email headers in send custom email function.
This makes it a bit more convenient to encode most of the email configuration inside a single template file.
This commit is contained in:
@@ -12,10 +12,12 @@ from zerver.models import ScheduledEmail, get_user_profile_by_id, \
|
|||||||
|
|
||||||
import datetime
|
import datetime
|
||||||
from email.utils import parseaddr, formataddr
|
from email.utils import parseaddr, formataddr
|
||||||
|
from email.parser import Parser
|
||||||
|
from email.policy import default
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
import ujson
|
import ujson
|
||||||
import hashlib
|
import hashlib
|
||||||
import shutil
|
|
||||||
|
|
||||||
import os
|
import os
|
||||||
from typing import Any, Dict, List, Mapping, Optional, Tuple
|
from typing import Any, Dict, List, Mapping, Optional, Tuple
|
||||||
@@ -53,6 +55,13 @@ class FromAddress:
|
|||||||
with override_language(language):
|
with override_language(language):
|
||||||
return _("Zulip Account Security")
|
return _("Zulip Account Security")
|
||||||
|
|
||||||
|
def get_header(option: Optional[str], header: Optional[str], name: str) -> str:
|
||||||
|
if option and header:
|
||||||
|
raise DoubledEmailArgumentException(name)
|
||||||
|
if not option and not header:
|
||||||
|
raise NoEmailArgumentException(name)
|
||||||
|
return str(option or header)
|
||||||
|
|
||||||
def send_custom_email(users: List[UserProfile], options: Dict[str, Any]) -> None:
|
def send_custom_email(users: List[UserProfile], options: Dict[str, Any]) -> None:
|
||||||
"""
|
"""
|
||||||
Can be used directly with from a management shell with
|
Can be used directly with from a management shell with
|
||||||
@@ -64,7 +73,9 @@ def send_custom_email(users: List[UserProfile], options: Dict[str, Any]) -> None
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
with open(options["markdown_template_path"]) as f:
|
with open(options["markdown_template_path"]) as f:
|
||||||
email_template_hash = hashlib.sha256(f.read().encode('utf-8')).hexdigest()[0:32]
|
text = f.read()
|
||||||
|
parsed_email_template = Parser(policy=default).parsestr(text)
|
||||||
|
email_template_hash = hashlib.sha256(text.encode('utf-8')).hexdigest()[0:32]
|
||||||
|
|
||||||
email_filename = "custom_email_%s.source.html" % (email_template_hash,)
|
email_filename = "custom_email_%s.source.html" % (email_template_hash,)
|
||||||
email_id = "zerver/emails/custom_email_%s" % (email_template_hash,)
|
email_id = "zerver/emails/custom_email_%s" % (email_template_hash,)
|
||||||
@@ -75,7 +86,8 @@ def send_custom_email(users: List[UserProfile], options: Dict[str, Any]) -> None
|
|||||||
|
|
||||||
# First, we render the markdown input file just like our
|
# First, we render the markdown input file just like our
|
||||||
# user-facing docs with render_markdown_path.
|
# user-facing docs with render_markdown_path.
|
||||||
shutil.copyfile(options['markdown_template_path'], plain_text_template_path)
|
with open(plain_text_template_path, "w") as f:
|
||||||
|
f.write(parsed_email_template.get_payload())
|
||||||
|
|
||||||
from zerver.templatetags.app_filters import render_markdown_path
|
from zerver.templatetags.app_filters import render_markdown_path
|
||||||
rendered_input = render_markdown_path(plain_text_template_path.replace("templates/", ""))
|
rendered_input = render_markdown_path(plain_text_template_path.replace("templates/", ""))
|
||||||
@@ -90,10 +102,9 @@ def send_custom_email(users: List[UserProfile], options: Dict[str, Any]) -> None
|
|||||||
rendered_input))
|
rendered_input))
|
||||||
|
|
||||||
with open(subject_path, "w") as f:
|
with open(subject_path, "w") as f:
|
||||||
f.write(options["subject"])
|
f.write(get_header(options.get("subject"),
|
||||||
|
parsed_email_template.get("subject"), "subject"))
|
||||||
|
|
||||||
# Then, we compile the email template using inline_email_css to
|
|
||||||
# add our standard styling to the paragraph tags (etc.).
|
|
||||||
inline_template(email_filename)
|
inline_template(email_filename)
|
||||||
|
|
||||||
# Finally, we send the actual emails.
|
# Finally, we send the actual emails.
|
||||||
@@ -105,7 +116,10 @@ def send_custom_email(users: List[UserProfile], options: Dict[str, Any]) -> None
|
|||||||
send_email(email_id, to_user_ids=[user_profile.id],
|
send_email(email_id, to_user_ids=[user_profile.id],
|
||||||
from_address=FromAddress.SUPPORT,
|
from_address=FromAddress.SUPPORT,
|
||||||
reply_to_email=options.get("reply_to"),
|
reply_to_email=options.get("reply_to"),
|
||||||
from_name=options["from_name"], context=context)
|
from_name=get_header(options.get("from_name"),
|
||||||
|
parsed_email_template.get("from"),
|
||||||
|
"from_name"),
|
||||||
|
context=context)
|
||||||
|
|
||||||
def build_email(template_prefix: str, to_user_ids: Optional[List[int]]=None,
|
def build_email(template_prefix: str, to_user_ids: Optional[List[int]]=None,
|
||||||
to_emails: Optional[List[str]]=None, from_name: Optional[str]=None,
|
to_emails: Optional[List[str]]=None, from_name: Optional[str]=None,
|
||||||
@@ -182,6 +196,18 @@ def build_email(template_prefix: str, to_user_ids: Optional[List[int]]=None,
|
|||||||
class EmailNotDeliveredException(Exception):
|
class EmailNotDeliveredException(Exception):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
class DoubledEmailArgumentException(Exception):
|
||||||
|
def __init__(self, argument_name: str) -> None:
|
||||||
|
msg = "Argument '%s' is ambiguously present in both options and email template." % (
|
||||||
|
argument_name)
|
||||||
|
super().__init__(msg)
|
||||||
|
|
||||||
|
class NoEmailArgumentException(Exception):
|
||||||
|
def __init__(self, argument_name: str) -> None:
|
||||||
|
msg = "Argument '%s' is required in either options or email template." % (
|
||||||
|
argument_name)
|
||||||
|
super().__init__(msg)
|
||||||
|
|
||||||
# When changing the arguments to this function, you may need to write a
|
# When changing the arguments to this function, you may need to write a
|
||||||
# migration to change or remove any emails in ScheduledEmail.
|
# migration to change or remove any emails in ScheduledEmail.
|
||||||
def send_email(template_prefix: str, to_user_ids: Optional[List[int]]=None,
|
def send_email(template_prefix: str, to_user_ids: Optional[List[int]]=None,
|
||||||
|
|||||||
@@ -18,13 +18,11 @@ class Command(ZulipBaseCommand):
|
|||||||
type=str,
|
type=str,
|
||||||
help='Path to a markdown-format body for the email')
|
help='Path to a markdown-format body for the email')
|
||||||
parser.add_argument('--subject',
|
parser.add_argument('--subject',
|
||||||
required=True,
|
|
||||||
type=str,
|
type=str,
|
||||||
help='Subject line for the email')
|
help='Subject for the email. It can be declarated in markdown file in headers')
|
||||||
parser.add_argument('--from-name',
|
parser.add_argument('--from-name',
|
||||||
required=True,
|
|
||||||
type=str,
|
type=str,
|
||||||
help='From line for the email')
|
help='From line for the email. It can be declarated in markdown file in headers')
|
||||||
parser.add_argument('--reply-to',
|
parser.add_argument('--reply-to',
|
||||||
type=str,
|
type=str,
|
||||||
help='Optional reply-to line for the email')
|
help='Optional reply-to line for the email')
|
||||||
|
|||||||
Reference in New Issue
Block a user