diff --git a/lib/shared/dm-ops/add-members-spec.js b/lib/shared/dm-ops/add-members-spec.js
--- a/lib/shared/dm-ops/add-members-spec.js
+++ b/lib/shared/dm-ops/add-members-spec.js
@@ -170,6 +170,25 @@
     );
     return { rawMessageInfos, updateInfos };
   },
+  canBeProcessed(
+    dmOperation: DMAddMembersOperation,
+    viewerID: string,
+    utilities: ProcessDMOperationUtilities,
+  ) {
+    if (
+      utilities.threadInfos[dmOperation.existingThreadDetails.threadID] ||
+      dmOperation.addedUserIDs.includes(viewerID)
+    ) {
+      return { isProcessingPossible: true };
+    }
+    return {
+      isProcessingPossible: false,
+      reason: {
+        type: 'missing_thread',
+        threadID: dmOperation.existingThreadDetails.threadID,
+      },
+    };
+  },
 });
 
 export { addMembersSpec, createAddNewMembersResults };
diff --git a/lib/shared/dm-ops/change-thread-settings-spec.js b/lib/shared/dm-ops/change-thread-settings-spec.js
--- a/lib/shared/dm-ops/change-thread-settings-spec.js
+++ b/lib/shared/dm-ops/change-thread-settings-spec.js
@@ -1,8 +1,12 @@
 // @flow
 
+import invariant from 'invariant';
 import uuid from 'uuid';
 
-import { createAddNewMembersResults } from './add-members-spec.js';
+import {
+  addMembersSpec,
+  createAddNewMembersResults,
+} from './add-members-spec.js';
 import type {
   DMOperationSpec,
   ProcessDMOperationUtilities,
@@ -17,6 +21,25 @@
 import type { ClientUpdateInfo } from '../../types/update-types.js';
 import { values } from '../../utils/objects.js';
 
+function createAddMembersOperation(
+  dmOperation: DMChangeThreadSettingsOperation,
+) {
+  const { editorID, time, messageIDsPrefix, changes, existingThreadDetails } =
+    dmOperation;
+  const newMemberIDs =
+    changes.newMemberIDs && changes.newMemberIDs.length > 0
+      ? [...new Set(changes.newMemberIDs)]
+      : [];
+  return {
+    type: 'add_members',
+    editorID,
+    time,
+    messageID: `${messageIDsPrefix}/add_members`,
+    addedUserIDs: newMemberIDs,
+    existingThreadDetails,
+  };
+}
+
 const changeThreadSettingsSpec: DMOperationSpec<DMChangeThreadSettingsOperation> =
   Object.freeze({
     processDMOperation: async (
@@ -34,34 +57,15 @@
       const { name, description, color, avatar } = changes;
       const threadID = existingThreadDetails.threadID;
 
-      const newMemberIDs =
-        changes.newMemberIDs && changes.newMemberIDs.length > 0
-          ? [...new Set(changes.newMemberIDs)]
-          : null;
-
       let threadInfoToUpdate: ?(RawThreadInfo | LegacyRawThreadInfo) =
         utilities.threadInfos[threadID];
-      if (!threadInfoToUpdate && !newMemberIDs?.includes(viewerID)) {
-        // We can't perform this operation now. It should be queued for later.
-        return {
-          rawMessageInfos: [],
-          updateInfos: [],
-        };
-      }
-
       const updateInfos: Array<ClientUpdateInfo> = [];
       const rawMessageInfos: Array<RawMessageInfo> = [];
 
-      if (newMemberIDs) {
+      if (changes.newMemberIDs && changes.newMemberIDs.length > 0) {
+        const addMembersOperation = createAddMembersOperation(dmOperation);
         const addMembersResult = createAddNewMembersResults(
-          {
-            type: 'add_members',
-            editorID,
-            time,
-            messageID: `${messageIDsPrefix}/add_members`,
-            addedUserIDs: newMemberIDs,
-            existingThreadDetails,
-          },
+          addMembersOperation,
           viewerID,
           utilities,
         );
@@ -72,13 +76,7 @@
         rawMessageInfos.push(...addMembersResult.rawMessageInfos);
       }
 
-      if (!threadInfoToUpdate || !threadInfoToUpdate.thick) {
-        // We can't perform this operation now. It should be queued for later.
-        return {
-          rawMessageInfos: [],
-          updateInfos: [],
-        };
-      }
+      invariant(threadInfoToUpdate?.thick, 'Thread should be thick');
 
       const changedFields: { [string]: string | number } = {};
 
@@ -157,6 +155,17 @@
         updateInfos,
       };
     },
+    canBeProcessed(
+      dmOperation: DMChangeThreadSettingsOperation,
+      viewerID: string,
+      utilities: ProcessDMOperationUtilities,
+    ) {
+      return addMembersSpec.canBeProcessed(
+        createAddMembersOperation(dmOperation),
+        viewerID,
+        utilities,
+      );
+    },
   });
 
 export { changeThreadSettingsSpec };
diff --git a/lib/shared/dm-ops/create-sidebar-spec.js b/lib/shared/dm-ops/create-sidebar-spec.js
--- a/lib/shared/dm-ops/create-sidebar-spec.js
+++ b/lib/shared/dm-ops/create-sidebar-spec.js
@@ -105,6 +105,9 @@
         updateInfos: [threadJoinUpdateInfo],
       };
     },
