Change e-mail notifications to only be sent when recipient is offline (#17984)
* Change e-mail notifications to only be sent when recipient is offline Change the default for follow and mention notifications back on * Add preference to always send e-mail notifications * Change wording
This commit is contained in:
		@@ -54,7 +54,8 @@ class Settings::PreferencesController < Settings::BaseController
 | 
			
		||||
      :setting_use_pending_items,
 | 
			
		||||
      :setting_trends,
 | 
			
		||||
      :setting_crop_images,
 | 
			
		||||
      notification_emails: %i(follow follow_request reblog favourite mention digest report pending_account trending_tag),
 | 
			
		||||
      :setting_always_send_emails,
 | 
			
		||||
      notification_emails: %i(follow follow_request reblog favourite mention digest report pending_account trending_tag appeal),
 | 
			
		||||
      interactions: %i(must_be_follower must_be_following must_be_following_dm)
 | 
			
		||||
    )
 | 
			
		||||
  end
 | 
			
		||||
 
 | 
			
		||||
@@ -38,6 +38,7 @@ class UserSettingsDecorator
 | 
			
		||||
    user.settings['use_pending_items']   = use_pending_items_preference if change?('setting_use_pending_items')
 | 
			
		||||
    user.settings['trends']              = trends_preference if change?('setting_trends')
 | 
			
		||||
    user.settings['crop_images']         = crop_images_preference if change?('setting_crop_images')
 | 
			
		||||
    user.settings['always_send_emails']  = always_send_emails_preference if change?('setting_always_send_emails')
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def merged_notification_emails
 | 
			
		||||
@@ -132,6 +133,10 @@ class UserSettingsDecorator
 | 
			
		||||
    boolean_cast_setting 'setting_crop_images'
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def always_send_emails_preference
 | 
			
		||||
    boolean_cast_setting 'setting_always_send_emails'
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def boolean_cast_setting(key)
 | 
			
		||||
    ActiveModel::Type::Boolean.new.cast(settings[key])
 | 
			
		||||
  end
 | 
			
		||||
 
 | 
			
		||||
@@ -130,7 +130,7 @@ class User < ApplicationRecord
 | 
			
		||||
           :reduce_motion, :system_font_ui, :noindex, :theme, :display_media,
 | 
			
		||||
           :expand_spoilers, :default_language, :aggregate_reblogs, :show_application,
 | 
			
		||||
           :advanced_layout, :use_blurhash, :use_pending_items, :trends, :crop_images,
 | 
			
		||||
           :disable_swiping,
 | 
			
		||||
           :disable_swiping, :always_send_emails,
 | 
			
		||||
           to: :settings, prefix: :setting, allow_nil: false
 | 
			
		||||
 | 
			
		||||
  attr_reader :invite_code
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,8 @@
 | 
			
		||||
# frozen_string_literal: true
 | 
			
		||||
 | 
			
		||||
class NotifyService < BaseService
 | 
			
		||||
  include Redisable
 | 
			
		||||
 | 
			
		||||
  def call(recipient, type, activity)
 | 
			
		||||
    @recipient    = recipient
 | 
			
		||||
    @activity     = activity
 | 
			
		||||
@@ -8,10 +10,15 @@ class NotifyService < BaseService
 | 
			
		||||
 | 
			
		||||
    return if recipient.user.nil? || blocked?
 | 
			
		||||
 | 
			
		||||
    create_notification!
 | 
			
		||||
    @notification.save!
 | 
			
		||||
 | 
			
		||||
    # It's possible the underlying activity has been deleted
 | 
			
		||||
    # between the save call and now
 | 
			
		||||
    return if @notification.activity.nil?
 | 
			
		||||
 | 
			
		||||
    push_notification!
 | 
			
		||||
    push_to_conversation! if direct_message?
 | 
			
		||||
    send_email! if email_enabled?
 | 
			
		||||
    send_email! if email_needed?
 | 
			
		||||
  rescue ActiveRecord::RecordInvalid
 | 
			
		||||
    nil
 | 
			
		||||
  end
 | 
			
		||||
@@ -92,8 +99,8 @@ class NotifyService < BaseService
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def blocked?
 | 
			
		||||
    blocked   = @recipient.suspended?                            # Skip if the recipient account is suspended anyway
 | 
			
		||||
    blocked ||= from_self? && @notification.type != :poll        # Skip for interactions with self
 | 
			
		||||
    blocked   = @recipient.suspended?
 | 
			
		||||
    blocked ||= from_self? && @notification.type != :poll
 | 
			
		||||
 | 
			
		||||
    return blocked if message? && from_staff?
 | 
			
		||||
 | 
			
		||||
