diff --git a/lib/ops/message-store-ops.js b/lib/ops/message-store-ops.js
--- a/lib/ops/message-store-ops.js
+++ b/lib/ops/message-store-ops.js
@@ -7,6 +7,8 @@
   MessageStore,
   MessageStoreThreads,
   RawMessageInfo,
+  LocalMessageInfo,
+  ClientDBLocalMessageInfo,
 } from '../types/message-types.js';
 import {
   translateClientDBMessageInfoToRawMessageInfo,
@@ -54,6 +56,21 @@
   +type: 'remove_all_threads',
 };
 
+// MessageStore local ops
+export type ReplaceMessageStoreLocalMessageInfoOperation = {
+  +type: 'replace_local_message_info',
+  +payload: { +id: string, +localMessageInfo: LocalMessageInfo },
+};
+
+export type RemoveMessageStoreLocalMessageInfosOperation = {
+  +type: 'remove_local_message_infos',
+  +payload: { +ids: $ReadOnlyArray<string> },
+};
+
+export type RemoveMessageStoreAllLocalMessageInfosOperation = {
+  +type: 'remove_all_local_message_infos',
+};
+
 export type ClientDBReplaceMessageOperation = {
   +type: 'replace',
   +payload: ClientDBMessageInfo,
@@ -64,6 +81,11 @@
   +payload: { +threads: $ReadOnlyArray<ClientDBThreadMessageInfo> },
 };
 
+export type ClientDBReplaceLocalMessageInfoOperation = {
+  +type: 'replace_local_message_info',
+  +payload: ClientDBLocalMessageInfo,
+};
+
 export type MessageStoreOperation =
   | RemoveMessageOperation
   | ReplaceMessageOperation
@@ -72,7 +94,10 @@
   | RemoveAllMessagesOperation
   | ReplaceMessageStoreThreadsOperation
   | RemoveMessageStoreThreadsOperation
-  | RemoveMessageStoreAllThreadsOperation;
+  | RemoveMessageStoreAllThreadsOperation
+  | ReplaceMessageStoreLocalMessageInfoOperation
+  | RemoveMessageStoreLocalMessageInfosOperation
+  | RemoveMessageStoreAllLocalMessageInfosOperation;
 
 export type ClientDBMessageStoreOperation =
   | RemoveMessageOperation
@@ -82,7 +107,10 @@
   | RemoveAllMessagesOperation
   | ClientDBReplaceThreadsOperation
   | RemoveMessageStoreThreadsOperation
-  | RemoveMessageStoreAllThreadsOperation;
+  | RemoveMessageStoreAllThreadsOperation
+  | ClientDBReplaceLocalMessageInfoOperation
+  | RemoveMessageStoreLocalMessageInfosOperation
+  | RemoveMessageStoreAllLocalMessageInfosOperation;
 
 export const messageStoreOpsHandlers: BaseStoreOpsHandlers<
   MessageStore,
@@ -100,6 +128,8 @@
     }
     let processedMessages = { ...store.messages };
     let processedThreads = { ...store.threads };
+    let processedLocal = { ...store.local };
+
     for (const operation of ops) {
       if (operation.type === 'replace') {
         processedMessages[operation.payload.id] = operation.payload.messageInfo;
@@ -133,12 +163,22 @@
         }
       } else if (operation.type === 'remove_all_threads') {
         processedThreads = {};
+      } else if (operation.type === 'replace_local_message_info') {
+        processedLocal[operation.payload.id] =
+          operation.payload.localMessageInfo;
+      } else if (operation.type === 'remove_local_message_infos') {
+        for (const id of operation.payload.ids) {
+          delete processedLocal[id];
+        }
+      } else if (operation.type === 'remove_all_local_message_infos') {
+        processedLocal = {};
       }
     }
     return {
       ...store,
       threads: processedThreads,
       messages: processedMessages,
+      local: processedLocal,
     };
   },
 
@@ -179,6 +219,16 @@
             threads: dbThreadMessageInfos,
           },
         };
+      } else if (messageStoreOperation.type === 'replace_local_message_info') {
+        return {
+          type: 'replace_local_message_info',
+          payload: {
+            id: messageStoreOperation.payload.id,
+            localMessageInfo: JSON.stringify(
+              messageStoreOperation.payload.localMessageInfo,
+            ),
+          },
+        };
       }
       return messageStoreOperation;
     });
diff --git a/lib/types/message-types.js b/lib/types/message-types.js
--- a/lib/types/message-types.js
+++ b/lib/types/message-types.js
@@ -455,17 +455,23 @@
   threadMessageInfoValidator,
 );
 
+export type MessageStoreLocalMessageInfos = {
+  +[id: string]: LocalMessageInfo,
+};
+const messageStoreLocalMessageInfosValidator: TDict<MessageStoreLocalMessageInfos> =
+  t.dict(tID, localMessageInfoValidator);
+
 export type MessageStore = {
   +messages: { +[id: string]: RawMessageInfo },
   +threads: MessageStoreThreads,
-  +local: { +[id: string]: LocalMessageInfo },
+  +local: MessageStoreLocalMessageInfos,
   +currentAsOf: { +[keyserverID: string]: number },
 };
 export const messageStoreValidator: TInterface<MessageStore> =
   tShape<MessageStore>({
     messages: t.dict(tID, rawMessageInfoValidator),
     threads: messageStoreThreadsValidator,
-    local: t.dict(t.String, localMessageInfoValidator),
+    local: messageStoreLocalMessageInfosValidator,
     currentAsOf: t.dict(t.String, t.Number),
   });
 
@@ -496,6 +502,11 @@
   +start_reached: string,
 };
 
+export type ClientDBLocalMessageInfo = {
+  +id: string,
+  +localMessageInfo: string,
+};
+
 export const messageTruncationStatus = Object.freeze({
   // EXHAUSTIVE means we've reached the start of the thread. Either the result
   // set includes the very first message for that thread, or there is nothing