diff --git a/Session.xcodeproj/project.pbxproj b/Session.xcodeproj/project.pbxproj
index 7c9176f25..95da2432b 100644
--- a/Session.xcodeproj/project.pbxproj
+++ b/Session.xcodeproj/project.pbxproj
@@ -607,7 +607,6 @@
 		FD17D7B327F51E5B00122BE0 /* SSKSetting.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD17D7B227F51E5B00122BE0 /* SSKSetting.swift */; };
 		FD17D7B827F51ECA00122BE0 /* Migration.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD17D7B727F51ECA00122BE0 /* Migration.swift */; };
 		FD17D7BA27F51F2100122BE0 /* TargetMigrations.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD17D7B927F51F2100122BE0 /* TargetMigrations.swift */; };
-		FD17D7BD27F51F6900122BE0 /* GRDB+Notifications.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD17D7BC27F51F6900122BE0 /* GRDB+Notifications.swift */; };
 		FD17D7BF27F51F8200122BE0 /* ColumnExpressible.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD17D7BE27F51F8200122BE0 /* ColumnExpressible.swift */; };
 		FD17D7C127F5200100122BE0 /* TypedTableDefinition.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD17D7C027F5200100122BE0 /* TypedTableDefinition.swift */; };
 		FD17D7C327F5204C00122BE0 /* Database+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD17D7C227F5204C00122BE0 /* Database+Utilities.swift */; };
@@ -785,6 +784,7 @@
 		FDD2506E283711D600198BDA /* DifferenceKit+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDD2506D283711D600198BDA /* DifferenceKit+Utilities.swift */; };
 		FDD250702837199200198BDA /* GarbageCollectionJob.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDD2506F2837199200198BDA /* GarbageCollectionJob.swift */; };
 		FDD250722837234B00198BDA /* MediaGalleryNavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDD250712837234B00198BDA /* MediaGalleryNavigationController.swift */; };
+		FDE72118286C156E0093DF33 /* ChatSettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDE72117286C156E0093DF33 /* ChatSettingsViewController.swift */; };
 		FDE77F6B280FEB28002CFC5D /* ControlMessageProcessRecord.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDE77F6A280FEB28002CFC5D /* ControlMessageProcessRecord.swift */; };
 		FDED2E3C282E1B5D00B2CD2A /* UICollectionView+ReusableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDED2E3B282E1B5D00B2CD2A /* UICollectionView+ReusableView.swift */; };
 		FDF0B73C27FFD3D6004C14C5 /* LinkPreview.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF0B73B27FFD3D6004C14C5 /* LinkPreview.swift */; };
@@ -1671,7 +1671,6 @@
 		FD17D7B227F51E5B00122BE0 /* SSKSetting.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SSKSetting.swift; sourceTree = "<group>"; };
 		FD17D7B727F51ECA00122BE0 /* Migration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Migration.swift; sourceTree = "<group>"; };
 		FD17D7B927F51F2100122BE0 /* TargetMigrations.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TargetMigrations.swift; sourceTree = "<group>"; };
-		FD17D7BC27F51F6900122BE0 /* GRDB+Notifications.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "GRDB+Notifications.swift"; sourceTree = "<group>"; };
 		FD17D7BE27F51F8200122BE0 /* ColumnExpressible.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ColumnExpressible.swift; sourceTree = "<group>"; };
 		FD17D7C027F5200100122BE0 /* TypedTableDefinition.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TypedTableDefinition.swift; sourceTree = "<group>"; };
 		FD17D7C227F5204C00122BE0 /* Database+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Database+Utilities.swift"; sourceTree = "<group>"; };
@@ -1819,6 +1818,7 @@
 		FDD2506D283711D600198BDA /* DifferenceKit+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DifferenceKit+Utilities.swift"; sourceTree = "<group>"; };
 		FDD2506F2837199200198BDA /* GarbageCollectionJob.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GarbageCollectionJob.swift; sourceTree = "<group>"; };
 		FDD250712837234B00198BDA /* MediaGalleryNavigationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaGalleryNavigationController.swift; sourceTree = "<group>"; };
+		FDE72117286C156E0093DF33 /* ChatSettingsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatSettingsViewController.swift; sourceTree = "<group>"; };
 		FDE77F68280F9EDA002CFC5D /* JobRunnerError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JobRunnerError.swift; sourceTree = "<group>"; };
 		FDE77F6A280FEB28002CFC5D /* ControlMessageProcessRecord.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ControlMessageProcessRecord.swift; sourceTree = "<group>"; };
 		FDED2E3B282E1B5D00B2CD2A /* UICollectionView+ReusableView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UICollectionView+ReusableView.swift"; sourceTree = "<group>"; };
@@ -2798,6 +2798,7 @@
 				B886B4A62398B23E00211ABE /* QRCodeVC.swift */,
 				B86BD08523399CEF000F5AE3 /* SeedModal.swift */,
 				B8CCF6422397711F0091D419 /* SettingsVC.swift */,
+				FDE72117286C156E0093DF33 /* ChatSettingsViewController.swift */,
 				7B7CB18A270591630079FF93 /* ShareLogsModal.swift */,
 			);
 			path = Settings;
@@ -3544,7 +3545,6 @@
 				FD17D7C427F5206300122BE0 /* ColumnDefinition+Utilities.swift */,
 				FD17D7C227F5204C00122BE0 /* Database+Utilities.swift */,
 				FD17D7C627F5207C00122BE0 /* DatabaseMigrator+Utilities.swift */,
-				FD17D7BC27F51F6900122BE0 /* GRDB+Notifications.swift */,
 				FDF22210281B5E0B000A4995 /* TableRecord+Utilities.swift */,
 				FDF2220E281B55E6000A4995 /* QueryInterfaceRequest+Utilities.swift */,
 			);
@@ -5011,7 +5011,6 @@
 				FD9004152818B46300ABAAF6 /* JobRunner.swift in Sources */,
 				C3A7211A2558BCA10043A11F /* DiffieHellman.swift in Sources */,
 				C3A7225E2558C38D0043A11F /* Promise+Retaining.swift in Sources */,
-				FD17D7BD27F51F6900122BE0 /* GRDB+Notifications.swift in Sources */,
 				FD17D7CA27F546D900122BE0 /* _001_InitialSetupMigration.swift in Sources */,
 				C3D9E4D12567777D0040E4F3 /* OWSMediaUtils.swift in Sources */,
 				C3BBE0AA2554D4DE0050F1E3 /* Dictionary+Utilities.swift in Sources */,
@@ -5276,6 +5275,7 @@
 				EF764C351DB67CC5000D9A87 /* UIViewController+Permissions.m in Sources */,
 				45CD81EF1DC030E7004C9430 /* SyncPushTokensJob.swift in Sources */,
 				B83524A525C3BA4B0089A44F /* InfoMessageCell.swift in Sources */,
+				FDE72118286C156E0093DF33 /* ChatSettingsViewController.swift in Sources */,
 				B84A89BC25DE328A0040017D /* ProfilePictureVC.swift in Sources */,
 				34386A54207D271D009F5D9C /* NeverClearView.swift in Sources */,
 				FDCDB8E02811007F00352A0C /* HomeViewModel.swift in Sources */,
diff --git a/Session/Conversations/ConversationVC+Interaction.swift b/Session/Conversations/ConversationVC+Interaction.swift
index bb76b60dc..2aea6b825 100644
--- a/Session/Conversations/ConversationVC+Interaction.swift
+++ b/Session/Conversations/ConversationVC+Interaction.swift
@@ -942,7 +942,8 @@ extension ConversationVC:
                     db,
                     blindedId: sessionId,
                     openGroupServer: openGroupServer,
-                    openGroupPublicKey: openGroupPublicKey
+                    openGroupPublicKey: openGroupPublicKey,
+                    isCheckingForOutbox: false
                 )
             
             return try SessionThread
diff --git a/Session/Conversations/ConversationVC.swift b/Session/Conversations/ConversationVC.swift
index c3c767ae2..73038c611 100644
--- a/Session/Conversations/ConversationVC.swift
+++ b/Session/Conversations/ConversationVC.swift
@@ -571,6 +571,7 @@ final class ConversationVC: BaseVC, OWSConversationSettingsViewDelegate, Convers
         }
         
         if initialLoad || viewModel.threadData.threadIsMessageRequest != updatedThreadData.threadIsMessageRequest {
+            messageRequestView.isHidden = (updatedThreadData.threadIsMessageRequest == false)
             scrollButtonMessageRequestsBottomConstraint?.isActive = (updatedThreadData.threadIsMessageRequest == true)
             scrollButtonBottomConstraint?.isActive = (updatedThreadData.threadIsMessageRequest == false)
         }
@@ -595,8 +596,13 @@ final class ConversationVC: BaseVC, OWSConversationSettingsViewDelegate, Convers
         self.viewModel.updateThreadData(updatedThreadData)
         
         /// **Note:** This needs to happen **after** we have update the viewModel's thread data
-        if viewModel.threadData.currentUserIsClosedGroupMember != updatedThreadData.currentUserIsClosedGroupMember {
-            reloadInputViews()
+        if initialLoad || viewModel.threadData.currentUserIsClosedGroupMember != updatedThreadData.currentUserIsClosedGroupMember {
+            if !self.isFirstResponder {
+                self.becomeFirstResponder()
+            }
+            else {
+                self.reloadInputViews()
+            }
         }
     }
     
diff --git a/Session/Home/HomeVC.swift b/Session/Home/HomeVC.swift
index 53e2b98cf..2c50fa346 100644
--- a/Session/Home/HomeVC.swift
+++ b/Session/Home/HomeVC.swift
@@ -627,6 +627,7 @@ final class HomeVC: BaseVC, UITableViewDataSource, UITableViewDelegate, NewConve
                                     )
                                 )
                             )
+                        
                         try MessageSender.syncConfiguration(db, forceSyncNow: true)
                             .retainUntilComplete()
                     }
