diff --git a/ruby/db/migrate/20250817162004_add_trail_expires_reminder_columns_to_users.rb b/ruby/db/migrate/20250817162004_add_trail_expires_reminder_columns_to_users.rb new file mode 100644 index 000000000..869fa84a2 --- /dev/null +++ b/ruby/db/migrate/20250817162004_add_trail_expires_reminder_columns_to_users.rb @@ -0,0 +1,12 @@ + class AddTrailExpiresReminderColumnsToUsers < ActiveRecord::Migration + def self.up + execute "ALTER TABLE users ADD COLUMN trial_expires_reminder1_sent_at TIMESTAMP" + execute "ALTER TABLE users ADD COLUMN trial_expires_reminder2_sent_at TIMESTAMP" + execute "ALTER TABLE users ADD COLUMN trial_expires_reminder3_sent_at TIMESTAMP" + end + def self.down + execute "ALTER TABLE users DROP COLUMN trial_expires_reminder1_sent_at" + execute "ALTER TABLE users DROP COLUMN trial_expires_reminder2_sent_at" + execute "ALTER TABLE users DROP COLUMN trial_expires_reminder3_sent_at" + end + end diff --git a/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/trial_expires_reminder1.html.erb b/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/trial_expires_reminder1.html.erb new file mode 100644 index 000000000..b4ad59e1e --- /dev/null +++ b/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/trial_expires_reminder1.html.erb @@ -0,0 +1,14 @@ +

<%=I18n.t('user_mailer.trial_expires_reminder1.greeting') -%> <%= @user.first_name -%> -

+ +

+ <%=I18n.t('user_mailer.trial_expires_reminder1.paragraph1').html_safe -%> +

+ +

+ <%=I18n.t('user_mailer.trial_expires_reminder1.paragraph2').html_safe -%> +

+ +

+ <%=I18n.t('user_mailer.trial_expires_reminder1.regards') -%>,
+ <%=I18n.t('user_mailer.trial_expires_reminder1.signature') -%> +

\ No newline at end of file diff --git a/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/trial_expires_reminder1.text.erb b/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/trial_expires_reminder1.text.erb new file mode 100644 index 000000000..36c478dfb --- /dev/null +++ b/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/trial_expires_reminder1.text.erb @@ -0,0 +1,8 @@ +<%=I18n.t('user_mailer.trial_expires_reminder1.greeting') -%> <%= @user.first_name -%> - + +<%=I18n.t('user_mailer.trial_expires_reminder1.paragraph1') -%> + +<%=I18n.t('user_mailer.trial_expires_reminder1.paragraph2') -%> + +<%=I18n.t('user_mailer.trial_expires_reminder1.regards') -%>, +<%=I18n.t('user_mailer.trial_expires_reminder1.signature') -%> \ No newline at end of file diff --git a/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/trial_expires_reminder2.html.erb b/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/trial_expires_reminder2.html.erb new file mode 100644 index 000000000..88f61b0aa --- /dev/null +++ b/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/trial_expires_reminder2.html.erb @@ -0,0 +1,18 @@ +

<%=I18n.t('user_mailer.trial_expires_reminder2.greeting') -%> <%= @user.first_name -%> -

+ +

+ <%=I18n.t('user_mailer.trial_expires_reminder2.paragraph1').html_safe -%> +

+ +

+ <%=I18n.t('user_mailer.trial_expires_reminder2.paragraph2').html_safe -%> +

+ +

+ <%=I18n.t('user_mailer.trial_expires_reminder2.paragraph3').html_safe -%> +

+ +

+ <%=I18n.t('user_mailer.trial_expires_reminder2.regards') -%>,
+ <%=I18n.t('user_mailer.trial_expires_reminder2.signature') -%> +

\ No newline at end of file diff --git a/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/trial_expires_reminder2.text.erb b/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/trial_expires_reminder2.text.erb new file mode 100644 index 000000000..85d7112b2 --- /dev/null +++ b/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/trial_expires_reminder2.text.erb @@ -0,0 +1,10 @@ +<%=I18n.t('user_mailer.trial_expires_reminder2.greeting') -%> <%= @user.first_name -%> - + +<%=I18n.t('user_mailer.trial_expires_reminder2.paragraph1') -%> + +<%=I18n.t('user_mailer.trial_expires_reminder2.paragraph2') -%> + +<%=I18n.t('user_mailer.trial_expires_reminder2.paragraph3') -%> + +<%=I18n.t('user_mailer.trial_expires_reminder2.regards') -%>, +<%=I18n.t('user_mailer.trial_expires_reminder2.signature') -%> \ No newline at end of file diff --git a/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/trial_expires_reminder3.html.erb b/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/trial_expires_reminder3.html.erb new file mode 100644 index 000000000..b5d7f9cc3 --- /dev/null +++ b/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/trial_expires_reminder3.html.erb @@ -0,0 +1,18 @@ +

<%=I18n.t('user_mailer.trial_expires_reminder3.greeting') -%> <%= @user.first_name -%> -

