diff --git a/SignalMessaging/utils/ThreadUtil.h b/SignalMessaging/utils/ThreadUtil.h
index 843758b2f..ec5070d1f 100644
--- a/SignalMessaging/utils/ThreadUtil.h
+++ b/SignalMessaging/utils/ThreadUtil.h
@@ -46,9 +46,7 @@ NS_ASSUME_NONNULL_BEGIN
 
 #pragma mark - Durable Message Enqueue
 
-+ (TSOutgoingMessage *)enqueueFriendRequestAcceptanceMessageInThread:(TSThread *)thread;
 + (void)enqueueDeviceLinkMessage:(LKDeviceLinkMessage *)message;
-+ (void)enqueueUnlinkDeviceMessage:(LKUnlinkDeviceMessage *)message;
 
 + (TSOutgoingMessage *)enqueueMessageWithText:(NSString *)fullMessageText
                                      inThread:(TSThread *)thread
diff --git a/SignalMessaging/utils/ThreadUtil.m b/SignalMessaging/utils/ThreadUtil.m
index bd0510f6a..33b3795c5 100644
--- a/SignalMessaging/utils/ThreadUtil.m
+++ b/SignalMessaging/utils/ThreadUtil.m
@@ -85,15 +85,6 @@ typedef void (^BuildOutgoingMessageCompletionBlock)(TSOutgoingMessage *savedMess
 
 #pragma mark - Durable Message Enqueue
 
-+ (LKEphemeralMessage *)enqueueFriendRequestAcceptanceMessageInThread:(TSThread *)thread
-{
-    LKEphemeralMessage *message = [[LKEphemeralMessage alloc] initInThread:thread];
-    [self.dbConnection asyncReadWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
-        [self.messageSenderJobQueue addMessage:message transaction:transaction];
-    }];
-    return message;
-}
-
 + (void)enqueueDeviceLinkMessage:(LKDeviceLinkMessage *)message
 {
     [self.dbConnection asyncReadWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
@@ -101,13 +92,6 @@ typedef void (^BuildOutgoingMessageCompletionBlock)(TSOutgoingMessage *savedMess
     }];
 }
 
-+ (void)enqueueUnlinkDeviceMessage:(LKUnlinkDeviceMessage *)message
-{
-    [self.dbConnection asyncReadWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
-        [self.messageSenderJobQueue addMessage:message transaction:transaction];
-    }];
-}
-
 + (TSOutgoingMessage *)enqueueMessageWithText:(NSString *)fullMessageText
                                      inThread:(TSThread *)thread
                              quotedReplyModel:(nullable OWSQuotedReplyModel *)quotedReplyModel
