Skip trial emails for active users

This commit is contained in:
Eugene Burmakin 2025-08-19 18:55:22 +02:00
parent cdbd51c9f9
commit b9764d39c3
6 changed files with 103 additions and 54 deletions

View file

@ -6,8 +6,19 @@ class Users::MailerSendingJob < ApplicationJob
def perform(user_id, email_type, **options)
user = User.find(user_id)
if trial_related_email?(email_type) && user.active?
Rails.logger.info "Skipping #{email_type} email for user #{user_id} - user is already subscribed"
return
end
params = { user: user }.merge(options)
UsersMailer.with(params).public_send(email_type).deliver_later
end
private
def trial_related_email?(email_type)
%w[trial_expires_soon trial_expired].include?(email_type.to_s)
end
end

View file

@ -23,18 +23,6 @@ class Import < ApplicationRecord
user_data_archive: 8
}
private
def file_size_within_limit
return unless file.attached?
if file.blob.byte_size > 11.megabytes
errors.add(:file, 'is too large. Trial users can only upload files up to 10MB.')
end
end
public
def process!
if user_data_archive?
process_user_data_archive!
@ -71,4 +59,12 @@ class Import < ApplicationRecord
def remove_attached_file
file.purge_later
end
def file_size_within_limit
return unless file.attached?
if file.blob.byte_size > 11.megabytes
errors.add(:file, 'is too large. Trial users can only upload files up to 10MB.')
end
end
end

View file

@ -28,7 +28,7 @@ class User < ApplicationRecord # rubocop:disable Metrics/ClassLength
attribute :admin, :boolean, default: false
enum :status, { inactive: 0, active: 1, trial: 3 }
enum :status, { inactive: 0, active: 1, trial: 2 }
def safe_settings
Users::SafeSettings.new(settings)

View file