+    canBeProcessed() {
+      return { isProcessingPossible: true };
+    },
   });
 
 export { createSidebarSpec };
diff --git a/lib/shared/dm-ops/create-thread-spec.js b/lib/shared/dm-ops/create-thread-spec.js
--- a/lib/shared/dm-ops/create-thread-spec.js
+++ b/lib/shared/dm-ops/create-thread-spec.js
@@ -187,6 +187,9 @@
         updateInfos: [threadJoinUpdateInfo],
       };
     },
+    canBeProcessed() {
+      return { isProcessingPossible: true };
+    },
   });
 
 export {
diff --git a/lib/shared/dm-ops/dm-op-spec.js b/lib/shared/dm-ops/dm-op-spec.js
--- a/lib/shared/dm-ops/dm-op-spec.js
+++ b/lib/shared/dm-ops/dm-op-spec.js
@@ -16,4 +16,14 @@
     viewerID: string,
     utilities: ProcessDMOperationUtilities,
   ) => Promise<DMOperationResult>,
+  +canBeProcessed: (
+    dmOp: DMOp,
+    viewerID: string,
+    utilities: ProcessDMOperationUtilities,
+  ) =>
+    | { +isProcessingPossible: true }
+    | {
+        +isProcessingPossible: false,
+        +reason: { +type: 'missing_thread', +threadID: string },
+      },
 };
diff --git a/lib/shared/dm-ops/join-thread-spec.js b/lib/shared/dm-ops/join-thread-spec.js
--- a/lib/shared/dm-ops/join-thread-spec.js
+++ b/lib/shared/dm-ops/join-thread-spec.js
@@ -18,10 +18,7 @@
   messageTruncationStatus,
   type RawMessageInfo,
 } from '../../types/message-types.js';
-import {
-  minimallyEncodeMemberInfo,
-  type ThickRawThreadInfo,
-} from '../../types/minimally-encoded-thread-permissions-types.js';
+import { minimallyEncodeMemberInfo } from '../../types/minimally-encoded-thread-permissions-types.js';
 import { joinThreadSubscription } from '../../types/subscription-types.js';
 import type { ThickMemberInfo } from '../../types/thread-types.js';
 import { updateTypes } from '../../types/update-types-enum.js';
