diff --git a/templates/zerver/emails/onboarding_team_to_zulip.html b/templates/zerver/emails/onboarding_team_to_zulip.html new file mode 100644 index 0000000000..bfbe08c869 --- /dev/null +++ b/templates/zerver/emails/onboarding_team_to_zulip.html @@ -0,0 +1,40 @@ +{% extends "zerver/emails/email_base_default.html" %} + +{% block illustration %} + +{% endblock %} + +{% block content %} + +

+ {% trans %}If you've already decided to use Zulip for your organization, welcome! You can use our guide for setting up your organization to get started.{% endtrans %} +

+

+ {% trans %}Otherwise, here is some advice we often hear from customers for evaluating any team chat product:{% endtrans %} +

    +
  1. {% trans %}Invite your teammates to explore with you and share their unique perspectives.{% endtrans %} + {% trans %}Use the app itself to chat about your impressions.{% endtrans %} +
  2. +
  3. {% trans %}Run a week-long trial with your team, without using any other chat tools. This is the only way to truly experience how a new chat app will help your team communicate.{% endtrans %} +
  4. +
+

+

+ {% trans %}Zulip is designed to enable efficient communication, and we hope these tips help your team experience it in action.{% endtrans %} +

+ +

+ {% if corporate_enabled %} + {{macros.contact_us_zulip_cloud(support_email)}} + {% else %} + {{macros.contact_us_self_hosted(support_email)}} + {% endif %} +

+ +{% endblock %} + +{% block manage_preferences %} + +

{% trans %}Unsubscribe from welcome emails for {{ realm_name }}{% endtrans %}