diff --git a/SignalServiceKit/src/Account/PreKeyRefreshOperation.swift b/SignalServiceKit/src/Account/PreKeyRefreshOperation.swift
index d859dfa7a..6a2be9e71 100644
--- a/SignalServiceKit/src/Account/PreKeyRefreshOperation.swift
+++ b/SignalServiceKit/src/Account/PreKeyRefreshOperation.swift
@@ -38,20 +38,7 @@ public class RefreshPreKeysOperation: OWSOperation {
         
         // Loki: Doing this on the global queue to match Signal
         DispatchQueue.global().async {
-            guard self.primaryStorage.currentSignedPrekeyId() == nil else {
-                print("[Loki] Skipping pre key refresh; using existing signed pre key.")
-                return self.reportSuccess()
-            }
-            
-            let signedPreKeyRecord = self.primaryStorage.generateRandomSignedRecord()
-            signedPreKeyRecord.markAsAcceptedByService()
-            self.primaryStorage.storeSignedPreKey(signedPreKeyRecord.id, signedPreKeyRecord: signedPreKeyRecord)
-            self.primaryStorage.setCurrentSignedPrekeyId(signedPreKeyRecord.id)
-            
-            TSPreKeyManager.clearPreKeyUpdateFailureCount()
-            TSPreKeyManager.clearSignedPreKeyRecords()
-            
-            print("[Loki] Pre keys refreshed successfully.")
+            SessionProtocol.refreshPreKeys()
             self.reportSuccess()
         }
         
diff --git a/SignalServiceKit/src/Account/RotateSignedKeyOperation.swift b/SignalServiceKit/src/Account/RotateSignedKeyOperation.swift
index c99cb3733..fbbc97de7 100644
--- a/SignalServiceKit/src/Account/RotateSignedKeyOperation.swift
+++ b/SignalServiceKit/src/Account/RotateSignedKeyOperation.swift
@@ -29,15 +29,7 @@ public class RotateSignedPreKeyOperation: OWSOperation {
         
         // Loki: Doing this on the global queue to match Signal
         DispatchQueue.global().async {
-            let signedPreKeyRecord = self.primaryStorage.generateRandomSignedRecord()
-            signedPreKeyRecord.markAsAcceptedByService()
-            self.primaryStorage.storeSignedPreKey(signedPreKeyRecord.id, signedPreKeyRecord: signedPreKeyRecord)
-            self.primaryStorage.setCurrentSignedPrekeyId(signedPreKeyRecord.id)
-            
-            TSPreKeyManager.clearPreKeyUpdateFailureCount()
-            TSPreKeyManager.clearSignedPreKeyRecords()
-            
-            print("[Loki] Pre keys rotated successfully.")
+            SessionProtocol.rotatePreKeys()
             self.reportSuccess()
         }
 
diff --git a/SignalServiceKit/src/Contacts/TSThread.m b/SignalServiceKit/src/Contacts/TSThread.m
index 59860ed44..8aebd14af 100644
--- a/SignalServiceKit/src/Contacts/TSThread.m
+++ b/SignalServiceKit/src/Contacts/TSThread.m
@@ -216,10 +216,7 @@ ConversationColorName const kConversationColorName_Default = ConversationColorNa
     if (!IsNoteToSelfEnabled()) {
         return NO;
     }
-    NSString *localNumber = self.tsAccountManager.localNumber;
-    NSString *masterDeviceHexEncodedPublicKey = [NSUserDefaults.standardUserDefaults stringForKey:@"masterDeviceHexEncodedPublicKey"];
-    bool isOurNumber = [self.contactIdentifier isEqualToString:localNumber] || (masterDeviceHexEncodedPublicKey != nil && [self.contactIdentifier isEqualToString:masterDeviceHexEncodedPublicKey]);
-    return (!self.isGroupThread && self.contactIdentifier != nil && isOurNumber);
+    return [SessionProtocol isMessageNoteToSelf:self];
 }
 
 #pragma mark - To be subclassed.
diff --git a/SignalServiceKit/src/Loki/Messaging/SessionProtocol+Receiving.swift b/SignalServiceKit/src/Loki/Messaging/SessionProtocol+Receiving.swift
index 6fa4cf4c0..bd7f27135 100644
--- a/SignalServiceKit/src/Loki/Messaging/SessionProtocol+Receiving.swift
+++ b/SignalServiceKit/src/Loki/Messaging/SessionProtocol+Receiving.swift
@@ -27,7 +27,29 @@ public extension SessionProtocol {
     /// Only ever modified from the message processing queue (`OWSBatchMessageProcessor.processingQueue`).
     private static var syncMessageTimestamps: [String:Set<UInt64>] = [:]
 
+    // MARK: - General
+    @objc(shouldSkipMessageDecryptResult:)
+    public static func shouldSkipMessageDecryptResult(_ result: OWSMessageDecryptResult) -> Bool {
+        // Called from OWSMessageReceiver to prevent messages from even being added to the processing queue for some reason
+        return result.source == getUserHexEncodedPublicKey() // NOTE: This doesn't take into account multi device
+    }
+
     // MARK: - Session Handling
+    @objc(handleDecryptionError:forHexEncodedPublicKey:using:)
+    public static func handleDecryptionError(_ rawValue: Int32, for hexEncodedPublicKey: String, using transaction: YapDatabaseReadWriteTransaction) {
+        let type = TSErrorMessageType(rawValue: rawValue)
+        let masterHexEncodedPublicKey = storage.getMasterHexEncodedPublicKey(for: hexEncodedPublicKey, in: transaction) ?? hexEncodedPublicKey
+        let thread = TSContactThread.getOrCreateThread(withContactId: masterHexEncodedPublicKey, transaction: transaction)
+        // Show the session reset prompt upon certain errors
+        switch type {
+        case .noSession, .invalidMessage, .invalidKeyException:
+            // Store the source device's public key in case it was a secondary device
+            thread.addSessionRestoreDevice(hexEncodedPublicKey, transaction: transaction)
+        default: break
+        }
+
+    }
+
     @objc(isSessionRestoreMessage:)
     public static func isSessionRestoreMessage(_ dataMessage: SSKProtoDataMessage) -> Bool {
         let sessionRestoreFlag = SSKProtoDataMessage.SSKProtoDataMessageFlags.sessionRestore
diff --git a/SignalServiceKit/src/Loki/Messaging/SessionProtocol+Sending.swift b/SignalServiceKit/src/Loki/Messaging/SessionProtocol+Sending.swift
index 4200fa699..1dd9380bf 100644
--- a/SignalServiceKit/src/Loki/Messaging/SessionProtocol+Sending.swift
+++ b/SignalServiceKit/src/Loki/Messaging/SessionProtocol+Sending.swift
@@ -54,6 +54,16 @@ public extension SessionProtocol {
 
     // TODO: Check that the behaviors described above make sense
 
+    @objc(isMessageNoteToSelf:)
+    public static func isMessageNoteToSelf(_ thread: TSThread) -> Bool {
+        guard let thread = thread as? TSContactThread else { return false }
+        var isNoteToSelf = false
+        storage.dbReadConnection.read { transaction in
+            isNoteToSelf = LokiDatabaseUtilities.isUserLinkedDevice(thread.contactIdentifier(), transaction: transaction)
+        }
+        return isNoteToSelf
+    }
+
     @objc(isMessageNoteToSelf:inThread:)
     public static func isMessageNoteToSelf(_ message: TSOutgoingMessage, in thread: TSThread) -> Bool {
         guard let thread = thread as? TSContactThread, !(message is OWSOutgoingSyncMessage) && !(message is DeviceLinkMessage) else { return false }
@@ -314,6 +324,30 @@ public extension SessionProtocol {
 
 
 
+    // MARK: - Typing Indicators
+    public static func shouldSendTypingIndicator(for thread: TSThread) -> Bool {
+        return !thread.isGroupThread() && !isMessageNoteToSelf(thread)
+    }
+
+
+
+    // MARK: - Receipts
+    // Used from OWSReadReceiptManager
+    @objc(shouldSendReadReceiptForThread:)
+    public static func shouldSendReadReceipt(for thread: TSThread) -> Bool {
+        return !isMessageNoteToSelf(thread) && !thread.isGroupThread()
+    }
+
+    // TODO: Not sure how these two relate
+
+    // Used from OWSOutgoingReceiptManager
+    @objc(shouldSendReceiptForThread:)
+    public static func shouldSendReceipt(for thread: TSThread) -> Bool {
+        return thread.friendRequestStatus == .friends && !thread.isGroupThread()
+    }
+
+
+
     // MARK: - Sessions
     // BEHAVIOR NOTE: OWSMessageSender.throws_encryptedMessageForMessageSend:recipientId:plaintext:transaction: sets
     // isFriendRequest to true if the message in question is a friend request or a device linking request, but NOT if
@@ -321,6 +355,42 @@ public extension SessionProtocol {
 
     // TODO: Does the above make sense?
 
+    public static func createPreKeys() {
+        // We don't generate PreKeyRecords here.
+        // This is because we need the records to be linked to a contact since we don't have a central server.
+        // It's done automatically when we generate a pre key bundle to send to a contact (`generatePreKeyBundleForContact:`).
+        // You can use `getOrCreatePreKeyForContact:` to generate one if needed.
+        let signedPreKeyRecord = storage.generateRandomSignedRecord()
+        signedPreKeyRecord.markAsAcceptedByService()
+        storage.storeSignedPreKey(signedPreKeyRecord.id, signedPreKeyRecord: signedPreKeyRecord)
+        storage.setCurrentSignedPrekeyId(signedPreKeyRecord.id)
+        print("[Loki] Pre keys created successfully.")
+    }
+
+    public static func refreshPreKeys() {
+        guard storage.currentSignedPrekeyId() == nil else {
+            print("[Loki] Skipping pre key refresh; using existing signed pre key.")
+            return
+        }
+        let signedPreKeyRecord = storage.generateRandomSignedRecord()
+        signedPreKeyRecord.markAsAcceptedByService()
+        storage.storeSignedPreKey(signedPreKeyRecord.id, signedPreKeyRecord: signedPreKeyRecord)
+        storage.setCurrentSignedPrekeyId(signedPreKeyRecord.id)
+        TSPreKeyManager.clearPreKeyUpdateFailureCount()
+        TSPreKeyManager.clearSignedPreKeyRecords()
+        print("[Loki] Pre keys refreshed successfully.")
+    }
+
+    public static func rotatePreKeys() {
+        let signedPreKeyRecord = storage.generateRandomSignedRecord()
+        signedPreKeyRecord.markAsAcceptedByService()
+        storage.storeSignedPreKey(signedPreKeyRecord.id, signedPreKeyRecord: signedPreKeyRecord)
+        storage.setCurrentSignedPrekeyId(signedPreKeyRecord.id)
+        TSPreKeyManager.clearPreKeyUpdateFailureCount()
+        TSPreKeyManager.clearSignedPreKeyRecords()
+        print("[Loki] Pre keys rotated successfully.")
+    }
+
     public static func shouldUseFallbackEncryption(_ message: TSOutgoingMessage) -> Bool {
         return !isSessionRequired(for: message)
     }
diff --git a/SignalServiceKit/src/Messages/Interactions/OWSLinkPreview.swift b/SignalServiceKit/src/Messages/Interactions/OWSLinkPreview.swift
index 5a865f5b9..15e75f922 100644
--- a/SignalServiceKit/src/Messages/Interactions/OWSLinkPreview.swift
+++ b/SignalServiceKit/src/Messages/Interactions/OWSLinkPreview.swift
@@ -696,65 +696,6 @@ public class OWSLinkPreview: MTLModel {
         })
         return promise
     }
-    
-    public class func getImagePreview(from url: String, in transaction: YapDatabaseReadWriteTransaction) -> Promise<OWSLinkPreview> {
-        // Get the MIME type
-        guard let imageFileExtension = fileExtension(forImageUrl: url), let imageMIMEType = mimetype(forImageFileExtension: imageFileExtension) else {
-            return Promise(error: LinkPreviewError.invalidInput)
-        }
-        
-        return downloadImage(url: url).map { data in
-            // Make sure the downloaded image has the correct MIME type
-            guard let newImageMIMEType = NSData(data: data).ows_guessMimeType() else {
-                throw LinkPreviewError.invalidContent
-            }
-            
-            // Save the attachment
-            guard let attachmentId = saveAttachmentIfPossible(imageData: data, mimeType: newImageMIMEType, transaction: transaction) else {
-                Logger.verbose("Failed to save attachment for: \(url).")
-                throw LinkPreviewError.attachmentFailedToSave
-            }
-            
-            // If it's a GIF and the data we have is not a GIF then we need to render a link preview without attachments
-            if (imageMIMEType == OWSMimeTypeImageGif && newImageMIMEType != OWSMimeTypeImageGif) {
-                return OWSLinkPreview(urlString: url, title: nil, imageAttachmentId: attachmentId)
-            }
-            
-            return OWSLinkPreview(urlString: url, title: nil, imageAttachmentId: attachmentId, isDirectAttachment: true)
-        }
-    }
-    
-    @objc(getImagePreviewWithURL:transaction:)
-    public class func objc_getImagePreview(url: String, in transaction: YapDatabaseReadWriteTransaction) -> AnyPromise {
-        return AnyPromise.from(getImagePreview(from: url, in: transaction))
-    }
-    
-    public class func downloadImage(url imageUrl: String) -> Promise<Data> {
-        guard OWSLinkPreview.featureEnabled else {
-            return Promise(error: LinkPreviewError.featureDisabled)
-        }
-        
-        guard SSKPreferences.areLinkPreviewsEnabled else {
-            return Promise(error: LinkPreviewError.featureDisabled)
-        }
-
-        guard isValidMediaUrl(imageUrl) else {
-            Logger.error("Invalid image URL.")
-            return Promise.init(error: LinkPreviewError.invalidInput)
-        }
-        
-        guard let imageFileExtension = fileExtension(forImageUrl: imageUrl) else {
-            Logger.error("Image URL has unknown or invalid file extension: \(imageUrl).")
-            return Promise.init(error: LinkPreviewError.invalidInput)
-        }
-        
-        guard let imageMimeType = mimetype(forImageFileExtension: imageFileExtension) else {
-            Logger.error("Image URL has unknown or invalid content type: \(imageUrl).")
-            return Promise.init(error: LinkPreviewError.invalidInput)
-        }
-        
-        return downloadImage(url: imageUrl, imageMimeType: imageMimeType)
-    }
 
     private class func downloadImage(url urlString: String, imageMimeType: String) -> Promise<Data> {
 
diff --git a/SignalServiceKit/src/Messages/Interactions/TSMessage.h b/SignalServiceKit/src/Messages/Interactions/TSMessage.h
index 2d12315e1..5b54d0437 100644
--- a/SignalServiceKit/src/Messages/Interactions/TSMessage.h
+++ b/SignalServiceKit/src/Messages/Interactions/TSMessage.h
@@ -100,10 +100,6 @@ typedef NS_ENUM(NSInteger, LKMessageFriendRequestStatus) {
 
 - (void)saveOpenGroupServerMessageID:(uint64_t)serverMessageID in:(YapDatabaseReadWriteTransaction *_Nullable)transaction;
 
-#pragma mark - Link Preview
-
-- (void)generateLinkPreviewIfNeededFromURL:(NSString *)url;
-
 @end
 
 NS_ASSUME_NONNULL_END
diff --git a/SignalServiceKit/src/Messages/Interactions/TSMessage.m b/SignalServiceKit/src/Messages/Interactions/TSMessage.m
index 586826a0d..baadfd461 100644
--- a/SignalServiceKit/src/Messages/Interactions/TSMessage.m
+++ b/SignalServiceKit/src/Messages/Interactions/TSMessage.m
@@ -511,44 +511,6 @@ static const NSUInteger OWSMessageSchemaVersion = 4;
     }
 }
 
-#pragma mark - Link Previews
-
-- (void)generateLinkPreviewIfNeededFromURL:(NSString *)url {
-    [OWSLinkPreview tryToBuildPreviewInfoObjcWithPreviewUrl:url]
-    .thenOn(dispatch_get_main_queue(), ^(OWSLinkPreviewDraft *linkPreviewDraft) {
-        [self.dbReadWriteConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
-            OWSLinkPreview *linkPreview = [OWSLinkPreview buildValidatedLinkPreviewFromInfo:linkPreviewDraft transaction:transaction error:nil];
-            self.linkPreview = linkPreview;
-            [self saveWithTransaction:transaction];
-        }];
-    })
-    .catchOn(dispatch_get_main_queue(), ^(NSError *error) {
-        // If we failed to get a link preview due to an invalid content type error then this could be a direct image link
-        if ([OWSLinkPreview isInvalidContentError:error]) {
-            __block AnyPromise *promise;
-            [self.dbReadWriteConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
-                promise = [OWSLinkPreview getImagePreviewWithURL:url transaction:transaction]
-                .thenOn(dispatch_get_main_queue(), ^(OWSLinkPreview *linkPreview) {
-                    // If we managed to get a direct image preview then render it
-                    [self.dbReadWriteConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
-                        if (linkPreview.isDirectAttachment) {
-                            [self addAttachmentWithID:linkPreview.imageAttachmentId in:transaction];
-                            TSAttachmentStream *attachment = [TSAttachmentStream fetchObjectWithUniqueID:linkPreview.imageAttachmentId transaction:transaction];
-                            attachment.albumMessageId = self.uniqueId;
-                            attachment.isUploaded = true;
-                            [attachment saveWithTransaction:transaction];
-                        } else {
-                            // Do nothing
-                        }
-                    }];
-                });
-            }];
-            return promise;
-        }
-        @throw error;
-    });
-}
-
 @end
 
 NS_ASSUME_NONNULL_END
diff --git a/SignalServiceKit/src/Messages/OWSMessageDecrypter.m b/SignalServiceKit/src/Messages/OWSMessageDecrypter.m
index aba11134a..15b1943a4 100644
--- a/SignalServiceKit/src/Messages/OWSMessageDecrypter.m
+++ b/SignalServiceKit/src/Messages/OWSMessageDecrypter.m
@@ -673,28 +673,12 @@ NSError *EnsureDecryptError(NSError *_Nullable error, NSString *fallbackErrorDes
         OWSAssertDebug(errorMessage);
         if (errorMessage != nil) {
             [errorMessage saveWithTransaction:transaction];
-            [self handleSessionRestoreForErrorMessage:errorMessage envelope:envelope transaction:transaction];
+            [SessionProtocol handleDecryptionError:errorMessage.errorType forHexEncodedPublicKey:envelope.source using:transaction];
             [self notifyUserForErrorMessage:errorMessage envelope:envelope transaction:transaction];
         }
     }];
 }
 
