diff --git a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/forum/ForumListViewModel.kt b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/forum/ForumListViewModel.kt
index 6dbdad4dbb691a352719d234f089ae831ac26a4d..be9909579d02c41139750d40161a61119e96aace 100644
--- a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/forum/ForumListViewModel.kt
+++ b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/forum/ForumListViewModel.kt
@@ -32,6 +32,7 @@ import org.briarproject.briar.api.forum.ForumManager
 import org.briarproject.briar.api.forum.event.ForumPostReceivedEvent
 import org.briarproject.briar.desktop.forum.conversation.ForumConversationViewModel
 import org.briarproject.briar.desktop.threadedgroup.ThreadedGroupListViewModel
+import org.briarproject.briar.desktop.threadedgroup.ThreadedGroupMessageReadEvent
 import org.briarproject.briar.desktop.threading.BriarExecutors
 import org.briarproject.briar.desktop.utils.removeFirst
 import org.briarproject.briar.desktop.utils.replaceFirst
@@ -62,7 +63,8 @@ class ForumListViewModel
                 updateItem(e.groupId) { it.updateOnPostReceived(e.header) }
             }
 
-            is ForumPostReadEvent -> {
+            is ThreadedGroupMessageReadEvent -> {
+                if (e.clientId != clientId) return
                 updateItem(e.groupId) { it.updateOnPostsRead(e.numMarkedRead) }
             }
         }
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 75570a87a2001d1d84b6ba4c81e407314efdc44c..cd7afa13d7f2791a08d3ccad7b3d41b2cfa259e3 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
@@ -32,6 +32,7 @@ import org.briarproject.bramble.api.event.EventBus
 import org.briarproject.bramble.api.identity.IdentityManager
 import org.briarproject.bramble.api.identity.LocalAuthor
 import org.briarproject.bramble.api.lifecycle.LifecycleManager
+import org.briarproject.bramble.api.sync.ClientId
 import org.briarproject.bramble.api.sync.GroupId
 import org.briarproject.bramble.api.sync.MessageId
 import org.briarproject.bramble.api.system.Clock
