diff --git a/keyserver/src/push/send.js b/keyserver/src/push/send.js
--- a/keyserver/src/push/send.js
+++ b/keyserver/src/push/send.js
@@ -12,7 +12,10 @@
 
 import genesis from 'lib/facts/genesis.js';
 import { oldValidUsernameRegex } from 'lib/shared/account-utils.js';
-import { isUserMentioned } from 'lib/shared/mention-utils.js';
+import {
+  isUserMentioned,
+  extractChatMentions,
+} from 'lib/shared/mention-utils.js';
 import {
   createMessageInfo,
   sortMessageInfoList,
@@ -23,6 +26,7 @@
 import {
   rawThreadInfoFromServerThreadInfo,
   threadInfoFromRawThreadInfo,
+  extractThreadID,
 } from 'lib/shared/thread-utils.js';
 import type { Platform, PlatformDetails } from 'lib/types/device-types.js';
 import { messageTypes } from 'lib/types/message-types-enum.js';
@@ -108,7 +112,12 @@
 
   const [
     unreadCounts,
-    { usersToCollapsableNotifInfo, serverThreadInfos, userInfos },
+    {
+      usersToCollapsableNotifInfo,
+      serverThreadInfos,
+      userInfos,
+      userThreadIDs,
+    },
     dbIDs,
   ] = await Promise.all([
     getUnreadCounts(Object.keys(pushInfo)),
@@ -180,13 +189,19 @@
         continue;
       }
       const badgeOnly = !displayBanner && !userWasMentioned;
-
+      const mentionableThreadIDs = await getMentionableThreads(
+        threadID,
+        userThreadIDs[userID],
+        userID,
+      );
       const notifTargetUserInfo = { id: userID, username };
       const notifTexts = await notifTextsForMessageInfo(
         allMessageInfos,
-        threadInfo,
+        threadID,
         notifTargetUserInfo,
         getENSNames,
+        mentionableThreadIDs,
+        threadInfos,
       );
       if (!notifTexts) {
         continue;
@@ -477,7 +492,6 @@
   }
 }
 
-// eslint-disable-next-line no-unused-vars
 async function getMentionableThreads(
   messageThreadID: string,
   mentionedThreadIDs: $ReadOnlySet<string>,
@@ -596,8 +610,13 @@
   const usersToCollapsableNotifInfo = await fetchCollapsableNotifs(pushInfo);
 
   const threadIDs = new Set();
+  const userThreadIDs = {};
+  const mentionThreadIDs = new Set();
   const threadWithChangedNamesToMessages = new Map();
-  const addThreadIDsFromMessageInfos = (rawMessageInfo: RawMessageInfo) => {
+  const addThreadIDsFromMessageInfos = (
+    rawMessageInfo: RawMessageInfo,
+    userID: string,
+  ) => {
     const threadID = rawMessageInfo.threadID;
     threadIDs.add(threadID);
     const messageSpec = messageSpecs[rawMessageInfo.type];
@@ -617,14 +636,33 @@
         threadWithChangedNamesToMessages.set(threadID, [rawMessageInfo.id]);
       }
     }
+    const unwrappedMessageInfo =
+      rawMessageInfo.type === messageTypes.SIDEBAR_SOURCE
+        ? rawMessageInfo.sourceMessage
+        : rawMessageInfo;
+    if (
+      unwrappedMessageInfo &&
+      unwrappedMessageInfo.type === messageTypes.TEXT
+    ) {
+      for (const { threadID: mentionedThreadID } of extractChatMentions(
+        unwrappedMessageInfo.text,
+      )) {
+        const transformedMentionedThreadID = extractThreadID(mentionedThreadID);
+        mentionThreadIDs.add(transformedMentionedThreadID);
+        userThreadIDs[userID].add(transformedMentionedThreadID);
+      }
+    }
   };
   for (const userID in usersToCollapsableNotifInfo) {
+    if (!userThreadIDs[userID]) {
+      userThreadIDs[userID] = new Set();
+    }
     for (const notifInfo of usersToCollapsableNotifInfo[userID]) {
       for (const rawMessageInfo of notifInfo.existingMessageInfos) {
-        addThreadIDsFromMessageInfos(rawMessageInfo);
+        addThreadIDsFromMessageInfos(rawMessageInfo, userID);
       }
       for (const rawMessageInfo of notifInfo.newMessageInfos) {
-        addThreadIDsFromMessageInfos(rawMessageInfo);
+        addThreadIDsFromMessageInfos(rawMessageInfo, userID);
       }
     }
   }
@@ -632,6 +670,9 @@
   const promises = {};
   // These threadInfos won't have currentUser set
   promises.threadResult = fetchServerThreadInfos({ threadIDs });
+  promises.mentionThreadResult = fetchServerThreadInfos({
+    threadIDs: mentionThreadIDs,
+  });
   if (threadWithChangedNamesToMessages.size > 0) {
     const typesThatAffectName = [
       messageTypes.CHANGE_SETTINGS,
@@ -664,7 +705,9 @@
     promises.oldNames = dbQuery(oldNameQuery);
   }
 
-  const { threadResult, oldNames } = await promiseAll(promises);
+  const { threadResult, oldNames, mentionThreadResult } = await promiseAll(
+    promises,
+  );
   const serverThreadInfos = threadResult.threadInfos;
   if (oldNames) {
     const [result] = oldNames;
@@ -679,7 +722,15 @@
     usersToCollapsableNotifInfo,
   );
 
-  return { usersToCollapsableNotifInfo, serverThreadInfos, userInfos };
+  return {
+    usersToCollapsableNotifInfo,
+    serverThreadInfos: {
+      ...serverThreadInfos,
+      ...mentionThreadResult.threadInfos,
+    },
+    userInfos,
+    userThreadIDs,
+  };
 }
 
 async function fetchNotifUserInfos(
diff --git a/lib/shared/messages/message-spec.js b/lib/shared/messages/message-spec.js
--- a/lib/shared/messages/message-spec.js
+++ b/lib/shared/messages/message-spec.js
@@ -51,6 +51,8 @@
     threadInfo: ThreadInfo,
   ) => Promise<?NotifTexts>,
   +notifTargetUserInfo: UserInfo,
+  +threadInfos: { +[id: string]: ThreadInfo },
+  +mentionableThreadIDs: $ReadOnlySet<string>,
 };
 
 export type GeneratesNotifsParams = {
diff --git a/lib/shared/messages/text-message-spec.js b/lib/shared/messages/text-message-spec.js
--- a/lib/shared/messages/text-message-spec.js
+++ b/lib/shared/messages/text-message-spec.js
@@ -40,7 +40,7 @@
   stripSpoilersFromNotifications,
   stripSpoilersFromMarkdownAST,
 } from '../markdown.js';
