diff --git a/lib/selectors/chat-selectors.js b/lib/selectors/chat-selectors.js
--- a/lib/selectors/chat-selectors.js
+++ b/lib/selectors/chat-selectors.js
@@ -457,7 +457,8 @@
 
     if (
       messageInfo.type === messageTypes.REACTION ||
-      messageInfo.type === messageTypes.EDIT_MESSAGE
+      messageInfo.type === messageTypes.EDIT_MESSAGE ||
+      messageInfo.type === messageTypes.DELETE_MESSAGE
     ) {
       continue;
     }
diff --git a/lib/shared/message-utils.js b/lib/shared/message-utils.js
--- a/lib/shared/message-utils.js
+++ b/lib/shared/message-utils.js
@@ -42,6 +42,7 @@
   defaultNumberPerThread,
   messageTruncationStatus,
 } from '../types/message-types.js';
+import type { DeleteMessageInfo } from '../types/messages/delete.js';
 import type {
   EditMessageInfo,
   RawEditMessageInfo,
@@ -387,7 +388,8 @@
     | ComposableMessageInfo
     | RobotextMessageInfo
     | ReactionMessageInfo
-    | EditMessageInfo,
+    | EditMessageInfo
+    | DeleteMessageInfo,
   threadInfo: ThreadInfo,
   parentThreadInfo: ?ThreadInfo,
   markdownRules: ParserRules,
@@ -402,7 +404,8 @@
       messageInfo.type !== messageTypes.IMAGES &&
       messageInfo.type !== messageTypes.MULTIMEDIA &&
       messageInfo.type !== messageTypes.REACTION &&
-      messageInfo.type !== messageTypes.EDIT_MESSAGE,
+      messageInfo.type !== messageTypes.EDIT_MESSAGE &&
+      messageInfo.type !== messageTypes.DELETE_MESSAGE,
     'messageTitle can only be auto-generated for RobotextMessageInfo',
   );
   return robotextForMessageInfo(messageInfo, threadInfo, parentThreadInfo);
@@ -639,7 +642,8 @@
     (message.type === messageTypes.REACTION ||
       message.type === messageTypes.EDIT_MESSAGE ||
       message.type === messageTypes.SIDEBAR_SOURCE ||
-      message.type === messageTypes.TOGGLE_PIN) &&
+      message.type === messageTypes.TOGGLE_PIN ||
+      message.type === messageTypes.DELETE_MESSAGE) &&
     !messageSpecs[message.type].canBeSidebarSource
   );
 }
diff --git a/lib/shared/messages/delete-message-spec.js b/lib/shared/messages/delete-message-spec.js
new file mode 100644
--- /dev/null
+++ b/lib/shared/messages/delete-message-spec.js
@@ -0,0 +1,66 @@
+// @flow
+
+import invariant from 'invariant';
+
+import type { MessageSpec } from './message-spec.js';
+import {
+  assertMessageType,
+  messageTypes,
+} from '../../types/message-types-enum.js';
+import type { ClientDBMessageInfo } from '../../types/message-types.js';
+import type {
+  DeleteMessageData,
+  DeleteMessageInfo,
+  RawDeleteMessageInfo,
+} from '../../types/messages/delete.js';
+import { rawDeleteMessageInfoValidator } from '../../types/messages/delete.js';
+import type { RelativeUserInfo } from '../../types/user-types.js';
+
+export const deleteMessageSpec: MessageSpec<
+  DeleteMessageData,
+  RawDeleteMessageInfo,
+  DeleteMessageInfo,
+> = Object.freeze({
+  rawMessageInfoFromClientDB(
+    clientDBMessageInfo: ClientDBMessageInfo,
+  ): RawDeleteMessageInfo {
+    const messageType = assertMessageType(parseInt(clientDBMessageInfo.type));
+    invariant(
+      messageType === messageTypes.DELETE_MESSAGE,
+      'message must be of type DELETE_MESSAGE',
+    );
+    invariant(
+      clientDBMessageInfo.content !== undefined &&
+        clientDBMessageInfo.content !== null,
+      'content must be defined for DeleteMessage',
+    );
+    const content = JSON.parse(clientDBMessageInfo.content);
+
+    return {
+      type: messageTypes.DELETE_MESSAGE,
+      id: clientDBMessageInfo.id,
+      threadID: clientDBMessageInfo.thread,
+      time: parseInt(clientDBMessageInfo.time),
+      creatorID: clientDBMessageInfo.user,
+      targetMessageID: content.targetMessageID,
+    };
+  },
+
+  createMessageInfo(
+    rawMessageInfo: RawDeleteMessageInfo,
+    creator: RelativeUserInfo,
+  ): DeleteMessageInfo {
+    return {
+      type: messageTypes.DELETE_MESSAGE,
+      id: rawMessageInfo.id,
+      threadID: rawMessageInfo.threadID,
+      creator,
+      time: rawMessageInfo.time,
+      targetMessageID: rawMessageInfo.targetMessageID,
+    };
+  },
+
+  canBeSidebarSource: false,
+  canBePinned: false,
+  validator: rawDeleteMessageInfoValidator,
+});
diff --git a/lib/shared/messages/message-specs.js b/lib/shared/messages/message-specs.js
--- a/lib/shared/messages/message-specs.js
+++ b/lib/shared/messages/message-specs.js
@@ -8,6 +8,7 @@
 import { createSubThreadMessageSpec } from './create-sub-thread-message-spec.js';
 import { createThreadMessageSpec } from './create-thread-message-spec.js';
 import { deleteEntryMessageSpec } from './delete-entry-message-spec.js';