@@ -64,6 +65,8 @@ class ForumConversationViewModel @Inject constructor(
     eventBus
 ) {
 
+    override val clientId: ClientId = ForumManager.CLIENT_ID
+
     @UiExecutor
     override fun eventOccurred(e: Event) {
         if (e is ForumPostReceivedEvent) {
diff --git a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/navigation/BriarSidebar.kt b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/navigation/BriarSidebar.kt
index debaba99ef997ade1f47a3f989dc9a0e1c68f9ce..d8b408af28688b0eec23c97efeeef4e08902fdb4 100644
--- a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/navigation/BriarSidebar.kt
+++ b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/navigation/BriarSidebar.kt
@@ -88,7 +88,8 @@ fun BriarSidebar(
         Spacer(Modifier.height(4.dp))
 
         BriarSidebarButton(UiMode.CONTACTS, messageCount.privateMessages)
-        if (configuration.shouldEnablePrivateGroups()) BriarSidebarButton(UiMode.GROUPS)
+        if (configuration.shouldEnablePrivateGroups())
+            BriarSidebarButton(UiMode.GROUPS, messageCount.privateGroupMessages)
         if (configuration.shouldEnableForums()) BriarSidebarButton(UiMode.FORUMS, messageCount.forumPosts)
         if (configuration.shouldEnableBlogs()) BriarSidebarButton(UiMode.BLOGS)
 
diff --git a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/navigation/SidebarViewModel.kt b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/navigation/SidebarViewModel.kt
index b6b7591a663c2abee9a9872d56d39f89022c6649..8d1e6b74c1afe40db6e51886c29c9b450415d475 100644
--- a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/navigation/SidebarViewModel.kt
+++ b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/navigation/SidebarViewModel.kt
@@ -29,6 +29,7 @@ import org.briarproject.briar.desktop.threading.UiExecutor
 import org.briarproject.briar.desktop.ui.MessageCounter
 import org.briarproject.briar.desktop.ui.MessageCounterData
 import org.briarproject.briar.desktop.ui.MessageCounterDataType.Forum
+import org.briarproject.briar.desktop.ui.MessageCounterDataType.PrivateGroup
 import org.briarproject.briar.desktop.ui.MessageCounterDataType.PrivateMessage
 import org.briarproject.briar.desktop.ui.UiMode
 import org.briarproject.briar.desktop.viewmodel.ViewModel
@@ -69,6 +70,7 @@ constructor(
         when (type) {
             PrivateMessage -> _messageCount.update { copy(privateMessages = count) }
             Forum -> _messageCount.update { copy(forumPosts = count) }
+            PrivateGroup -> _messageCount.update { copy(privateGroupMessages = count) }
         }
     }
 
@@ -97,5 +99,6 @@ constructor(
     data class MessageCount(
         val privateMessages: Int = 0,
         val forumPosts: Int = 0,
+        val privateGroupMessages: Int = 0,
     )
 }
diff --git a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/notification/AbstractNotificationProvider.kt b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/notification/AbstractNotificationProvider.kt
index c71c81bbf55f254af762145447a0187ef3c04dfa..29511a0a8d5e82b5b6940624a23c8a79322d03ac 100644
--- a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/notification/AbstractNotificationProvider.kt
+++ b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/notification/AbstractNotificationProvider.kt
@@ -37,4 +37,11 @@ abstract class AbstractNotificationProvider : VisualNotificationProvider {
         else
             i18nF("notifications.message.forum.several_forums", num, forums)
     )
+
+    override fun notifyPrivateGroupMessages(num: Int, groups: Int) = sendNotification(
+        if (groups == 1)
+            i18nP("notifications.message.group.one_group", num)
+        else
+            i18nF("notifications.message.group.several_groups", num, groups)
+    )
 }
diff --git a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/notification/NotificationProvider.kt b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/notification/NotificationProvider.kt
index ac9888246a373ff021cf18702bb3b205c09131a0..adf09ce57498214adbc86613eea98276eca0066a 100644
--- a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/notification/NotificationProvider.kt
+++ b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/notification/NotificationProvider.kt
@@ -37,6 +37,7 @@ interface NotificationProvider {
     fun uninit()
     fun notifyPrivateMessages(num: Int, contacts: Int)
     fun notifyForumPosts(num: Int, forums: Int)
+    fun notifyPrivateGroupMessages(num: Int, groups: Int)
 }
 
 interface VisualNotificationProvider : NotificationProvider
@@ -52,4 +53,5 @@ object StubNotificationProvider : VisualNotificationProvider {
     override fun uninit() {}
     override fun notifyPrivateMessages(num: Int, contacts: Int) {}
     override fun notifyForumPosts(num: Int, forums: Int) {}
+    override fun notifyPrivateGroupMessages(num: Int, groups: Int) {}
 }
diff --git a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/notification/SoundNotificationProvider.kt b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/notification/SoundNotificationProvider.kt
index ef5edba4e7403cc405af7dc78711c453d50fbf7b..f005f97e1f2f80e33c312b24427be0cb355ad425 100644
--- a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/notification/SoundNotificationProvider.kt
+++ b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/notification/SoundNotificationProvider.kt
@@ -57,4 +57,5 @@ object SoundNotificationProvider : NotificationProvider {
 
     override fun notifyPrivateMessages(num: Int, contacts: Int) = playSound()
     override fun notifyForumPosts(num: Int, forums: Int) = playSound()
+    override fun notifyPrivateGroupMessages(num: Int, groups: Int) = playSound()
 }
diff --git a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/privategroup/PrivateGroupItem.kt b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/privategroup/PrivateGroupItem.kt
index fce9fc24abaf6268df5058c547c579fa7307ecc9..1f32e27acd2c6f28f7a74a745137873c409cadd8 100644
--- a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/privategroup/PrivateGroupItem.kt
+++ b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/privategroup/PrivateGroupItem.kt
@@ -20,7 +20,7 @@ package org.briarproject.briar.desktop.privategroup
 
 import org.briarproject.bramble.api.sync.GroupId
 import org.briarproject.briar.api.client.MessageTracker
-import org.briarproject.briar.api.client.PostHeader
+import org.briarproject.briar.api.privategroup.GroupMessageHeader
 import org.briarproject.briar.api.privategroup.PrivateGroup
 import org.briarproject.briar.desktop.threadedgroup.ThreadedGroupItem
 import kotlin.math.max
@@ -43,13 +43,13 @@ data class PrivateGroupItem(
     override val id: GroupId = privateGroup.id
     override val name: String = privateGroup.name
 
-    fun updateOnPostReceived(header: PostHeader) =
+    fun updateOnMessageReceived(header: GroupMessageHeader) =
         copy(
             msgCount = msgCount + 1,
             unread = if (header.isRead) unread else unread + 1,
             timestamp = max(header.timestamp, this.timestamp)
         )
 
-    fun updateOnPostsRead(num: Int) =
+    fun updateOnMessagesRead(num: Int) =
         copy(unread = unread - num)
 }
diff --git a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/privategroup/PrivateGroupListViewModel.kt b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/privategroup/PrivateGroupListViewModel.kt
index 5997082470cf0c26ed1f95e442f71bbbffacbaf4..c85d0807fc13a614fda27e511c8735754f17aadd 100644
--- a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/privategroup/PrivateGroupListViewModel.kt
+++ b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/privategroup/PrivateGroupListViewModel.kt
@@ -42,6 +42,7 @@ import org.briarproject.briar.api.privategroup.PrivateGroupManager
 import org.briarproject.briar.api.privategroup.event.GroupMessageAddedEvent
 import org.briarproject.briar.desktop.privategroup.conversation.PrivateGroupConversationViewModel
 import org.briarproject.briar.desktop.threadedgroup.ThreadedGroupListViewModel
+import org.briarproject.briar.desktop.threadedgroup.ThreadedGroupMessageReadEvent
 import org.briarproject.briar.desktop.threading.BriarExecutors
 import org.briarproject.briar.desktop.utils.removeFirst
 import org.briarproject.briar.desktop.utils.replaceFirst
@@ -70,13 +71,13 @@ class PrivateGroupListViewModel
         super.eventOccurred(e)
         when (e) {
             is GroupMessageAddedEvent -> {
-                updateItem(e.groupId) { it.updateOnPostReceived(e.header) }
+                updateItem(e.groupId) { it.updateOnMessageReceived(e.header) }
             }
 
-            // TODO
-            /*is ForumPostReadEvent -> {
-                updateItem(e.groupId) { it.updateOnPostsRead(e.numMarkedRead) }
-            }*/
+            is ThreadedGroupMessageReadEvent -> {
+                if (e.clientId != clientId) return
+                updateItem(e.groupId) { it.updateOnMessagesRead(e.numMarkedRead) }
+            }
         }
     }
 
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 d803d29109dd732dc53fe3317feb896980ae88a4..5e9677446aa0ecbe3eadb6349f110fc215fd67a9 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
@@ -32,6 +32,7 @@ import org.briarproject.bramble.api.event.EventBus
 import org.briarproject.bramble.api.identity.IdentityManager
 import org.briarproject.bramble.api.identity.LocalAuthor
 import org.briarproject.bramble.api.lifecycle.LifecycleManager
+import org.briarproject.bramble.api.sync.ClientId
 import org.briarproject.bramble.api.sync.GroupId
 import org.briarproject.bramble.api.sync.MessageId
 import org.briarproject.bramble.api.system.Clock
@@ -65,6 +66,8 @@ class PrivateGroupConversationViewModel @Inject constructor(
     eventBus
 ) {
 
+    override val clientId: ClientId = PrivateGroupManager.CLIENT_ID
+
     @UiExecutor
     override fun eventOccurred(e: Event) {
         // todo: handle incoming messages
diff --git a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/forum/ForumPostReadEvent.kt b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/threadedgroup/ThreadedGroupMessageReadEvent.kt
similarity index 80%
rename from briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/forum/ForumPostReadEvent.kt
rename to briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/threadedgroup/ThreadedGroupMessageReadEvent.kt
index f0da6e2e9c035a2ebc4ed4b302935bbcc82663ee..a1867d403a00c7bd97c415593de95d7b90aabc27 100644
--- a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/forum/ForumPostReadEvent.kt
+++ b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/threadedgroup/ThreadedGroupMessageReadEvent.kt
@@ -16,15 +16,18 @@
  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
  */
 
-package org.briarproject.briar.desktop.forum
+package org.briarproject.briar.desktop.threadedgroup
 
 import org.briarproject.bramble.api.event.Event
+import org.briarproject.bramble.api.sync.ClientId
 import org.briarproject.bramble.api.sync.GroupId
 
 /**
- * An event that is broadcast when [numMarkedRead] post(s) in a forum was marked as read.
+ * An event that is broadcast when [numMarkedRead] post(s) in a forum
+ * or message(s) in a private group are marked as read.
  */
-data class ForumPostReadEvent(
+data class ThreadedGroupMessageReadEvent(
+    val clientId: ClientId,
     val groupId: GroupId,
     val numMarkedRead: Int,
 ) : Event()
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 3e5122191d51d39fcdc4490504a93bac2fef2277..36f62241ee229afcb35e32a93466d61e5ded25a0 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
@@ -25,12 +25,13 @@ import org.briarproject.bramble.api.db.Transaction
 import org.briarproject.bramble.api.db.TransactionManager
 import org.briarproject.bramble.api.event.EventBus
 import org.briarproject.bramble.api.lifecycle.LifecycleManager
+import org.briarproject.bramble.api.sync.ClientId
 import org.briarproject.bramble.api.sync.GroupId
 import org.briarproject.bramble.api.sync.MessageId
 import org.briarproject.briar.api.client.PostHeader
 import org.briarproject.briar.client.MessageTreeImpl
-import org.briarproject.briar.desktop.forum.ForumPostReadEvent
 import org.briarproject.briar.desktop.threadedgroup.ThreadedGroupItem
+import org.briarproject.briar.desktop.threadedgroup.ThreadedGroupMessageReadEvent
 import org.briarproject.briar.desktop.threadedgroup.sharing.ThreadedGroupSharingViewModel
 import org.briarproject.briar.desktop.threading.BriarExecutors
 import org.briarproject.briar.desktop.threading.UiExecutor
@@ -45,6 +46,8 @@ abstract class ThreadedConversationViewModel(
     private val eventBus: EventBus,
 ) : EventListenerDbViewModel(briarExecutors, lifecycleManager, db, eventBus) {
 
+    protected abstract val clientId: ClientId
+
     private val _threadedGroupItem = mutableStateOf<ThreadedGroupItem?>(null)
     val groupItem = _threadedGroupItem.asState()
 
@@ -128,7 +131,7 @@ abstract class ThreadedConversationViewModel(
             }
             // 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(ForumPostReadEvent(groupId, readIds.size))
+            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/ui/BriarUi.kt b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/ui/BriarUi.kt
index e528ff6c69c61f72bd10be5f1191d047a4418abe..20b1a68c054dce67976ae83b43f881bb5e053709 100644
--- a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/ui/BriarUi.kt
+++ b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/ui/BriarUi.kt
@@ -57,6 +57,7 @@ import org.briarproject.briar.desktop.settings.UnencryptedSettings.Theme.AUTO
 import org.briarproject.briar.desktop.settings.UnencryptedSettings.Theme.DARK
 import org.briarproject.briar.desktop.theme.BriarTheme
 import org.briarproject.briar.desktop.ui.MessageCounterDataType.Forum
+import org.briarproject.briar.desktop.ui.MessageCounterDataType.PrivateGroup
 import org.briarproject.briar.desktop.ui.MessageCounterDataType.PrivateMessage
 import org.briarproject.briar.desktop.ui.Screen.EXPIRED
 import org.briarproject.briar.desktop.ui.Screen.MAIN
@@ -139,6 +140,7 @@ constructor(
                 val notificationCoolDown = 5.seconds.inWholeMilliseconds
                 var lastNotificationPrivateMessage = 0L
                 var lastNotificationForum = 0L
+                var lastNotificationPrivateGroup = 0L
 
                 val eventListener = EventListener { e ->
                     when (e) {
@@ -157,6 +159,7 @@ constructor(
                         // reset notification cool-down
                         lastNotificationPrivateMessage = 0
                         lastNotificationForum = 0
+                        lastNotificationPrivateGroup = 0
                     }
                 }
                 val messageCounterListener: MessageCounterListener = { (type, total, groups, inc) ->
@@ -169,6 +172,10 @@ constructor(
                             Forum -> {
                                 { notifyForumPosts(total, groups) }
                             }
+
+                            PrivateGroup -> {
+                                { notifyPrivateGroupMessages(total, groups) }
+                            }
                         }
                         val (lastNotification, setLastNotification) = when (type) {
                             PrivateMessage -> lastNotificationPrivateMessage to { v: Long ->
@@ -176,6 +183,10 @@ constructor(
                             }
 
                             Forum -> lastNotificationForum to { v: Long -> lastNotificationForum = v }
+
+                            PrivateGroup -> lastNotificationPrivateGroup to { v: Long ->
+                                lastNotificationPrivateGroup = v
+                            }
                         }
 
                         window.iconImage = iconBadge
diff --git a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/ui/MessageCounter.kt b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/ui/MessageCounter.kt
index 31cc4f6360bfd306df5e270c490d72997c1b9ebe..bc79db45e7f26fee2aa654571da9ac411c2669d8 100644
--- a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/ui/MessageCounter.kt
+++ b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/ui/MessageCounter.kt
@@ -25,7 +25,7 @@ interface MessageCounter {
     fun removeListener(listener: MessageCounterListener): Boolean
 }
 
-enum class MessageCounterDataType { PrivateMessage, Forum }
+enum class MessageCounterDataType { PrivateMessage, Forum, PrivateGroup }
 
 /**
  * Data holder for MessageCounter updates.
diff --git a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/ui/MessageCounterImpl.kt b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/ui/MessageCounterImpl.kt
index 876ec358b2db63676de624f4044cf960daf203a5..e8d5a5cc0b2cd991810b8b0abb18b61df79f7a4a 100644
--- a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/ui/MessageCounterImpl.kt
+++ b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/ui/MessageCounterImpl.kt
@@ -32,11 +32,14 @@ import org.briarproject.briar.api.conversation.ConversationManager
 import org.briarproject.briar.api.conversation.event.ConversationMessageReceivedEvent
 import org.briarproject.briar.api.forum.ForumManager
 import org.briarproject.briar.api.forum.event.ForumPostReceivedEvent
+import org.briarproject.briar.api.privategroup.PrivateGroupManager
+import org.briarproject.briar.api.privategroup.event.GroupMessageAddedEvent
 import org.briarproject.briar.desktop.conversation.ConversationMessagesReadEvent
-import org.briarproject.briar.desktop.forum.ForumPostReadEvent
+import org.briarproject.briar.desktop.threadedgroup.ThreadedGroupMessageReadEvent
 import org.briarproject.briar.desktop.threading.BriarExecutors
 import org.briarproject.briar.desktop.threading.UiExecutor
 import org.briarproject.briar.desktop.ui.MessageCounterDataType.Forum
+import org.briarproject.briar.desktop.ui.MessageCounterDataType.PrivateGroup
 import org.briarproject.briar.desktop.ui.MessageCounterDataType.PrivateMessage
 import org.briarproject.briar.desktop.utils.KLoggerUtils.e
 import javax.inject.Inject
@@ -47,6 +50,7 @@ constructor(
     private val contactManager: ContactManager,
     private val conversationManager: ConversationManager,
     private val forumManager: ForumManager,
+    private val privateGroupManager: PrivateGroupManager,
     private val briarExecutors: BriarExecutors,
     eventBus: EventBus,
 ) : MessageCounter {
@@ -61,6 +65,9 @@ constructor(
     @UiExecutor
     private val countForumPosts = Multiset<GroupId>()
 
+    @UiExecutor
+    private val countPrivateGroupMessages = Multiset<GroupId>()
+
     private val listeners = mutableListOf<MessageCounterListener>()
 
     init {
@@ -75,6 +82,9 @@ constructor(
                             val countForumPostsMap = forumManager.getForums(txn).associate { f ->
                                 f.id to forumManager.getGroupCount(txn, f.id).unreadCount
                             }
+                            val countPrivateGroupMessageMap = privateGroupManager.getPrivateGroups(txn).associate { g ->
+                                g.id to privateGroupManager.getGroupCount(txn, g.id).unreadCount
+                            }
                             txn.attach {
                                 countPrivateMessagesMap.forEach { (id, count) ->
                                     countPrivateMessages.addCount(id, count)
@@ -82,9 +92,13 @@ constructor(
                                 countForumPostsMap.forEach { (id, count) ->
                                     countForumPosts.addCount(id, count)
                                 }
+                                countPrivateGroupMessageMap.forEach { (id, count) ->
+                                    countPrivateGroupMessages.addCount(id, count)
+                                }
 
                                 informListeners(PrivateMessage, true)
                                 informListeners(Forum, true)
+                                informListeners(PrivateGroup, true)
                             }
                         }
                     }
@@ -116,22 +130,45 @@ constructor(
                     informListeners(Forum, true)
                 }
 
-                is ForumPostReadEvent -> {
+                is GroupMessageAddedEvent -> {
+                    if (e.isLocal) return@addListener
+
+                    countPrivateGroupMessages.add(e.groupId)
+                    informListeners(PrivateGroup, true)
+                }
+
+                is ThreadedGroupMessageReadEvent -> {
                     try {
-                        countForumPosts.removeCount(e.groupId, e.numMarkedRead)
+                        when (e.clientId) {
+                            ForumManager.CLIENT_ID -> {
+                                countForumPosts.removeCount(e.groupId, e.numMarkedRead)
+                                informListeners(Forum, false)
+                            }
+
+                            PrivateGroupManager.CLIENT_ID -> {
+                                countPrivateGroupMessages.removeCount(e.groupId, e.numMarkedRead)
+                                informListeners(PrivateGroup, false)
+                            }
+                        }
                     } catch (e: NoSuchElementException) {
                         LOG.e(e) {
-                            "inconsistent state in MessageCounterImpl.countForumPosts: " +
+                            "inconsistent state in MessageCounterImpl: " +
                                 "trying to remove non-existing element"
                         }
                     }
-                    informListeners(Forum, false)
                 }
 
                 is GroupRemovedEvent -> {
-                    if (e.group.clientId == ForumManager.CLIENT_ID) {
-                        countForumPosts.removeAll(e.group.id)
-                        informListeners(Forum, false)
+                    when (e.group.clientId) {
+                        ForumManager.CLIENT_ID -> {
+                            countForumPosts.removeAll(e.group.id)
+                            informListeners(Forum, false)
+                        }
+
+                        PrivateGroupManager.CLIENT_ID -> {
+                            countPrivateGroupMessages.removeAll(e.group.id)
+                            informListeners(PrivateGroup, false)
+                        }
                     }
                 }
             }
@@ -146,6 +183,7 @@ constructor(
         val groupCount = when (type) {
             PrivateMessage -> countPrivateMessages
             Forum -> countForumPosts
+            PrivateGroup -> countPrivateGroupMessages
         }
         l.invoke(MessageCounterData(type, groupCount.total, groupCount.unique, increment))
     }
diff --git a/briar-desktop/src/main/resources/strings/BriarDesktop.properties b/briar-desktop/src/main/resources/strings/BriarDesktop.properties
index a16d623948c096b66b647693bb2c109d1cb6902f..28dcf45281b9af1fe0b189e0ccb2667b062c14a9 100644
--- a/briar-desktop/src/main/resources/strings/BriarDesktop.properties
+++ b/briar-desktop/src/main/resources/strings/BriarDesktop.properties
@@ -398,6 +398,8 @@ notifications.message.private.one_chat={0, plural, one {New private message.} ot
 notifications.message.private.several_chats={0} new messages in {1} private chats.
 notifications.message.forum.one_forum={0, plural, one {New forum post.} other {{0} new forum posts.}}
 notifications.message.forum.several_forums={0} new posts in {1} forums.
+notifications.message.group.one_group={0, plural, one {New private group message.} other {{0} new private group messages.}}
+notifications.message.group.several_groups={0} new messages in {1} private groups.
 
 # Settings
 settings.title=Settings