diff --git a/lib/shared/markdown.js b/lib/shared/markdown.js
--- a/lib/shared/markdown.js
+++ b/lib/shared/markdown.js
@@ -1,11 +1,13 @@
 // @flow
 
 import invariant from 'invariant';
+import * as React from 'react';
 
 import {
   markdownUserMentionRegex,
   decodeChatMentionText,
 } from './mention-utils.js';
+import { useENSNames } from '../hooks/ens-cache.js';
 import type {
   ChatMentionCandidates,
   RelativeMemberInfo,
@@ -197,15 +199,42 @@
   };
 }
 
-function createMemberMapForUserMentions(
+const useENSNamesOptions = { allAtOnce: true };
+function useMemberMapForUserMentions(
   members: $ReadOnlyArray<RelativeMemberInfo>,
 ): $ReadOnlyMap<string, string> {
-  const membersMap = new Map<string, string>();
-  members.forEach(member => {
-    if (member.role && member.username) {
-      membersMap.set(member.username.toLowerCase(), member.id);
+  const membersWithRole = React.useMemo(
+    () => members.filter(member => member.role),
+    [members],
+  );
+
+  const resolvedMembers = useENSNames(membersWithRole, useENSNamesOptions);
+  const resolvedMembersMap: $ReadOnlyMap<string, RelativeMemberInfo> =
+    React.useMemo(
+      () => new Map(resolvedMembers.map(member => [member.id, member])),
+      [resolvedMembers],
+    );
+
+  const membersMap = React.useMemo(() => {
+    const map = new Map<string, string>();
+
+    for (const member of membersWithRole) {
+      const rawUsername = member.username;
+
+      if (rawUsername) {
+        map.set(rawUsername.toLowerCase(), member.id);
+      }
+
+      const resolvedMember = resolvedMembersMap.get(member.id);
+      const resolvedUsername = resolvedMember?.username;
+
+      if (resolvedUsername && resolvedUsername !== rawUsername) {
+        map.set(resolvedUsername.toLowerCase(), member.id);
+      }
     }
-  });
+
+    return map;
+  }, [membersWithRole, resolvedMembersMap]);
 
   return membersMap;
 }
@@ -368,7 +397,7 @@
   jsonPrint,
   matchList,
   parseList,
-  createMemberMapForUserMentions,
+  useMemberMapForUserMentions,
   matchUserMentions,
   parseUserMentions,
   stripSpoilersFromNotifications,
diff --git a/native/markdown/rules.react.js b/native/markdown/rules.react.js
--- a/native/markdown/rules.react.js
+++ b/native/markdown/rules.react.js
@@ -9,7 +9,6 @@
 import { chatMentionRegex } from 'lib/shared/mention-utils.js';
 import type {
   ChatMentionCandidates,
-  RelativeMemberInfo,
   ThreadInfo,
 } from 'lib/types/thread-types.js';
 
@@ -363,22 +362,23 @@
   chatMentionCandidates: ChatMentionCandidates,
 ): (useDarkStyle: boolean) => MarkdownRules {
   const { members } = threadInfo;
+  const membersMap = SharedMarkdown.useMemberMapForUserMentions(members);
+
   return React.useMemo(
     () =>
       _memoize<[boolean], MarkdownRules>((useDarkStyle: boolean) =>
-        textMessageRules(members, chatMentionCandidates, useDarkStyle),
+        textMessageRules(chatMentionCandidates, useDarkStyle, membersMap),
       ),
-    [members, chatMentionCandidates],
+    [chatMentionCandidates, membersMap],
   );
 }
 
 function textMessageRules(
-  members: $ReadOnlyArray<RelativeMemberInfo>,
   chatMentionCandidates: ChatMentionCandidates,
   useDarkStyle: boolean,
+  membersMap: $ReadOnlyMap<string, string>,
 ): MarkdownRules {
   const baseRules = fullMarkdownRules(useDarkStyle);
-  const membersMap = SharedMarkdown.createMemberMapForUserMentions(members);
 
   return {
     ...baseRules,
@@ -428,15 +428,20 @@
 }
 
 let defaultTextMessageRules = null;
+const defaultMembersMap = new Map<string, string>();
 
 function getDefaultTextMessageRules(
   overrideDefaultChatMentionCandidates: ChatMentionCandidates = {},
 ): MarkdownRules {
   if (Object.keys(overrideDefaultChatMentionCandidates).length > 0) {
-    return textMessageRules([], overrideDefaultChatMentionCandidates, false);
+    return textMessageRules(
+      overrideDefaultChatMentionCandidates,
+      false,
+      defaultMembersMap,
+    );
   }
   if (!defaultTextMessageRules) {
-    defaultTextMessageRules = textMessageRules([], {}, false);
+    defaultTextMessageRules = textMessageRules({}, false, defaultMembersMap);
   }
   return defaultTextMessageRules;
 }
diff --git a/web/markdown/rules.react.js b/web/markdown/rules.react.js
--- a/web/markdown/rules.react.js
+++ b/web/markdown/rules.react.js
@@ -8,7 +8,6 @@
 import { chatMentionRegex } from 'lib/shared/mention-utils.js';
 import type {
   ChatMentionCandidates,
-  RelativeMemberInfo,
   ThreadInfo,
 } from 'lib/types/thread-types.js';
 
@@ -169,22 +168,23 @@
   chatMentionCandidates: ChatMentionCandidates,
 ): boolean => MarkdownRules {
   const { members } = threadInfo;
+  const membersMap = SharedMarkdown.useMemberMapForUserMentions(members);
+
   return React.useMemo(
     () =>
       _memoize<[boolean], MarkdownRules>((useDarkStyle: boolean) =>
-        textMessageRules(members, chatMentionCandidates, useDarkStyle),
+        textMessageRules(chatMentionCandidates, useDarkStyle, membersMap),
       ),
-    [chatMentionCandidates, members],
+    [chatMentionCandidates, membersMap],
   );
 }
 
 function textMessageRules(
-  members: $ReadOnlyArray<RelativeMemberInfo>,
   chatMentionCandidates: ChatMentionCandidates,
   useDarkStyle: boolean,
+  membersMap: $ReadOnlyMap<string, string>,
 ): MarkdownRules {
   const baseRules = markdownRules(useDarkStyle);
-  const membersMap = SharedMarkdown.createMemberMapForUserMentions(members);
 
   return {
     ...baseRules,
@@ -232,15 +232,20 @@
 }
 
 let defaultTextMessageRules = null;
+const defaultMembersMap = new Map<string, string>();
 
 function getDefaultTextMessageRules(
   overrideDefaultChatMentionCandidates: ChatMentionCandidates = {},
 ): MarkdownRules {
   if (Object.keys(overrideDefaultChatMentionCandidates).length > 0) {
-    return textMessageRules([], overrideDefaultChatMentionCandidates, false);
+    return textMessageRules(
+      overrideDefaultChatMentionCandidates,
+      false,
+      defaultMembersMap,
+    );
   }
   if (!defaultTextMessageRules) {
-    defaultTextMessageRules = textMessageRules([], {}, false);
+    defaultTextMessageRules = textMessageRules({}, false, defaultMembersMap);
   }
   return defaultTextMessageRules;
 }