@ -10,42 +10,88 @@ RSpec.describe Users::MailerSendingJob, type: :job do
describe '#perform' do
context 'when email_type is welcome' do
it 'sends welcome email' do
it 'sends welcome email to trial user' do
expect(UsersMailer).to receive(:with).with({ user: user })
expect(UsersMailer).to receive(:welcome).and_return(mailer_double)
expect(mailer_double).to receive(:deliver_later)
described_class.perform_now(user.id, 'welcome')
end
it 'sends welcome email to active user' do
active_user = create(:user)
expect(UsersMailer).to receive(:with).with({ user: active_user })
expect(UsersMailer).to receive(:welcome).and_return(mailer_double)
expect(mailer_double).to receive(:deliver_later)
described_class.perform_now(active_user.id, 'welcome')
end
end
context 'when email_type is explore_features' do
it 'sends explore_features email' do
it 'sends explore_features email to trial user' do
expect(UsersMailer).to receive(:with).with({ user: user })
expect(UsersMailer).to receive(:explore_features).and_return(mailer_double)
expect(mailer_double).to receive(:deliver_later)
described_class.perform_now(user.id, 'explore_features')
end
it 'sends explore_features email to active user' do
active_user = create(:user)
expect(UsersMailer).to receive(:with).with({ user: active_user })
expect(UsersMailer).to receive(:explore_features).and_return(mailer_double)
expect(mailer_double).to receive(:deliver_later)
described_class.perform_now(active_user.id, 'explore_features')
end
end
context 'when email_type is trial_expires_soon' do
it 'sends trial_expires_soon email' do
expect(UsersMailer).to receive(:with).with({ user: user })
expect(UsersMailer).to receive(:trial_expires_soon).and_return(mailer_double)
expect(mailer_double).to receive(:deliver_later)
context 'with trial user' do
it 'sends trial_expires_soon email' do
expect(UsersMailer).to receive(:with).with({ user: user })
expect(UsersMailer).to receive(:trial_expires_soon).and_return(mailer_double)
expect(mailer_double).to receive(:deliver_later)
described_class.perform_now(user.id, 'trial_expires_soon')
described_class.perform_now(user.id, 'trial_expires_soon')
end
end
context 'with active user' do
let(:active_user) { create(:user) }
it 'skips sending trial_expires_soon email' do
expect(UsersMailer).not_to receive(:with)
expect(UsersMailer).not_to receive(:trial_expires_soon)
expect(Rails.logger).to receive(:info).with("Skipping trial_expires_soon email for user #{active_user.id} - user is already subscribed")
described_class.perform_now(active_user.id, 'trial_expires_soon')
end
end
end
context 'when email_type is trial_expired' do
it 'sends trial_expired email' do
expect(UsersMailer).to receive(:with).with({ user: user })
expect(UsersMailer).to receive(:trial_expired).and_return(mailer_double)
expect(mailer_double).to receive(:deliver_later)
context 'with trial user' do
it 'sends trial_expired email' do
expect(UsersMailer).to receive(:with).with({ user: user })
expect(UsersMailer).to receive(:trial_expired).and_return(mailer_double)
expect(mailer_double).to receive(:deliver_later)
described_class.perform_now(user.id, 'trial_expired')
described_class.perform_now(user.id, 'trial_expired')
end
end
context 'with active user' do
let(:active_user) { create(:user) }
it 'skips sending trial_expired email' do
expect(UsersMailer).not_to receive(:with)
expect(UsersMailer).not_to receive(:trial_expired)
expect(Rails.logger).to receive(:info).with("Skipping trial_expired email for user #{active_user.id} - user is already subscribed")
described_class.perform_now(active_user.id, 'trial_expired')
end
end
end
@ -72,4 +118,28 @@ RSpec.describe Users::MailerSendingJob, type: :job do
end
end
end
describe '#trial_related_email?' do
subject { described_class.new }
it 'returns true for trial_expires_soon' do
expect(subject.send(:trial_related_email?, 'trial_expires_soon')).to be true
end
it 'returns true for trial_expired' do
expect(subject.send(:trial_related_email?, 'trial_expired')).to be true
end
it 'returns false for welcome' do
expect(subject.send(:trial_related_email?, 'welcome')).to be false
end
it 'returns false for explore_features' do
expect(subject.send(:trial_related_email?, 'explore_features')).to be false
end
it 'returns false for unknown email types' do
expect(subject.send(:trial_related_email?, 'unknown_email')).to be false
end
end
end

View file

@ -18,7 +18,7 @@ RSpec.describe User, type: :model do
end
describe 'enums' do
it { is_expected.to define_enum_for(:status).with_values(inactive: 0, active: 1, trial: 3) }
it { is_expected.to define_enum_for(:status).with_values(inactive: 0, active: 1, trial: 2) }
end
describe 'callbacks' do

View file

@ -1,28 +0,0 @@
# Trials Feature Checklist
## ✅ Already Implemented
- [x] **7-day trial activation** - `set_trial` method sets `status: :trial` and `active_until: 7.days.from_now`
- [x] **Welcome email** - Sent immediately after registration
- [x] **Scheduled emails** - Feature exploration (day 2), trial expires soon (day 5), trial expired (day 7)
- [x] **Trial status enum** - `{ inactive: 0, active: 1, trial: 3 }`
- [x] **Navbar Trial Display** - Show number of days left in trial at subscribe button
- [x] **Account Deletion Cleanup** - User deletes account during trial, cleanup scheduled emails
- [x] Worker to not send emails if user is deleted
## ❌ Missing/TODO Items
### Core Requirements
- [x] **Specs** - Add specs for all implemented features
- [x] User model trial callbacks and methods
- [x] Trial webhook job with JWT encoding
- [x] Mailer sending job for all email types
- [x] JWT encoding service
## Manager (separate application)
- [ ] **Manager Webhook** - Create user in Manager service after registration
- [ ] **Manager callback** - Manager should daily check user statuses and once trial is expired, update user status to inactive in Dawarich
- [ ] **Trial Credit** - Should trial time be credited to first paid month?
- [ ] Yes, Manager after payment adds subscription duration to user's active_until
- [ ] **User Reactivation** - Handle user returning after trial expired