+ +

+ <%=I18n.t('user_mailer.trial_expires_reminder3.paragraph1').html_safe -%> +

+ +

+ <%=I18n.t('user_mailer.trial_expires_reminder3.paragraph2').html_safe -%> +

+ +

+ <%=I18n.t('user_mailer.trial_expires_reminder3.paragraph3').html_safe -%> +

+ +

+ <%=I18n.t('user_mailer.trial_expires_reminder3.regards') -%>,
+ <%=I18n.t('user_mailer.trial_expires_reminder3.signature') -%> +

\ No newline at end of file diff --git a/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/trial_expires_reminder3.text.erb b/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/trial_expires_reminder3.text.erb new file mode 100644 index 000000000..2461bb066 --- /dev/null +++ b/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/trial_expires_reminder3.text.erb @@ -0,0 +1,10 @@ +<%=I18n.t('user_mailer.trial_expires_reminder3.greeting') -%> <%= @user.first_name -%> - + +<%=I18n.t('user_mailer.trial_expires_reminder3.paragraph1') -%> + +<%=I18n.t('user_mailer.trial_expires_reminder3.paragraph2') -%> + +<%=I18n.t('user_mailer.trial_expires_reminder3.paragraph3') -%> + +<%=I18n.t('user_mailer.trial_expires_reminder3.regards') -%>, +<%=I18n.t('user_mailer.trial_expires_reminder3.signature') -%> \ No newline at end of file diff --git a/ruby/lib/jam_ruby/lib/trial_expires_reminder.rb b/ruby/lib/jam_ruby/lib/trial_expires_reminder.rb new file mode 100644 index 000000000..e4dd9540e --- /dev/null +++ b/ruby/lib/jam_ruby/lib/trial_expires_reminder.rb @@ -0,0 +1,64 @@ +# lib/jam_ruby/lib/trial_expires_reminder.rb +# +# Sends reminder emails after a user's free trial has ended. +# +# Usage: JamRuby::TrialExpiresReminder.send_reminders +# +module JamRuby + class TrialExpiresReminder + @@log = Logging.logger[TrialExpiresReminder] + + FIRST_NOTIFICATION_CHECK = 1.day.ago.to_s + SECOND_NOTIFICATION_CHECK = 5.days.ago.to_s + THIRD_NOTIFICATION_CHECK = 9.days.ago + + def self.prospect_users(cutoff_date) + User.where("(users.subscription_trial_ends_at IS NOT NULL AND users.subscription_trial_ends_at > ?)", cutoff_date) + end + + def self.reminder1_users(cutoff_date) + prospect_users(cutoff_date).where("users.subscription_last_checked_at < ? AND users.subscription_trial_ends_at < ? AND users.trial_expires_reminder1_sent_at IS NULL", 1.day.ago, FIRST_NOTIFICATION_CHECK) + end + + def self.reminder2_users(cutoff_date) + prospect_users(cutoff_date).where("users.subscription_last_checked_at < ? AND users.subscription_trial_ends_at < ? AND trial_expires_reminder1_sent_at IS NOT NULL AND users.trial_expires_reminder2_sent_at IS NULL", 1.day.ago, SECOND_NOTIFICATION_CHECK) + end + + def self.reminder3_users(cutoff_date) + prospect_users(cutoff_date).where("users.subscription_last_checked_at < ? AND users.subscription_trial_ends_at < ? AND trial_expires_reminder2_sent_at IS NOT NULL AND users.trial_expires_reminder3_sent_at IS NULL", 1.day.ago, THIRD_NOTIFICATION_CHECK) + end + + def self.send_reminders + cod = Date.parse(Rails.application.config.trial_expires_reminders_effective_from_date) # Define a cutoff date for the trial expires reminder emails + + reminder1_users(cod).find_each(batch_size: 100) do |user| + begin + UserMailer.trial_expires_reminder1(user).deliver_now + user.update_attribute(:trial_expires_reminder1_sent_at, Time.now) + rescue Exception => e + @@log.error("unable to send trial_expires_reminder1 email #{e}") + puts "unable to send trial_expires_reminder1 email #{e}" + end + end + reminder2_users(cod).find_each(batch_size: 100) do |user| + begin + UserMailer.trial_expires_reminder2(user).deliver_now + user.update_attribute(:trial_expires_reminder2_sent_at, Time.now) + rescue Exception => e + @@log.error("unable to send trial_expires_reminder2 email #{e}") + puts "unable to send trial_expires_reminder2 email #{e}" + end + end + reminder3_users(cod).find_each(batch_size: 100) do |user| + begin + UserMailer.trial_expires_reminder3(user).deliver_now + user.update_attribute(:trial_expires_reminder3_sent_at, Time.now) + rescue Exception => e + @@log.error("unable to send trial_expires_reminder3 email #{e}") + puts "unable to send trial_expires_reminder3 email #{e}" + end + end + end + + end +end \ No newline at end of file diff --git a/ruby/spec/jam_ruby/lib/trail_expires_reminder_spec.rb b/ruby/spec/jam_ruby/lib/trail_expires_reminder_spec.rb new file mode 100644 index 000000000..9943e6368 --- /dev/null +++ b/ruby/spec/jam_ruby/lib/trail_expires_reminder_spec.rb @@ -0,0 +1,116 @@ +require "spec_helper" + +describe TrialExpiresReminder do + let(:user1) { FactoryGirl.create(:user) } + let(:user2) { FactoryGirl.create(:user) } + let(:user3) { FactoryGirl.create(:user) } + + before(:each) do + ActionMailer::Base.delivery_method = :test + ActionMailer::Base.perform_deliveries = true + ActionMailer::Base.deliveries = [] + + User.delete_all + UserMailer.deliveries.clear + Rails.application.config.trial_expires_reminders_effective_from_date = 2.weeks.ago.to_s + end + + after(:each) do + ActionMailer::Base.deliveries.clear + end + + it "sends reminder emails to users whose trials are about to expire" do + user1.subscription_trial_ends_at = 1.days.from_now + user1.subscription_last_checked_at = 2.days.ago + user1.save! + + user2.subscription_trial_ends_at = 1.days.ago + user2.subscription_last_checked_at = 2.days.ago + user2.save! + + TrialExpiresReminder.send_reminders + + expect(ActionMailer::Base.deliveries.count).to eq(1) + expect(ActionMailer::Base.deliveries.map(&:to).flatten).to include(user2.email) + # Check if the first reminder email was sent by verifying the subject + expect(ActionMailer::Base.deliveries.last.subject).to include("Your free gold trial has expired, but you have great options to keep playing!") + + expect(user2.reload.trial_expires_reminder1_sent_at).not_to be_nil + end + + it "does not send reminder emails to users who have already received them" do + user1.subscription_trial_ends_at = 1.days.ago + user1.subscription_last_checked_at = 2.days.ago + user1.trial_expires_reminder1_sent_at = Time.now + user1.save! + + TrialExpiresReminder.send_reminders + + expect(ActionMailer::Base.deliveries.count).to eq(0) + end + + it "sends the second reminder email to users whose trials are about to expire" do + user1.subscription_trial_ends_at = 4.days.ago + user1.subscription_last_checked_at = 1.days.ago + user1.trial_expires_reminder1_sent_at = Time.now + user1.save! + + user2.subscription_trial_ends_at = 5.days.ago + user2.subscription_last_checked_at = 1.days.ago + user2.trial_expires_reminder1_sent_at = Time.now + user2.save! + + TrialExpiresReminder.send_reminders + + expect(ActionMailer::Base.deliveries.count).to eq(1) + expect(ActionMailer::Base.deliveries.map(&:to).flatten).to include(user2.email) + # Check if the second reminder email was sent by verifying the subject + expect(ActionMailer::Base.deliveries.last.subject).to include("Don’t forget to check your options to keep playing") + + expect(user2.reload.trial_expires_reminder2_sent_at).not_to be_nil + end + + it "sends the third reminder email to users whose trials are about to expire" do + user1.subscription_trial_ends_at = 10.days.ago + user1.subscription_last_checked_at = 1.days.ago + user1.trial_expires_reminder1_sent_at = 6.days.ago + user1.trial_expires_reminder2_sent_at = 4.days.ago + user1.save! + + TrialExpiresReminder.send_reminders + + expect(ActionMailer::Base.deliveries.count).to eq(1) + expect(ActionMailer::Base.deliveries.map(&:to).flatten).to include(user1.email) + # Check if the third reminder email was sent by verifying the subject + expect(ActionMailer::Base.deliveries.last.subject).to include("One last reminder!") + + expect(user1.reload.trial_expires_reminder3_sent_at).not_to be_nil + end + + it "sends first and second and third reminder emails to users whose trials are about to expire" do + user1.subscription_trial_ends_at = 2.days.ago + user1.subscription_last_checked_at = 1.days.ago + user1.save! + + user2.subscription_trial_ends_at = 6.days.ago + user2.subscription_last_checked_at = 1.days.ago + user2.trial_expires_reminder1_sent_at = 4.days.ago + user2.save! + + user3.subscription_trial_ends_at = 10.days.ago + user3.subscription_last_checked_at = 2.days.ago + user3.trial_expires_reminder1_sent_at = 6.days.ago + user3.trial_expires_reminder2_sent_at = 4.days.ago + user3.save! + + TrialExpiresReminder.send_reminders + + expect(user1.reload.trial_expires_reminder1_sent_at).not_to be_nil + expect(user2.reload.trial_expires_reminder2_sent_at).not_to be_nil + expect(user3.reload.trial_expires_reminder3_sent_at).not_to be_nil + + expect(ActionMailer::Base.deliveries.count).to eq(3) + expect(ActionMailer::Base.deliveries.map(&:to).flatten).to include(user1.email, user2.email, user3.email) + + end +end \ No newline at end of file