@@ -37,6 +34,9 @@
   ) => {
     const { editorID, time, messageID, existingThreadDetails } = dmOperation;
 
+    const currentThreadInfo =
+      utilities.threadInfos[existingThreadDetails.threadID];
+
     const joinThreadMessage = {
       type: messageTypes.JOIN_THREAD,
       id: messageID,
@@ -45,9 +45,7 @@
       time,
     };
 
-    const currentThreadInfoOptional =
-      utilities.threadInfos[existingThreadDetails.threadID];
-    if (userIsMember(currentThreadInfoOptional, editorID)) {
+    if (userIsMember(currentThreadInfo, editorID)) {
       return {
         rawMessageInfos: [joinThreadMessage],
         updateInfos: [
@@ -97,14 +95,7 @@
         updateInfos.push(repliesCountUpdate);
       }
     } else {
-      if (!currentThreadInfoOptional || !currentThreadInfoOptional.thick) {
-        // We can't perform this operation now. It should be queued for later.
-        return {
-          rawMessageInfos: [],
-          updateInfos: [],
-        };
-      }
-      const currentThreadInfo: ThickRawThreadInfo = currentThreadInfoOptional;
+      invariant(currentThreadInfo.thick, 'Thread should be thick');
 
       rawMessageInfos.push(joinThreadMessage);
       const defaultRoleID = values(currentThreadInfo.roles).find(role =>
@@ -153,6 +144,22 @@
       updateInfos,
     };
   },
+  canBeProcessed(
+    dmOperation: DMJoinThreadOperation,
+    viewerID: string,
+    utilities: ProcessDMOperationUtilities,
+  ) {
+    if (utilities.threadInfos[dmOperation.existingThreadDetails.threadID]) {
+      return { isProcessingPossible: true };
+    }
+    return {
+      isProcessingPossible: false,
+      reason: {
+        type: 'missing_thread',
+        threadID: dmOperation.existingThreadDetails.threadID,
+      },
+    };
+  },
 });
 
 export { joinThreadSpec };
diff --git a/lib/shared/dm-ops/leave-thread-spec.js b/lib/shared/dm-ops/leave-thread-spec.js
--- a/lib/shared/dm-ops/leave-thread-spec.js
+++ b/lib/shared/dm-ops/leave-thread-spec.js
@@ -1,5 +1,6 @@
 // @flow
 
+import invariant from 'invariant';
 import uuid from 'uuid';
 
 import type {
@@ -9,7 +10,6 @@
 import { createUpdateUnreadCountUpdate } from './dm-op-utils.js';
 import type { DMLeaveThreadOperation } from '../../types/dm-ops.js';
 import { messageTypes } from '../../types/message-types-enum.js';
-import type { ThickRawThreadInfo } from '../../types/minimally-encoded-thread-permissions-types.js';
 import { threadTypes } from '../../types/thread-types-enum.js';
 import { updateTypes } from '../../types/update-types-enum.js';
 import type { ClientUpdateInfo } from '../../types/update-types.js';
@@ -23,15 +23,8 @@
   ) => {
     const { editorID, time, messageID, threadID } = dmOperation;
 
-    const threadInfoOptional = utilities.threadInfos[threadID];
-    if (!threadInfoOptional || !threadInfoOptional.thick) {
-      // We can't perform this operation now. It should be queued for later.
-      return {
-        rawMessageInfos: [],
-        updateInfos: [],
-      };
-    }
-    const threadInfo: ThickRawThreadInfo = threadInfoOptional;
+    const threadInfo = utilities.threadInfos[threadID];
+    invariant(threadInfo.thick, 'Thread should be thick');
 
     const leaveThreadMessage = {
       type: messageTypes.LEAVE_THREAD,
@@ -86,6 +79,22 @@
       updateInfos,
     };
   },
+  canBeProcessed(
+    dmOperation: DMLeaveThreadOperation,
+    viewerID: string,
+    utilities: ProcessDMOperationUtilities,
+  ) {
+    if (utilities.threadInfos[dmOperation.threadID]) {
+      return { isProcessingPossible: true };
+    }
+    return {
+      isProcessingPossible: false,
+      reason: {
+        type: 'missing_thread',
+        threadID: dmOperation.threadID,
+      },
+    };
+  },
 });
 
 export { leaveThreadSpec };
