diff --git a/src/components/dms/ConvoMenu.tsx b/src/components/dms/ConvoMenu.tsx
index 0a1d3f01..16e8d98c 100644
--- a/src/components/dms/ConvoMenu.tsx
+++ b/src/components/dms/ConvoMenu.tsx
@@ -1,19 +1,27 @@
 import React, {useCallback} from 'react'
 import {Keyboard, Pressable, View} from 'react-native'
-import {AppBskyActorDefs, ChatBskyConvoDefs} from '@atproto/api'
+import {
+  AppBskyActorDefs,
+  ChatBskyConvoDefs,
+  ModerationDecision,
+} from '@atproto/api'
 import {msg, Trans} from '@lingui/macro'
 import {useLingui} from '@lingui/react'
 import {useNavigation} from '@react-navigation/native'
 
 import {NavigationProp} from '#/lib/routes/types'
+import {listUriToHref} from '#/lib/strings/url-helpers'
+import {Shadow} from '#/state/cache/types'
 import {
   useConvoQuery,
   useMarkAsReadMutation,
 } from '#/state/queries/messages/conversation'
 import {useLeaveConvo} from '#/state/queries/messages/leave-conversation'
 import {useMuteConvo} from '#/state/queries/messages/mute-conversation'
+import {useProfileBlockMutationQueue} from '#/state/queries/profile'
 import * as Toast from '#/view/com/util/Toast'
 import {atoms as a, useTheme} from '#/alf'
+import * as Dialog from '#/components/Dialog'
 import {ArrowBoxLeft_Stroke2_Corner0_Rounded as ArrowBoxLeft} from '#/components/icons/ArrowBoxLeft'
 import {DotGrid_Stroke2_Corner0_Rounded as DotsHorizontal} from '#/components/icons/DotGrid'
 import {Flag_Stroke2_Corner0_Rounded as Flag} from '#/components/icons/Flag'
@@ -22,8 +30,10 @@ import {Person_Stroke2_Corner0_Rounded as Person} from '#/components/icons/Perso
 import {PersonCheck_Stroke2_Corner0_Rounded as PersonCheck} from '#/components/icons/PersonCheck'
 import {PersonX_Stroke2_Corner0_Rounded as PersonX} from '#/components/icons/PersonX'
 import {SpeakerVolumeFull_Stroke2_Corner0_Rounded as Unmute} from '#/components/icons/Speaker'
+import {InlineLinkText} from '#/components/Link'
 import * as Menu from '#/components/Menu'
 import * as Prompt from '#/components/Prompt'
