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
@@ -10,26 +10,15 @@
 } from './dm-op-spec.js';
 import type { DMAddMembersOperation } from '../../types/dm-ops.js';
 import { messageTypes } from '../../types/message-types-enum.js';
-import { type RawMessageInfo } from '../../types/message-types.js';
 import type { AddMembersMessageData } from '../../types/messages/add-members.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';
-import type { ClientUpdateInfo } from '../../types/update-types.js';
 import { values } from '../../utils/objects.js';
 import { rawMessageInfoFromMessageData } from '../message-utils.js';
 import { roleIsDefaultRole, userIsMember } from '../thread-utils.js';
 
-export type AddMembersResult = {
-  rawMessageInfos: Array<RawMessageInfo>,
-  updateInfos: Array<ClientUpdateInfo>,
-  threadInfo: ?ThickRawThreadInfo,
-};
-
 function createAddNewMembersMessageDataFromDMOperation(
   dmOperation: DMAddMembersOperation,
 ): AddMembersMessageData {
diff --git a/lib/shared/dm-ops/add-viewer-to-thread-members-spec.js b/lib/shared/dm-ops/add-viewer-to-thread-members-spec.js
--- a/lib/shared/dm-ops/add-viewer-to-thread-members-spec.js
+++ b/lib/shared/dm-ops/add-viewer-to-thread-members-spec.js
@@ -2,9 +2,11 @@
 
 import uuid from 'uuid';
 
-import type { AddMembersResult } from './add-members-spec.js';
 import { createThickRawThreadInfo } from './create-thread-spec.js';
-import type { DMOperationSpec } from './dm-op-spec.js';
+import type {
+  DMOperationSpec,
+  ProcessDMOperationUtilities,
+} from './dm-op-spec.js';
 import type { DMAddViewerToThreadMembersOperation } from '../../types/dm-ops.js';
 import { messageTypes } from '../../types/message-types-enum.js';
 import { messageTruncationStatus } from '../../types/message-types.js';
@@ -12,6 +14,7 @@
 import { joinThreadSubscription } from '../../types/subscription-types.js';
 import { updateTypes } from '../../types/update-types-enum.js';
 import { rawMessageInfoFromMessageData } from '../message-utils.js';
+import { userIsMember } from '../thread-utils.js';
 
 function createAddViewerToThreadMembersMessageDataFromDMOp(
   dmOperation: DMAddViewerToThreadMembersOperation,
@@ -26,49 +29,6 @@
   };
 }
 
-function createAddViewerToThreadMembersResults(
-  dmOperation: DMAddViewerToThreadMembersOperation,
-  viewerID: string,
-): AddMembersResult {
-  const { time, messageID, addedUserIDs, existingThreadDetails } = dmOperation;
-  const messageData =
-    createAddViewerToThreadMembersMessageDataFromDMOp(dmOperation);
-
-  const rawMessageInfos = [
-    rawMessageInfoFromMessageData(messageData, messageID),
-  ];
-
-  const resultThreadInfo = createThickRawThreadInfo(
-    {
-      ...existingThreadDetails,
-      allMemberIDsWithSubscriptions: [
-        ...existingThreadDetails.allMemberIDsWithSubscriptions,
-        ...addedUserIDs.map(id => ({
-          id,
-          subscription: joinThreadSubscription,
-        })),
-      ],
-    },
-    viewerID,
-  );
-  const updateInfos = [
-    {
-      type: updateTypes.JOIN_THREAD,
-      id: uuid.v4(),
-      time,
-      threadInfo: resultThreadInfo,
-      rawMessageInfos,
-      truncationStatus: messageTruncationStatus.EXHAUSTIVE,
-      rawEntryInfos: [],
-    },
-  ];
-  return {
-    rawMessageInfos: [],
-    updateInfos,
-    threadInfo: resultThreadInfo,
-  };
-}
-
 const addViewerToThreadMembersSpec: DMOperationSpec<DMAddViewerToThreadMembersOperation> =
   Object.freeze({
     notificationsCreationData: async (
@@ -81,9 +41,85 @@
     processDMOperation: async (
       dmOperation: DMAddViewerToThreadMembersOperation,
       viewerID: string,
+      utilities: ProcessDMOperationUtilities,
     ) => {
-      const { rawMessageInfos, updateInfos } =
-        createAddViewerToThreadMembersResults(dmOperation, viewerID);
+      const { time, messageID, addedUserIDs, existingThreadDetails } =
+        dmOperation;
+      const messageData =
+        createAddViewerToThreadMembersMessageDataFromDMOp(dmOperation);
+
+      const rawMessageInfos = [
+        rawMessageInfoFromMessageData(messageData, messageID),
+      ];
+
+      const threadID = existingThreadDetails.threadID;
+      const currentThreadInfo = utilities.threadInfos[threadID];
+      if (currentThreadInfo && !currentThreadInfo.thick) {
+        return {
+          rawMessageInfos: [],
+          updateInfos: [],
+        };
+      }
+
+      const memberTimestamps = {
+        ...currentThreadInfo?.timestamps?.members,
+      };
+      const newMembers = [];
+      for (const userID of addedUserIDs) {
+        if (!memberTimestamps[userID]) {
+          memberTimestamps[userID] = {
+            isMember: time,
+            subscription: existingThreadDetails.creationTime,
+          };
+        }
+
+        if (memberTimestamps[userID].isMember > time) {
+          continue;
+        }
+
+        memberTimestamps[userID] = {
+          ...memberTimestamps[userID],
+          isMember: time,
+        };
+
+        if (!userIsMember(currentThreadInfo, userID)) {
+          newMembers.push(userID);
+        }
+      }
+
+      const resultThreadInfo = createThickRawThreadInfo(
+        {
+          ...existingThreadDetails,
+          allMemberIDsWithSubscriptions: [
+            ...existingThreadDetails.allMemberIDsWithSubscriptions,
+            ...newMembers.map(id => ({
+              id,
+              subscription: joinThreadSubscription,
+            })),
+          ],
+        },
+        viewerID,
+      );
+      const updateInfos = [
+        {
+          type: updateTypes.JOIN_THREAD,
+          id: uuid.v4(),
+          time,
+          threadInfo: {
+            ...resultThreadInfo,
+            timestamps: {
+              ...resultThreadInfo.timestamps,
+              members: {
+                ...resultThreadInfo.timestamps.members,
+                ...memberTimestamps,
+              },
+            },
+          },
+          rawMessageInfos,
+          truncationStatus: messageTruncationStatus.EXHAUSTIVE,
+          rawEntryInfos: [],
+        },
+      ];
       return { rawMessageInfos, updateInfos };
     },
     canBeProcessed(
@@ -106,6 +142,5 @@
 
 export {
   addViewerToThreadMembersSpec,
-  createAddViewerToThreadMembersResults,
   createAddViewerToThreadMembersMessageDataFromDMOp,
 };