diff --git a/lib/shared/dm-ops/process-dm-ops.js b/lib/shared/dm-ops/process-dm-ops.js
--- a/lib/shared/dm-ops/process-dm-ops.js
+++ b/lib/shared/dm-ops/process-dm-ops.js
@@ -32,6 +32,15 @@
         console.log('ignored DMOperation because logged out');
         return;
       }
+      const processingCheckResult = dmOpSpecs[dmOp.type].canBeProcessed(
+        dmOp,
+        viewerID,
+        utilities,
+      );
+      if (!processingCheckResult.isProcessingPossible) {
+        // TODO queue for later
+        return;
+      }
       const { rawMessageInfos, updateInfos } = await dmOpSpecs[
         dmOp.type
       ].processDMOperation(dmOp, viewerID, utilities);
diff --git a/lib/shared/dm-ops/remove-members-spec.js b/lib/shared/dm-ops/remove-members-spec.js
--- a/lib/shared/dm-ops/remove-members-spec.js
+++ b/lib/shared/dm-ops/remove-members-spec.js
@@ -1,5 +1,6 @@
 // @flow
 
+import invariant from 'invariant';
 import uuid from 'uuid';
 
 import type {
@@ -9,7 +10,6 @@
 import { createUpdateUnreadCountUpdate } from './dm-op-utils.js';
 import type { DMRemoveMembersOperation } from '../../types/dm-ops.js';
 import { messageTypes } from '../../types/message-types-enum.js';
-import type { ThickRawThreadInfo } from '../../types/minimally-encoded-thread-permissions-types.js';
 import { threadTypes } from '../../types/thread-types-enum.js';
 import { updateTypes } from '../../types/update-types-enum.js';
 import type { ClientUpdateInfo } from '../../types/update-types.js';
@@ -24,15 +24,8 @@
       const { editorID, time, messageID, threadID, removedUserIDs } =
         dmOperation;
 
-      const threadInfoOptional = utilities.threadInfos[threadID];
-      if (!threadInfoOptional || !threadInfoOptional.thick) {
-        // We can't perform this operation now. It should be queued for later.
-        return {
-          rawMessageInfos: [],
-          updateInfos: [],
-        };
-      }
-      const threadInfo: ThickRawThreadInfo = threadInfoOptional;
+      const threadInfo = utilities.threadInfos[threadID];
+      invariant(threadInfo.thick, 'Thread should be thick');
 
       const removeMembersMessage = {
         type: messageTypes.REMOVE_MEMBERS,
@@ -90,6 +83,22 @@
         updateInfos,
       };
     },
+    canBeProcessed(
+      dmOperation: DMRemoveMembersOperation,
+      viewerID: string,
+      utilities: ProcessDMOperationUtilities,
+    ) {
+      if (utilities.threadInfos[dmOperation.threadID]) {
+        return { isProcessingPossible: true };
+      }
+      return {
+        isProcessingPossible: false,
+        reason: {
+          type: 'missing_thread',
+          threadID: dmOperation.threadID,
+        },
+      };
+    },
   });
 
 export { removeMembersSpec };
diff --git a/lib/shared/dm-ops/send-edit-message-spec.js b/lib/shared/dm-ops/send-edit-message-spec.js
--- a/lib/shared/dm-ops/send-edit-message-spec.js
+++ b/lib/shared/dm-ops/send-edit-message-spec.js
@@ -42,19 +42,33 @@
         });
       }
       const threadInfo = utilities.threadInfos[threadID];
