chore: Update account deletion email copy (#12317)

Update the email copies for account deletion emails 

## Manual Deletion 

<img width="1378" height="678" alt="Screenshot 2025-08-29 at 2 41 40 PM"
src="https://github.com/user-attachments/assets/63d7ad97-ad51-4a8b-9ef3-d427fa467f8a"
/>

## Inactive Deletion

<img width="2946" height="1808" alt="image"
src="https://github.com/user-attachments/assets/bb50d08c-8701-4f93-af29-0b1d948a4009"
/>

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Sojan Jose <sojan@pepalo.com>
This commit is contained in:
Pranav
2025-08-31 07:01:41 -07:00
committed by GitHub
parent 0a9edd4c3b
commit e863a52262
7 changed files with 118 additions and 124 deletions

View File

@@ -1,10 +1,22 @@
class AdministratorNotifications::AccountNotificationMailer < AdministratorNotifications::BaseMailer class AdministratorNotifications::AccountNotificationMailer < AdministratorNotifications::BaseMailer
def account_deletion(account, reason = 'manual_deletion') def account_deletion_user_initiated(account, reason)
subject = 'Your account has been marked for deletion' subject = 'Your Chatwoot account deletion has been scheduled'
action_url = settings_url('general') action_url = settings_url('general')
meta = { meta = {
'account_name' => account.name, '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 'reason' => reason
} }
@@ -45,4 +57,14 @@ class AdministratorNotifications::AccountNotificationMailer < AdministratorNotif
send_notification(subject, action_url: action_url, meta: meta) send_notification(subject, action_url: action_url, meta: meta)
end 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 end

View File

@@ -1,16 +0,0 @@
<p>Hello,</p>
<p>Your account <strong>{{ meta.account_name }}</strong> has been marked for deletion. The account will be permanently deleted on <strong>{{ meta.deletion_date }}</strong>.</p>
{% if meta.reason == 'manual_deletion' %}
<p>This action was requested by one of the administrators of your account.</p>
{% else %}
<p>Reason for deletion: {{ meta.reason }}</p>
{% endif %}
<p>If this was done in error, you can cancel the deletion process by visiting your account settings.</p>
<p><a href="{{ action_url }}">Cancel Account Deletion</a></p>
<p>Thank you,<br>
Team Chatwoot</p>

View File

@@ -0,0 +1,21 @@
<p>Hello there,</p>
<p>We've noticed that your Chatwoot account <strong>{{ meta.account_name }}</strong> has been inactive for some time. Because of this, it's scheduled for deletion on <strong>{{ meta.deletion_date }}</strong>.</p>
<p><strong>How do I keep my account?</strong></p>
<p>Log in to your Chatwoot account before <strong>{{ meta.deletion_date }}</strong>. From your account settings, you can <a href="{{ action_url }}">cancel the deletion</a> and continue using your account.</p>
<p><strong>What happens if I don't cancel?</strong></p>
<p>Unless you cancel the account deletion before <strong>{{ meta.deletion_date }}</strong>, your account and all associated data — including conversations, contacts, reports, and settings — will be permanently deleted.</p>
<p><strong>Why are we doing this?</strong></p>
<p>To keep things secure and efficient, we regularly remove inactive accounts so our systems remain optimized for active teams.</p>
<p>If you have any questions, feel free to reach us at <a href="mailto:hello@chatwoot.com">hello@chatwoot.com</a>.</p>
<p>— The Chatwoot Team</p>

View File

@@ -0,0 +1,16 @@
<p>Hello there,</p>
<p>An account administrator has requested deletion of the Chatwoot account <strong>{{ meta.account_name }}</strong>. The account is scheduled for deletion on <strong>{{ meta.deletion_date }}</strong>.</p>
<p><strong>What happens next?</strong></p>
<ul>
<li>The account will remain accessible until the scheduled deletion date.</li>
<li>After that, all data including conversations, contacts, integrations, and settings will be permanently removed.</li>
</ul>
<p>If you change your mind before the deletion date, you can <a href="{{ action_url }}">cancel this request</a> by visiting your account settings.</p>
<p>— The Chatwoot Team</p>

View File

@@ -4,10 +4,22 @@ module Enterprise::Account
def manually_managed_features; end def manually_managed_features; end
def mark_for_deletion(reason = 'manual_deletion') 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 # 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 result
end end

View File

@@ -229,18 +229,27 @@ RSpec.describe Account, type: :model do
describe '#mark_for_deletion' do describe '#mark_for_deletion' do
it 'sets the marked_for_deletion_at and marked_for_deletion_reason attributes' do it 'sets the marked_for_deletion_at and marked_for_deletion_reason attributes' do
expect 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) 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 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 mailer = double
expect(AdministratorNotifications::AccountNotificationMailer).to receive(:with).with(account: account).and_return(mailer) 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) 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 end
it 'returns true when successful' do it 'returns true when successful' do

View File

@@ -1,116 +1,46 @@
require 'rails_helper' require 'rails_helper'
require Rails.root.join 'spec/mailers/administrator_notifications/shared/smtp_config_shared.rb'
RSpec.describe AdministratorNotifications::AccountNotificationMailer do 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) } before do
let!(:admin) { create(:user, account: account, role: :administrator) } 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 describe '#account_deletion_user_initiated' do
let(:reason) { 'manual_deletion' } it 'sets the correct subject for user-initiated deletion' do
let(:mail) { described_class.with(account: account).account_deletion(account, reason) } mail = mailer.account_deletion_user_initiated(account, 'manual_deletion')
let(:deletion_date) { 7.days.from_now.iso8601 } expect(mail.subject).to eq('Your Chatwoot account deletion has been scheduled')
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
end end
end end
describe 'contact_import_complete' do describe '#account_deletion_for_inactivity' do
let!(:data_import) { build(:data_import, total_records: 10, processed_records: 8) } it 'sets the correct subject for system-initiated deletion' do
let(:mail) { described_class.with(account: account).contact_import_complete(data_import).deliver_now } mail = mailer.account_deletion_for_inactivity(account, 'Account Inactive')
expect(mail.subject).to eq('Your Chatwoot account is scheduled for deletion due to inactivity')
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])
end end
end end
describe 'contact_import_failed' do describe '#format_deletion_date' do
let(:mail) { described_class.with(account: account).contact_import_failed.deliver_now } it 'formats a valid date string' do
date_str = '2024-12-31T23:59:59Z'
it 'renders the subject' do formatted = described_class.new.send(:format_deletion_date, date_str)
expect(mail.subject).to eq('Contact Import Failed') expect(formatted).to eq('December 31, 2024')
end end
it 'renders the receiver email' do it 'handles blank dates' do
expect(mail.to).to eq([admin.email]) formatted = described_class.new.send(:format_deletion_date, nil)
end expect(formatted).to eq('Unknown')
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.")
end end
it 'renders the receiver email' do it 'handles invalid dates' do
expect(mail.to).to eq([admin.email]) formatted = described_class.new.send(:format_deletion_date, 'invalid-date')
end expect(formatted).to eq('Unknown')
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')
end end
end end
end end