-- (void)handleSessionRestoreForErrorMessage:(TSErrorMessage *)errorMessage
-                                   envelope:(SSKProtoEnvelope *)envelope
-                                   transaction:(YapDatabaseReadWriteTransaction *)transaction
-{
-    NSString *hexEncodedPublicKey = [LKDatabaseUtilities getMasterHexEncodedPublicKeyFor:envelope.source in:transaction] ?: envelope.source;
-    TSThread *contactThread = [TSContactThread getOrCreateThreadWithContactId:hexEncodedPublicKey transaction:transaction];
-       
-   // Trigger a session restore prompt if we get specific errors
-   if (errorMessage.errorType == TSErrorMessageNoSession ||
-       errorMessage.errorType == TSErrorMessageInvalidMessage ||
-       errorMessage.errorType == TSErrorMessageInvalidKeyException) {
-       // We want to store the source device's public key into the session restore in case it's a secondary device message
-       [((TSContactThread *)contactThread) addSessionRestoreDevice:envelope.source transaction:transaction];
-   }
-}
-
 - (void)notifyUserForErrorMessage:(TSErrorMessage *)errorMessage
                          envelope:(SSKProtoEnvelope *)envelope
                       transaction:(YapDatabaseReadWriteTransaction *)transaction
diff --git a/SignalServiceKit/src/Messages/OWSMessageReceiver.m b/SignalServiceKit/src/Messages/OWSMessageReceiver.m
index d39208cde..be638dd24 100644
--- a/SignalServiceKit/src/Messages/OWSMessageReceiver.m
+++ b/SignalServiceKit/src/Messages/OWSMessageReceiver.m
@@ -398,8 +398,7 @@ NSString *const OWSMessageDecryptJobFinderExtensionGroup = @"OWSMessageProcessin
             OWSAssertDebug(transaction);
 
             // Loki: Don't process any messages from ourself
