diff --git a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/forum/ForumScreen.kt b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/forum/ForumScreen.kt
index ec14f6f62386a6c789a34cbde6f671c5798407ed..3eaa778c3335258f1139d50b2d5d3173c909b210 100644
--- a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/forum/ForumScreen.kt
+++ b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/forum/ForumScreen.kt
@@ -30,8 +30,8 @@ fun ForumScreen(
 ) = ThreadedGroupScreen(
     strings = ForumStrings,
     viewModel = viewModel,
-    dropdownMenu = { sharingViewModel, expanded, onClose, onLeaveForumClick ->
+    dropdownMenu = { sharingViewModel, expanded, onClose, onMarkReadClick, onLeaveForumClick ->
         val forumSharingViewModel = sharingViewModel as ForumSharingViewModel
-        ForumDropdownMenu(forumSharingViewModel, expanded, onClose, onLeaveForumClick)
+        ForumDropdownMenu(forumSharingViewModel, expanded, onClose, onMarkReadClick, onLeaveForumClick)
     }
 )
diff --git a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/forum/conversation/ForumConversationViewModel.kt b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/forum/conversation/ForumConversationViewModel.kt
index 4d7e26cf4f19041d600cc55989bb34fa41d8a8ee..429db62a02136c110bd1a1973fe2da0b8c6a2c06 100644
--- a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/forum/conversation/ForumConversationViewModel.kt
+++ b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/forum/conversation/ForumConversationViewModel.kt
@@ -112,8 +112,8 @@ class ForumConversationViewModel @Inject constructor(
     }
 
     @DatabaseExecutor
-    override fun markThreadItemRead(groupId: GroupId, id: MessageId) =
-        forumManager.setReadFlag(groupId, id, true)
+    override fun markThreadItemRead(txn: Transaction, groupId: GroupId, id: MessageId) =
+        forumManager.setReadFlag(txn, groupId, id, true)
 
     @UiExecutor
     override fun deleteGroup() {
diff --git a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/forum/conversation/ForumDropdownMenu.kt b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/forum/conversation/ForumDropdownMenu.kt
index 871d3b4b031d9dff17909d9e0a65aebc0d41a305..19b06b6ddf2351e998901b9aa02d19716202cb87 100644
--- a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/forum/conversation/ForumDropdownMenu.kt
+++ b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/forum/conversation/ForumDropdownMenu.kt
@@ -35,6 +35,7 @@ fun ForumDropdownMenu(
     forumSharingViewModel: ForumSharingViewModel,
     expanded: Boolean,
     onClose: () -> Unit,
+    onMarkReadClick: () -> Unit,
     onLeaveForumClick: () -> Unit,
 ) = DropdownMenu(
     expanded = expanded,
@@ -74,6 +75,17 @@ fun ForumDropdownMenu(
             style = MaterialTheme.typography.body2,
         )
     }
+    DropdownMenuItem(
+        onClick = {
+            onClose()
+            onMarkReadClick()
+        }
+    ) {
+        Text(
+            i18n("group.mark.read"),
+            style = MaterialTheme.typography.body2,
+        )
+    }
     DropdownMenuItem(
         onClick = {
             onClose()
diff --git a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/forum/sharing/ForumSharingViewModel.kt b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/forum/sharing/ForumSharingViewModel.kt
index d47f5e7b7c22062edd9c02cc7e295f69ee6ff0a5..773c13f4a58e66c2528653aafff1156c353df907 100644
--- a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/forum/sharing/ForumSharingViewModel.kt
+++ b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/forum/sharing/ForumSharingViewModel.kt
@@ -129,7 +129,7 @@ class ForumSharingViewModel @Inject constructor(
         }
     }
 
-    fun loadSharingStatus(txn: Transaction, groupId: GroupId) {
+    private fun loadSharingStatus(txn: Transaction, groupId: GroupId) {
         val map = contactManager.getContacts(txn).associate { contact ->
             contact.id to forumSharingManager.getSharingStatus(txn, groupId, contact)
         }
diff --git a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/privategroup/PrivateGroupScreen.kt b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/privategroup/PrivateGroupScreen.kt
index 48d13acb0b5fbe271b8d473529985a263dc4c2e8..9a94982bce8cb00559c25737837258e33bbd9f76 100644
--- a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/privategroup/PrivateGroupScreen.kt
+++ b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/privategroup/PrivateGroupScreen.kt
@@ -43,12 +43,13 @@ fun PrivateGroupScreen(
     ThreadedGroupScreen(
         strings = PrivateGroupStrings,
         viewModel = viewModel,
-        dropdownMenu = { sharingViewModel, expanded, onClose, onLeaveOrDissolvePrivateGroupClick ->
+        dropdownMenu = { sharingViewModel, expanded, onClose, onMarkReadClick, onLeaveOrDissolvePrivateGroupClick ->
             val privateGroupSharingViewModel = sharingViewModel as PrivateGroupSharingViewModel
             PrivateGroupDropdownMenu(
                 privateGroupSharingViewModel = privateGroupSharingViewModel,
                 expanded = expanded,
                 onClose = onClose,
+                onMarkReadClick = onMarkReadClick,
                 onLeaveOrDissolvePrivateGroupClick = onLeaveOrDissolvePrivateGroupClick
             )
         },
diff --git a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/privategroup/conversation/PrivateGroupConversationViewModel.kt b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/privategroup/conversation/PrivateGroupConversationViewModel.kt
index 81695874c4c5eee2a49db8d285f6cba876edbd8b..ab44963e734c59fee00cadad2ef448793d6fcfc0 100644
--- a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/privategroup/conversation/PrivateGroupConversationViewModel.kt
+++ b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/privategroup/conversation/PrivateGroupConversationViewModel.kt
@@ -135,8 +135,8 @@ class PrivateGroupConversationViewModel @Inject constructor(
     }
 
     @DatabaseExecutor
-    override fun markThreadItemRead(groupId: GroupId, id: MessageId) =
-        privateGroupManager.setReadFlag(groupId, id, true)
+    override fun markThreadItemRead(txn: Transaction, groupId: GroupId, id: MessageId) =
+        privateGroupManager.setReadFlag(txn, groupId, id, true)
 
     @UiExecutor
     override fun deleteGroup() {
diff --git a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/privategroup/conversation/PrivateGroupDropdownMenu.kt b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/privategroup/conversation/PrivateGroupDropdownMenu.kt
index a23d807ab2a6388b56000fb5241d929006d73217..efb37e4216ea7691d484a9b307c3a71a1f54eedd 100644
--- a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/privategroup/conversation/PrivateGroupDropdownMenu.kt
+++ b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/privategroup/conversation/PrivateGroupDropdownMenu.kt
@@ -35,6 +35,7 @@ fun PrivateGroupDropdownMenu(
     privateGroupSharingViewModel: PrivateGroupSharingViewModel,
     expanded: Boolean,
     onClose: () -> Unit,
+    onMarkReadClick: () -> Unit,
     onLeaveOrDissolvePrivateGroupClick: () -> Unit,
 ) = DropdownMenu(
     expanded = expanded,
@@ -76,6 +77,17 @@ fun PrivateGroupDropdownMenu(
             )
         }
     }
+    DropdownMenuItem(
+        onClick = {
+            onClose()
+            onMarkReadClick()
+        }
+    ) {
+        Text(
+            i18n("group.mark.read"),
+            style = MaterialTheme.typography.body2,
+        )
+    }
     DropdownMenuItem(
         onClick = {
             onClose()
diff --git a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/threadedgroup/conversation/ThreadedConversationViewModel.kt b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/threadedgroup/conversation/ThreadedConversationViewModel.kt
index 4d8fe22105a8287730e0911d41565630172313d9..8190c5fd61dab16c7ab66128e6fec03dfa54eee2 100644
--- a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/threadedgroup/conversation/ThreadedConversationViewModel.kt
+++ b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/threadedgroup/conversation/ThreadedConversationViewModel.kt
@@ -112,13 +112,13 @@ abstract class ThreadedConversationViewModel(
     }
 
     @DatabaseExecutor
-    abstract fun markThreadItemRead(groupId: GroupId, id: MessageId)
+    abstract fun markThreadItemRead(txn: Transaction, groupId: GroupId, id: MessageId)
 
     @UiExecutor
-    fun markThreadItemsRead(ids: List<MessageId>) {
+    fun markThreadItemsRead(ids: List<MessageId>? = null) {
         // TODO messageTree.get(id) would be nice, but not in briar-core
         val readIds = (state.value as? Loaded)?.posts?.filter { item ->
-            !item.isRead && ids.contains(item.id)
+            ids?.contains(item.id) ?: true && !item.isRead
         }?.map { item ->
             item.isRead = true
             item.id
@@ -126,17 +126,17 @@ abstract class ThreadedConversationViewModel(
 
         val groupId = _threadedGroupItem.value?.id
         if (readIds.isNotEmpty() && groupId != null) {
-            runOnDbThread {
+            runOnDbThreadWithTransaction(false) { txn ->
                 readIds.forEach { id ->
-                    markThreadItemRead(groupId, id)
+                    markThreadItemRead(txn, groupId, id)
+                }
+                txn.attach(ThreadedGroupMessageReadEvent(clientId, groupId, readIds.size))
+                txn.attach {
+                    // TODO replace immutable ThreadItems instead to avoid recomposing whole list
+                    val messageTree = (state.value as? Loaded)?.messageTree ?: return@attach
+                    _state.value = Loaded(messageTree)
                 }
             }
-            // we don't attach this to the transaction that actually changes the DB,
-            // but that should be fine for this purpose of just decrementing a counter
-            eventBus.broadcast(ThreadedGroupMessageReadEvent(clientId, groupId, readIds.size))
-            // TODO replace immutable ThreadItems instead to avoid recomposing whole list
-            val messageTree = (state.value as? Loaded)?.messageTree ?: return
-            _state.value = Loaded(messageTree)
         }
     }
 
diff --git a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/threadedgroup/conversation/ThreadedGroupConversationScreen.kt b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/threadedgroup/conversation/ThreadedGroupConversationScreen.kt
index 2aebe4f82db729ac6188fcd89e35f87bf7a21612..86b607c30a572eb4bc3b94446bb423f0e27bb158 100644
--- a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/threadedgroup/conversation/ThreadedGroupConversationScreen.kt
+++ b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/threadedgroup/conversation/ThreadedGroupConversationScreen.kt
@@ -76,6 +76,7 @@ fun ThreadedGroupConversationScreen(
                     threadedGroupItem = groupItem,
                     sharingViewModel = viewModel.sharingViewModel,
                     onGroupDelete = viewModel::deleteGroup,
+                    onMarkRead = viewModel::markThreadItemsRead,
                     dropdownMenu = dropdownMenu,
                 )
             }
@@ -108,6 +109,7 @@ private fun ThreadedGroupConversationHeader(
     strings: ThreadedGroupStrings,
     threadedGroupItem: ThreadedGroupItem,
     sharingViewModel: ThreadedGroupSharingViewModel,
+    onMarkRead: () -> Unit,
     onGroupDelete: () -> Unit,
     dropdownMenu: ThreadedGroupDropdownMenu,
 ) {
@@ -151,7 +153,8 @@ private fun ThreadedGroupConversationHeader(
                 dropdownMenu(
                     sharingViewModel,
                     menuState.value == MAIN,
-                    close
+                    close,
+                    onMarkRead,
                 ) { deleteGroupDialogVisible.value = true }
             }
         }
diff --git a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/threadedgroup/conversation/ThreadedGroupDropdownMenu.kt b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/threadedgroup/conversation/ThreadedGroupDropdownMenu.kt
index dc6909d9505934e72a080f532719be1e6502ebd2..b9cd05701592c685fa392d5839110a1aff498c1b 100644
--- a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/threadedgroup/conversation/ThreadedGroupDropdownMenu.kt
+++ b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/threadedgroup/conversation/ThreadedGroupDropdownMenu.kt
@@ -25,5 +25,6 @@ typealias ThreadedGroupDropdownMenu = @Composable (
     sharingViewModel: ThreadedGroupSharingViewModel,
     expanded: Boolean,
     onClose: () -> Unit,
+    onMarkReadClick: () -> Unit,
     onLeaveGroupClick: () -> Unit,
 ) -> Unit
diff --git a/briar-desktop/src/main/resources/strings/BriarDesktop.properties b/briar-desktop/src/main/resources/strings/BriarDesktop.properties
index 327cf981bbdef395e682b82504017b7868d42e0b..afa4b54bbd27f4fdb3732dd95d7dee33fec63d7c 100644
--- a/briar-desktop/src/main/resources/strings/BriarDesktop.properties
+++ b/briar-desktop/src/main/resources/strings/BriarDesktop.properties
@@ -209,6 +209,7 @@ group.member.created_you=You created the group
 group.member.created_contact={0} created the group
 group.invite.action.title=Invite Contacts
 group.invite.action.no_contacts=No contacts yet. You can only invite contacts to your private group.
+group.mark.read=Mark as read
 
 # Introduction
 introduction.introduce=Make Introduction