diff --git a/Session/Home/HomeViewModel.swift b/Session/Home/HomeViewModel.swift
index a1ff37bfd..8a6022005 100644
--- a/Session/Home/HomeViewModel.swift
+++ b/Session/Home/HomeViewModel.swift
@@ -268,6 +268,7 @@ public class HomeViewModel {
                 SectionModel(
                     section: .threads,
                     elements: data
+                        .filter { $0.id != SessionThreadViewModel.invalidId }
                         .sorted { lhs, rhs -> Bool in
                             if lhs.threadIsPinned && !rhs.threadIsPinned { return true }
                             if !lhs.threadIsPinned && rhs.threadIsPinned { return false }
diff --git a/Session/Meta/AppDelegate.swift b/Session/Meta/AppDelegate.swift
index f090f7728..045566300 100644
--- a/Session/Meta/AppDelegate.swift
+++ b/Session/Meta/AppDelegate.swift
@@ -102,12 +102,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
             name: .registrationStateDidChange,
             object: nil
         )
-        NotificationCenter.default.addObserver(
-            self,
-            selector: #selector(handleDataNukeRequested),   // TODO: This differently???
-            name: .dataNukeRequested,
-            object: nil
-        )
         NotificationCenter.default.addObserver(
             self,
             selector: #selector(showMissedCallTipsIfNeeded(_:)),
@@ -455,35 +449,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
     // MARK: - Notification Handling
     
     @objc private func registrationStateDidChange() {
-        enableBackgroundRefreshIfNecessary()
-
-        guard Identity.userExists() else { return }
-        
-        startPollersIfNeeded()
-    }
-    
-    @objc public func handleDataNukeRequested() {
-        let isUsingFullAPNs: Bool = UserDefaults.standard[.isUsingFullAPNs]
-        let maybeDeviceToken: String? = UserDefaults.standard[.deviceToken]
-        // TODO: Clean up how this works
-        if isUsingFullAPNs, let deviceToken: String = maybeDeviceToken {
-            let data: Data = Data(hex: deviceToken)
-            PushNotificationAPI.unregister(data).retainUntilComplete()
-        }
-        
-        GRDBStorage.shared.write { db in
-            _ = try SessionThread.deleteAll(db)
-            _ = try Identity.deleteAll(db)
-        }
-        
-        SnodeAPI.clearSnodePool()
-        stopPollers()
-        
-        let wasUnlinked: Bool = UserDefaults.standard[.wasUnlinked]
-        SessionApp.resetAppData {
-            // Resetting the data clears the old user defaults. We need to restore the unlink default.
-            UserDefaults.standard[.wasUnlinked] = wasUnlinked
-        }
+        handleActivation()
     }
     
     @objc public func showMissedCallTipsIfNeeded(_ notification: Notification) {
diff --git a/Session/Meta/SessionApp.swift b/Session/Meta/SessionApp.swift
index c223987b2..dadebd048 100644
--- a/Session/Meta/SessionApp.swift
+++ b/Session/Meta/SessionApp.swift
@@ -63,6 +63,7 @@ public struct SessionApp {
 
         GRDBStorage.resetAllStorage()
         ProfileManager.resetProfileStorage()
+        Attachment.resetAttachmentStorage()
         AppEnvironment.shared.notificationPresenter.clearAllNotifications()
 
         onReset?()
diff --git a/Session/Meta/Translations/de.lproj/Localizable.strings b/Session/Meta/Translations/de.lproj/Localizable.strings
index 9eb1c3640..3ebda7272 100644
--- a/Session/Meta/Translations/de.lproj/Localizable.strings
+++ b/Session/Meta/Translations/de.lproj/Localizable.strings
@@ -656,3 +656,7 @@
 "ALERT_ERROR_TITLE" = "Fehler";
 "LOADING_CONVERSATIONS" = "Loading Conversations...";
 "DATABASE_MIGRATION_FAILED" = "An error occurred when optimising the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore our device\n\nWarning: Restoring your device will result in loss of any data older than two weeks";
+"CHATS_TITLE" = "Chats";
+"MESSAGE_TRIMMING_TITLE" = "Message Trimming";
+"MESSAGE_TRIMMING_OPEN_GROUP_TITLE" = "Delete Old Open Group Messages";
+"MESSAGE_TRIMMING_OPEN_GROUP_DESCRIPTION" = "Automatically delete open group messages which are older than 6 months when starting the app";
diff --git a/Session/Meta/Translations/en.lproj/Localizable.strings b/Session/Meta/Translations/en.lproj/Localizable.strings
index 60cee62fc..f46ff4cbf 100644
--- a/Session/Meta/Translations/en.lproj/Localizable.strings
+++ b/Session/Meta/Translations/en.lproj/Localizable.strings
@@ -656,3 +656,7 @@
 "ALERT_ERROR_TITLE" = "Error";
 "LOADING_CONVERSATIONS" = "Loading Conversations...";
 "DATABASE_MIGRATION_FAILED" = "An error occurred when optimising the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore our device\n\nWarning: Restoring your device will result in loss of any data older than two weeks";
+"CHATS_TITLE" = "Chats";
+"MESSAGE_TRIMMING_TITLE" = "Message Trimming";
+"MESSAGE_TRIMMING_OPEN_GROUP_TITLE" = "Delete Old Open Group Messages";
+"MESSAGE_TRIMMING_OPEN_GROUP_DESCRIPTION" = "Automatically delete open group messages which are older than 6 months when starting the app";
diff --git a/Session/Meta/Translations/es.lproj/Localizable.strings b/Session/Meta/Translations/es.lproj/Localizable.strings
index 9ffdb9505..6c0ce3a4f 100644
--- a/Session/Meta/Translations/es.lproj/Localizable.strings
+++ b/Session/Meta/Translations/es.lproj/Localizable.strings
@@ -656,3 +656,7 @@
 "ALERT_ERROR_TITLE" = "Fallo";
 "LOADING_CONVERSATIONS" = "Loading Conversations...";
 "DATABASE_MIGRATION_FAILED" = "An error occurred when optimising the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore our device\n\nWarning: Restoring your device will result in loss of any data older than two weeks";
+"CHATS_TITLE" = "Chats";
+"MESSAGE_TRIMMING_TITLE" = "Message Trimming";
+"MESSAGE_TRIMMING_OPEN_GROUP_TITLE" = "Delete Old Open Group Messages";
+"MESSAGE_TRIMMING_OPEN_GROUP_DESCRIPTION" = "Automatically delete open group messages which are older than 6 months when starting the app";
diff --git a/Session/Meta/Translations/fa.lproj/Localizable.strings b/Session/Meta/Translations/fa.lproj/Localizable.strings
index 0c0b2a806..cf91d20ac 100644
--- a/Session/Meta/Translations/fa.lproj/Localizable.strings
+++ b/Session/Meta/Translations/fa.lproj/Localizable.strings
@@ -656,3 +656,7 @@
 "ALERT_ERROR_TITLE" = "خطاء";
 "LOADING_CONVERSATIONS" = "Loading Conversations...";
 "DATABASE_MIGRATION_FAILED" = "An error occurred when optimising the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore our device\n\nWarning: Restoring your device will result in loss of any data older than two weeks";
+"CHATS_TITLE" = "Chats";
+"MESSAGE_TRIMMING_TITLE" = "Message Trimming";
+"MESSAGE_TRIMMING_OPEN_GROUP_TITLE" = "Delete Old Open Group Messages";
+"MESSAGE_TRIMMING_OPEN_GROUP_DESCRIPTION" = "Automatically delete open group messages which are older than 6 months when starting the app";
diff --git a/Session/Meta/Translations/fi.lproj/Localizable.strings b/Session/Meta/Translations/fi.lproj/Localizable.strings
index 057b87804..c30b60393 100644
--- a/Session/Meta/Translations/fi.lproj/Localizable.strings
+++ b/Session/Meta/Translations/fi.lproj/Localizable.strings
@@ -656,3 +656,7 @@
 "ALERT_ERROR_TITLE" = "Error";
 "LOADING_CONVERSATIONS" = "Loading Conversations...";
 "DATABASE_MIGRATION_FAILED" = "An error occurred when optimising the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore our device\n\nWarning: Restoring your device will result in loss of any data older than two weeks";
+"CHATS_TITLE" = "Chats";
+"MESSAGE_TRIMMING_TITLE" = "Message Trimming";
+"MESSAGE_TRIMMING_OPEN_GROUP_TITLE" = "Delete Old Open Group Messages";
+"MESSAGE_TRIMMING_OPEN_GROUP_DESCRIPTION" = "Automatically delete open group messages which are older than 6 months when starting the app";
diff --git a/Session/Meta/Translations/fr.lproj/Localizable.strings b/Session/Meta/Translations/fr.lproj/Localizable.strings
index 42a04ad7a..820120d7c 100644
--- a/Session/Meta/Translations/fr.lproj/Localizable.strings
+++ b/Session/Meta/Translations/fr.lproj/Localizable.strings
@@ -656,3 +656,7 @@
 "ALERT_ERROR_TITLE" = "Erreur";
 "LOADING_CONVERSATIONS" = "Loading Conversations...";
 "DATABASE_MIGRATION_FAILED" = "An error occurred when optimising the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore our device\n\nWarning: Restoring your device will result in loss of any data older than two weeks";
+"CHATS_TITLE" = "Chats";
+"MESSAGE_TRIMMING_TITLE" = "Message Trimming";
+"MESSAGE_TRIMMING_OPEN_GROUP_TITLE" = "Delete Old Open Group Messages";
+"MESSAGE_TRIMMING_OPEN_GROUP_DESCRIPTION" = "Automatically delete open group messages which are older than 6 months when starting the app";
diff --git a/Session/Meta/Translations/hi.lproj/Localizable.strings b/Session/Meta/Translations/hi.lproj/Localizable.strings
index 6cb8421e2..e862c25c0 100644
--- a/Session/Meta/Translations/hi.lproj/Localizable.strings
+++ b/Session/Meta/Translations/hi.lproj/Localizable.strings
@@ -656,3 +656,7 @@
 "ALERT_ERROR_TITLE" = "Error";
 "LOADING_CONVERSATIONS" = "Loading Conversations...";
 "DATABASE_MIGRATION_FAILED" = "An error occurred when optimising the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore our device\n\nWarning: Restoring your device will result in loss of any data older than two weeks";
+"CHATS_TITLE" = "Chats";
+"MESSAGE_TRIMMING_TITLE" = "Message Trimming";
+"MESSAGE_TRIMMING_OPEN_GROUP_TITLE" = "Delete Old Open Group Messages";
+"MESSAGE_TRIMMING_OPEN_GROUP_DESCRIPTION" = "Automatically delete open group messages which are older than 6 months when starting the app";
diff --git a/Session/Meta/Translations/hr.lproj/Localizable.strings b/Session/Meta/Translations/hr.lproj/Localizable.strings
index 1ea6f38fa..e40b5492e 100644
--- a/Session/Meta/Translations/hr.lproj/Localizable.strings
+++ b/Session/Meta/Translations/hr.lproj/Localizable.strings
@@ -656,3 +656,7 @@
 "ALERT_ERROR_TITLE" = "Error";
 "LOADING_CONVERSATIONS" = "Loading Conversations...";
 "DATABASE_MIGRATION_FAILED" = "An error occurred when optimising the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore our device\n\nWarning: Restoring your device will result in loss of any data older than two weeks";
+"CHATS_TITLE" = "Chats";
+"MESSAGE_TRIMMING_TITLE" = "Message Trimming";
+"MESSAGE_TRIMMING_OPEN_GROUP_TITLE" = "Delete Old Open Group Messages";
+"MESSAGE_TRIMMING_OPEN_GROUP_DESCRIPTION" = "Automatically delete open group messages which are older than 6 months when starting the app";
diff --git a/Session/Meta/Translations/id-ID.lproj/Localizable.strings b/Session/Meta/Translations/id-ID.lproj/Localizable.strings
index d46d1a539..3ffd04b51 100644
--- a/Session/Meta/Translations/id-ID.lproj/Localizable.strings
+++ b/Session/Meta/Translations/id-ID.lproj/Localizable.strings
@@ -656,3 +656,7 @@
 "ALERT_ERROR_TITLE" = "Galat";
 "LOADING_CONVERSATIONS" = "Loading Conversations...";
 "DATABASE_MIGRATION_FAILED" = "An error occurred when optimising the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore our device\n\nWarning: Restoring your device will result in loss of any data older than two weeks";
+"CHATS_TITLE" = "Chats";
+"MESSAGE_TRIMMING_TITLE" = "Message Trimming";
+"MESSAGE_TRIMMING_OPEN_GROUP_TITLE" = "Delete Old Open Group Messages";
+"MESSAGE_TRIMMING_OPEN_GROUP_DESCRIPTION" = "Automatically delete open group messages which are older than 6 months when starting the app";
diff --git a/Session/Meta/Translations/it.lproj/Localizable.strings b/Session/Meta/Translations/it.lproj/Localizable.strings
index d3d10bec1..bbd0183d8 100644
--- a/Session/Meta/Translations/it.lproj/Localizable.strings
+++ b/Session/Meta/Translations/it.lproj/Localizable.strings
@@ -656,3 +656,7 @@
 "ALERT_ERROR_TITLE" = "Errore";
 "LOADING_CONVERSATIONS" = "Loading Conversations...";
 "DATABASE_MIGRATION_FAILED" = "An error occurred when optimising the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore our device\n\nWarning: Restoring your device will result in loss of any data older than two weeks";
+"CHATS_TITLE" = "Chats";
+"MESSAGE_TRIMMING_TITLE" = "Message Trimming";
+"MESSAGE_TRIMMING_OPEN_GROUP_TITLE" = "Delete Old Open Group Messages";
+"MESSAGE_TRIMMING_OPEN_GROUP_DESCRIPTION" = "Automatically delete open group messages which are older than 6 months when starting the app";
diff --git a/Session/Meta/Translations/ja.lproj/Localizable.strings b/Session/Meta/Translations/ja.lproj/Localizable.strings
index 2e984b53e..108805053 100644
--- a/Session/Meta/Translations/ja.lproj/Localizable.strings
+++ b/Session/Meta/Translations/ja.lproj/Localizable.strings
@@ -656,3 +656,7 @@
 "ALERT_ERROR_TITLE" = "エラー";
 "LOADING_CONVERSATIONS" = "Loading Conversations...";
 "DATABASE_MIGRATION_FAILED" = "An error occurred when optimising the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore our device\n\nWarning: Restoring your device will result in loss of any data older than two weeks";
+"CHATS_TITLE" = "Chats";
+"MESSAGE_TRIMMING_TITLE" = "Message Trimming";
+"MESSAGE_TRIMMING_OPEN_GROUP_TITLE" = "Delete Old Open Group Messages";
+"MESSAGE_TRIMMING_OPEN_GROUP_DESCRIPTION" = "Automatically delete open group messages which are older than 6 months when starting the app";
diff --git a/Session/Meta/Translations/nl.lproj/Localizable.strings b/Session/Meta/Translations/nl.lproj/Localizable.strings
index d577ac53e..6aadc1caf 100644
--- a/Session/Meta/Translations/nl.lproj/Localizable.strings
+++ b/Session/Meta/Translations/nl.lproj/Localizable.strings
@@ -656,3 +656,7 @@
 "ALERT_ERROR_TITLE" = "Error";
 "LOADING_CONVERSATIONS" = "Loading Conversations...";
 "DATABASE_MIGRATION_FAILED" = "An error occurred when optimising the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore our device\n\nWarning: Restoring your device will result in loss of any data older than two weeks";
+"CHATS_TITLE" = "Chats";
+"MESSAGE_TRIMMING_TITLE" = "Message Trimming";
+"MESSAGE_TRIMMING_OPEN_GROUP_TITLE" = "Delete Old Open Group Messages";
+"MESSAGE_TRIMMING_OPEN_GROUP_DESCRIPTION" = "Automatically delete open group messages which are older than 6 months when starting the app";
diff --git a/Session/Meta/Translations/pl.lproj/Localizable.strings b/Session/Meta/Translations/pl.lproj/Localizable.strings
index 4b8359749..2e93d89d6 100644
--- a/Session/Meta/Translations/pl.lproj/Localizable.strings
+++ b/Session/Meta/Translations/pl.lproj/Localizable.strings
@@ -656,3 +656,7 @@
 "ALERT_ERROR_TITLE" = "Błąd";
 "LOADING_CONVERSATIONS" = "Loading Conversations...";
 "DATABASE_MIGRATION_FAILED" = "An error occurred when optimising the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore our device\n\nWarning: Restoring your device will result in loss of any data older than two weeks";
+"CHATS_TITLE" = "Chats";
+"MESSAGE_TRIMMING_TITLE" = "Message Trimming";
+"MESSAGE_TRIMMING_OPEN_GROUP_TITLE" = "Delete Old Open Group Messages";
+"MESSAGE_TRIMMING_OPEN_GROUP_DESCRIPTION" = "Automatically delete open group messages which are older than 6 months when starting the app";
diff --git a/Session/Meta/Translations/pt_BR.lproj/Localizable.strings b/Session/Meta/Translations/pt_BR.lproj/Localizable.strings
index 38089a102..3a933083a 100644
--- a/Session/Meta/Translations/pt_BR.lproj/Localizable.strings
+++ b/Session/Meta/Translations/pt_BR.lproj/Localizable.strings
@@ -656,3 +656,7 @@
 "ALERT_ERROR_TITLE" = "Erro";
 "LOADING_CONVERSATIONS" = "Loading Conversations...";
 "DATABASE_MIGRATION_FAILED" = "An error occurred when optimising the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore our device\n\nWarning: Restoring your device will result in loss of any data older than two weeks";
+"CHATS_TITLE" = "Chats";
+"MESSAGE_TRIMMING_TITLE" = "Message Trimming";
+"MESSAGE_TRIMMING_OPEN_GROUP_TITLE" = "Delete Old Open Group Messages";
+"MESSAGE_TRIMMING_OPEN_GROUP_DESCRIPTION" = "Automatically delete open group messages which are older than 6 months when starting the app";
diff --git a/Session/Meta/Translations/ru.lproj/Localizable.strings b/Session/Meta/Translations/ru.lproj/Localizable.strings
index 8918c45ec..e6f0421e3 100644
--- a/Session/Meta/Translations/ru.lproj/Localizable.strings
+++ b/Session/Meta/Translations/ru.lproj/Localizable.strings
@@ -656,3 +656,7 @@
 "ALERT_ERROR_TITLE" = "Ошибка";
 "LOADING_CONVERSATIONS" = "Loading Conversations...";
 "DATABASE_MIGRATION_FAILED" = "An error occurred when optimising the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore our device\n\nWarning: Restoring your device will result in loss of any data older than two weeks";
+"CHATS_TITLE" = "Chats";
+"MESSAGE_TRIMMING_TITLE" = "Message Trimming";
+"MESSAGE_TRIMMING_OPEN_GROUP_TITLE" = "Delete Old Open Group Messages";
+"MESSAGE_TRIMMING_OPEN_GROUP_DESCRIPTION" = "Automatically delete open group messages which are older than 6 months when starting the app";
diff --git a/Session/Meta/Translations/si.lproj/Localizable.strings b/Session/Meta/Translations/si.lproj/Localizable.strings
index 92bdd53c6..04d1ea3d2 100644
--- a/Session/Meta/Translations/si.lproj/Localizable.strings
+++ b/Session/Meta/Translations/si.lproj/Localizable.strings
@@ -656,3 +656,7 @@
 "ALERT_ERROR_TITLE" = "Error";
 "LOADING_CONVERSATIONS" = "Loading Conversations...";
 "DATABASE_MIGRATION_FAILED" = "An error occurred when optimising the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore our device\n\nWarning: Restoring your device will result in loss of any data older than two weeks";
+"CHATS_TITLE" = "Chats";
+"MESSAGE_TRIMMING_TITLE" = "Message Trimming";
+"MESSAGE_TRIMMING_OPEN_GROUP_TITLE" = "Delete Old Open Group Messages";
+"MESSAGE_TRIMMING_OPEN_GROUP_DESCRIPTION" = "Automatically delete open group messages which are older than 6 months when starting the app";
diff --git a/Session/Meta/Translations/sk.lproj/Localizable.strings b/Session/Meta/Translations/sk.lproj/Localizable.strings
index 9341ea3a5..d2ed3836a 100644
--- a/Session/Meta/Translations/sk.lproj/Localizable.strings
+++ b/Session/Meta/Translations/sk.lproj/Localizable.strings
@@ -656,3 +656,7 @@
 "ALERT_ERROR_TITLE" = "Error";
 "LOADING_CONVERSATIONS" = "Loading Conversations...";
 "DATABASE_MIGRATION_FAILED" = "An error occurred when optimising the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore our device\n\nWarning: Restoring your device will result in loss of any data older than two weeks";
+"CHATS_TITLE" = "Chats";
+"MESSAGE_TRIMMING_TITLE" = "Message Trimming";
+"MESSAGE_TRIMMING_OPEN_GROUP_TITLE" = "Delete Old Open Group Messages";
+"MESSAGE_TRIMMING_OPEN_GROUP_DESCRIPTION" = "Automatically delete open group messages which are older than 6 months when starting the app";
diff --git a/Session/Meta/Translations/sv.lproj/Localizable.strings b/Session/Meta/Translations/sv.lproj/Localizable.strings
index 2fa616d8c..617b930c5 100644
--- a/Session/Meta/Translations/sv.lproj/Localizable.strings
+++ b/Session/Meta/Translations/sv.lproj/Localizable.strings
@@ -656,3 +656,7 @@
 "ALERT_ERROR_TITLE" = "Error";
 "LOADING_CONVERSATIONS" = "Loading Conversations...";
 "DATABASE_MIGRATION_FAILED" = "An error occurred when optimising the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore our device\n\nWarning: Restoring your device will result in loss of any data older than two weeks";
+"CHATS_TITLE" = "Chats";
+"MESSAGE_TRIMMING_TITLE" = "Message Trimming";
+"MESSAGE_TRIMMING_OPEN_GROUP_TITLE" = "Delete Old Open Group Messages";
+"MESSAGE_TRIMMING_OPEN_GROUP_DESCRIPTION" = "Automatically delete open group messages which are older than 6 months when starting the app";
diff --git a/Session/Meta/Translations/th.lproj/Localizable.strings b/Session/Meta/Translations/th.lproj/Localizable.strings
index c51a61b79..5832a403b 100644
--- a/Session/Meta/Translations/th.lproj/Localizable.strings
+++ b/Session/Meta/Translations/th.lproj/Localizable.strings
@@ -656,3 +656,7 @@
 "ALERT_ERROR_TITLE" = "Error";
 "LOADING_CONVERSATIONS" = "Loading Conversations...";
 "DATABASE_MIGRATION_FAILED" = "An error occurred when optimising the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore our device\n\nWarning: Restoring your device will result in loss of any data older than two weeks";
+"CHATS_TITLE" = "Chats";
+"MESSAGE_TRIMMING_TITLE" = "Message Trimming";
+"MESSAGE_TRIMMING_OPEN_GROUP_TITLE" = "Delete Old Open Group Messages";
+"MESSAGE_TRIMMING_OPEN_GROUP_DESCRIPTION" = "Automatically delete open group messages which are older than 6 months when starting the app";
diff --git a/Session/Meta/Translations/vi-VN.lproj/Localizable.strings b/Session/Meta/Translations/vi-VN.lproj/Localizable.strings
index 185293321..5835cc170 100644
--- a/Session/Meta/Translations/vi-VN.lproj/Localizable.strings
+++ b/Session/Meta/Translations/vi-VN.lproj/Localizable.strings
@@ -656,3 +656,7 @@
 "ALERT_ERROR_TITLE" = "Error";
 "LOADING_CONVERSATIONS" = "Loading Conversations...";
 "DATABASE_MIGRATION_FAILED" = "An error occurred when optimising the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore our device\n\nWarning: Restoring your device will result in loss of any data older than two weeks";
+"CHATS_TITLE" = "Chats";
+"MESSAGE_TRIMMING_TITLE" = "Message Trimming";
+"MESSAGE_TRIMMING_OPEN_GROUP_TITLE" = "Delete Old Open Group Messages";
+"MESSAGE_TRIMMING_OPEN_GROUP_DESCRIPTION" = "Automatically delete open group messages which are older than 6 months when starting the app";
diff --git a/Session/Meta/Translations/zh-Hant.lproj/Localizable.strings b/Session/Meta/Translations/zh-Hant.lproj/Localizable.strings
index f5cb6a74a..333724265 100644
--- a/Session/Meta/Translations/zh-Hant.lproj/Localizable.strings
+++ b/Session/Meta/Translations/zh-Hant.lproj/Localizable.strings
@@ -656,3 +656,7 @@
 "ALERT_ERROR_TITLE" = "Error";
 "LOADING_CONVERSATIONS" = "Loading Conversations...";
 "DATABASE_MIGRATION_FAILED" = "An error occurred when optimising the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore our device\n\nWarning: Restoring your device will result in loss of any data older than two weeks";
+"CHATS_TITLE" = "Chats";
+"MESSAGE_TRIMMING_TITLE" = "Message Trimming";
+"MESSAGE_TRIMMING_OPEN_GROUP_TITLE" = "Delete Old Open Group Messages";
+"MESSAGE_TRIMMING_OPEN_GROUP_DESCRIPTION" = "Automatically delete open group messages which are older than 6 months when starting the app";
diff --git a/Session/Meta/Translations/zh_CN.lproj/Localizable.strings b/Session/Meta/Translations/zh_CN.lproj/Localizable.strings
index 62f75a4ec..637d27c67 100644
--- a/Session/Meta/Translations/zh_CN.lproj/Localizable.strings
+++ b/Session/Meta/Translations/zh_CN.lproj/Localizable.strings
@@ -656,3 +656,7 @@
 "ALERT_ERROR_TITLE" = "错误";
 "LOADING_CONVERSATIONS" = "Loading Conversations...";
 "DATABASE_MIGRATION_FAILED" = "An error occurred when optimising the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore our device\n\nWarning: Restoring your device will result in loss of any data older than two weeks";
+"CHATS_TITLE" = "Chats";
+"MESSAGE_TRIMMING_TITLE" = "Message Trimming";
+"MESSAGE_TRIMMING_OPEN_GROUP_TITLE" = "Delete Old Open Group Messages";
+"MESSAGE_TRIMMING_OPEN_GROUP_DESCRIPTION" = "Automatically delete open group messages which are older than 6 months when starting the app";
diff --git a/Session/Settings/ChatSettingsViewController.swift b/Session/Settings/ChatSettingsViewController.swift
new file mode 100644
index 000000000..1e013aea6
--- /dev/null
+++ b/Session/Settings/ChatSettingsViewController.swift
@@ -0,0 +1,63 @@
+// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
+
+import UIKit
+import SessionUIKit
+import SignalUtilitiesKit
+
+// FIXME: Refactor to be MVVM and use database observation
+class ChatSettingsViewController: OWSTableViewController {
+    // MARK: - Lifecycle
+    
+    override func viewDidLoad() {
+        super.viewDidLoad()
+        
+        self.updateTableContents()
+        
+        ViewControllerUtilities.setUpDefaultSessionStyle(for: self, title: "CHATS_TITLE".localized(), hasCustomBackButton: false)
+        
+        let closeButton: UIBarButtonItem = UIBarButtonItem(image: UIImage(named: "X"), style: .plain, target: self, action: #selector(close(_:)))
+        self.navigationItem.leftBarButtonItem = closeButton
+    }
+    
+    override func viewDidAppear(_ animated: Bool) {
+        super.viewDidAppear(animated)
+        
+        self.updateTableContents()
+    }
+    
+    // MARK: - Table Contents
+    
+    func updateTableContents() {
+        let updatedContents: OWSTableContents = OWSTableContents()
+        
+        let messageTrimming: OWSTableSection = OWSTableSection()
+        messageTrimming.headerTitle = "MESSAGE_TRIMMING_TITLE".localized()
+        messageTrimming.footerTitle = "MESSAGE_TRIMMING_OPEN_GROUP_DESCRIPTION".localized()
+        messageTrimming.add(OWSTableItem.switch(
+            withText: "MESSAGE_TRIMMING_OPEN_GROUP_TITLE".localized(),
+            isOn: { GRDBStorage.shared[.trimOpenGroupMessagesOlderThanSixMonths] },
+            target: self,
+            selector: #selector(didToggleTrimOpenGroupsSwitch(_:))
+        ))
+        updatedContents.addSection(messageTrimming)
+        
+        self.contents = updatedContents
+    }
+
+    // MARK: - Actions
+    
+    @objc private func didToggleTrimOpenGroupsSwitch(_ sender: UISwitch) {
+        GRDBStorage.shared.writeAsync(
+            updates: { db in
+                db[.trimOpenGroupMessagesOlderThanSixMonths] = !sender.isOn
+            },
+            completion: { [weak self] _, _ in
+                self?.updateTableContents()
+            }
+        )
+    }
+    
+    @objc private func close(_ sender: UIBarButtonItem) {
+        self.navigationController?.dismiss(animated: true)
+    }
+}
diff --git a/Session/Settings/NukeDataModal.swift b/Session/Settings/NukeDataModal.swift
index 06a35c38c..823c7f474 100644
--- a/Session/Settings/NukeDataModal.swift
+++ b/Session/Settings/NukeDataModal.swift
@@ -157,11 +157,8 @@ final class NukeDataModal: Modal {
             GRDBStorage.shared
                 .writeAsync { db in try MessageSender.syncConfiguration(db, forceSyncNow: true) }
                 .ensure(on: DispatchQueue.main) {
+                    self?.deleteAllLocalData()
                     self?.dismiss(animated: true, completion: nil) // Dismiss the loader
-                    
-                    UserDefaults.removeAll() // Not done in the nuke data implementation as unlinking requires this to happen later
-                    General.cache.mutate { $0.encodedPublicKey = nil } // Remove the cached key so it gets re-cached on next access
-                    NotificationCenter.default.post(name: .dataNukeRequested, object: nil)
                 }
                 .retainUntilComplete()
         }
@@ -177,9 +174,7 @@ final class NukeDataModal: Modal {
                         let potentiallyMaliciousSnodes = confirmations.compactMap { $0.value == false ? $0.key : nil }
                         
                         if potentiallyMaliciousSnodes.isEmpty {
-                            General.cache.mutate { $0.encodedPublicKey = nil } // Remove the cached key so it gets re-cached on next access
-                            UserDefaults.removeAll() // Not done in the nuke data implementation as unlinking requires this to happen later
-                            NotificationCenter.default.post(name: .dataNukeRequested, object: nil)
+                            self?.deleteAllLocalData()
                         }
                         else {
                             let message: String
@@ -205,4 +200,36 @@ final class NukeDataModal: Modal {
                     }
             }
     }
+    
+    private func deleteAllLocalData() {
+        // Unregister push notifications if needed
+        let isUsingFullAPNs: Bool = UserDefaults.standard[.isUsingFullAPNs]
+        let maybeDeviceToken: String? = UserDefaults.standard[.deviceToken]
+        
+        if isUsingFullAPNs, let deviceToken: String = maybeDeviceToken {
+            let data: Data = Data(hex: deviceToken)
+            PushNotificationAPI.unregister(data).retainUntilComplete()
+        }
+        
+        // Clear out the user defaults
+        UserDefaults.removeAll()
+        
+        // Remove the cached key so it gets re-cached on next access
+        General.cache.mutate { $0.encodedPublicKey = nil }
+        
+        // Clear the Snode pool
+        SnodeAPI.clearSnodePool()
+        
+        // Stop any pollers
+        (UIApplication.shared.delegate as? AppDelegate)?.stopPollers()
+        
+        // Call through to the SessionApp's "resetAppData" which will wipe out logs, database and
+        // profile storage
+        let wasUnlinked: Bool = UserDefaults.standard[.wasUnlinked]
+        
+        SessionApp.resetAppData {
+            // Resetting the data clears the old user defaults. We need to restore the unlink default.
+            UserDefaults.standard[.wasUnlinked] = wasUnlinked
+        }
+    }
 }
diff --git a/Session/Settings/SettingsVC.swift b/Session/Settings/SettingsVC.swift
index 2cd3a4799..56e8b4500 100644
--- a/Session/Settings/SettingsVC.swift
+++ b/Session/Settings/SettingsVC.swift
@@ -322,6 +322,8 @@ final class SettingsVC: BaseVC, AvatarViewHelperDelegate {
             getSeparator(),
             getSettingButton(withTitle: NSLocalizedString("MESSAGE_REQUESTS_TITLE", comment: ""), color: Colors.text, action: #selector(showMessageRequests)),
             getSeparator(),
+            getSettingButton(withTitle: NSLocalizedString("CHATS_TITLE", comment: ""), color: Colors.text, action: #selector(showChatSettings)),
+            getSeparator(),
             getSettingButton(withTitle: NSLocalizedString("vc_settings_recovery_phrase_button_title", comment: ""), color: Colors.text, action: #selector(showSeed)),
             getSeparator(),
             getSettingButton(withTitle: NSLocalizedString("vc_settings_clear_all_data_button_title", comment: ""), color: Colors.destructive, action: #selector(clearAllData)),
@@ -629,6 +631,11 @@ final class SettingsVC: BaseVC, AvatarViewHelperDelegate {
         self.navigationController?.pushViewController(viewController, animated: true)
     }
     
+    @objc private func showChatSettings() {
+        let chatSettingsVC = ChatSettingsViewController()
+        navigationController!.pushViewController(chatSettingsVC, animated: true)
+    }
+    
     @objc private func showSeed() {
         let seedModal = SeedModal()
         seedModal.modalPresentationStyle = .overFullScreen
diff --git a/Session/Shared/FullConversationCell.swift b/Session/Shared/FullConversationCell.swift
index fa9973805..e8beb16b1 100644
--- a/Session/Shared/FullConversationCell.swift
+++ b/Session/Shared/FullConversationCell.swift
@@ -433,6 +433,7 @@ public final class FullConversationCell: UITableViewCell {
                 in: Interaction.previewText(
                     variant: (cellViewModel.interactionVariant ?? .standardIncoming),
                     body: cellViewModel.interactionBody,
+                    threadContactDisplayName: cellViewModel.threadContactName(),
                     authorDisplayName: cellViewModel.authorName(for: cellViewModel.threadVariant),
                     attachmentDescriptionInfo: cellViewModel.interactionAttachmentDescriptionInfo,
                     attachmentCount: cellViewModel.interactionAttachmentCount,
diff --git a/SessionMessagingKit/Calls/WebRTCSession.swift b/SessionMessagingKit/Calls/WebRTCSession.swift
index 20345393c..3f82bda0c 100644
--- a/SessionMessagingKit/Calls/WebRTCSession.swift
+++ b/SessionMessagingKit/Calls/WebRTCSession.swift
@@ -169,6 +169,7 @@ public final class WebRTCSession : NSObject, RTCPeerConnectionDelegate {
                     return seal.reject(error)
                 }
             }
+            
             GRDBStorage.shared
                 .writeAsync { db in
                     try MessageSender
diff --git a/SessionMessagingKit/Database/Migrations/_002_SetupStandardJobs.swift b/SessionMessagingKit/Database/Migrations/_002_SetupStandardJobs.swift
index 616038fde..ac82f040a 100644
--- a/SessionMessagingKit/Database/Migrations/_002_SetupStandardJobs.swift
+++ b/SessionMessagingKit/Database/Migrations/_002_SetupStandardJobs.swift
@@ -48,7 +48,7 @@ enum _002_SetupStandardJobs: Migration {
             
             _ = try Job(
                 variant: .garbageCollection,
-                behaviour: .recurringOnLaunch,
+                behaviour: .recurringOnActive,
                 details: GarbageCollectionJob.Details(
                     typesToCollect: GarbageCollectionJob.Types.allCases
                 )
diff --git a/SessionMessagingKit/Database/Models/Attachment.swift b/SessionMessagingKit/Database/Models/Attachment.swift
index 7b69b5e19..c04ea6aef 100644
--- a/SessionMessagingKit/Database/Models/Attachment.swift
+++ b/SessionMessagingKit/Database/Models/Attachment.swift
@@ -263,6 +263,7 @@ extension Attachment: CustomStringConvertible {
         // We only support multi-attachment sending of images so we can just default to the image attachment
         // if there were multiple attachments
         guard count == 1 else { return "\(emoji(for: OWSMimeTypeImageJpeg)) \("ATTACHMENT".localized())" }
+        
         if MIMETypeUtil.isAudio(descriptionInfo.contentType) {
             // a missing filename is the legacy way to determine if an audio attachment is
             // a voice note vs. other arbitrary audio attachments.
@@ -583,12 +584,9 @@ extension Attachment {
         return attachmentsFolder
     }()
     
-    private static var thumbnailsFolder: String = {
-        let attachmentsFolder: String = sharedDataAttachmentsDirPath
-        OWSFileSystem.ensureDirectoryExists(attachmentsFolder)
-        
-        return attachmentsFolder
-    }()
+    public static func resetAttachmentStorage() {
+        try? FileManager.default.removeItem(atPath: Attachment.sharedDataAttachmentsDirPath)
+    }
     
     public static func originalFilePath(id: String, mimeType: String, sourceFilename: String?) -> String? {
         return MIMETypeUtil.filePath(
diff --git a/SessionMessagingKit/Database/Models/BlindedIdLookup.swift b/SessionMessagingKit/Database/Models/BlindedIdLookup.swift
index 79d033931..6d63624ae 100644
--- a/SessionMessagingKit/Database/Models/BlindedIdLookup.swift
+++ b/SessionMessagingKit/Database/Models/BlindedIdLookup.swift
@@ -74,6 +74,7 @@ public extension BlindedIdLookup {
         blindedId: String,
         openGroupServer: String,
         openGroupPublicKey: String,
+        isCheckingForOutbox: Bool,
         dependencies: SMKDependencies = SMKDependencies()
     ) throws -> BlindedIdLookup {
         var lookup: BlindedIdLookup = (try? BlindedIdLookup
@@ -92,11 +93,11 @@ public extension BlindedIdLookup {
         // We now need to try to match the blinded id to an existing contact, this can only be done by looping
         // through all approved contacts and generating a blinded id for the provided open group for each to
         // see if it matches the provided blindedId
-        let approvedContactCursor: RecordCursor<Contact> = try Contact
-            .filter(Contact.Columns.isApproved == true)
+        let contactsThatApprovedMeCursor: RecordCursor<Contact> = try Contact
+            .filter(Contact.Columns.didApproveMe == true)
             .fetchCursor(db)
-            
-        while let contact: Contact = try approvedContactCursor.next() {
+        
+        while let contact: Contact = try contactsThatApprovedMeCursor.next() {
             guard dependencies.sodium.sessionId(contact.id, matchesBlindedId: blindedId, serverPublicKey: openGroupPublicKey, genericHash: dependencies.genericHash) else {
                 continue
             }
@@ -105,6 +106,16 @@ public extension BlindedIdLookup {
             lookup = try lookup
                 .with(sessionId: contact.id)
                 .saved(db)
+            
+            // There is an edge-case where the contact might not have their 'isApproved' flag set to true
+            // but if we have a `BlindedIdLookup` for them and are performing the lookup from the outbox
+            // then that means we sent them a message request and the 'isApproved' flag should be true
+            if isCheckingForOutbox && !contact.isApproved {
+                try Contact
+                    .filter(id: contact.id)
+                    .updateAll(db, Contact.Columns.isApproved.set(to: true))
+            }
+            
             break
         }
         
diff --git a/SessionMessagingKit/Database/Models/Interaction.swift b/SessionMessagingKit/Database/Models/Interaction.swift
index d082e108d..238816699 100644
--- a/SessionMessagingKit/Database/Models/Interaction.swift
+++ b/SessionMessagingKit/Database/Models/Interaction.swift
@@ -690,6 +690,7 @@ public extension Interaction {
     static func previewText(
         variant: Variant,
         body: String?,
+        threadContactDisplayName: String = "",
         authorDisplayName: String = "",
         attachmentDescriptionInfo: Attachment.DescriptionInfo? = nil,
         attachmentCount: Int? = nil,
@@ -764,7 +765,7 @@ public extension Interaction {
                     )
                 else { return (body ?? "") }
                 
-                return messageInfo.previewText(authorDisplayName: authorDisplayName)
+                return messageInfo.previewText(threadContactDisplayName: threadContactDisplayName)
         }
     }
     
diff --git a/SessionMessagingKit/Jobs/Types/GarbageCollectionJob.swift b/SessionMessagingKit/Jobs/Types/GarbageCollectionJob.swift
index 42c8de672..78225cf0c 100644
--- a/SessionMessagingKit/Jobs/Types/GarbageCollectionJob.swift
+++ b/SessionMessagingKit/Jobs/Types/GarbageCollectionJob.swift
@@ -52,7 +52,7 @@ public enum GarbageCollectionJob: JobExecutor {
                 }
                 
                 /// Remove any old open group messages - open group messages which are older than six months
-                if details.typesToCollect.contains(.oldOpenGroupMessages) {
+                if details.typesToCollect.contains(.oldOpenGroupMessages) && db[.trimOpenGroupMessagesOlderThanSixMonths] {
                     let interaction: TypedTableAlias<Interaction> = TypedTableAlias()
                     let thread: TypedTableAlias<SessionThread> = TypedTableAlias()
                     
diff --git a/SessionMessagingKit/Messages/Control Messages/CallMessage.swift b/SessionMessagingKit/Messages/Control Messages/CallMessage.swift
index e53aa06fc..533f771ba 100644
--- a/SessionMessagingKit/Messages/Control Messages/CallMessage.swift	
+++ b/SessionMessagingKit/Messages/Control Messages/CallMessage.swift	
@@ -235,24 +235,24 @@ public extension CallMessage {
         
         // MARK: - Content
         
-        func previewText(authorDisplayName: String) -> String {
+        func previewText(threadContactDisplayName: String) -> String {
             switch state {
                 case .incoming:
                     return String(
                         format: "call_incoming".localized(),
-                        authorDisplayName
+                        threadContactDisplayName
                     )
                     
                 case .outgoing:
                     return String(
                         format: "call_outgoing".localized(),
-                        authorDisplayName
+                        threadContactDisplayName
                     )
                     
                 case .missed, .permissionDenied:
                     return String(
                         format: "call_missed".localized(),
-                        authorDisplayName
+                        threadContactDisplayName
                     )
                 
                 // TODO: We should do better here
diff --git a/SessionMessagingKit/Open Groups/OpenGroupManager.swift b/SessionMessagingKit/Open Groups/OpenGroupManager.swift
index 94a9d099b..57da8bfd3 100644
--- a/SessionMessagingKit/Open Groups/OpenGroupManager.swift	
+++ b/SessionMessagingKit/Open Groups/OpenGroupManager.swift	
@@ -582,7 +582,9 @@ public final class OpenGroupManager: NSObject {
                             db,
                             blindedId: message.recipient,
                             openGroupServer: server.lowercased(),
-                            openGroupPublicKey: openGroup.publicKey
+                            openGroupPublicKey: openGroup.publicKey,
+                            isCheckingForOutbox: true,
+                            dependencies: dependencies
                         )
                     }()
                     let syncTarget: String = (lookup.sessionId ?? message.recipient)
diff --git a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+ConfigurationMessages.swift b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+ConfigurationMessages.swift
index 9fc4b1ce5..1259720ae 100644
--- a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+ConfigurationMessages.swift	
+++ b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+ConfigurationMessages.swift	
@@ -51,6 +51,20 @@ extension MessageReceiver {
             try message.contacts.forEach { contactInfo in
                 guard let sessionId: String = contactInfo.publicKey else { return }
                 
+                // If the contact is a blinded contact then only add them if they haven't already been
+                // unblinded
+                if SessionId.Prefix(from: sessionId) == .blinded {
+                    let hasUnblindedContact: Bool = (try? BlindedIdLookup
+                        .filter(BlindedIdLookup.Columns.blindedId == sessionId)
+                        .filter(BlindedIdLookup.Columns.sessionId != nil)
+                        .isNotEmpty(db))
+                        .defaulting(to: false)
+                    
+                    if hasUnblindedContact {
+                        return
+                    }
+                }
+                
                 // Note: We only update the contact and profile records if the data has actually changed
                 // in order to avoid triggering UI updates for every thread on the home screen
                 let contact: Contact = Contact.fetchOrCreate(db, id: sessionId)
diff --git a/SessionMessagingKit/Sending & Receiving/MessageSender.swift b/SessionMessagingKit/Sending & Receiving/MessageSender.swift
index a1c045c46..f470754c0 100644
--- a/SessionMessagingKit/Sending & Receiving/MessageSender.swift	
+++ b/SessionMessagingKit/Sending & Receiving/MessageSender.swift	
@@ -233,10 +233,18 @@ public final class MessageSender {
                                 isSyncMessage: isSyncMessage
                             )
                             
-                            let shouldNotify = (
-                                (message is VisibleMessage || message is UnsendRequest) &&
-                                !isSyncMessage
-                            )
+                            let shouldNotify: Bool = {
+                                switch message {
+                                    case is VisibleMessage, is UnsendRequest: return !isSyncMessage
+                                    case let callMessage as CallMessage:
+                                        switch callMessage.kind {
+                                            case .preOffer: return true
+                                            default: return false
+                                        }
+                                        
+                                    default: return false
+                                }
+                            }()
                             
                             /*
                             if let closedGroupControlMessage = message as? ClosedGroupControlMessage, case .new = closedGroupControlMessage.kind {
diff --git a/SessionMessagingKit/Shared Models/MessageViewModel.swift b/SessionMessagingKit/Shared Models/MessageViewModel.swift
index 2756a29a7..363e98069 100644
--- a/SessionMessagingKit/Shared Models/MessageViewModel.swift	
+++ b/SessionMessagingKit/Shared Models/MessageViewModel.swift	
@@ -10,11 +10,13 @@ fileprivate typealias AttachmentInteractionInfo = MessageViewModel.AttachmentInt
 fileprivate typealias TypingIndicatorInfo = MessageViewModel.TypingIndicatorInfo
 
 public struct MessageViewModel: FetchableRecordWithRowId, Decodable, Equatable, Hashable, Identifiable, Differentiable {
+    public static let threadIdKey: SQL = SQL(stringLiteral: CodingKeys.threadId.stringValue)
     public static let threadVariantKey: SQL = SQL(stringLiteral: CodingKeys.threadVariant.stringValue)
     public static let threadIsTrustedKey: SQL = SQL(stringLiteral: CodingKeys.threadIsTrusted.stringValue)
     public static let threadHasDisappearingMessagesEnabledKey: SQL = SQL(stringLiteral: CodingKeys.threadHasDisappearingMessagesEnabled.stringValue)
     public static let threadOpenGroupServerKey: SQL = SQL(stringLiteral: CodingKeys.threadOpenGroupServer.stringValue)
     public static let threadOpenGroupPublicKeyKey: SQL = SQL(stringLiteral: CodingKeys.threadOpenGroupPublicKey.stringValue)
+    public static let threadContactNameInternalKey: SQL = SQL(stringLiteral: CodingKeys.threadContactNameInternal.stringValue)
     public static let rowIdKey: SQL = SQL(stringLiteral: CodingKeys.rowId.stringValue)
     public static let authorNameInternalKey: SQL = SQL(stringLiteral: CodingKeys.authorNameInternal.stringValue)
     public static let stateKey: SQL = SQL(stringLiteral: CodingKeys.state.stringValue)
@@ -58,11 +60,13 @@ public struct MessageViewModel: FetchableRecordWithRowId, Decodable, Equatable,
     
     // Thread Info
     
+    public let threadId: String
     public let threadVariant: SessionThread.Variant
     public let threadIsTrusted: Bool
     public let threadHasDisappearingMessagesEnabled: Bool
     public let threadOpenGroupServer: String?
     public let threadOpenGroupPublicKey: String?
+    private let threadContactNameInternal: String?
     
     // Interaction Info
     
@@ -133,11 +137,13 @@ public struct MessageViewModel: FetchableRecordWithRowId, Decodable, Equatable,
     
     public func with(attachments: [Attachment]) -> MessageViewModel {
         return MessageViewModel(
+            threadId: self.threadId,
             threadVariant: self.threadVariant,
             threadIsTrusted: self.threadIsTrusted,
             threadHasDisappearingMessagesEnabled: self.threadHasDisappearingMessagesEnabled,
             threadOpenGroupServer: self.threadOpenGroupServer,
             threadOpenGroupPublicKey: self.threadOpenGroupPublicKey,
+            threadContactNameInternal: self.threadContactNameInternal,
             rowId: self.rowId,
             id: self.id,
             variant: self.variant,
@@ -281,11 +287,13 @@ public struct MessageViewModel: FetchableRecordWithRowId, Decodable, Equatable,
         }()
         
         return ViewModel(
+            threadId: self.threadId,
             threadVariant: self.threadVariant,
             threadIsTrusted: self.threadIsTrusted,
             threadHasDisappearingMessagesEnabled: self.threadHasDisappearingMessagesEnabled,
             threadOpenGroupServer: self.threadOpenGroupServer,
             threadOpenGroupPublicKey: self.threadOpenGroupPublicKey,
+            threadContactNameInternal: self.threadContactNameInternal,
             rowId: self.rowId,
             id: self.id,
             variant: self.variant,
@@ -298,6 +306,12 @@ public struct MessageViewModel: FetchableRecordWithRowId, Decodable, Equatable,
                 Interaction.previewText(
                     variant: self.variant,
                     body: self.body,
+                    threadContactDisplayName: Profile.displayName(
+                        for: self.threadVariant,
+                        id: self.threadId,
+                        name: self.threadContactNameInternal,
+                        nickname: nil  // Folded into 'threadContactNameInternal' within the Query
+                    ),
                     authorDisplayName: authorDisplayName,
                     attachmentDescriptionInfo: self.attachments?.first.map { firstAttachment in
                         Attachment.DescriptionInfo(
@@ -428,11 +442,13 @@ public extension MessageViewModel {
     
     // Note: This init method is only used system-created cells or empty states
     init(isTypingIndicator: Bool? = nil) {
+        self.threadId = "INVALID_THREAD_ID"
         self.threadVariant = .contact
         self.threadIsTrusted = false
         self.threadHasDisappearingMessagesEnabled = false
         self.threadOpenGroupServer = nil
         self.threadOpenGroupPublicKey = nil
+        self.threadContactNameInternal = nil
         
         // Interaction Info
         
@@ -552,6 +568,10 @@ public extension MessageViewModel {
             let quote: TypedTableAlias<Quote> = TypedTableAlias()
             let linkPreview: TypedTableAlias<LinkPreview> = TypedTableAlias()
             
+            let threadProfileTableLiteral: SQL = SQL(stringLiteral: "threadProfile")
+            let profileIdColumnLiteral: SQL = SQL(stringLiteral: Profile.Columns.id.name)
+            let profileNicknameColumnLiteral: SQL = SQL(stringLiteral: Profile.Columns.nickname.name)
+            let profileNameColumnLiteral: SQL = SQL(stringLiteral: Profile.Columns.name.name)
             let interactionStateInteractionIdColumnLiteral: SQL = SQL(stringLiteral: RecipientState.Columns.interactionId.name)
             let readReceiptTableLiteral: SQL = SQL(stringLiteral: "readReceipt")
             let readReceiptReadTimestampMsColumnLiteral: SQL = SQL(stringLiteral: RecipientState.Columns.readTimestampMs.name)
@@ -561,9 +581,10 @@ public extension MessageViewModel {
             let groupMemberProfileIdColumnLiteral: SQL = SQL(stringLiteral: GroupMember.Columns.profileId.name)
             let groupMemberRoleColumnLiteral: SQL = SQL(stringLiteral: GroupMember.Columns.role.name)
             
-            let numColumnsBeforeLinkedRecords: Int = 18
+            let numColumnsBeforeLinkedRecords: Int = 20
             let request: SQLRequest<ViewModel> = """
                 SELECT
+                    \(thread[.id]) AS \(ViewModel.threadIdKey),
                     \(thread[.variant]) AS \(ViewModel.threadVariantKey),
                     -- Default to 'true' for non-contact threads
                     IFNULL(\(contact[.isTrusted]), true) AS \(ViewModel.threadIsTrustedKey),
@@ -571,6 +592,7 @@ public extension MessageViewModel {
                     IFNULL(\(disappearingMessagesConfig[.isEnabled]), false) AS \(ViewModel.threadHasDisappearingMessagesEnabledKey),
                     \(openGroup[.server]) AS \(ViewModel.threadOpenGroupServerKey),
                     \(openGroup[.publicKey]) AS \(ViewModel.threadOpenGroupPublicKeyKey),
+                    IFNULL(\(threadProfileTableLiteral).\(profileNicknameColumnLiteral), \(threadProfileTableLiteral).\(profileNameColumnLiteral)) AS \(ViewModel.threadContactNameInternalKey),
             
                     \(interaction.alias[Column.rowID]) AS \(ViewModel.rowIdKey),
                     \(interaction[.id]),
@@ -610,6 +632,7 @@ public extension MessageViewModel {
                 FROM \(Interaction.self)
                 JOIN \(SessionThread.self) ON \(thread[.id]) = \(interaction[.threadId])
                 LEFT JOIN \(Contact.self) ON \(contact[.id]) = \(interaction[.threadId])
+                LEFT JOIN \(Profile.self) AS \(threadProfileTableLiteral) ON \(threadProfileTableLiteral).\(profileIdColumnLiteral) = \(interaction[.threadId])
                 LEFT JOIN \(DisappearingMessagesConfiguration.self) ON \(disappearingMessagesConfig[.threadId]) = \(interaction[.threadId])
                 LEFT JOIN \(OpenGroup.self) ON \(openGroup[.threadId]) = \(interaction[.threadId])
                 LEFT JOIN \(Profile.self) ON \(profile[.id]) = \(interaction[.authorId])
diff --git a/SessionMessagingKit/Shared Models/SessionThreadViewModel.swift b/SessionMessagingKit/Shared Models/SessionThreadViewModel.swift
index d4f7083d3..e9132b51d 100644
--- a/SessionMessagingKit/Shared Models/SessionThreadViewModel.swift	
+++ b/SessionMessagingKit/Shared Models/SessionThreadViewModel.swift	
@@ -52,6 +52,7 @@ public struct SessionThreadViewModel: FetchableRecordWithRowId, Decodable, Equat
     public static let interactionIsOpenGroupInvitationKey: SQL = SQL(stringLiteral: CodingKeys.interactionIsOpenGroupInvitation.stringValue)
     public static let interactionAttachmentDescriptionInfoKey: SQL = SQL(stringLiteral: CodingKeys.interactionAttachmentDescriptionInfo.stringValue)
     public static let interactionAttachmentCountKey: SQL = SQL(stringLiteral: CodingKeys.interactionAttachmentCount.stringValue)
+    public static let threadContactNameInternalKey: SQL = SQL(stringLiteral: CodingKeys.threadContactNameInternal.stringValue)
     public static let authorNameInternalKey: SQL = SQL(stringLiteral: CodingKeys.authorNameInternal.stringValue)
     public static let currentUserPublicKeyKey: SQL = SQL(stringLiteral: CodingKeys.currentUserPublicKey.stringValue)
     
@@ -75,11 +76,15 @@ public struct SessionThreadViewModel: FetchableRecordWithRowId, Decodable, Equat
     public let threadMemberNames: String?
     
     public let threadIsNoteToSelf: Bool
-    public var threadIsMessageRequest: Bool?
+    
+    /// This flag indicates whether the thread is an outgoing message request
+    public let threadIsMessageRequest: Bool?
+    
+    /// This flag indicates whether the thread is an incoming message request
     public let threadRequiresApproval: Bool?
     public let threadShouldBeVisible: Bool?
     public let threadIsPinned: Bool
-    public var threadIsBlocked: Bool?
+    public let threadIsBlocked: Bool?
     public let threadMutedUntilTimestamp: TimeInterval?
     public let threadOnlyNotifyForMentions: Bool?
     public let threadMessageDraft: String?
@@ -116,6 +121,7 @@ public struct SessionThreadViewModel: FetchableRecordWithRowId, Decodable, Equat
     public let interactionAttachmentCount: Int?
     
     public let authorId: String?
+    private let threadContactNameInternal: String?
     private let authorNameInternal: String?
     public let currentUserPublicKey: String
     
@@ -172,6 +178,21 @@ public struct SessionThreadViewModel: FetchableRecordWithRowId, Decodable, Equat
         }
     }
     
+    /// This function returns the thread contact profile name formatted for the specific type of thread provided
+    ///
+    /// **Note:** The 'threadVariant' parameter is used for profile context but in the search results we actually want this
+    /// to always behave as the `contact` variant which is why this needs to be a function instead of just using the provided
+    /// parameter
+    public func threadContactName() -> String {
+        return Profile.displayName(
+            for: .contact,
+            id: threadId,
+            name: threadContactNameInternal,
+            nickname: nil,  // Folded into 'threadContactNameInternal' within the Query
+            customFallback: "Anonymous"
+        )
+    }
+    
     /// This function returns the profile name formatted for the specific type of thread provided
     ///
     /// **Note:** The 'threadVariant' parameter is used for profile context but in the search results we actually want this
@@ -251,6 +272,7 @@ public extension SessionThreadViewModel {
         self.interactionAttachmentCount = nil
         
         self.authorId = nil
+        self.threadContactNameInternal = nil
         self.authorNameInternal = nil
         self.currentUserPublicKey = getUserHexEncodedPublicKey()
     }
@@ -282,6 +304,8 @@ public extension SessionThreadViewModel {
             let profile: TypedTableAlias<Profile> = TypedTableAlias()
             
             let profileIdColumnLiteral: SQL = SQL(stringLiteral: Profile.Columns.id.name)
+            let profileNicknameColumnLiteral: SQL = SQL(stringLiteral: Profile.Columns.nickname.name)
+            let profileNameColumnLiteral: SQL = SQL(stringLiteral: Profile.Columns.name.name)
             let firstInteractionAttachmentLiteral: SQL = SQL(stringLiteral: "firstInteractionAttachment")
             let interactionAttachmentAttachmentIdColumnLiteral: SQL = SQL(stringLiteral: InteractionAttachment.Columns.attachmentId.name)
             let interactionAttachmentInteractionIdColumnLiteral: SQL = SQL(stringLiteral: InteractionAttachment.Columns.interactionId.name)
@@ -338,6 +362,7 @@ public extension SessionThreadViewModel {
                     COUNT(\(interactionAttachment[.interactionId])) AS \(ViewModel.interactionAttachmentCountKey),
             
                     \(interaction[.authorId]),
+                    IFNULL(\(ViewModel.contactProfileKey).\(profileNicknameColumnLiteral), \(ViewModel.contactProfileKey).\(profileNameColumnLiteral)) AS \(ViewModel.threadContactNameInternalKey),
                     IFNULL(\(profile[.nickname]), \(profile[.name])) AS \(ViewModel.authorNameInternalKey),
                     \(SQL("\(userPublicKey)")) AS \(ViewModel.currentUserPublicKeyKey)
                 
@@ -549,11 +574,8 @@ public extension SessionThreadViewModel {
                 (
                     \(thread[.shouldBeVisible]) = true AND
                     \(SQL("\(thread[.variant]) = \(SessionThread.Variant.contact)")) AND
-                    \(SQL("\(thread[.id]) != \(userPublicKey)")) AND (
-                        -- A '!= true' check doesn't work properly so we need to be explicit
-                        \(contact[.isApproved]) IS NULL OR
-                        \(contact[.isApproved]) = false
-                    )
+                    \(SQL("\(thread[.id]) != \(userPublicKey)")) AND
+                    IFNULL(\(contact[.isApproved]), false) = false
                 ) AS \(ViewModel.threadIsMessageRequestKey),
                 (
                     \(SQL("\(thread[.variant]) = \(SessionThread.Variant.contact)")) AND (
diff --git a/SessionMessagingKit/Utilities/Preferences.swift b/SessionMessagingKit/Utilities/Preferences.swift
index 243efec10..414417396 100644
--- a/SessionMessagingKit/Utilities/Preferences.swift
+++ b/SessionMessagingKit/Utilities/Preferences.swift
@@ -41,6 +41,9 @@ public extension Setting.BoolKey {
     /// Controls whether Calls are enabled
     static let areCallsEnabled: Setting.BoolKey = "areCallsEnabled"
     
+    /// Controls whether open group messages older than 6 months should be deleted
+    static let trimOpenGroupMessagesOlderThanSixMonths: Setting.BoolKey = "trimOpenGroupMessagesOlderThanSixMonths"
+    
     /// Controls whether the message requests item has been hidden on the home screen
     static let hasHiddenMessageRequests: Setting.BoolKey = "hasHiddenMessageRequests"
     
diff --git a/SessionNotificationServiceExtension/NotificationServiceExtension.swift b/SessionNotificationServiceExtension/NotificationServiceExtension.swift
index 5d831d2d6..66cee426b 100644
--- a/SessionNotificationServiceExtension/NotificationServiceExtension.swift
+++ b/SessionNotificationServiceExtension/NotificationServiceExtension.swift
@@ -104,7 +104,7 @@ public final class NotificationServiceExtension: UNNotificationServiceExtension
                             
                             guard case .preOffer = callMessage.kind else { return self.completeSilenty() }
                             
-                            if db[.areCallsEnabled] {
+                            if !db[.areCallsEnabled] {
                                 if let sender: String = callMessage.sender, let interaction: Interaction = try MessageReceiver.insertCallInfoMessage(db, for: callMessage, state: .permissionDenied) {
                                     let thread: SessionThread = try SessionThread.fetchOrCreate(db, id: sender, variant: .contact)
 
diff --git a/SessionUtilitiesKit/Database/GRDBStorage.swift b/SessionUtilitiesKit/Database/GRDBStorage.swift
index 3d59ac1e6..ddda89bef 100644
--- a/SessionUtilitiesKit/Database/GRDBStorage.swift
+++ b/SessionUtilitiesKit/Database/GRDBStorage.swift
@@ -280,18 +280,16 @@ public final class GRDBStorage {
     // MARK: - File Management
     
     public static func resetAllStorage() {
-        NotificationCenter.default.post(name: .resetStorage, object: nil)
+        // Just in case they haven't been removed for some reason, delete the legacy database & keys
+        SUKLegacy.clearLegacyDatabaseInstance()
+        try? SUKLegacy.deleteLegacyDatabaseFilesAndKey()
+        
+        GRDBStorage.shared.isValid = false
+        GRDBStorage.shared.hasCompletedMigrations = false
+        GRDBStorage.shared.dbWriter = nil
         
-        // This might be redundant but in the spirit of thoroughness...
         self.deleteDatabaseFiles()
-
         try? self.deleteDbKeys()
-
-        if CurrentAppContext().isMainApp {
-//            TSAttachmentStream.deleteAttachments()
-        }
-
-        // TODO: Delete Profiles on Disk?
     }
     
     public/*private*/ static func deleteDatabaseFiles() {
diff --git a/SessionUtilitiesKit/Database/Types/PagedDatabaseObserver.swift b/SessionUtilitiesKit/Database/Types/PagedDatabaseObserver.swift
index d011ca025..561bb409e 100644
--- a/SessionUtilitiesKit/Database/Types/PagedDatabaseObserver.swift
+++ b/SessionUtilitiesKit/Database/Types/PagedDatabaseObserver.swift
@@ -254,7 +254,7 @@ public class PagedDatabaseObserver<ObservedTable, T>: TransactionObserver where
         }
         
         // Fetch the indexes of the rowIds so we can determine whether they should be added to the screen
-        let itemIndexes: [Int64] = PagedData.indexes(
+        let itemIndexes: [PagedData.RowIndexInfo] = PagedData.indexes(
             db,
             rowIds: changesToQuery.map { $0.rowId },
             tableName: pagedTableName,
@@ -262,7 +262,7 @@ public class PagedDatabaseObserver<ObservedTable, T>: TransactionObserver where
             orderSQL: orderSQL,
             filterSQL: filterSQL
         )
-        let relatedChangeIndexes: [Int64] = PagedData.indexes(
+        let relatedChangeIndexes: [PagedData.RowIndexInfo] = PagedData.indexes(
             db,
             rowIds: Array(pagedRowIdsForRelatedChanges),
             tableName: pagedTableName,
@@ -275,36 +275,34 @@ public class PagedDatabaseObserver<ObservedTable, T>: TransactionObserver where
         // which shouldn't - values less than 'currentCount' or if there is at least one value less than
         // 'currentCount' and the indexes are sequential (ie. more than the current loaded content was
         // added at once)
-        func determineValidChanges<T>(for indexes: [Int64], with data: [T]) -> [T] {
+        func determineValidChanges(for indexInfo: [PagedData.RowIndexInfo]) -> [Int64] {
+            let indexes: [Int64] = Array(indexInfo
+                .map { $0.rowIndex }
+                .sorted()
+                .asSet())
             let indexesAreSequential: Bool = (indexes.map { $0 - 1 }.dropFirst() == indexes.dropLast())
-            let hasOneValidIndex: Bool = indexes.contains(where: { index -> Bool in
-                index >= updatedPageInfo.pageOffset && (
-                    index < updatedPageInfo.currentCount ||
+            let hasOneValidIndex: Bool = indexInfo.contains(where: { info -> Bool in
+                info.rowIndex >= updatedPageInfo.pageOffset && (
+                    info.rowIndex < updatedPageInfo.currentCount ||
                     updatedPageInfo.currentCount == 0
                 )
             })
             
             return (indexesAreSequential && hasOneValidIndex ?
-                data :
-                zip(indexes, data)
-                    .filter { index, _ -> Bool in
-                        index >= updatedPageInfo.pageOffset && (
-                            index < updatedPageInfo.currentCount ||
+                indexInfo.map { $0.rowId } :
+                indexInfo
+                    .filter { info -> Bool in
+                        info.rowIndex >= updatedPageInfo.pageOffset && (
+                            info.rowIndex < updatedPageInfo.currentCount ||
                             updatedPageInfo.currentCount == 0
                         )
                     }
-                    .map { _, value -> T in value }
+                    .map { info -> Int64 in info.rowId }
             )
         }
-        let validChanges: [PagedData.TrackedChange] = determineValidChanges(
-            for: itemIndexes,
-            with: changesToQuery
-        )
-        let validRelatedChangeRowIds: [Int64] = determineValidChanges(
-            for: relatedChangeIndexes,
-            with: Array(pagedRowIdsForRelatedChanges)
-        )
-        let countBefore: Int = itemIndexes.filter { $0 < updatedPageInfo.pageOffset }.count
+        let validChangeRowIds: [Int64] = determineValidChanges(for: itemIndexes)
+        let validRelatedChangeRowIds: [Int64] = determineValidChanges(for: relatedChangeIndexes)
+        let countBefore: Int = itemIndexes.filter { $0.rowIndex < updatedPageInfo.pageOffset }.count
         
         // Update the offset and totalCount even if the rows are outside of the current page (need to
         // in order to ensure the 'load more' sections are accurate)
@@ -312,18 +310,24 @@ public class PagedDatabaseObserver<ObservedTable, T>: TransactionObserver where
             pageSize: updatedPageInfo.pageSize,
             pageOffset: (updatedPageInfo.pageOffset + countBefore),
             currentCount: updatedPageInfo.currentCount,
-            totalCount: (updatedPageInfo.totalCount + validChanges.filter { $0.kind == .insert }.count)
+            totalCount: (
+                updatedPageInfo.totalCount +
+                changesToQuery
+                    .filter { $0.kind == .insert }
+                    .filter { validChangeRowIds.contains($0.rowId) }
+                    .count
+            )
         )
 
         // If there are no valid row ids then stop here (trigger updates though since the page info
         // has changes)
-        guard !validChanges.isEmpty || !validRelatedChangeRowIds.isEmpty else {
+        guard !validChangeRowIds.isEmpty || !validRelatedChangeRowIds.isEmpty else {
             updateDataAndCallbackIfNeeded(updatedDataCache, updatedPageInfo, true)
             return
         }
 
         // Fetch the inserted/updated rows
-        let targetRowIds: [Int64] = Array((validChanges.map { $0.rowId } + validRelatedChangeRowIds).asSet())
+        let targetRowIds: [Int64] = Array((validChangeRowIds + validRelatedChangeRowIds).asSet())
         let updatedItems: [T] = (try? dataQuery(targetRowIds)
             .fetchAll(db))
             .defaulting(to: [])
@@ -808,6 +812,11 @@ public enum PagedData {
         }
     }
     
+    fileprivate struct RowIndexInfo: Decodable, FetchableRecord {
+        let rowId: Int64
+        let rowIndex: Int64
+    }
+    
     // MARK: - Internal Functions
     
     fileprivate static func totalCount(
@@ -891,12 +900,13 @@ public enum PagedData {
         requiredJoinSQL: SQL? = nil,
         orderSQL: SQL,
         filterSQL: SQL
-    ) -> [Int64] {
+    ) -> [RowIndexInfo] {
         guard !rowIds.isEmpty else { return [] }
         
         let tableNameLiteral: SQL = SQL(stringLiteral: tableName)
-        let request: SQLRequest<Int64> = """
+        let request: SQLRequest<RowIndexInfo> = """
             SELECT
+                data.rowId AS rowId,
                 (data.rowIndex - 1) AS rowIndex -- Converting from 1-Indexed to 0-indexed
             FROM (
                 SELECT
@@ -1057,7 +1067,7 @@ public class AssociatedRecord<T, PagedType>: ErasedAssociatedRecord where T: Fet
         // If the associated data change isn't related to the paged type then no need to continue
         guard !pagedRowIds.isEmpty else { return (oldCount != countAfterDeletions) }
         
-        let pagedItemIndexes: [Int64] = PagedData.indexes(
+        let pagedItemIndexes: [PagedData.RowIndexInfo] = PagedData.indexes(
             db,
             rowIds: pagedRowIds,
             tableName: pagedTableName,
@@ -1078,9 +1088,9 @@ public class AssociatedRecord<T, PagedType>: ErasedAssociatedRecord where T: Fet
         /// Instead of following the pattern the `PagedDatabaseObserver` does where we get the proper `validRowIds` we
         /// basically have to check if there is a single valid index, and if so retrieve and store all data related to the changes for this
         /// commit - this will mean in some cases we cache data which is actually unrelated to the filtered paged data
-        let hasOneValidIndex: Bool = pagedItemIndexes.contains(where: { index -> Bool in
-            index >= pageInfo.pageOffset && (
-                index < pageInfo.currentCount ||
+        let hasOneValidIndex: Bool = pagedItemIndexes.contains(where: { info -> Bool in
+            info.rowIndex >= pageInfo.pageOffset && (
+                info.rowIndex < pageInfo.currentCount ||
                 pageInfo.currentCount == 0
             )
         })
diff --git a/SessionUtilitiesKit/Database/Utilities/GRDB+Notifications.swift b/SessionUtilitiesKit/Database/Utilities/GRDB+Notifications.swift
deleted file mode 100644
index fe1d4f95e..000000000
--- a/SessionUtilitiesKit/Database/Utilities/GRDB+Notifications.swift
+++ /dev/null
@@ -1,11 +0,0 @@
-// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
-
-import Foundation
-
-public extension Notification.Name {
-    static let resetStorage = Notification.Name("resetStorage")
-}
-
-@objc public extension NSNotification {
-    @objc static let resetStorage = Notification.Name.resetStorage.rawValue as NSString
-}
diff --git a/SignalUtilitiesKit/Utilities/Notification+Loki.swift b/SignalUtilitiesKit/Utilities/Notification+Loki.swift
index 46d08fadf..958e41249 100644
--- a/SignalUtilitiesKit/Utilities/Notification+Loki.swift
+++ b/SignalUtilitiesKit/Utilities/Notification+Loki.swift
@@ -6,8 +6,6 @@ public extension Notification.Name {
     static let contactOnlineStatusChanged = Notification.Name("contactOnlineStatusChanged")
     static let threadDeleted = Notification.Name("threadDeleted")
     static let threadSessionRestoreDevicesChanged = Notification.Name("threadSessionRestoreDevicesChanged")
-    // Interaction
-    static let dataNukeRequested = Notification.Name("dataNukeRequested")
 }
 
 @objc public extension NSNotification {
@@ -16,6 +14,4 @@ public extension Notification.Name {
     @objc static let contactOnlineStatusChanged = Notification.Name.contactOnlineStatusChanged.rawValue as NSString
     @objc static let threadDeleted = Notification.Name.threadDeleted.rawValue as NSString
     @objc static let threadSessionRestoreDevicesChanged = Notification.Name.threadSessionRestoreDevicesChanged.rawValue as NSString
-    // Interaction
-    @objc static let dataNukeRequested = Notification.Name.dataNukeRequested.rawValue as NSString
 }