-            ECKeyPair *_Nullable userKeyPair = OWSIdentityManager.sharedManager.identityKeyPair;
-            if (userKeyPair && [result.source isEqualToString:userKeyPair.hexEncodedPublicKey]) {
+            if ([SessionProtocol shouldSkipMessageDecryptResult:result]) {
                 dispatch_async(self.serialQueue, ^{
                     completion(YES);
                 });
diff --git a/SignalServiceKit/src/Messages/OWSOutgoingReceiptManager.m b/SignalServiceKit/src/Messages/OWSOutgoingReceiptManager.m
index 35b330d3b..d248e7460 100644
--- a/SignalServiceKit/src/Messages/OWSOutgoingReceiptManager.m
+++ b/SignalServiceKit/src/Messages/OWSOutgoingReceiptManager.m
@@ -173,12 +173,10 @@ NSString *const kOutgoingReadReceiptManagerCollection = @"kOutgoingReadReceiptMa
         }
 
         TSThread *thread = [TSContactThread getOrCreateThreadWithContactId:recipientId];
-        
-        // If we aren't friends with the user then don't send out any receipts
-        if (thread.friendRequestStatus != LKThreadFriendRequestStatusFriends) { continue; }
-        
-        // Don't send any receipts for groups
-        if (thread.isGroupThread) { continue; }
+
+        if (![SessionProtocol shouldSendReceiptForThread:thread]) {
+            continue;
+        }
         
         OWSReceiptsForSenderMessage *message;
         NSString *receiptName;
diff --git a/SignalServiceKit/src/Messages/OWSReadReceiptManager.m b/SignalServiceKit/src/Messages/OWSReadReceiptManager.m
index 3d51b8f38..17d86049a 100644
--- a/SignalServiceKit/src/Messages/OWSReadReceiptManager.m
+++ b/SignalServiceKit/src/Messages/OWSReadReceiptManager.m
@@ -286,19 +286,10 @@ NSString *const OWSReadReceiptManagerAreReadReceiptsEnabled = @"areReadReceiptsE
                 self.toLinkedDevicesReadReceiptMap[threadUniqueId] = newReadReceipt;
             }
 
