def do_send_missedmessage_events_reply_in_zulip(user_profile, missed_messages, message_count): # type: (UserProfile, List[Message], int) -> None """ Send a reminder email to a user if she's missed some PMs by being offline. The email will have its reply to address set to a limited used email address that will send a zulip message to the correct recipient. This allows the user to respond to missed PMs, huddles, and @-mentions directly from the email. `user_profile` is the user to send the reminder to `missed_messages` is a list of Message objects to remind about they should all have the same recipient and subject """ from zerver.context_processors import common_context # Disabled missedmessage emails internally if not user_profile.enable_offline_email_notifications: return recipients = set( (msg.recipient_id, msg.subject) for msg in missed_messages) if len(recipients) != 1: raise ValueError( 'All missed_messages must have the same recipient and subject %r' % recipients) unsubscribe_link = one_click_unsubscribe_link(user_profile, "missed_messages") context = common_context(user_profile) context.update({ 'name': user_profile.full_name, 'messages': build_message_list(user_profile, missed_messages), 'message_count': message_count, 'mention': missed_messages[0].recipient.type == Recipient.STREAM, 'unsubscribe_link': unsubscribe_link, }) # If this setting (email mirroring integration) is enabled, only then # can users reply to email to send message to Zulip. Thus, one must # ensure to display warning in the template. if settings.EMAIL_GATEWAY_PATTERN: context.update({ 'reply_warning': False, 'reply_to_zulip': True, }) else: context.update({ 'reply_warning': True, 'reply_to_zulip': False, }) from zerver.lib.email_mirror import create_missed_message_address reply_to_address = create_missed_message_address(user_profile, missed_messages[0]) if reply_to_address == FromAddress.NOREPLY: reply_to_name = None else: reply_to_name = "Zulip" senders = list(set(m.sender for m in missed_messages)) if (missed_messages[0].recipient.type == Recipient.HUDDLE): display_recipient = get_display_recipient(missed_messages[0].recipient) # Make sure that this is a list of strings, not a string. assert not isinstance(display_recipient, Text) other_recipients = [ r['full_name'] for r in display_recipient if r['id'] != user_profile.id ] context.update({'group_pm': True}) if len(other_recipients) == 2: huddle_display_name = u"%s" % (" and ".join(other_recipients)) context.update({'huddle_display_name': huddle_display_name}) elif len(other_recipients) == 3: huddle_display_name = u"%s, %s, and %s" % ( other_recipients[0], other_recipients[1], other_recipients[2]) context.update({'huddle_display_name': huddle_display_name}) else: huddle_display_name = u"%s, and %s others" % (', '.join( other_recipients[:2]), len(other_recipients) - 2) context.update({'huddle_display_name': huddle_display_name}) elif (missed_messages[0].recipient.type == Recipient.PERSONAL): context.update({'private_message': True}) else: # Keep only the senders who actually mentioned the user # # TODO: When we add wildcard mentions that send emails, add # them to the filter here. senders = list( set(m.sender for m in missed_messages if UserMessage.objects.filter( message=m, user_profile=user_profile, flags=UserMessage.flags.mentioned).exists())) context.update({'at_mention': True}) context.update({ 'sender_str': ", ".join(sender.full_name for sender in senders), 'realm_str': user_profile.realm.name, }) from_name, from_address = None, None if len(senders) == 1 and settings.SEND_MISSED_MESSAGE_EMAILS_AS_USER: # If this setting is enabled, you can reply to the Zulip # missed message emails directly back to the original sender. # However, one must ensure the Zulip server is in the SPF # record for the domain, or there will be spam/deliverability # problems. sender = senders[0] from_name, from_address = (sender.full_name, sender.email) context.update({ 'reply_warning': False, 'reply_to_zulip': False, }) email_dict = { 'template_prefix': 'zerver/emails/missed_message', 'to_email': display_email(user_profile), 'from_name': from_name, 'from_address': from_address, 'reply_to_email': formataddr((reply_to_name, reply_to_address)), 'context': context } queue_json_publish("missedmessage_email_senders", email_dict, send_email_from_dict) user_profile.last_reminder = timezone_now() user_profile.save(update_fields=['last_reminder'])
def handle_digest_email(user_profile_id, cutoff): # type: (int, float) -> None user_profile = UserProfile.objects.get(id=user_profile_id) # Convert from epoch seconds to a datetime object. cutoff_date = datetime.datetime.fromtimestamp(int(cutoff), tz=pytz.utc) all_messages = UserMessage.objects.filter( user_profile=user_profile, message__pub_date__gt=cutoff_date).order_by("message__pub_date") context = common_context(user_profile) # Start building email template data. context.update({ 'name': user_profile.full_name, 'unsubscribe_link': one_click_unsubscribe_link(user_profile, "digest") }) # Gather recent missed PMs, re-using the missed PM email logic. # You can't have an unread message that you sent, but when testing # this causes confusion so filter your messages out. pms = all_messages.filter(~Q(message__recipient__type=Recipient.STREAM) & ~Q(message__sender=user_profile)) # Show up to 4 missed PMs. pms_limit = 4 context['unread_pms'] = build_message_list( user_profile, [pm.message for pm in pms[:pms_limit]]) context['remaining_unread_pms_count'] = min(0, len(pms) - pms_limit) home_view_recipients = [ sub.recipient for sub in Subscription.objects.filter( user_profile=user_profile, active=True, in_home_view=True) ] stream_messages = all_messages.filter( message__recipient__type=Recipient.STREAM, message__recipient__in=home_view_recipients) # Gather hot conversations. context["hot_conversations"] = gather_hot_conversations( user_profile, stream_messages) # Gather new streams. new_streams_count, new_streams = gather_new_streams( user_profile, cutoff_date) context["new_streams"] = new_streams context["new_streams_count"] = new_streams_count # Gather users who signed up recently. new_users_count, new_users = gather_new_users(user_profile, cutoff_date) context["new_users"] = new_users # We don't want to send emails containing almost no information. if enough_traffic(context["unread_pms"], context["hot_conversations"], new_streams_count, new_users_count): logger.info("Sending digest email for %s" % (user_profile.email, )) # Send now, as a ScheduledJob send_future_email('zerver/emails/digest', display_email(user_profile), from_name="Zulip Digest", from_address=FromAddress.NOREPLY, context=context)
def do_send_missedmessage_events_reply_in_zulip(user_profile, missed_messages, message_count): # type: (UserProfile, List[Message], int) -> None """ Send a reminder email to a user if she's missed some PMs by being offline. The email will have its reply to address set to a limited used email address that will send a zulip message to the correct recipient. This allows the user to respond to missed PMs, huddles, and @-mentions directly from the email. `user_profile` is the user to send the reminder to `missed_messages` is a list of Message objects to remind about they should all have the same recipient and subject """ from zerver.context_processors import common_context # Disabled missedmessage emails internally if not user_profile.enable_offline_email_notifications: return recipients = set((msg.recipient_id, msg.subject) for msg in missed_messages) if len(recipients) != 1: raise ValueError( 'All missed_messages must have the same recipient and subject %r' % recipients ) unsubscribe_link = one_click_unsubscribe_link(user_profile, "missed_messages") context = common_context(user_profile) context.update({ 'name': user_profile.full_name, 'messages': build_message_list(user_profile, missed_messages), 'message_count': message_count, 'mention': missed_messages[0].recipient.type == Recipient.STREAM, 'unsubscribe_link': unsubscribe_link, }) # If this setting (email mirroring integration) is enabled, only then # can users reply to email to send message to Zulip. Thus, one must # ensure to display warning in the template. if settings.EMAIL_GATEWAY_PATTERN: context.update({ 'reply_warning': False, 'reply_to_zulip': True, }) else: context.update({ 'reply_warning': True, 'reply_to_zulip': False, }) from zerver.lib.email_mirror import create_missed_message_address address = create_missed_message_address(user_profile, missed_messages[0]) senders = list(set(m.sender for m in missed_messages)) if (missed_messages[0].recipient.type == Recipient.HUDDLE): display_recipient = get_display_recipient(missed_messages[0].recipient) # Make sure that this is a list of strings, not a string. assert not isinstance(display_recipient, Text) other_recipients = [r['full_name'] for r in display_recipient if r['id'] != user_profile.id] context.update({'group_pm': True}) if len(other_recipients) == 2: huddle_display_name = u"%s" % (" and ".join(other_recipients)) context.update({'huddle_display_name': huddle_display_name}) elif len(other_recipients) == 3: huddle_display_name = u"%s, %s, and %s" % (other_recipients[0], other_recipients[1], other_recipients[2]) context.update({'huddle_display_name': huddle_display_name}) else: huddle_display_name = u"%s, and %s others" % (', '.join(other_recipients[:2]), len(other_recipients) - 2) context.update({'huddle_display_name': huddle_display_name}) elif (missed_messages[0].recipient.type == Recipient.PERSONAL): context.update({'private_message': True}) else: # Keep only the senders who actually mentioned the user # # TODO: When we add wildcard mentions that send emails, add # them to the filter here. senders = list(set(m.sender for m in missed_messages if UserMessage.objects.filter(message=m, user_profile=user_profile, flags=UserMessage.flags.mentioned).exists())) context.update({'at_mention': True}) context.update({ 'sender_str': ", ".join(sender.full_name for sender in senders), 'realm_str': user_profile.realm.name, }) from_email = None if len(senders) == 1 and settings.SEND_MISSED_MESSAGE_EMAILS_AS_USER: # If this setting is enabled, you can reply to the Zulip # missed message emails directly back to the original sender. # However, one must ensure the Zulip server is in the SPF # record for the domain, or there will be spam/deliverability # problems. sender = senders[0] from_email = '"%s" <%s>' % (sender.full_name, sender.email) context.update({ 'reply_warning': False, 'reply_to_zulip': False, }) email_dict = { 'template_prefix': 'zerver/emails/missed_message', 'to_email': display_email(user_profile), 'from_email': from_email, 'reply_to_email': address, 'context': context} queue_json_publish("missedmessage_email_senders", email_dict, send_email_from_dict) user_profile.last_reminder = timezone_now() user_profile.save(update_fields=['last_reminder'])
def do_send_missedmessage_events_reply_in_zulip(user_profile, missed_messages, message_count): # type: (UserProfile, List[Message], int) -> None """ Send a reminder email to a user if she's missed some PMs by being offline. The email will have its reply to address set to a limited used email address that will send a zulip message to the correct recipient. This allows the user to respond to missed PMs, huddles, and @-mentions directly from the email. `user_profile` is the user to send the reminder to `missed_messages` is a list of Message objects to remind about they should all have the same recipient and subject """ from zerver.context_processors import common_context # Disabled missedmessage emails internally if not user_profile.enable_offline_email_notifications: return recipients = set( (msg.recipient_id, msg.subject) for msg in missed_messages) if len(recipients) != 1: raise ValueError( 'All missed_messages must have the same recipient and subject %r' % recipients) unsubscribe_link = one_click_unsubscribe_link(user_profile, "missed_messages") context = common_context(user_profile) context.update({ 'name': user_profile.full_name, 'messages': build_message_list(user_profile, missed_messages), 'message_count': message_count, 'mention': missed_messages[0].recipient.type == Recipient.STREAM, 'unsubscribe_link': unsubscribe_link, }) # If this setting (email mirroring integration) is enabled, only then # can users reply to email to send message to Zulip. Thus, one must # ensure to display warning in the template. if settings.EMAIL_GATEWAY_PATTERN: context.update({ 'reply_warning': False, 'reply_to_zulip': True, }) else: context.update({ 'reply_warning': True, 'reply_to_zulip': False, }) from zerver.lib.email_mirror import create_missed_message_address address = create_missed_message_address(user_profile, missed_messages[0]) senders = set(m.sender.full_name for m in missed_messages) sender_str = ", ".join(senders) context.update({ 'sender_str': sender_str, }) from_email = None if len(senders) == 1 and settings.SEND_MISSED_MESSAGE_EMAILS_AS_USER: # If this setting is enabled, you can reply to the Zulip # missed message emails directly back to the original sender. # However, one must ensure the Zulip server is in the SPF # record for the domain, or there will be spam/deliverability # problems. sender = missed_messages[0].sender from_email = '"%s" <%s>' % (sender_str, sender.email) context.update({ 'reply_warning': False, 'reply_to_zulip': False, }) email_dict = { 'template_prefix': 'zerver/emails/missed_message', 'to_email': display_email(user_profile), 'from_email': from_email, 'reply_to_email': address, 'context': context } queue_json_publish("missedmessage_email_senders", email_dict, send_email_from_dict) user_profile.last_reminder = timezone_now() user_profile.save(update_fields=['last_reminder'])
def handle_digest_email(user_profile_id, cutoff): # type: (int, float) -> None user_profile = UserProfile.objects.get(id=user_profile_id) # Convert from epoch seconds to a datetime object. cutoff_date = datetime.datetime.fromtimestamp(int(cutoff), tz=pytz.utc) all_messages = UserMessage.objects.filter( user_profile=user_profile, message__pub_date__gt=cutoff_date).order_by("message__pub_date") context = common_context(user_profile) # Start building email template data. context.update({ 'name': user_profile.full_name, 'unsubscribe_link': one_click_unsubscribe_link(user_profile, "digest") }) # Gather recent missed PMs, re-using the missed PM email logic. # You can't have an unread message that you sent, but when testing # this causes confusion so filter your messages out. pms = all_messages.filter( ~Q(message__recipient__type=Recipient.STREAM) & ~Q(message__sender=user_profile)) # Show up to 4 missed PMs. pms_limit = 4 context['unread_pms'] = build_message_list( user_profile, [pm.message for pm in pms[:pms_limit]]) context['remaining_unread_pms_count'] = min(0, len(pms) - pms_limit) home_view_recipients = [sub.recipient for sub in Subscription.objects.filter( user_profile=user_profile, active=True, in_home_view=True)] stream_messages = all_messages.filter( message__recipient__type=Recipient.STREAM, message__recipient__in=home_view_recipients) # Gather hot conversations. context["hot_conversations"] = gather_hot_conversations( user_profile, stream_messages) # Gather new streams. new_streams_count, new_streams = gather_new_streams( user_profile, cutoff_date) context["new_streams"] = new_streams context["new_streams_count"] = new_streams_count # Gather users who signed up recently. new_users_count, new_users = gather_new_users( user_profile, cutoff_date) context["new_users"] = new_users # We don't want to send emails containing almost no information. if enough_traffic(context["unread_pms"], context["hot_conversations"], new_streams_count, new_users_count): logger.info("Sending digest email for %s" % (user_profile.email,)) # Send now, as a ScheduledJob send_future_email('zerver/emails/digest', display_email(user_profile), context=context)