Move sending family invitation email to a background job

This commit is contained in:
Eugene Burmakin 2025-11-07 11:49:21 +01:00
parent a6f2bd3662
commit eed9480a9e
5 changed files with 104 additions and 12 deletions

View file

@ -13,9 +13,10 @@ class Family::Invitations::CleanupJob < ApplicationJob
Rails.logger.info "Updated #{expired_count} expired family invitations"
cleanup_threshold = 30.days.ago
deleted_count = Family::Invitation.where(status: [:expired, :cancelled])
.where('updated_at < ?', cleanup_threshold)
.delete_all
deleted_count =
Family::Invitation.where(status: %i[expired cancelled])
.where('updated_at < ?', cleanup_threshold)
.delete_all
Rails.logger.info "Deleted #{deleted_count} old family invitations"

View file

@ -0,0 +1,13 @@
# frozen_string_literal: true
class Family::Invitations::SendingJob < ApplicationJob
queue_as :families
def perform(invitation_id)
invitation = Family::Invitation.find_by(id: invitation_id)
return unless invitation&.pending?
FamilyMailer.invitation(invitation).deliver_now
end
end

View file

@ -19,8 +19,8 @@ module Families
return false unless invite_sendable?
ActiveRecord::Base.transaction do
create_invitation
send_invitation_email
invitation = create_invitation
send_invitation_email(invitation)
send_notification
end
@ -80,16 +80,18 @@ module Families
)
end
def send_invitation_email
# Send email in background with retry logic
FamilyMailer.invitation(@invitation).deliver_later(
queue: :mailer,
retry: 3,
wait: 30.seconds
)
def send_invitation_email(invitation)
Families::Invitations::SendingJob.perform_later(invitation.id)
end
def send_notification
message =
if DawarichSettings.self_hosted?
"Family invitation sent to #{email} if SMTP is configured properly. If you're not using SMTP, copy the invitation link from the family page and share it manually."
else
"Family invitation sent to #{email}"
end
Notification.create!(
user: invited_by,
kind: :info,

View file

@ -0,0 +1,71 @@
# frozen_string_literal: true
require 'rails_helper'
RSpec.describe Family::Invitations::SendingJob, type: :job do
let(:user) { create(:user) }
let(:family) { create(:family, creator: user) }
let(:invitation) { create(:family_invitation, family: family, invited_by: user, status: :pending) }
describe '#perform' do
context 'when invitation exists and is pending' do
it 'sends the invitation email' do
mailer_double = double('mailer')
expect(FamilyMailer).to receive(:invitation).with(invitation).and_return(mailer_double)
expect(mailer_double).to receive(:deliver_now)
described_class.perform_now(invitation.id)
end
end
context 'when invitation does not exist' do
it 'does not raise an error' do
expect do
described_class.perform_now(999_999)
end.not_to raise_error
end
it 'does not send any email' do
expect(FamilyMailer).not_to receive(:invitation)
described_class.perform_now(999_999)
end
end
context 'when invitation is not pending' do
let(:accepted_invitation) do
create(:family_invitation, family: family, invited_by: user, status: :accepted)
end
it 'does not send the invitation email' do
expect(FamilyMailer).not_to receive(:invitation)
described_class.perform_now(accepted_invitation.id)
end
end
context 'when invitation is cancelled' do
let(:cancelled_invitation) do
create(:family_invitation, family: family, invited_by: user, status: :cancelled)
end
it 'does not send the invitation email' do
expect(FamilyMailer).not_to receive(:invitation)
described_class.perform_now(cancelled_invitation.id)
end
end
context 'integration test' do
it 'actually sends the email' do
expect do
described_class.perform_now(invitation.id)
end.to change { ActionMailer::Base.deliveries.count }.by(1)
email = ActionMailer::Base.deliveries.last
expect(email.to).to include(invitation.email)
expect(email.subject).to include("You've been invited to join #{family.name}")
end
end
end
end

View file

@ -21,6 +21,11 @@ RSpec.describe Families::Invite do
expect(invitation.invited_by).to eq(owner)
end
it 'enqueues invitation sending job' do
expect(Family::Invitations::SendingJob).to receive(:perform_later).with(an_instance_of(Integer))
service.call
end
it 'sends invitation email' do
expect(FamilyMailer).to receive(:invitation).and_call_original
expect_any_instance_of(ActionMailer::MessageDelivery).to receive(:deliver_later)