mirror of
				https://github.com/zulip/zulip.git
				synced 2025-11-04 05:53:43 +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