-      if (threadInfo) {
-        const repliesCountUpdate = createUpdateUnreadCountUpdate(threadInfo, [
-          editMessage,
-        ]);
-        if (repliesCountUpdate) {
-          updateInfos.push(repliesCountUpdate);
-        }
+      const repliesCountUpdate = createUpdateUnreadCountUpdate(threadInfo, [
+        editMessage,
+      ]);
+      if (repliesCountUpdate) {
+        updateInfos.push(repliesCountUpdate);
       }
       return {
         rawMessageInfos: [editMessage],
         updateInfos,
       };
     },
+    canBeProcessed(
+      dmOperation: DMSendEditMessageOperation,
+      viewerID: string,
+      utilities: ProcessDMOperationUtilities,
+    ) {
+      if (utilities.threadInfos[dmOperation.threadID]) {
+        return { isProcessingPossible: true };
+      }
+      return {
+        isProcessingPossible: false,
+        reason: {
+          type: 'missing_thread',
+          threadID: dmOperation.threadID,
+        },
+      };
+    },
   });
 
 export { sendEditMessageSpec };
diff --git a/lib/shared/dm-ops/send-reaction-message-spec.js b/lib/shared/dm-ops/send-reaction-message-spec.js
--- a/lib/shared/dm-ops/send-reaction-message-spec.js
+++ b/lib/shared/dm-ops/send-reaction-message-spec.js
@@ -50,19 +50,33 @@
         });
       }
       const threadInfo = utilities.threadInfos[threadID];
-      if (threadInfo) {
-        const repliesCountUpdate = createUpdateUnreadCountUpdate(threadInfo, [
-          reactionMessage,
-        ]);
-        if (repliesCountUpdate) {
-          updateInfos.push(repliesCountUpdate);
-        }
+      const repliesCountUpdate = createUpdateUnreadCountUpdate(threadInfo, [
+        reactionMessage,
+      ]);
+      if (repliesCountUpdate) {
+        updateInfos.push(repliesCountUpdate);
       }
       return {
         rawMessageInfos: [reactionMessage],
         updateInfos,
       };
     },
+    canBeProcessed(
+      dmOperation: DMSendReactionMessageOperation,
+      viewerID: string,
+      utilities: ProcessDMOperationUtilities,
+    ) {
+      if (utilities.threadInfos[dmOperation.threadID]) {
+        return { isProcessingPossible: true };
+      }
+      return {
+        isProcessingPossible: false,
+        reason: {
+          type: 'missing_thread',
+          threadID: dmOperation.threadID,
+        },
+      };
+    },
   });
 
 export { sendReactionMessageSpec };
diff --git a/lib/shared/dm-ops/send-text-message-spec.js b/lib/shared/dm-ops/send-text-message-spec.js
--- a/lib/shared/dm-ops/send-text-message-spec.js
+++ b/lib/shared/dm-ops/send-text-message-spec.js
@@ -39,19 +39,33 @@
         });
       }
       const threadInfo = utilities.threadInfos[threadID];
-      if (threadInfo) {
-        const repliesCountUpdate = createUpdateUnreadCountUpdate(threadInfo, [
-          textMessage,
-        ]);
-        if (repliesCountUpdate) {
-          updateInfos.push(repliesCountUpdate);
-        }
+      const repliesCountUpdate = createUpdateUnreadCountUpdate(threadInfo, [
+        textMessage,
+      ]);
+      if (repliesCountUpdate) {
+        updateInfos.push(repliesCountUpdate);
       }
       return {
         rawMessageInfos: [textMessage],
         updateInfos,
       };
     },
+    canBeProcessed(
+      dmOperation: DMSendTextMessageOperation,
+      viewerID: string,
+      utilities: ProcessDMOperationUtilities,
+    ) {
+      if (utilities.threadInfos[dmOperation.threadID]) {
+        return { isProcessingPossible: true };
+      }
+      return {
+        isProcessingPossible: false,
+        reason: {
+          type: 'missing_thread',
+          threadID: dmOperation.threadID,
+        },
+      };
+    },
   });
 
 export { sendTextMessageSpec };