-            __block BOOL isNoteToSelf;
-            [OWSPrimaryStorage.sharedManager.dbReadConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) {
-                isNoteToSelf = [LKDatabaseUtilities isUserLinkedDevice:message.authorId in:transaction];
-            }];
-            
-            if (isNoteToSelf) {
-                OWSLogVerbose(@"Ignoring read receipt for self-sender.");
+            if (![SessionProtocol shouldSendReadReceiptForThread:message.thread]) {
                 return;
             }
             
-            // Don't send any receipts for groups
-            if (message.thread.isGroupThread) { return; }
-            
             if ([self areReadReceiptsEnabled]) {
                 OWSLogVerbose(@"Enqueuing read receipt for sender.");
                 [self.outgoingReceiptManager enqueueReadReceiptForEnvelope:messageAuthorId timestamp:message.timestamp];
diff --git a/SignalServiceKit/src/Messages/TSGroupModel.m b/SignalServiceKit/src/Messages/TSGroupModel.m
index ba34114e1..4815594be 100644
--- a/SignalServiceKit/src/Messages/TSGroupModel.m
+++ b/SignalServiceKit/src/Messages/TSGroupModel.m
@@ -153,6 +153,7 @@ const int32_t kGroupIdLength = 16;
                                       stringByAppendingString:NSLocalizedString(@"YOU_WERE_REMOVED", @"")];
         } else {
             NSArray *removedMemberNames = [[newModel.removedMembers allObjects] map:^NSString*(NSString* item) {
+                // TODO: Shouldn't this use DisplayNameUtilities?
                 return [contactsManager displayNameForPhoneIdentifier:item];
             }];
             if ([removedMemberNames count] > 1) {
diff --git a/SignalServiceKit/src/Messages/UD/OWSUDManager.swift b/SignalServiceKit/src/Messages/UD/OWSUDManager.swift
index 30a7b9c04..8649decc5 100644
--- a/SignalServiceKit/src/Messages/UD/OWSUDManager.swift
+++ b/SignalServiceKit/src/Messages/UD/OWSUDManager.swift
@@ -425,9 +425,9 @@ public class OWSUDManagerImpl: NSObject, OWSUDManager {
     
     private func generateSenderCertificate() -> Promise<(certificateData: Data, certificate: SMKSenderCertificate)> {
         return Promise<(certificateData: Data, certificate: SMKSenderCertificate)> { seal in
-            // Loki: Generate a sender certifate locally
-            let sender = OWSIdentityManager.shared().identityKeyPair()?.hexEncodedPublicKey
-            let certificate = SMKSenderCertificate(senderDeviceId: OWSDevicePrimaryDeviceId, senderRecipientId: sender!)
+            // Loki: Generate a sender certificate locally
+            let sender = OWSIdentityManager.shared().identityKeyPair()!.hexEncodedPublicKey
+            let certificate = SMKSenderCertificate(senderDeviceId: OWSDevicePrimaryDeviceId, senderRecipientId: sender)
             let certificateAsData = try certificate.serialized()
             guard isValidCertificate(certificate) else {
                 throw OWSUDError.invalidData(description: "Invalid sender certificate.")
@@ -439,14 +439,14 @@ public class OWSUDManagerImpl: NSObject, OWSUDManager {
     @objc
     public func getSenderCertificate() -> SMKSenderCertificate? {
         do {
-            let sender = OWSIdentityManager.shared().identityKeyPair()?.hexEncodedPublicKey
-            let certificate = SMKSenderCertificate(senderDeviceId: OWSDevicePrimaryDeviceId, senderRecipientId: sender!)
+            let sender = OWSIdentityManager.shared().identityKeyPair()!.hexEncodedPublicKey
+            let certificate = SMKSenderCertificate(senderDeviceId: OWSDevicePrimaryDeviceId, senderRecipientId: sender)
             guard self.isValidCertificate(certificate) else {
                 throw OWSUDError.invalidData(description: "Invalid sender certificate returned by server")
             }
             return certificate
         } catch {
-            Logger.error("\(error)")
+            print("[Loki] Couldn't get UD sender certificate due to error: \(error).")
             return nil
         }
     }
diff --git a/SignalServiceKit/src/Util/TypingIndicators.swift b/SignalServiceKit/src/Util/TypingIndicators.swift
index 14e1e8993..58cff90ac 100644
--- a/SignalServiceKit/src/Util/TypingIndicators.swift
+++ b/SignalServiceKit/src/Util/TypingIndicators.swift
@@ -324,15 +324,7 @@ public class TypingIndicatorsImpl: NSObject, TypingIndicators {
             }
             
             // Loki: Don't send typing indicators in group or note to self threads
-            if (thread.isGroupThread()) {
-                return
-            } else {
-                var isNoteToSelf = false
-                OWSPrimaryStorage.shared().dbReadConnection.read { transaction in
-                    isNoteToSelf = LokiDatabaseUtilities.isUserLinkedDevice(thread.contactIdentifier()!, transaction: transaction)
-                }
-                if isNoteToSelf { return }
-            }
+            if !SessionProtocol.shouldSendTypingIndicator(for: thread) { return }
 
             let message = TypingIndicatorMessage(thread: thread, action: action)
             messageSender.sendPromise(message: message).retainUntilComplete()