mirror of
				https://github.com/zulip/zulip.git
				synced 2025-11-03 21:43:21 +00:00 
			
		
		
		
	management: Add command to send a custom email to users.
This allows us to email sets of users on a server with a nicely formatted email similar to our onboarding emails, built off of a Markdown template. The code was based on send_password_reset_email, but it doesn't replace that use case, since one cannot include special values like password reset tokens in these emails.
This commit is contained in:
		
							
								
								
									
										103
									
								
								zerver/management/commands/send_custom_email.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										103
									
								
								zerver/management/commands/send_custom_email.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,103 @@
 | 
			
		||||
import hashlib
 | 
			
		||||
import shutil
 | 
			
		||||
import subprocess
 | 
			
		||||
 | 
			
		||||
from argparse import ArgumentParser
 | 
			
		||||
from typing import Any, Dict, List
 | 
			
		||||
 | 
			
		||||
from zerver.lib.management import CommandError, ZulipBaseCommand
 | 
			
		||||
from zerver.lib.send_email import FromAddress, send_email
 | 
			
		||||
from zerver.models import UserProfile
 | 
			
		||||
from zerver.templatetags.app_filters import render_markdown_path
 | 
			
		||||
 | 
			
		||||
def send_custom_email(users: List[UserProfile], options: Dict[str, Any]) -> None:
 | 
			
		||||
    """
 | 
			
		||||
    Can be used directly with from a management shell with
 | 
			
		||||
    send_custom_email(user_profile_list, dict(
 | 
			
		||||
        markdown_template_path="/path/to/markdown/file.md",
 | 
			
		||||
        subject="Email Subject",
 | 
			
		||||
        from_name="Sender Name")
 | 
			
		||||
    )
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    with open(options["markdown_template_path"], "r") as f:
 | 
			
		||||
        email_template_hash = hashlib.sha256(f.read().encode('utf-8')).hexdigest()[0:32]
 | 
			
		||||
    email_id = "zerver/emails/custom_email_%s" % (email_template_hash,)
 | 
			
		||||
    markdown_email_base_template_path = "templates/zerver/emails/custom_email_base.pre.html"
 | 
			
		||||
    html_source_template_path = "templates/%s.source.html" % (email_id,)
 | 
			
		||||
    plain_text_template_path = "templates/%s.txt" % (email_id,)
 | 
			
		||||
    subject_path = "templates/%s.subject.txt" % (email_id,)
 | 
			
		||||
 | 
			
		||||
    # First, we render the markdown input file just like our
 | 
			
		||||
    # user-facing docs with render_markdown_path.
 | 
			
		||||
    shutil.copyfile(options['markdown_template_path'], plain_text_template_path)
 | 
			
		||||
    rendered_input = render_markdown_path(plain_text_template_path.replace("templates/", ""))
 | 
			
		||||
 | 
			
		||||
    # And then extend it with our standard email headers.
 | 
			
		||||
    with open(html_source_template_path, "w") as f:
 | 
			
		||||
        with open(markdown_email_base_template_path, "r") as base_template:
 | 
			
		||||
            # Note that we're doing a hacky non-Jinja2 substitution here;
 | 
			
		||||
            # we do this because the normal render_markdown_path ordering
 | 
			
		||||
            # doesn't commute properly with inline-email-css.
 | 
			
		||||
            f.write(base_template.read().replace('{{ rendered_input }}',
 | 
			
		||||
                                                 rendered_input))
 | 
			
		||||
 | 
			
		||||
    with open(subject_path, "w") as f:
 | 
			
		||||
        f.write(options["subject"])
 | 
			
		||||
 | 
			
		||||
    # Then, we compile the email template using inline-email-css to
 | 
			
		||||
    # add our standard styling to the paragraph tags (etc.).
 | 
			
		||||
    #
 | 
			
		||||
    # TODO: Ideally, we'd just refactor inline-email-css to
 | 
			
		||||
    # compile this one template, not all of them.
 | 
			
		||||
    subprocess.check_call(["./scripts/setup/inline-email-css"])
 | 
			
		||||
 | 
			
		||||
    # Finally, we send the actual emails.
 | 
			
		||||
    for user_profile in users:
 | 
			
		||||
        context = {
 | 
			
		||||
            'realm_uri': user_profile.realm.uri,
 | 
			
		||||
            'realm_name': user_profile.realm.name,
 | 
			
		||||
        }
 | 
			
		||||
        send_email(email_id, to_user_ids=[user_profile.id],
 | 
			
		||||
                   from_address=FromAddress.SUPPORT,
 | 
			
		||||
                   from_name=options["from_name"], context=context)
 | 
			
		||||
 | 
			
		||||
class Command(ZulipBaseCommand):
 | 
			
		||||
    help = """Send email to specified email address."""
 | 
			
		||||
 | 
			
		||||
    def add_arguments(self, parser: ArgumentParser) -> None:
 | 
			
		||||
        parser.add_argument('--entire-server', action="store_true", default=False,
 | 
			
		||||
                            help="Send to every user on the server. ")
 | 
			
		||||
        parser.add_argument('--markdown-template-path', '--path',
 | 
			
		||||
                            dest='markdown_template_path',
 | 
			
		||||
                            required=True,
 | 
			
		||||
                            type=str,
 | 
			
		||||
                            help='Path to a markdown-format body for the email')
 | 
			
		||||
        parser.add_argument('--subject',
 | 
			
		||||
                            required=True,
 | 
			
		||||
                            type=str,
 | 
			
		||||
                            help='Subject line for the email')
 | 
			
		||||
        parser.add_argument('--from-name',
 | 
			
		||||
                            required=True,
 | 
			
		||||
                            type=str,
 | 
			
		||||
                            help='From line for the email')
 | 
			
		||||
 | 
			
		||||
        self.add_user_list_args(parser,
 | 
			
		||||
                                help="Email addresses of user(s) to send emails to.",
 | 
			
		||||
                                all_users_help="Send to every user on the realm.")
 | 
			
		||||
        self.add_realm_args(parser)
 | 
			
		||||
 | 
			
		||||
    def handle(self, *args: Any, **options: str) -> None:
 | 
			
		||||
        if options["entire_server"]:
 | 
			
		||||
            users = UserProfile.objects.filter(is_active=True, is_bot=False,
 | 
			
		||||
                                               is_mirror_dummy=False)
 | 
			
		||||
        else:
 | 
			
		||||
            realm = self.get_realm(options)
 | 
			
		||||
            try:
 | 
			
		||||
                users = self.get_users(options, realm, is_bot=False)
 | 
			
		||||
            except CommandError as error:
 | 
			
		||||
                if str(error) == "You have to pass either -u/--users or -a/--all-users.":
 | 
			
		||||
                    raise CommandError("You have to pass -u/--users or -a/--all-users or --entire-server.")
 | 
			
		||||
                raise error
 | 
			
		||||
 | 
			
		||||
        send_custom_email(users, options)
 | 
			
		||||
		Reference in New Issue
	
	Block a user