+ +{% endblock %} diff --git a/templates/zerver/emails/onboarding_team_to_zulip.subject.txt b/templates/zerver/emails/onboarding_team_to_zulip.subject.txt new file mode 100644 index 0000000000..45396f9697 --- /dev/null +++ b/templates/zerver/emails/onboarding_team_to_zulip.subject.txt @@ -0,0 +1 @@ +{{ _("Choosing the chat app for your team") }} diff --git a/templates/zerver/emails/onboarding_team_to_zulip.txt b/templates/zerver/emails/onboarding_team_to_zulip.txt new file mode 100644 index 0000000000..8450f101f2 --- /dev/null +++ b/templates/zerver/emails/onboarding_team_to_zulip.txt @@ -0,0 +1,19 @@ +{% trans %}If you've already decided to use Zulip for your organization, welcome! You can use our guide for setting up your organization to get started.{% endtrans %} [ {{ get_organization_started }} ] + +{% trans %}Otherwise, here is some advice we often hear from customers for evaluating any team chat product:{% endtrans %} + + +1. {% trans %}Invite your teammates to explore with you and share their unique perspectives.{% endtrans %} [ {{ invite_users }} ] {% trans %}Use the app itself to chat about your impressions.{% endtrans %} + +2. {% trans %}Run a week-long trial with your team, without using any other chat tools. This is the only way to truly experience how a new chat app will help your team communicate.{% endtrans %} [ {{ trying_out_zulip }} ] + +{% trans %}Zulip is designed to enable efficient communication, and we hope these tips help your team experience it in action.{% endtrans %} [ {{ why_zulip }} ] + +{% if corporate_enabled %} + {% trans %}Do you have questions or feedback to share? Contact us at {{ support_email }} — we'd love to help!{% endtrans %} +{% else %} + {% trans %}If you have any questions, please contact this Zulip server's administrators at {{ support_email }}.{% endtrans %} +{% endif %} + +---- +{% trans %}Unsubscribe from welcome emails for {{ realm_name }}{% endtrans %}: {{ unsubscribe_link }} diff --git a/zerver/lib/email_notifications.py b/zerver/lib/email_notifications.py index 74a6c60279..8dfcec7ca7 100644 --- a/zerver/lib/email_notifications.py +++ b/zerver/lib/email_notifications.py @@ -682,6 +682,7 @@ def get_onboarding_email_schedule(user: UserProfile) -> Dict[str, timedelta]: # or comes in while they are dealing with their inbox. "onboarding_zulip_topics": timedelta(days=2, hours=-1), "onboarding_zulip_guide": timedelta(days=4, hours=-1), + "onboarding_team_to_zulip": timedelta(days=6, hours=-1), } user_tz = user.timezone @@ -693,17 +694,24 @@ def get_onboarding_email_schedule(user: UserProfile) -> Dict[str, timedelta]: # -Do not send emails on Saturday or Sunday # -Have at least one weekday between each (potential) email + # User signed up on Monday + if signup_day == 1: + # Send onboarding_team_to_zulip on Tuesday + onboarding_emails["onboarding_team_to_zulip"] = timedelta(days=8, hours=-1) + # User signed up on Tuesday if signup_day == 2: - # Send onboarding_zulip_topics on Thursday # Send onboarding_zulip_guide on Monday onboarding_emails["onboarding_zulip_guide"] = timedelta(days=6, hours=-1) + # Send onboarding_team_to_zulip on Wednesday + onboarding_emails["onboarding_team_to_zulip"] = timedelta(days=8, hours=-1) # User signed up on Wednesday if signup_day == 3: - # Send onboarding_zulip_topics on Friday # Send onboarding_zulip_guide on Tuesday onboarding_emails["onboarding_zulip_guide"] = timedelta(days=6, hours=-1) + # Send onboarding_team_to_zulip on Thursday + onboarding_emails["onboarding_team_to_zulip"] = timedelta(days=8, hours=-1) # User signed up on Thursday if signup_day == 4: @@ -711,6 +719,8 @@ def get_onboarding_email_schedule(user: UserProfile) -> Dict[str, timedelta]: onboarding_emails["onboarding_zulip_topics"] = timedelta(days=4, hours=-1) # Send onboarding_zulip_guide on Wednesday onboarding_emails["onboarding_zulip_guide"] = timedelta(days=6, hours=-1) + # Send onboarding_team_to_zulip on Friday + onboarding_emails["onboarding_team_to_zulip"] = timedelta(days=8, hours=-1) # User signed up on Friday if signup_day == 5: @@ -718,6 +728,15 @@ def get_onboarding_email_schedule(user: UserProfile) -> Dict[str, timedelta]: onboarding_emails["onboarding_zulip_topics"] = timedelta(days=4, hours=-1) # Send onboarding_zulip_guide on Thursday onboarding_emails["onboarding_zulip_guide"] = timedelta(days=6, hours=-1) + # Send onboarding_team_to_zulip on Monday + onboarding_emails["onboarding_team_to_zulip"] = timedelta(days=10, hours=-1) + + # User signed up on Saturday; no adjustments needed + + # User signed up on Sunday + if signup_day == 7: + # Send onboarding_team_to_zulip on Monday + onboarding_emails["onboarding_team_to_zulip"] = timedelta(days=8, hours=-1) return onboarding_emails @@ -881,6 +900,28 @@ def enqueue_welcome_emails(user: UserProfile, realm_creation: bool = False) -> N delay=onboarding_email_schedule["onboarding_zulip_guide"], ) + # We only send the onboarding_team_to_zulip email to user who created the organization. + if realm_creation: + onboarding_team_to_zulip_context = common_context(user) + onboarding_team_to_zulip_context.update( + unsubscribe_link=unsubscribe_link, + get_organization_started=realm_url + + "/help/getting-your-organization-started-with-zulip", + invite_users=realm_url + "/help/invite-users-to-join", + trying_out_zulip=realm_url + "/help/trying-out-zulip", + why_zulip="https://zulip.com/why-zulip/", + ) + + send_future_email( + "zerver/emails/onboarding_team_to_zulip", + user.realm, + to_user_ids=[user.id], + from_name=from_name, + from_address=from_address, + context=onboarding_team_to_zulip_context, + delay=onboarding_email_schedule["onboarding_team_to_zulip"], + ) + def convert_html_to_markdown(html: str) -> str: # html2text is GPL licensed, so run it as a subprocess. diff --git a/zerver/models.py b/zerver/models.py index d8128d8e45..6c4ebbd158 100644 --- a/zerver/models.py +++ b/zerver/models.py @@ -4635,6 +4635,7 @@ EMAIL_TYPES = { "account_registered": ScheduledEmail.WELCOME, "onboarding_zulip_topics": ScheduledEmail.WELCOME, "onboarding_zulip_guide": ScheduledEmail.WELCOME, + "onboarding_team_to_zulip": ScheduledEmail.WELCOME, "digest": ScheduledEmail.DIGEST, "invitation_reminder": ScheduledEmail.INVITATION_REMINDER, } diff --git a/zerver/tests/test_email_log.py b/zerver/tests/test_email_log.py index 0e2bdce2eb..3dc6ae9954 100644 --- a/zerver/tests/test_email_log.py +++ b/zerver/tests/test_email_log.py @@ -26,7 +26,7 @@ class EmailLogTest(ZulipTestCase): output_log = ( "INFO:root:Emails sent in development are available at http://testserver/emails" ) - self.assertEqual(m.output, [output_log for i in range(17)]) + self.assertEqual(m.output, [output_log for i in range(18)]) def test_forward_address_details(self) -> None: try: diff --git a/zerver/tests/test_email_notifications.py b/zerver/tests/test_email_notifications.py index 5a85061c23..bcf3ace6d6 100644 --- a/zerver/tests/test_email_notifications.py +++ b/zerver/tests/test_email_notifications.py @@ -482,12 +482,12 @@ class TestFollowupEmails(ZulipTestCase): def test_followup_emails_for_regular_realms(self) -> None: cordelia = self.example_user("cordelia") send_account_registered_email(self.example_user("cordelia"), realm_creation=True) - enqueue_welcome_emails(self.example_user("cordelia")) + enqueue_welcome_emails(self.example_user("cordelia"), realm_creation=True) scheduled_emails = ScheduledEmail.objects.filter(users=cordelia).order_by( "scheduled_timestamp" ) assert scheduled_emails is not None - self.assert_length(scheduled_emails, 2) + self.assert_length(scheduled_emails, 3) self.assertEqual( orjson.loads(scheduled_emails[0].data)["template_prefix"], "zerver/emails/account_registered", @@ -496,6 +496,10 @@ class TestFollowupEmails(ZulipTestCase): orjson.loads(scheduled_emails[1].data)["template_prefix"], "zerver/emails/onboarding_zulip_guide", ) + self.assertEqual( + orjson.loads(scheduled_emails[2].data)["template_prefix"], + "zerver/emails/onboarding_team_to_zulip", + ) deliver_scheduled_emails(scheduled_emails[0]) from django.core.mail import outbox @@ -513,12 +517,12 @@ class TestFollowupEmails(ZulipTestCase): ) cordelia.realm.save() send_account_registered_email(self.example_user("cordelia"), realm_creation=True) - enqueue_welcome_emails(self.example_user("cordelia")) + enqueue_welcome_emails(self.example_user("cordelia"), realm_creation=True) scheduled_emails = ScheduledEmail.objects.filter(users=cordelia).order_by( "scheduled_timestamp" ) assert scheduled_emails is not None - self.assert_length(scheduled_emails, 2) + self.assert_length(scheduled_emails, 3) self.assertEqual( orjson.loads(scheduled_emails[0].data)["template_prefix"], "zerver/emails/account_registered", @@ -527,6 +531,10 @@ class TestFollowupEmails(ZulipTestCase): orjson.loads(scheduled_emails[1].data)["template_prefix"], "zerver/emails/onboarding_zulip_guide", ) + self.assertEqual( + orjson.loads(scheduled_emails[2].data)["template_prefix"], + "zerver/emails/onboarding_team_to_zulip", + ) deliver_scheduled_emails(scheduled_emails[0]) from django.core.mail import outbox @@ -562,6 +570,7 @@ class TestOnboardingEmailDelay(ZulipTestCase): date_joined: str, onboarding_zulip_topics: int, onboarding_zulip_guide: int, + onboarding_team_to_zulip: int, ) -> None: DAY_OF_WEEK = { "Monday": datetime(2018, 1, 1, 1, 0, 0, 0, tzinfo=timezone.utc), @@ -591,35 +600,43 @@ class TestOnboardingEmailDelay(ZulipTestCase): self.assertEqual(day_sent, onboarding_zulip_guide) self.assertNotIn(day_sent, WEEKEND) + # onboarding_team_to_zulip + day_sent = ( + DAY_OF_WEEK[date_joined] + onboarding_email_schedule["onboarding_team_to_zulip"] + ).isoweekday() + self.assertEqual(day_sent, onboarding_team_to_zulip) + self.assertNotIn(day_sent, WEEKEND) + def test_get_onboarding_email_schedule(self) -> None: user_profile = self.example_user("hamlet") - # joined Monday: schedule = Wednesday:3, Friday:5, - self.verify_onboarding_email_schedule(user_profile, "Monday", 3, 5) + # joined Monday: schedule = Wednesday:3, Friday:5, Tuesday:2 + self.verify_onboarding_email_schedule(user_profile, "Monday", 3, 5, 2) - # joined Tuesday: schedule = Thursday:4, Monday:1 - self.verify_onboarding_email_schedule(user_profile, "Tuesday", 4, 1) + # joined Tuesday: schedule = Thursday:4, Monday:1, Wednesday:3 + self.verify_onboarding_email_schedule(user_profile, "Tuesday", 4, 1, 3) - # joined Wednesday: schedule = Friday:5, Tuesday:2 - self.verify_onboarding_email_schedule(user_profile, "Wednesday", 5, 2) + # joined Wednesday: schedule = Friday:5, Tuesday:2, Thursday:4 + self.verify_onboarding_email_schedule(user_profile, "Wednesday", 5, 2, 4) - # joined Thursday: schedule = Monday:1, Wednesday:3 - self.verify_onboarding_email_schedule(user_profile, "Thursday", 1, 3) + # joined Thursday: schedule = Monday:1, Wednesday:3, Friday:5 + self.verify_onboarding_email_schedule(user_profile, "Thursday", 1, 3, 5) - # joined Friday: schedule = Tuesday:2, Thursday:4 - self.verify_onboarding_email_schedule(user_profile, "Friday", 2, 4) + # joined Friday: schedule = Tuesday:2, Thursday:4, Monday:1 + self.verify_onboarding_email_schedule(user_profile, "Friday", 2, 4, 1) - # joined Saturday: schedule = Monday:1, Wednesday:3 - self.verify_onboarding_email_schedule(user_profile, "Saturday", 1, 3) + # joined Saturday: schedule = Monday:1, Wednesday:3, Friday:5 + self.verify_onboarding_email_schedule(user_profile, "Saturday", 1, 3, 5) - # joined Sunday: schedule = Tuesday:2, Thursday:4 - self.verify_onboarding_email_schedule(user_profile, "Sunday", 2, 4) + # joined Sunday: schedule = Tuesday:2, Thursday:4, Monday:1 + self.verify_onboarding_email_schedule(user_profile, "Sunday", 2, 4, 1) def test_time_offset_for_onboarding_email_schedule(self) -> None: user_profile = self.example_user("hamlet") days_delayed = { "4": timedelta(days=4, hours=-1), "6": timedelta(days=6, hours=-1), + "8": timedelta(days=8, hours=-1), } # Time offset of America/Phoenix is -07:00 @@ -641,6 +658,12 @@ class TestOnboardingEmailDelay(ZulipTestCase): days_delayed["6"], ) + # onboarding_team_to_zulip sent on Friday + self.assertEqual( + onboarding_email_schedule["onboarding_team_to_zulip"], + days_delayed["8"], + ) + class TestCustomWelcomeEmailSender(ZulipTestCase): def test_custom_welcome_email_sender(self) -> None: diff --git a/zerver/tests/test_example.py b/zerver/tests/test_example.py index 1d33724b48..3471b817bd 100644 --- a/zerver/tests/test_example.py +++ b/zerver/tests/test_example.py @@ -402,7 +402,7 @@ class TestDevelopmentEmailsLog(ZulipTestCase): "INFO:root:Emails sent in development are available at http://testserver/emails" ) # logger.output is a list of all the log messages captured. Verify it is as expected. - self.assertEqual(logger.output, [output_log] * 17) + self.assertEqual(logger.output, [output_log] * 18) # Now, lets actually go the URL the above call redirects to, i.e., /emails/ result = self.client_get(result["Location"]) diff --git a/zerver/views/development/email_log.py b/zerver/views/development/email_log.py index 40560c6eae..72f757a061 100644 --- a/zerver/views/development/email_log.py +++ b/zerver/views/development/email_log.py @@ -147,7 +147,7 @@ def generate_all_emails(request: HttpRequest) -> HttpResponse: send_account_registered_email(get_user_by_delivery_email("iago@zulip.com", realm)) # Onboarding emails for admin user - enqueue_welcome_emails(get_user_by_delivery_email("iago@zulip.com", realm)) + enqueue_welcome_emails(get_user_by_delivery_email("iago@zulip.com", realm), realm_creation=True) # Realm reactivation email do_send_realm_reactivation_email(realm, acting_user=None)