diff --git a/app/jobs/regular/notify_mailing_list_subscribers.rb b/app/jobs/regular/notify_mailing_list_subscribers.rb
index 540b472c233..c273ce0e47b 100644
--- a/app/jobs/regular/notify_mailing_list_subscribers.rb
+++ b/app/jobs/regular/notify_mailing_list_subscribers.rb
@@ -35,8 +35,19 @@ module Jobs
users.each do |user|
if Guardian.new(user).can_see?(post)
begin
- message = UserNotifications.mailing_list_notify(user, post)
- Email::Sender.new(message, :mailing_list, user).send if message
+ if EmailLog.reached_max_emails?(user)
+ EmailLog.create!(
+ email_type: 'mailing_list',
+ to_address: user.email,
+ user_id: user.id,
+ post_id: post.id,
+ skipped: true,
+ skipped_reason: "[MailingList] #{I18n.t('email_log.exceeded_limit')}"
+ )
+ else
+ message = UserNotifications.mailing_list_notify(user, post)
+ Email::Sender.new(message, :mailing_list, user).send if message
+ end
rescue => e
Discourse.handle_job_exception(e, error_context(args, "Sending post to mailing list subscribers", { user_id: user.id, user_email: user.email }))
end
diff --git a/app/jobs/regular/user_email.rb b/app/jobs/regular/user_email.rb
index ca7b73dc710..160629dfefe 100644
--- a/app/jobs/regular/user_email.rb
+++ b/app/jobs/regular/user_email.rb
@@ -113,6 +113,10 @@ module Jobs
email_args[:new_email] = user.email
end
+ if EmailLog.reached_max_emails?(user)
+ return skip_message(I18n.t('email_log.exceeded_limit'))
+ end
+
message = UserNotifications.send(type, user, email_args)
# Update the to address if we have a custom one
diff --git a/app/mailers/user_notifications.rb b/app/mailers/user_notifications.rb
index 943b2a127a5..f549b606c2f 100644
--- a/app/mailers/user_notifications.rb
+++ b/app/mailers/user_notifications.rb
@@ -317,6 +317,11 @@ class UserNotifications < ActionMailer::Base
end
end
+ reached_limit = SiteSetting.max_emails_per_day_per_user > 0
+ reached_limit &&= (EmailLog.where(user_id: user.id, skipped: false)
+ .where('created_at > ?', 1.day.ago)
+ .count) >= (SiteSetting.max_emails_per_day_per_user-1)
+
topic_excerpt = ""
if opts[:use_template_html]
topic_excerpt = post.excerpt.gsub("\n", " ") if post.is_first_post? && post.excerpt
@@ -326,6 +331,7 @@ class UserNotifications < ActionMailer::Base
template: 'email/notification',
format: :html,
locals: { context_posts: context_posts,
+ reached_limit: reached_limit,
post: post,
in_reply_to_post: in_reply_to_post,
classes: RTL.new(user).css_class
@@ -339,10 +345,11 @@ class UserNotifications < ActionMailer::Base
template << "_staged" if user.staged?
end
+
email_opts = {
topic_title: title,
topic_excerpt: topic_excerpt,
- message: email_post_markdown(post),
+ message: email_post_markdown(post) + (reached_limit ? "\n\n#{I18n.t "user_notifications.reached_limit", count: SiteSetting.max_emails_per_day_per_user}" : ""),
url: post.url,
post_id: post.id,
topic_id: post.topic_id,
diff --git a/app/models/email_log.rb b/app/models/email_log.rb
index 7a8e9ef6673..eb6c1a31738 100644
--- a/app/models/email_log.rb
+++ b/app/models/email_log.rb
@@ -13,6 +13,16 @@ class EmailLog < ActiveRecord::Base
User.where(id: user_id).update_all("last_emailed_at = CURRENT_TIMESTAMP") if user_id.present? && !skipped
end
+ def self.reached_max_emails?(user)
+ return false if SiteSetting.max_emails_per_day_per_user == 0
+
+ count = sent.where('created_at > ?', 1.day.ago)
+ .where(user_id: user.id)
+ .count
+
+ count >= SiteSetting.max_emails_per_day_per_user
+ end
+
def self.count_per_day(start_date, end_date)
sent.where("created_at BETWEEN ? AND ?", start_date, end_date)
.group("DATE(created_at)")
diff --git a/app/views/email/notification.html.erb b/app/views/email/notification.html.erb
index 363fa65ca44..d4c200807bf 100644
--- a/app/views/email/notification.html.erb
+++ b/app/views/email/notification.html.erb
@@ -10,12 +10,12 @@
<% end %>
<% if in_reply_to_post.present? %>
-
<%= t "user_notifications.in_reply_to" %>
+ <%= t "user_notifications.in_reply_to" %>
<%= render partial: 'email/post', locals: { post: in_reply_to_post, use_excerpt: true} %>
<% end %>
<% if context_posts.present? %>
- <%= t "user_notifications.previous_discussion" %>
+ <%= t "user_notifications.previous_discussion" %>
<% context_posts.each do |p| %>
<%= render partial: 'email/post', locals: { post: p, use_excerpt: false } %>
<% end %>
@@ -23,6 +23,9 @@
+ <% if reached_limit %>
+
+ <% end %>
diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml
index c6dc3a4c450..6d6505b7811 100644
--- a/config/locales/server.en.yml
+++ b/config/locales/server.en.yml
@@ -1161,6 +1161,7 @@ en:
unsubscribe_via_email_footer: "Attach an unsubscribe link to the footer of sent emails"
delete_email_logs_after_days: "Delete email logs after (N) days. 0 to keep indefinitely"
+ max_emails_per_day_per_user: "Maximum number of emails to send users per day. 0 to disable the limit"
manual_polling_enabled: "Push emails using the API for email replies."
pop3_polling_enabled: "Poll via POP3 for email replies."
@@ -2037,6 +2038,9 @@ en:
user_notifications:
previous_discussion: "Previous Replies"
+ reached_limit:
+ one: "WARNING: you reached the limit of daily emails. Further email notifications will be suppressed."
+ other: "WARNING: you reached the limit of %{count} daily emails. Further email notifications will be suppressed."
in_reply_to: "In Reply To"
unsubscribe:
title: "Unsubscribe"
@@ -2363,6 +2367,7 @@ en:
post_deleted: "post was deleted by the author"
user_suspended: "user was suspended"
already_read: "user has already read this post"
+ exceeded_limit: "Exceeded max_emails_per_day_per_user"
message_blank: "message is blank"
message_to_blank: "message.to is blank"
text_part_body_blank: "text_part.body is blank"
diff --git a/config/site_settings.yml b/config/site_settings.yml
index dd1e45019a6..096b4eab5ad 100644
--- a/config/site_settings.yml
+++ b/config/site_settings.yml
@@ -559,6 +559,7 @@ email:
delete_email_logs_after_days:
default: 365
min: 0
+ max_emails_per_day_per_user: 100
files:
max_image_size_kb: 3072
diff --git a/spec/jobs/notify_mailing_list_subscribers_spec.rb b/spec/jobs/notify_mailing_list_subscribers_spec.rb
index d3a0022f1fa..f63b4225ccc 100644
--- a/spec/jobs/notify_mailing_list_subscribers_spec.rb
+++ b/spec/jobs/notify_mailing_list_subscribers_spec.rb
@@ -3,9 +3,34 @@ require "rails_helper"
describe Jobs::NotifyMailingListSubscribers do
context "with mailing list on" do
- before { SiteSetting.stubs(:default_email_mailing_list_mode).returns(true) }
+ before { SiteSetting.default_email_mailing_list_mode = true }
let(:user) { Fabricate(:user) }
+ context "SiteSetting.max_emails_per_day_per_user" do
+
+ it 'stops sending mail once limit is reached' do
+ SiteSetting.max_emails_per_day_per_user = 2
+ post = Fabricate(:post)
+
+ user.email_logs.create(email_type: 'blah', to_address: user.email, user_id: user.id)
+ user.email_logs.create(email_type: 'blah', to_address: user.email, user_id: user.id)
+
+ Jobs::NotifyMailingListSubscribers.new.execute(post_id: post.id)
+ expect(EmailLog.where(user_id: user.id, skipped: true).count).to eq(1)
+ end
+ end
+
+ context "totally skipped if mailing list mode disabled" do
+
+ it "sends no email to the user" do
+ SiteSetting.disable_mailing_list_mode = true
+
+ post = Fabricate(:post)
+ Jobs::NotifyMailingListSubscribers.new.execute(post_id: post.id)
+ expect(EmailLog.count).to eq(0)
+ end
+ end
+
context "with a valid post" do
let!(:post) { Fabricate(:post, user: user) }
diff --git a/spec/jobs/user_email_spec.rb b/spec/jobs/user_email_spec.rb
index 92fd5abd2a3..e7405bade79 100644
--- a/spec/jobs/user_email_spec.rb
+++ b/spec/jobs/user_email_spec.rb
@@ -193,6 +193,17 @@ describe Jobs::UserEmail do
Jobs::UserEmail.new.execute(type: :user_mentioned, user_id: user.id, notification_id: notification.id)
end
+ it "does not send notification if limit is reached" do
+ SiteSetting.max_emails_per_day_per_user = 2
+
+ user.email_logs.create(email_type: 'blah', to_address: user.email, user_id: user.id)
+ user.email_logs.create(email_type: 'blah', to_address: user.email, user_id: user.id)
+
+ Jobs::UserEmail.new.execute(type: :user_mentioned, user_id: user.id, notification_id: notification.id, post_id: post.id)
+
+ expect(EmailLog.where(user_id: user.id, skipped: true).count).to eq(1)
+ end
+
it "doesn't send the mail if the user is using mailing list mode" do
Email::Sender.any_instance.expects(:send).never
user.user_option.update_column(:mailing_list_mode, true)
@@ -205,7 +216,7 @@ describe Jobs::UserEmail do
# When post does not have a topic
post = Fabricate(:post)
post.topic.destroy
- Jobs::UserEmail.new.execute(type: :user_mentioned, user_id: user.id, notification_type: "posted", post_id: post)
+ Jobs::UserEmail.new.execute(type: :user_mentioned, user_id: user.id, notification_type: "posted", post_id: post.id)
end
it "doesn't send the email if the post has been user deleted" do
diff --git a/spec/mailers/user_notifications_spec.rb b/spec/mailers/user_notifications_spec.rb
index ebc9b27e4c5..00ceb74a3af 100644
--- a/spec/mailers/user_notifications_spec.rb
+++ b/spec/mailers/user_notifications_spec.rb
@@ -243,6 +243,31 @@ describe UserNotifications do
end
end
+
+ it 'adds a warning when mail limit is reached' do
+ SiteSetting.max_emails_per_day_per_user = 2
+ user = Fabricate(:user)
+ user.email_logs.create(email_type: 'blah', to_address: user.email, user_id: user.id, skipped: false)
+
+ post = Fabricate(:post)
+ reply = Fabricate(:post, topic_id: post.topic_id)
+
+ notification = Fabricate(:notification, topic_id: post.topic_id, post_number: reply.post_number,
+ user: post.user, data: {original_username: 'bob'}.to_json)
+
+ mail = UserNotifications.user_replied(
+ user,
+ post: reply,
+ notification_type: notification.notification_type,
+ notification_data_hash: notification.data_hash
+ )
+
+ # WARNING: you reached the limit of 100 email notifications per day. Further emails will be suppressed.
+ # Consider watching less topics or disabling mailing list mode.
+ expect(mail.html_part.to_s).to match("WARNING: ")
+ expect(mail.body.to_s).to match("WARNING: ")
+ end
+
def expects_build_with(condition)
UserNotifications.any_instance.expects(:build_email).with(user.email, condition)
mailer = UserNotifications.send(mail_type, user,
diff --git a/spec/models/email_log_spec.rb b/spec/models/email_log_spec.rb
index 8c33e2b5f8b..3b5ca7df465 100644
--- a/spec/models/email_log_spec.rb
+++ b/spec/models/email_log_spec.rb
@@ -26,6 +26,21 @@ describe EmailLog do
end
end
+ describe '#reached_max_emails?' do
+ it "tracks when max emails are reached" do
+ SiteSetting.max_emails_per_day_per_user = 2
+ user.email_logs.create(email_type: 'blah', to_address: user.email, user_id: user.id, skipped: true)
+ user.email_logs.create(email_type: 'blah', to_address: user.email, user_id: user.id)
+ user.email_logs.create(email_type: 'blah', to_address: user.email, user_id: user.id, created_at: 3.days.ago)
+
+ expect(EmailLog.reached_max_emails?(user)).to eq(false)
+
+ user.email_logs.create(email_type: 'blah', to_address: user.email, user_id: user.id)
+
+ expect(EmailLog.reached_max_emails?(user)).to eq(true)
+ end
+ end
+
describe '#count_per_day' do
it "counts sent emails" do
user.email_logs.create(email_type: 'blah', to_address: user.email)