+import { deleteMessageSpec } from './delete-message-spec.js';
 import { editEntryMessageSpec } from './edit-entry-message-spec.js';
 import { editMessageSpec } from './edit-message-spec.js';
 import { joinThreadMessageSpec } from './join-thread-message-spec.js';
@@ -53,4 +54,5 @@
   [messageTypes.EDIT_MESSAGE]: editMessageSpec,
   [messageTypes.TOGGLE_PIN]: togglePinMessageSpec,
   [messageTypes.UPDATE_RELATIONSHIP]: updateRelationshipMessageSpec,
+  [messageTypes.DELETE_MESSAGE]: deleteMessageSpec,
 });
diff --git a/lib/types/message-types-enum.js b/lib/types/message-types-enum.js
--- a/lib/types/message-types-enum.js
+++ b/lib/types/message-types-enum.js
@@ -37,6 +37,7 @@
   EDIT_MESSAGE: 20,
   TOGGLE_PIN: 21,
   UPDATE_RELATIONSHIP: 22,
+  DELETE_MESSAGE: 23,
 });
 export type MessageType = $Values<typeof messageTypes>;
 export function assertMessageType(ourMessageType: number): MessageType {
@@ -63,7 +64,8 @@
       ourMessageType === 19 ||
       ourMessageType === 20 ||
       ourMessageType === 21 ||
-      ourMessageType === 22,
+      ourMessageType === 22 ||
+      ourMessageType === 23,
     'number is not MessageType enum',
   );
   return ourMessageType;
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
@@ -58,6 +58,12 @@
   type RawDeleteEntryMessageInfo,
   rawDeleteEntryMessageInfoValidator,
 } from './messages/delete-entry.js';