-import { isUserMentioned } from '../mention-utils.js';
+import { isUserMentioned, renderChatMentions } from '../mention-utils.js';
 import { notifTextsForSidebarCreation } from '../notif-utils.js';
 import {
   threadIsGroupChat,
@@ -244,8 +244,13 @@
       messageInfo.type === messageTypes.TEXT,
       'messageInfo should be messageTypes.TEXT!',
     );
-    const notificationTextWithoutSpoilers = stripSpoilersFromNotifications(
+    const notificationTextWithRenderedChatMentions = await renderChatMentions(
       messageInfo.text,
+      params.threadInfos,
+      params.mentionableThreadIDs,
+    );
+    const notificationTextWithoutSpoilers = stripSpoilersFromNotifications(
+      notificationTextWithRenderedChatMentions,
     );
     if (!threadInfo.name && !threadIsGroupChat(threadInfo)) {
       const thread = ET.thread({ display: 'uiName', threadInfo });
diff --git a/lib/shared/notif-utils.js b/lib/shared/notif-utils.js
--- a/lib/shared/notif-utils.js
+++ b/lib/shared/notif-utils.js
@@ -34,15 +34,19 @@
 
 async function notifTextsForMessageInfo(
   messageInfos: MessageInfo[],
-  threadInfo: ThreadInfo,
+  threadID: string,
   notifTargetUserInfo: UserInfo,
   getENSNames: ?GetENSNames,
+  mentionableThreadIDs: Set<string>,
+  threadInfos: { +[id: string]: ThreadInfo },
 ): Promise<?ResolvedNotifTexts> {
   const fullNotifTexts = await fullNotifTextsForMessageInfo(
     messageInfos,
-    threadInfo,
+    threadID,
     notifTargetUserInfo,
     getENSNames,
+    mentionableThreadIDs,
+    threadInfos,
   );
   if (!fullNotifTexts) {
     return fullNotifTexts;
@@ -209,9 +213,11 @@
 
 async function fullNotifTextsForMessageInfo(
   messageInfos: $ReadOnlyArray<MessageInfo>,
-  threadInfo: ThreadInfo,
+  threadID: string,
   notifTargetUserInfo: UserInfo,
   getENSNames: ?GetENSNames,
+  mentionableThreadIDs: $ReadOnlySet<string>,
+  threadInfos: { +[id: string]: ThreadInfo },
 ): Promise<?ResolvedNotifTexts> {
   const mostRecentType = mostRecentMessageInfoType(messageInfos);
   const messageSpec = messageSpecs[mostRecentType];
@@ -225,14 +231,21 @@
   ) =>
     fullNotifTextsForMessageInfo(
       innerMessageInfos,
-      innerThreadInfo,
+      innerThreadInfo.id,
       notifTargetUserInfo,
       getENSNames,
+      mentionableThreadIDs,
+      threadInfos,
     );
   const unresolvedNotifTexts = await messageSpec.notificationTexts(
     messageInfos,
-    threadInfo,
-    { notifTargetUserInfo, notificationTexts: innerNotificationTexts },
+    threadInfos[threadID],
+    {
+      notifTargetUserInfo,
+      notificationTexts: innerNotificationTexts,
+      threadInfos,
+      mentionableThreadIDs,
+    },
   );
   if (!unresolvedNotifTexts) {
     return unresolvedNotifTexts;