Skip to content

Instantly share code, notes, and snippets.

@vishnu-narayanan
Created February 9, 2026 11:21
Show Gist options
  • Select an option

  • Save vishnu-narayanan/9091e277f966eab6d3bc6bdc177e060d to your computer and use it in GitHub Desktop.

Select an option

Save vishnu-narayanan/9091e277f966eab6d3bc6bdc177e060d to your computer and use it in GitHub Desktop.
delete conversations older than x months
# Purge conversations created before a given cutoff date.
# Copy this script to the ./scripts directory and run it with:
# Usage:
# bundle exec rails runner scripts/purge_old_conversations.rb
#
# Ensure
# you have a full database backup (pg_dump) before running.
# scale up sidekiq workers to ensure you have capacity for background deletion jobs this will enqueue
BATCH_SIZE = 500
DEFAULT_CUTOFF_MONTHS = 24
print 'Enter Account ID (leave blank for all accounts): '
account_input = $stdin.gets.strip
account = account_input.present? ? Account.find(account_input.to_i) : nil
print "Enter cutoff in months (default: #{DEFAULT_CUTOFF_MONTHS}): "
months_input = $stdin.gets.strip
months = months_input.present? ? months_input.to_i : DEFAULT_CUTOFF_MONTHS
cutoff_date = months.months.ago
scope = account ? account.conversations : Conversation.all
scope = scope.where('conversations.created_at < ?', cutoff_date)
puts "\nConversations created before #{cutoff_date.to_date}:"
puts '-' * 60
if account
puts " #{account.name} (ID: #{account.id}): #{scope.count}"
else
scope.joins(:account).group('accounts.name', 'accounts.id').count
.sort_by { |_, count| -count }
.each { |(name, id), count| puts " #{name} (ID: #{id}): #{count}" }
end
total = scope.count
puts '-' * 60
puts "Total: #{total}"
if total.zero?
puts 'Nothing to delete.'
exit
end
puts "\nThis will enqueue conversations for async deletion via Sidekiq."
puts 'Ensure you have a database backup before proceeding.'
print 'Do you want to proceed? (y/N): '
unless %w[y yes].include?($stdin.gets.strip.downcase)
puts 'Aborted.'
exit
end
total_enqueued = 0
scope.find_in_batches(batch_size: BATCH_SIZE) do |batch|
batch.each { |conversation| DeleteObjectJob.perform_later(conversation) }
total_enqueued += batch.size
puts "Enqueued #{total_enqueued}/#{total}..."
# sleep 10 # Uncomment to throttle if Sidekiq queues are backing up
end
puts "Done. #{total_enqueued} conversations enqueued for deletion."
puts 'Monitor the Sidekiq low queue for progress.'
@vishnu-narayanan
Copy link
Author

vishnu-narayanan commented Feb 9, 2026

Deleting Old Conversations

This script deletes conversations (and all associated messages, attachments, etc.) created before a specified cutoff date.

Before You Start

  1. Take a full database backup

  2. Ensure Sidekiq is running and scaled up
    Deletions are processed asynchronously on the low queue. Each conversation deletion triggers background jobs for cleaning up messages, attachments, notifications, etc. Scale up your Sidekiq workers to handle the load.

Running the Script

  1. Copy purge_old_conversations.rb to your Chatwoot installation directory(chatwoot/scripts/).

  2. Run:

    bundle exec rails runner scripts/purge_old_conversations.rb
  3. The script will prompt you for:

    • Account ID — leave blank to run across all accounts
    • Cutoff in months — default is 24 (2 years)
  4. It will show a preview of how many conversations will be deleted per account and ask for confirmation before proceeding.

Monitoring Progress

  • Check the Sidekiq dashboard at /monitoring/sidekiq on your Chatwoot instance

If Sidekiq jobs are queueing up

  1. Scale up Sidekiq workers
  2. Open the script and uncomment this line:
# sleep 10 # Uncomment to throttle if Sidekiq queues are backing up

Adjust the value (in seconds) as needed to add a delay between batches.

After Completion

Once the Sidekiq low queue is fully drained, run the following in psql to reclaim disk space:

VACUUM ANALYZE conversations, messages;

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment