mirror of
				https://github.com/zulip/zulip.git
				synced 2025-10-31 03:53:50 +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