From e863a52262abb18f40552447cdeb82888c1aef99 Mon Sep 17 00:00:00 2001 From: Pranav Date: Sun, 31 Aug 2025 07:01:41 -0700 Subject: [PATCH] chore: Update account deletion email copy (#12317) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Update the email copies for account deletion emails ## Manual Deletion Screenshot 2025-08-29 at 2 41 40 PM ## Inactive Deletion image --------- Co-authored-by: Cursor Agent Co-authored-by: Sojan Jose --- .../account_notification_mailer.rb | 28 +++- .../account_deletion.liquid | 16 --- .../account_deletion_for_inactivity.liquid | 21 +++ .../account_deletion_user_initiated.liquid | 16 +++ enterprise/app/models/enterprise/account.rb | 16 ++- spec/enterprise/models/account_spec.rb | 19 ++- .../account_notification_mailer_spec.rb | 126 ++++-------------- 7 files changed, 118 insertions(+), 124 deletions(-) delete mode 100644 app/views/mailers/administrator_notifications/account_notification_mailer/account_deletion.liquid create mode 100644 app/views/mailers/administrator_notifications/account_notification_mailer/account_deletion_for_inactivity.liquid create mode 100644 app/views/mailers/administrator_notifications/account_notification_mailer/account_deletion_user_initiated.liquid diff --git a/app/mailers/administrator_notifications/account_notification_mailer.rb b/app/mailers/administrator_notifications/account_notification_mailer.rb index 8837e4f8c..5ac753372 100644 --- a/app/mailers/administrator_notifications/account_notification_mailer.rb +++ b/app/mailers/administrator_notifications/account_notification_mailer.rb @@ -1,10 +1,22 @@ class AdministratorNotifications::AccountNotificationMailer < AdministratorNotifications::BaseMailer - def account_deletion(account, reason = 'manual_deletion') - subject = 'Your account has been marked for deletion' + def account_deletion_user_initiated(account, reason) + subject = 'Your Chatwoot account deletion has been scheduled' action_url = settings_url('general') meta = { 'account_name' => account.name, - 'deletion_date' => account.custom_attributes['marked_for_deletion_at'], + 'deletion_date' => format_deletion_date(account.custom_attributes['marked_for_deletion_at']), + 'reason' => reason + } + + send_notification(subject, action_url: action_url, meta: meta) + end + + def account_deletion_for_inactivity(account, reason) + subject = 'Your Chatwoot account is scheduled for deletion due to inactivity' + action_url = settings_url('general') + meta = { + 'account_name' => account.name, + 'deletion_date' => format_deletion_date(account.custom_attributes['marked_for_deletion_at']), 'reason' => reason } @@ -45,4 +57,14 @@ class AdministratorNotifications::AccountNotificationMailer < AdministratorNotif send_notification(subject, action_url: action_url, meta: meta) end + + private + + def format_deletion_date(deletion_date_str) + return 'Unknown' if deletion_date_str.blank? + + Time.zone.parse(deletion_date_str).strftime('%B %d, %Y') + rescue StandardError + 'Unknown' + end end diff --git a/app/views/mailers/administrator_notifications/account_notification_mailer/account_deletion.liquid b/app/views/mailers/administrator_notifications/account_notification_mailer/account_deletion.liquid deleted file mode 100644 index 0873dbac1..000000000 --- a/app/views/mailers/administrator_notifications/account_notification_mailer/account_deletion.liquid +++ /dev/null @@ -1,16 +0,0 @@ -

Hello,

- -

Your account {{ meta.account_name }} has been marked for deletion. The account will be permanently deleted on {{ meta.deletion_date }}.

- -{% if meta.reason == 'manual_deletion' %} -

This action was requested by one of the administrators of your account.

-{% else %} -

Reason for deletion: {{ meta.reason }}

-{% endif %} - -

If this was done in error, you can cancel the deletion process by visiting your account settings.

- -

Cancel Account Deletion

- -

Thank you,
-Team Chatwoot

\ No newline at end of file diff --git a/app/views/mailers/administrator_notifications/account_notification_mailer/account_deletion_for_inactivity.liquid b/app/views/mailers/administrator_notifications/account_notification_mailer/account_deletion_for_inactivity.liquid new file mode 100644 index 000000000..273eca12b --- /dev/null +++ b/app/views/mailers/administrator_notifications/account_notification_mailer/account_deletion_for_inactivity.liquid @@ -0,0 +1,21 @@ +

Hello there,

+ +

We've noticed that your Chatwoot account {{ meta.account_name }} has been inactive for some time. Because of this, it's scheduled for deletion on {{ meta.deletion_date }}.

+ +

How do I keep my account?

+ +

Log in to your Chatwoot account before {{ meta.deletion_date }}. From your account settings, you can cancel the deletion and continue using your account.

+ +

What happens if I don't cancel?

+ +

Unless you cancel the account deletion before {{ meta.deletion_date }}, your account and all associated data — including conversations, contacts, reports, and settings — will be permanently deleted.

+ +

Why are we doing this?

+ +

To keep things secure and efficient, we regularly remove inactive accounts so our systems remain optimized for active teams.

+ +

If you have any questions, feel free to reach us at hello@chatwoot.com.

+ +

— The Chatwoot Team

+ + diff --git a/app/views/mailers/administrator_notifications/account_notification_mailer/account_deletion_user_initiated.liquid b/app/views/mailers/administrator_notifications/account_notification_mailer/account_deletion_user_initiated.liquid new file mode 100644 index 000000000..2039a93df --- /dev/null +++ b/app/views/mailers/administrator_notifications/account_notification_mailer/account_deletion_user_initiated.liquid @@ -0,0 +1,16 @@ +

Hello there,

+ +

An account administrator has requested deletion of the Chatwoot account {{ meta.account_name }}. The account is scheduled for deletion on {{ meta.deletion_date }}.

+ +

What happens next?

+ +
    +
  • The account will remain accessible until the scheduled deletion date.
  • +
  • After that, all data including conversations, contacts, integrations, and settings will be permanently removed.
  • +
+ +

If you change your mind before the deletion date, you can cancel this request by visiting your account settings.

+ +

— The Chatwoot Team

+ + diff --git a/enterprise/app/models/enterprise/account.rb b/enterprise/app/models/enterprise/account.rb index fae44dab6..dff97c0ee 100644 --- a/enterprise/app/models/enterprise/account.rb +++ b/enterprise/app/models/enterprise/account.rb @@ -4,10 +4,22 @@ module Enterprise::Account def manually_managed_features; end def mark_for_deletion(reason = 'manual_deletion') - result = custom_attributes.merge!('marked_for_deletion_at' => 7.days.from_now.iso8601, 'marked_for_deletion_reason' => reason) && save + reason = reason.to_s == 'manual_deletion' ? 'manual_deletion' : 'inactivity' + + result = custom_attributes.merge!( + 'marked_for_deletion_at' => 7.days.from_now.iso8601, + 'marked_for_deletion_reason' => reason + ) && save # Send notification to admin users if the account was successfully marked for deletion - AdministratorNotifications::AccountNotificationMailer.with(account: self).account_deletion(self, reason).deliver_later if result + if result + mailer = AdministratorNotifications::AccountNotificationMailer.with(account: self) + if reason == 'manual_deletion' + mailer.account_deletion_user_initiated(self, reason).deliver_later + else + mailer.account_deletion_for_inactivity(self, reason).deliver_later + end + end result end diff --git a/spec/enterprise/models/account_spec.rb b/spec/enterprise/models/account_spec.rb index 4d851d50e..1c727328e 100644 --- a/spec/enterprise/models/account_spec.rb +++ b/spec/enterprise/models/account_spec.rb @@ -229,18 +229,27 @@ RSpec.describe Account, type: :model do describe '#mark_for_deletion' do it 'sets the marked_for_deletion_at and marked_for_deletion_reason attributes' do expect do - account.mark_for_deletion('test_reason') + account.mark_for_deletion('inactivity') end.to change { account.reload.custom_attributes['marked_for_deletion_at'] }.from(nil).to(be_present) - .and change { account.reload.custom_attributes['marked_for_deletion_reason'] }.from(nil).to('test_reason') + .and change { account.reload.custom_attributes['marked_for_deletion_reason'] }.from(nil).to('inactivity') end - it 'sends a notification email to admin users' do + it 'sends a user-initiated deletion email when reason is manual_deletion' do mailer = double expect(AdministratorNotifications::AccountNotificationMailer).to receive(:with).with(account: account).and_return(mailer) - expect(mailer).to receive(:account_deletion).with(account, 'test_reason').and_return(mailer) + expect(mailer).to receive(:account_deletion_user_initiated).with(account, 'manual_deletion').and_return(mailer) expect(mailer).to receive(:deliver_later) - account.mark_for_deletion('test_reason') + account.mark_for_deletion('manual_deletion') + end + + it 'sends a system-initiated deletion email when reason is not manual_deletion' do + mailer = double + expect(AdministratorNotifications::AccountNotificationMailer).to receive(:with).with(account: account).and_return(mailer) + expect(mailer).to receive(:account_deletion_for_inactivity).with(account, 'inactivity').and_return(mailer) + expect(mailer).to receive(:deliver_later) + + account.mark_for_deletion('inactivity') end it 'returns true when successful' do diff --git a/spec/mailers/administrator_notifications/account_notification_mailer_spec.rb b/spec/mailers/administrator_notifications/account_notification_mailer_spec.rb index 44df38e88..dbe6ac11b 100644 --- a/spec/mailers/administrator_notifications/account_notification_mailer_spec.rb +++ b/spec/mailers/administrator_notifications/account_notification_mailer_spec.rb @@ -1,116 +1,46 @@ require 'rails_helper' -require Rails.root.join 'spec/mailers/administrator_notifications/shared/smtp_config_shared.rb' RSpec.describe AdministratorNotifications::AccountNotificationMailer do - include_context 'with smtp config' + let(:account) { create(:account, name: 'Test Account') } + let(:mailer) { described_class.with(account: account) } + let(:class_instance) { described_class.new } - let!(:account) { create(:account) } - let!(:admin) { create(:user, account: account, role: :administrator) } + before do + allow(described_class).to receive(:new).and_return(class_instance) + allow(class_instance).to receive(:smtp_config_set_or_development?).and_return(true) + account.custom_attributes['marked_for_deletion_at'] = 7.days.from_now.iso8601 + account.save! + end - describe 'account_deletion' do - let(:reason) { 'manual_deletion' } - let(:mail) { described_class.with(account: account).account_deletion(account, reason) } - let(:deletion_date) { 7.days.from_now.iso8601 } - - before do - account.update!(custom_attributes: { - 'marked_for_deletion_at' => deletion_date, - 'marked_for_deletion_reason' => reason - }) - end - - it 'renders the subject' do - expect(mail.subject).to eq('Your account has been marked for deletion') - end - - it 'renders the receiver email' do - expect(mail.to).to eq([admin.email]) - end - - it 'includes the account name in the email body' do - expect(mail.body.encoded).to include(account.name) - end - - it 'includes the deletion date in the email body' do - expect(mail.body.encoded).to include(deletion_date) - end - - it 'includes a link to cancel the deletion' do - expect(mail.body.encoded).to include('Cancel Account Deletion') - end - - context 'when reason is manual_deletion' do - it 'includes the administrator message' do - expect(mail.body.encoded).to include('This action was requested by one of the administrators of your account') - end - end - - context 'when reason is not manual_deletion' do - let(:reason) { 'inactivity' } - - it 'includes the reason directly' do - expect(mail.body.encoded).to include('Reason for deletion: inactivity') - end + describe '#account_deletion_user_initiated' do + it 'sets the correct subject for user-initiated deletion' do + mail = mailer.account_deletion_user_initiated(account, 'manual_deletion') + expect(mail.subject).to eq('Your Chatwoot account deletion has been scheduled') end end - describe 'contact_import_complete' do - let!(:data_import) { build(:data_import, total_records: 10, processed_records: 8) } - let(:mail) { described_class.with(account: account).contact_import_complete(data_import).deliver_now } - - it 'renders the subject' do - expect(mail.subject).to eq('Contact Import Completed') - end - - it 'renders the processed records' do - expect(mail.body.encoded).to include('Number of records imported: 8') - expect(mail.body.encoded).to include('Number of records failed: 2') - end - - it 'renders the receiver email' do - expect(mail.to).to eq([admin.email]) + describe '#account_deletion_for_inactivity' do + it 'sets the correct subject for system-initiated deletion' do + mail = mailer.account_deletion_for_inactivity(account, 'Account Inactive') + expect(mail.subject).to eq('Your Chatwoot account is scheduled for deletion due to inactivity') end end - describe 'contact_import_failed' do - let(:mail) { described_class.with(account: account).contact_import_failed.deliver_now } - - it 'renders the subject' do - expect(mail.subject).to eq('Contact Import Failed') + describe '#format_deletion_date' do + it 'formats a valid date string' do + date_str = '2024-12-31T23:59:59Z' + formatted = described_class.new.send(:format_deletion_date, date_str) + expect(formatted).to eq('December 31, 2024') end - it 'renders the receiver email' do - expect(mail.to).to eq([admin.email]) - end - end - - describe 'contact_export_complete' do - let!(:file_url) { 'http://test.com/test' } - let(:mail) { described_class.with(account: account).contact_export_complete(file_url, admin.email).deliver_now } - - it 'renders the subject' do - expect(mail.subject).to eq("Your contact's export file is available to download.") + it 'handles blank dates' do + formatted = described_class.new.send(:format_deletion_date, nil) + expect(formatted).to eq('Unknown') end - it 'renders the receiver email' do - expect(mail.to).to eq([admin.email]) - end - end - - describe 'automation_rule_disabled' do - let(:rule) { instance_double(AutomationRule, name: 'Test Rule') } - let(:mail) { described_class.with(account: account).automation_rule_disabled(rule).deliver_now } - - it 'renders the subject' do - expect(mail.subject).to eq('Automation rule disabled due to validation errors.') - end - - it 'renders the receiver email' do - expect(mail.to).to eq([admin.email]) - end - - it 'includes the rule name in the email body' do - expect(mail.body.encoded).to include('Test Rule') + it 'handles invalid dates' do + formatted = described_class.new.send(:format_deletion_date, 'invalid-date') + expect(formatted).to eq('Unknown') end end end