+import type {
+  DeleteMessageData,
+  DeleteMessageInfo,
+  RawDeleteMessageInfo,
+} from './messages/delete.js';
+import { rawDeleteMessageInfoValidator } from './messages/delete.js';
 import {
   type EditEntryMessageData,
   type EditEntryMessageInfo,
@@ -261,7 +267,8 @@
   | ReactionMessageData
   | EditMessageData
   | TogglePinMessageData
-  | UpdateRelationshipMessageData;
+  | UpdateRelationshipMessageData
+  | DeleteMessageData;
 
 export type MultimediaMessageData = ImagesMessageData | MediaMessageData;
 
@@ -341,13 +348,15 @@
   | RawRobotextMessageInfo
   | RawSidebarSourceMessageInfo
   | RawReactionMessageInfo
-  | RawEditMessageInfo;
+  | RawEditMessageInfo
+  | RawDeleteMessageInfo;
 export const rawMessageInfoValidator: TUnion<RawMessageInfo> = t.union([
   rawComposableMessageInfoValidator,
   rawRobotextMessageInfoValidator,
   rawSidebarSourceMessageInfoValidator,
   rawReactionMessageInfoValidator,
   rawEditMessageInfoValidator,
+  rawDeleteMessageInfoValidator,
 ]);
 
 export type LocallyComposedMessageInfo =
@@ -429,7 +438,8 @@
   | RobotextMessageInfo
   | SidebarSourceMessageInfo
   | ReactionMessageInfo
-  | EditMessageInfo;
+  | EditMessageInfo
+  | DeleteMessageInfo;
 
 export type ThreadMessageInfo = {
   messageIDs: string[],
diff --git a/lib/types/messages/delete.js b/lib/types/messages/delete.js
new file mode 100644
--- /dev/null
+++ b/lib/types/messages/delete.js
@@ -0,0 +1,39 @@
+// @flow
+
+import t, { type TInterface } from 'tcomb';
+
+import { tID, tNumber, tShape, tUserID } from '../../utils/validation-utils.js';
+import { messageTypes } from '../message-types-enum.js';
+import type { RelativeUserInfo } from '../user-types.js';
+
+export type DeleteMessageData = {
+  +type: 23,
+  +threadID: string,
+  +creatorID: string,
+  +time: number,
+  +targetMessageID: string,
+};
+
+export type RawDeleteMessageInfo = $ReadOnly<{
+  ...DeleteMessageData,
+  +id: string,
+}>;
+
+export const rawDeleteMessageInfoValidator: TInterface<RawDeleteMessageInfo> =
+  tShape<RawDeleteMessageInfo>({
+    type: tNumber(messageTypes.DELETE_MESSAGE),
+    threadID: tID,
+    creatorID: tUserID,
+    time: t.Number,
+    targetMessageID: tID,
+    id: tID,
+  });
+
+export type DeleteMessageInfo = {
+  +type: 23,
+  +id: string,
+  +threadID: string,
+  +creator: RelativeUserInfo,
+  +time: number, // millisecond timestamp
+  +targetMessageID: string,
+};
diff --git a/lib/types/messages/unsupported.js b/lib/types/messages/unsupported.js
--- a/lib/types/messages/unsupported.js
+++ b/lib/types/messages/unsupported.js
@@ -6,6 +6,8 @@
   rawChangeRoleMessageInfoValidator,
   type RawChangeRoleMessageInfo,
 } from './change-role.js';
+import type { RawDeleteMessageInfo } from './delete.js';
+import { rawDeleteMessageInfoValidator } from './delete.js';
 import {
   rawEditMessageInfoValidator,
   type RawEditMessageInfo,
@@ -44,7 +46,8 @@
     | RawEditMessageInfo
     | RawMediaMessageInfo
     | RawReactionMessageInfo
-    | RawTogglePinMessageInfo,
+    | RawTogglePinMessageInfo
+    | RawDeleteMessageInfo,
 };
 
 export const rawUnsupportedMessageInfoValidator: TInterface<RawUnsupportedMessageInfo> =
@@ -65,6 +68,7 @@
       rawMediaMessageInfoValidator,
       rawReactionMessageInfoValidator,
       rawTogglePinMessageInfoValidator,
+      rawDeleteMessageInfoValidator,
       t.Object,
     ]),
   });
@@ -83,5 +87,6 @@
     | RawEditMessageInfo
     | RawMediaMessageInfo
     | RawReactionMessageInfo
-    | RawTogglePinMessageInfo,
+    | RawTogglePinMessageInfo
+    | RawDeleteMessageInfo,
 };
diff --git a/lib/types/validation.test.js b/lib/types/validation.test.js
--- a/lib/types/validation.test.js
+++ b/lib/types/validation.test.js
@@ -307,6 +307,14 @@
     time: 1682083295820,
     id: '92943',
   },
+  {
+    type: messageTypes.DELETE_MESSAGE,
+    threadID: '86033',
+    creatorID: '83928',
+    time: 1682083295820,
+    targetMessageID: '91607',
+    id: '92943',
+  },
 ];
 
 describe('message validation', () => {