+import {Text} from '#/components/Typography'
 import {Bubble_Stroke2_Corner2_Rounded as Bubble} from '../icons/Bubble'
 
 let ConvoMenu = ({
@@ -34,22 +44,35 @@ let ConvoMenu = ({
   showMarkAsRead,
   hideTrigger,
   triggerOpacity,
+  moderation,
 }: {
   convo: ChatBskyConvoDefs.ConvoView
-  profile: AppBskyActorDefs.ProfileViewBasic
-  onUpdateConvo?: (convo: ChatBskyConvoDefs.ConvoView) => void
+  profile: Shadow<AppBskyActorDefs.ProfileViewBasic>
   control?: Menu.MenuControlProps
   currentScreen: 'list' | 'conversation'
   showMarkAsRead?: boolean
   hideTrigger?: boolean
   triggerOpacity?: number
+  moderation: ModerationDecision
 }): React.ReactNode => {
   const navigation = useNavigation<NavigationProp>()
   const {_} = useLingui()
   const t = useTheme()
   const leaveConvoControl = Prompt.usePromptControl()
   const reportControl = Prompt.usePromptControl()
+  const blockedByListControl = Prompt.usePromptControl()
   const {mutate: markAsRead} = useMarkAsReadMutation()
+  const modui = moderation.ui('profileView')
+  const {listBlocks, userBlock} = React.useMemo(() => {
+    const blocks = modui.alerts.filter(alert => alert.type === 'blocking')
+    const listBlocks = blocks.filter(alert => alert.source.type === 'list')
+    const userBlock = blocks.find(alert => alert.source.type === 'user')
+    return {
+      listBlocks,
+      userBlock,
+    }
+  }, [modui])
+  const isBlocking = !!userBlock || !!listBlocks.length
 
   const {data: convo} = useConvoQuery(initialConvo)
 
@@ -70,6 +93,21 @@ let ConvoMenu = ({
     },
   })
 
+  const [queueBlock, queueUnblock] = useProfileBlockMutationQueue(profile)
+
+  const toggleBlock = React.useCallback(() => {
+    if (listBlocks.length) {
+      blockedByListControl.open()
+      return
+    }
+
+    if (userBlock) {
+      queueUnblock()
+    } else {
+      queueBlock()
+    }
+  }, [userBlock, listBlocks, blockedByListControl, queueBlock, queueUnblock])
+
   const {mutate: leaveConvo} = useLeaveConvo(convo?.id, {
     onSuccess: () => {
       if (currentScreen === 'conversation') {
@@ -146,18 +184,16 @@ let ConvoMenu = ({
             </Menu.Item>
           </Menu.Group>
           <Menu.Divider />
-          {/* TODO(samuel): implement this */}
           <Menu.Group>
             <Menu.Item
-              label={_(msg`Block account`)}
-              onPress={() => {}}
-              disabled>
+              label={
+                isBlocking ? _(msg`Unblock account`) : _(msg`Block account`)
+              }
+              onPress={toggleBlock}>
               <Menu.ItemText>
-                <Trans>Block account</Trans>
+                {isBlocking ? _(msg`Unblock account`) : _(msg`Block account`)}
               </Menu.ItemText>
-              <Menu.ItemIcon
-                icon={profile.viewer?.blocking ? PersonCheck : PersonX}
-              />
+              <Menu.ItemIcon icon={isBlocking ? PersonX : PersonCheck} />
             </Menu.Item>
             <Menu.Item
               label={_(msg`Report conversation`)}
@@ -202,6 +238,42 @@ let ConvoMenu = ({
         confirmButtonCta={_(msg`I understand`)}
         onConfirm={noop}
       />
+
+      <Prompt.Outer control={blockedByListControl} testID="blockedByListDialog">
+        <Prompt.TitleText>{_(msg`User blocked by list`)}</Prompt.TitleText>
+
+        <View style={[a.gap_sm, a.pb_lg]}>
+          <Text
+            selectable
+            style={[a.text_md, a.leading_snug, t.atoms.text_contrast_high]}>
+            {_(
+              msg`This account is blocked by one or more of your moderation lists. To unblock, please visit the lists directly and remove this user.`,
+            )}{' '}
+          </Text>
+
+          <Text style={[a.text_md, a.leading_snug, t.atoms.text_contrast_high]}>
+            {_(msg`Lists blocking this user:`)}{' '}
+            {listBlocks.map((block, i) =>
+              block.source.type === 'list' ? (
+                <React.Fragment key={block.source.list.uri}>
+                  {i === 0 ? null : ', '}
+                  <InlineLinkText
+                    to={listUriToHref(block.source.list.uri)}
+                    style={[a.text_md, a.leading_snug]}>
+                    {block.source.list.name}
+                  </InlineLinkText>
+                </React.Fragment>
+              ) : null,
+            )}
+          </Text>
+        </View>
+
+        <Prompt.Actions>
+          <Prompt.Cancel cta={_(msg`I understand`)} />
+        </Prompt.Actions>
+
+        <Dialog.Close />
+      </Prompt.Outer>
     </>
   )
 }
diff --git a/src/screens/Messages/Conversation/index.tsx b/src/screens/Messages/Conversation/index.tsx
index f382647a..05df3e23 100644
--- a/src/screens/Messages/Conversation/index.tsx
+++ b/src/screens/Messages/Conversation/index.tsx
@@ -3,7 +3,7 @@ import {TouchableOpacity, View} from 'react-native'
 import {KeyboardProvider} from 'react-native-keyboard-controller'
 import {KeyboardAvoidingView} from 'react-native-keyboard-controller'
 import {useSafeAreaInsets} from 'react-native-safe-area-context'
-import {AppBskyActorDefs} from '@atproto/api'
+import {AppBskyActorDefs, moderateProfile, ModerationOpts} from '@atproto/api'
 import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
 import {msg} from '@lingui/macro'
 import {useLingui} from '@lingui/react'
@@ -12,8 +12,12 @@ import {NativeStackScreenProps} from '@react-navigation/native-stack'
 
 import {CommonNavigatorParams, NavigationProp} from '#/lib/routes/types'
 import {useGate} from '#/lib/statsig/statsig'
+import {useProfileShadow} from '#/state/cache/profile-shadow'
 import {useCurrentConvoId} from '#/state/messages/current-convo-id'
+import {useModerationOpts} from '#/state/preferences/moderation-opts'
+import {useProfileQuery} from '#/state/queries/profile'
 import {BACK_HITSLOP} from 'lib/constants'
+import {sanitizeDisplayName} from 'lib/strings/display-names'
 import {isIOS, isWeb} from 'platform/detection'
 import {ConvoProvider, isConvoActive, useConvo} from 'state/messages/convo'
 import {ConvoStatus} from 'state/messages/convo/types'
@@ -27,6 +31,7 @@ import {ListMaybePlaceholder} from '#/components/Lists'
 import {Loader} from '#/components/Loader'
 import {Text} from '#/components/Typography'
 import {ClipClopGate} from '../gate'
+
 type Props = NativeStackScreenProps<
   CommonNavigatorParams,
   'MessagesConversation'
@@ -137,7 +142,7 @@ function Inner() {
 }
 
 let Header = ({
-  profile,
+  profile: initialProfile,
 }: {
   profile?: AppBskyActorDefs.ProfileViewBasic
 }): React.ReactNode => {
@@ -145,12 +150,8 @@ let Header = ({
   const {_} = useLingui()
   const {gtTablet} = useBreakpoints()
   const navigation = useNavigation<NavigationProp>()
-  const convoState = useConvo()
-
-  const isDeletedAccount = profile?.handle === 'missing.invalid'
-  const displayName = isDeletedAccount
-    ? 'Deleted Account'
-    : profile?.displayName
+  const moderationOpts = useModerationOpts()
+  const {data: profile} = useProfileQuery({did: initialProfile?.did})
 
   const onPressBack = useCallback(() => {
     if (isWeb) {
@@ -195,23 +196,12 @@ let Header = ({
       ) : (
         <View style={{width: 30}} />
       )}
-      <View style={[a.align_center, a.gap_sm, a.flex_1]}>
-        {profile ? (
-          <View style={[a.align_center]}>
-            <PreviewableUserAvatar size={32} profile={profile} />
-            <Text
-              style={[a.text_lg, a.font_bold, a.pt_sm, a.pb_2xs]}
-              numberOfLines={1}>
-              {displayName}
-            </Text>
-            {!isDeletedAccount && (
-              <Text style={[t.atoms.text_contrast_medium]} numberOfLines={1}>
-                @{profile.handle}
-              </Text>
-            )}
-          </View>
-        ) : (
-          <>
+
+      {profile && moderationOpts ? (
+        <HeaderReady profile={profile} moderationOpts={moderationOpts} />
+      ) : (
+        <>
+          <View style={[a.align_center, a.gap_sm, a.flex_1]}>
             <View
               style={[
                 {width: 32, height: 32},
@@ -234,19 +224,69 @@ let Header = ({
                 t.atoms.bg_contrast_25,
               ]}
             />
-          </>
-        )}
-      </View>
-      {isConvoActive(convoState) && profile ? (
-        <ConvoMenu
-          convo={convoState.convo}
-          profile={profile}
-          currentScreen="conversation"
-        />
-      ) : (
-        <View style={{width: 30}} />
+          </View>
+
+          <View style={{width: 30}} />
+        </>
       )}
     </View>
   )
 }
 Header = React.memo(Header)
+
+function HeaderReady({
+  profile: profileUnshadowed,
+  moderationOpts,
+}: {
+  profile: AppBskyActorDefs.ProfileViewBasic
+  moderationOpts: ModerationOpts
+}) {
+  const t = useTheme()
+  const convoState = useConvo()
+  const profile = useProfileShadow(profileUnshadowed)
+  const moderation = React.useMemo(
+    () => moderateProfile(profile, moderationOpts),
+    [profile, moderationOpts],
+  )
+
+  const isDeletedAccount = profile?.handle === 'missing.invalid'
+  const displayName = isDeletedAccount
+    ? 'Deleted Account'
+    : sanitizeDisplayName(
+        profile.displayName || profile.handle,
+        moderation.ui('displayName'),
+      )
+
+  return (
+    <>
+      <View style={[a.align_center, a.gap_sm, a.flex_1]}>
+        <View style={[a.align_center]}>
+          <PreviewableUserAvatar
+            size={32}
+            profile={profile}
+            moderation={moderation.ui('avatar')}
+          />
+          <Text
+            style={[a.text_lg, a.font_bold, a.pt_sm, a.pb_2xs]}
+            numberOfLines={1}>
+            {displayName}
+          </Text>
+          {!isDeletedAccount && (
+            <Text style={[t.atoms.text_contrast_medium]} numberOfLines={1}>
+              @{profile.handle}
+            </Text>
+          )}
+        </View>
+      </View>
+
+      {isConvoActive(convoState) && (
+        <ConvoMenu
+          convo={convoState.convo}
+          profile={profile}
+          currentScreen="conversation"
+          moderation={moderation}
+        />
+      )}
+    </>
+  )
+}
diff --git a/src/screens/Messages/List/ChatListItem.tsx b/src/screens/Messages/List/ChatListItem.tsx
index 57a8e034..aa47e950 100644
--- a/src/screens/Messages/List/ChatListItem.tsx
+++ b/src/screens/Messages/List/ChatListItem.tsx
@@ -1,13 +1,21 @@
 import React from 'react'
 import {View} from 'react-native'
-import {ChatBskyConvoDefs} from '@atproto/api'
+import {
+  AppBskyActorDefs,
+  ChatBskyConvoDefs,
+  moderateProfile,
+  ModerationOpts,
+} from '@atproto/api'
 import {msg} from '@lingui/macro'
 import {useLingui} from '@lingui/react'
 import {useNavigation} from '@react-navigation/native'
 
 import {NavigationProp} from '#/lib/routes/types'
 import {isNative} from '#/platform/detection'
+import {useProfileShadow} from '#/state/cache/profile-shadow'
+import {useModerationOpts} from '#/state/preferences/moderation-opts'
 import {useSession} from '#/state/session'
+import {sanitizeDisplayName} from 'lib/strings/display-names'
 import {TimeElapsed} from '#/view/com/util/TimeElapsed'
 import {UserAvatar} from '#/view/com/util/UserAvatar'
 import {atoms as a, useBreakpoints, useTheme, web} from '#/alf'
@@ -17,25 +25,53 @@ import {Bell2Off_Filled_Corner0_Rounded as BellStroke} from '#/components/icons/
 import {useMenuControl} from '#/components/Menu'
 import {Text} from '#/components/Typography'
 
-export function ChatListItem({
+export function ChatListItem({convo}: {convo: ChatBskyConvoDefs.ConvoView}) {
+  const {currentAccount} = useSession()
+  const otherUser = convo.members.find(
+    member => member.did !== currentAccount?.did,
+  )
+  const moderationOpts = useModerationOpts()
+
+  if (!otherUser || !moderationOpts) {
+    return null
+  }
+
+  return (
+    <ChatListItemReady
+      convo={convo}
+      profile={otherUser}
+      moderationOpts={moderationOpts}
+    />
+  )
+}
+
+function ChatListItemReady({
   convo,
-  index,
+  profile: profileUnshadowed,
+  moderationOpts,
 }: {
   convo: ChatBskyConvoDefs.ConvoView
-  index: number
+  profile: AppBskyActorDefs.ProfileViewBasic
+  moderationOpts: ModerationOpts
 }) {
   const t = useTheme()
   const {_} = useLingui()
   const {currentAccount} = useSession()
   const menuControl = useMenuControl()
   const {gtMobile} = useBreakpoints()
-  const otherUser = convo.members.find(
-    member => member.did !== currentAccount?.did,
+  const profile = useProfileShadow(profileUnshadowed)
+  const moderation = React.useMemo(
+    () => moderateProfile(profile, moderationOpts),
+    [profile, moderationOpts],
   )
-  const isDeletedAccount = otherUser?.handle === 'missing.invalid'
+
+  const isDeletedAccount = profile.handle === 'missing.invalid'
   const displayName = isDeletedAccount
     ? 'Deleted Account'
-    : otherUser?.displayName || otherUser?.handle
+    : sanitizeDisplayName(
+        profile.displayName || profile.handle,
+        moderation.ui('displayName'),
+      )
 
   let lastMessage = _(msg`No messages yet`)
   let lastMessageSentAt: string | null = null
@@ -73,10 +109,6 @@ export function ChatListItem({
     })
   }, [convo.id, navigation])
 
-  if (!otherUser) {
-    return null
-  }
-
   return (
     <View
       // @ts-expect-error web only
@@ -85,7 +117,7 @@ export function ChatListItem({
       onFocus={onFocus}
       onBlur={onMouseLeave}>
       <Button
-        label={otherUser.displayName || otherUser.handle}
+        label={profile.displayName || profile.handle}
         onPress={onPress}
         style={a.flex_1}
         onLongPress={isNative ? menuControl.open : undefined}>
@@ -98,10 +130,13 @@ export function ChatListItem({
               a.py_md,
               a.gap_md,
               (hovered || pressed) && t.atoms.bg_contrast_25,
-              index === 0 && [a.border_t, a.pt_lg],
               t.atoms.border_contrast_low,
             ]}>
-            <UserAvatar avatar={otherUser?.avatar} size={52} />
+            <UserAvatar
+              avatar={profile.avatar}
+              size={52}
+              moderation={moderation.ui('avatar')}
+            />
             <View style={[a.flex_1, a.flex_row, a.align_center]}>
               <View style={[a.flex_1]}>
                 <View
@@ -154,7 +189,7 @@ export function ChatListItem({
                   <Text
                     numberOfLines={1}
                     style={[a.text_sm, t.atoms.text_contrast_medium, a.pb_xs]}>
-                    @{otherUser.handle}
+                    @{profile.handle}
                   </Text>
                 )}
                 <Text
@@ -196,7 +231,7 @@ export function ChatListItem({
               )}
               <ConvoMenu
                 convo={convo}
-                profile={otherUser}
+                profile={profile}
                 control={menuControl}
                 currentScreen="list"
                 showMarkAsRead={convo.unreadCount > 0}
@@ -204,6 +239,7 @@ export function ChatListItem({
                 triggerOpacity={
                   !gtMobile || showActions || menuControl.isOpen ? 1 : 0
                 }
+                moderation={moderation}
               />
             </View>
           </View>
diff --git a/src/screens/Messages/List/index.tsx b/src/screens/Messages/List/index.tsx
index 6300a976..2ae17a14 100644
--- a/src/screens/Messages/List/index.tsx
+++ b/src/screens/Messages/List/index.tsx
@@ -29,14 +29,8 @@ import {ChatListItem} from './ChatListItem'
 
 type Props = NativeStackScreenProps<MessagesTabNavigatorParams, 'Messages'>
 
-function renderItem({
-  item,
-  index,
-}: {
-  item: ChatBskyConvoDefs.ConvoView
-  index: number
-}) {
-  return <ChatListItem convo={item} index={index} />
+function renderItem({item}: {item: ChatBskyConvoDefs.ConvoView}) {
+  return <ChatListItem convo={item} />
 }
 
 function keyExtractor(item: ChatBskyConvoDefs.ConvoView) {
@@ -232,6 +226,8 @@ function DesktopHeader({
         a.gap_lg,
         a.px_lg,
         a.py_sm,
+        a.border_b,
+        t.atoms.border_contrast_low,
       ]}>
       <Text style={[a.text_2xl, a.font_bold]}>
         <Trans>Messages</Trans>
diff --git a/src/state/cache/profile-shadow.ts b/src/state/cache/profile-shadow.ts
index ca791bc9..8d3e9404 100644
--- a/src/state/cache/profile-shadow.ts
+++ b/src/state/cache/profile-shadow.ts
@@ -6,6 +6,7 @@ import EventEmitter from 'eventemitter3'
 import {batchedUpdates} from '#/lib/batchedUpdates'
 import {findAllProfilesInQueryData as findAllProfilesInActorSearchQueryData} from '../queries/actor-search'
 import {findAllProfilesInQueryData as findAllProfilesInListMembersQueryData} from '../queries/list-members'
+import {findAllProfilesInQueryData as findAllProfilesInListConvosQueryData} from '../queries/messages/list-converations'
 import {findAllProfilesInQueryData as findAllProfilesInMyBlockedAccountsQueryData} from '../queries/my-blocked-accounts'
 import {findAllProfilesInQueryData as findAllProfilesInMyMutedAccountsQueryData} from '../queries/my-muted-accounts'
 import {findAllProfilesInQueryData as findAllProfilesInPostLikedByQueryData} from '../queries/post-liked-by'
@@ -105,4 +106,5 @@ function* findProfilesInCache(
   yield* findAllProfilesInProfileFollowsQueryData(queryClient, did)
   yield* findAllProfilesInSuggestedFollowsQueryData(queryClient, did)
   yield* findAllProfilesInActorSearchQueryData(queryClient, did)
+  yield* findAllProfilesInListConvosQueryData(queryClient, did)
 }
diff --git a/src/state/queries/messages/list-converations.ts b/src/state/queries/messages/list-converations.ts
index 36896241..f2c27706 100644
--- a/src/state/queries/messages/list-converations.ts
+++ b/src/state/queries/messages/list-converations.ts
@@ -1,6 +1,11 @@
 import {useCallback, useMemo} from 'react'
 import {ChatBskyConvoDefs, ChatBskyConvoListConvos} from '@atproto/api'
-import {useInfiniteQuery, useQueryClient} from '@tanstack/react-query'
+import {
+  InfiniteData,
+  QueryClient,
+  useInfiniteQuery,
+  useQueryClient,
+} from '@tanstack/react-query'
 
 import {useCurrentConvoId} from '#/state/messages/current-convo-id'
 import {DM_SERVICE_HEADERS} from '#/state/queries/messages/const'
@@ -140,3 +145,29 @@ function optimisticUpdate(
     })),
   }
 }
+
+export function* findAllProfilesInQueryData(
+  queryClient: QueryClient,
+  did: string,
+) {
+  const queryDatas = queryClient.getQueriesData<
+    InfiniteData<ChatBskyConvoListConvos.OutputSchema>
+  >({
+    queryKey: RQKEY,
+  })
+  for (const [_queryKey, queryData] of queryDatas) {
+    if (!queryData?.pages) {
+      continue
+    }
+
+    for (const page of queryData.pages) {
+      for (const convo of page.convos) {
+        for (const member of convo.members) {
+          if (member.did === did) {
+            yield member
+          }
+        }
+      }
+    }
+  }
+}