@@ -117,38 +124,52 @@ class NotifyService < BaseService
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def create_notification!
 | 
			
		||||
    @notification.save!
 | 
			
		||||
  def push_notification!
 | 
			
		||||
    push_to_streaming_api! if subscribed_to_streaming_api?
 | 
			
		||||
    push_to_web_push_subscriptions!
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def push_notification!
 | 
			
		||||
    return if @notification.activity.nil?
 | 
			
		||||
  def push_to_streaming_api!
 | 
			
		||||
    redis.publish("timeline:#{@recipient.id}:notifications", Oj.dump(event: :notification, payload: InlineRenderer.render(@notification, @recipient, :notification)))
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
    Redis.current.publish("timeline:#{@recipient.id}:notifications", Oj.dump(event: :notification, payload: InlineRenderer.render(@notification, @recipient, :notification)))
 | 
			
		||||
    send_push_notifications!
 | 
			
		||||
  def subscribed_to_streaming_api?
 | 
			
		||||
    redis.exists?("subscribed:timeline:#{@recipient.id}") || redis.exists?("subscribed:timeline:#{@recipient.id}:notifications")
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def push_to_conversation!
 | 
			
		||||
    return if @notification.activity.nil?
 | 
			
		||||
    AccountConversation.add_status(@recipient, @notification.target_status)
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def send_push_notifications!
 | 
			
		||||
    subscriptions_ids = ::Web::PushSubscription.where(user_id: @recipient.user.id)
 | 
			
		||||
                                               .select { |subscription| subscription.pushable?(@notification) }
 | 
			
		||||
                                               .map(&:id)
 | 
			
		||||
  def push_to_web_push_subscriptions!
 | 
			
		||||
    ::Web::PushNotificationWorker.push_bulk(web_push_subscriptions.select { |subscription| subscription.pushable?(@notification) }) { |subscription| [subscription.id, @notification.id] }
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
    ::Web::PushNotificationWorker.push_bulk(subscriptions_ids) do |subscription_id|
 | 
			
		||||
      [subscription_id, @notification.id]
 | 
			
		||||
    end
 | 
			
		||||
  def web_push_subscriptions
 | 
			
		||||
    @web_push_subscriptions ||= ::Web::PushSubscription.where(user_id: @recipient.user.id).to_a
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def subscribed_to_web_push?
 | 
			
		||||
    web_push_subscriptions.any?
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def send_email!
 | 
			
		||||
    return if @notification.activity.nil?
 | 
			
		||||
    NotificationMailer.public_send(@notification.type, @recipient, @notification).deliver_later(wait: 2.minutes)
 | 
			
		||||
    NotificationMailer.public_send(@notification.type, @recipient, @notification).deliver_later(wait: 2.minutes) if NotificationMailer.respond_to?(@notification.type)
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def email_enabled?
 | 
			
		||||
  def email_needed?
 | 
			
		||||
    (!recipient_online? || always_send_emails?) && send_email_for_notification_type?
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def recipient_online?
 | 
			
		||||
    subscribed_to_streaming_api? || subscribed_to_web_push?
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def always_send_emails?
 | 
			
		||||
    @recipient.user.settings.always_send_emails
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def send_email_for_notification_type?
 | 
			
		||||
    @recipient.user.settings.notification_emails[@notification.type.to_s]
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
 
 | 
			
		||||
@@ -25,6 +25,9 @@
 | 
			
		||||
        = ff.input :pending_account, as: :boolean, wrapper: :with_label
 | 
			
		||||
        = ff.input :trending_tag, as: :boolean, wrapper: :with_label
 | 
			
		||||
 | 
			
		||||
  .fields-group
 | 
			
		||||
    = f.input :setting_always_send_emails, as: :boolean, wrapper: :with_label
 | 
			
		||||
 | 
			
		||||
  .fields-group
 | 
			
		||||
    = f.simple_fields_for :notification_emails, hash_to_object(current_user.settings.notification_emails) do |ff|
 | 
			
		||||
      = ff.input :digest, as: :boolean, wrapper: :with_label
 | 
			
		||||
 
 | 
			
		||||
@@ -49,6 +49,7 @@ en:
 | 
			
		||||
        phrase: Will be matched regardless of casing in text or content warning of a post
 | 
			
		||||
        scopes: Which APIs the application will be allowed to access. If you select a top-level scope, you don't need to select individual ones.
 | 
			
		||||
        setting_aggregate_reblogs: Do not show new boosts for posts that have been recently boosted (only affects newly-received boosts)
 | 
			
		||||
        setting_always_send_emails: Normally e-mail notifications won't be sent when you are actively using Mastodon
 | 
			
		||||
        setting_default_sensitive: Sensitive media is hidden by default and can be revealed with a click
 | 
			
		||||
        setting_display_media_default: Hide media marked as sensitive
 | 
			
		||||
        setting_display_media_hide_all: Always hide media
 | 
			
		||||
@@ -151,6 +152,7 @@ en:
 | 
			
		||||
        phrase: Keyword or phrase
 | 
			
		||||
        setting_advanced_layout: Enable advanced web interface
 | 
			
		||||
        setting_aggregate_reblogs: Group boosts in timelines
 | 
			
		||||
        setting_always_send_emails: Always send e-mail notifications
 | 
			
		||||
        setting_auto_play_gif: Auto-play animated GIFs
 | 
			
		||||
        setting_boost_modal: Show confirmation dialog before boosting
 | 
			
		||||
        setting_crop_images: Crop images in non-expanded posts to 16x9
 | 
			
		||||
 
 | 
			
		||||
@@ -38,16 +38,17 @@ defaults: &defaults
 | 
			
		||||
  trendable_by_default: false
 | 
			
		||||
  crop_images: true
 | 
			
		||||
  notification_emails:
 | 
			
		||||
    follow: false
 | 
			
		||||
    follow: true
 | 
			
		||||
    reblog: false
 | 
			
		||||
    favourite: false
 | 
			
		||||
    mention: false
 | 
			
		||||
    mention: true
 | 
			
		||||
    follow_request: true
 | 
			
		||||
    digest: true
 | 
			
		||||
    report: true
 | 
			
		||||
    pending_account: true
 | 
			
		||||
    trending_tag: true
 | 
			
		||||
    appeal: true
 | 
			
		||||
  always_send_emails: false
 | 
			
		||||
  interactions:
 | 
			
		||||
    must_be_follower: false
 | 
			
		||||
    must_be_following: false
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user