From 150c7b11bb4b203bebe40404fd782b2415bd8059 Mon Sep 17 00:00:00 2001
From: Ricardo Garim <rswarovsky@gmail.com>
Date: Wed, 21 Aug 2024 20:59:11 -0300
Subject: [PATCH] refactor: Subscriptions out of DB Watcher (#32540)

Co-authored-by: Diego Sampaio <chinello@gmail.com>
---
 .../meteor/app/api/server/v1/subscriptions.ts |   1 +
 .../server/methods/saveSettings.ts            |  27 ++-
 .../server/functions/saveRoomCustomFields.ts  |   9 +-
 .../server/functions/saveRoomEncrypted.ts     |   7 +-
 .../server/functions/saveRoomName.ts          |  18 +-
 .../server/functions/saveRoomType.ts          |   9 +-
 .../functions/handleSuggestedGroupKey.ts      |   7 +-
 .../app/e2e/server/methods/updateGroupKey.ts  |  11 +-
 .../federation/server/endpoints/dispatch.js   |  23 ++-
 .../server/classes/ImportDataConverter.ts     |   9 +-
 .../functions/addUserToDefaultChannels.ts     |   8 +-
 .../app/lib/server/functions/addUserToRoom.ts |   8 +-
 .../app/lib/server/functions/archiveRoom.ts   |   9 +-
 .../lib/server/functions/cleanRoomHistory.ts  |  12 +-
 .../lib/server/functions/closeLivechatRoom.ts |  10 +-
 .../lib/server/functions/createDirectRoom.ts  |  12 +-
 .../app/lib/server/functions/createRoom.ts    |  12 +-
 .../app/lib/server/functions/deleteRoom.ts    |  19 +-
 .../app/lib/server/functions/deleteUser.ts    |   2 +-
 .../functions/relinquishRoomOwnerships.ts     |   7 +-
 .../server/functions/removeUserFromRoom.ts    |   7 +-
 .../saveCustomFieldsWithoutValidation.ts      |   6 +-
 .../lib/server/functions/saveUserIdentity.ts  |  38 +++-
 .../server/functions/setUserActiveStatus.ts   |  18 +-
 .../app/lib/server/functions/unarchiveRoom.ts |   9 +-
 .../server/functions/updateGroupDMsName.ts    |   7 +-
 .../app/lib/server/lib/notifyListener.ts      | 119 ++++++++++++
 .../lib/server/lib/notifyUsersOnMessage.ts    | 109 ++++++-----
 .../app/lib/server/methods/blockUser.ts       |  17 +-
 .../app/lib/server/methods/unblockUser.ts     |  18 +-
 .../app/livechat/server/lib/Contacts.ts       |  21 ++-
 apps/meteor/app/livechat/server/lib/Helper.ts |  23 ++-
 .../app/livechat/server/lib/LivechatTyped.ts  |  35 +++-
 .../server/unreadMessages.ts                  |  15 +-
 .../methods/saveNotificationSettings.ts       |  14 +-
 apps/meteor/app/threads/server/functions.ts   |  75 ++++----
 .../server/hooks/onCloseLivechat.ts           |   7 +-
 .../services/omnichannel.internalService.ts   |  30 ++-
 .../server/hooks/afterSaveMessage.ts          |   8 +-
 .../server/database/watchCollections.ts       |   3 +-
 apps/meteor/server/lib/readMessages.ts        |   6 +-
 apps/meteor/server/lib/resetUserE2EKey.ts     |  11 +-
 .../meteor/server/methods/addAllUserToRoom.ts |   6 +-
 apps/meteor/server/methods/addRoomLeader.ts   |   6 +-
 .../meteor/server/methods/addRoomModerator.ts |   6 +-
 apps/meteor/server/methods/addRoomOwner.ts    |   6 +-
 apps/meteor/server/methods/hideRoom.ts        |  10 +-
 apps/meteor/server/methods/ignoreUser.ts      |  17 +-
 apps/meteor/server/methods/openRoom.ts        |  10 +-
 .../meteor/server/methods/removeRoomLeader.ts |   6 +-
 .../server/methods/removeRoomModerator.ts     |   6 +-
 apps/meteor/server/methods/removeRoomOwner.ts |   6 +-
 .../server/methods/removeUserFromRoom.ts      |   7 +-
 .../server/methods/saveUserPreferences.ts     |  98 ++++++----
 apps/meteor/server/methods/toggleFavorite.ts  |  10 +-
 apps/meteor/server/models/dummy/BaseDummy.ts  |   7 +
 apps/meteor/server/models/raw/BaseRaw.ts      |  36 +++-
 apps/meteor/server/models/raw/Roles.ts        |  32 ++--
 .../meteor/server/models/raw/Subscriptions.ts | 171 +++++++++++++++---
 .../rocket-chat/adapters/Room.ts              |  96 ++++++----
 apps/meteor/server/services/team/service.ts   |   7 +-
 .../functions/closeLivechatRoom.tests.ts      |  14 +-
 .../model-typings/src/models/IBaseModel.ts    |   4 +-
 .../src/models/ISubscriptionsModel.ts         |  66 ++++++-
 64 files changed, 1112 insertions(+), 331 deletions(-)

diff --git a/apps/meteor/app/api/server/v1/subscriptions.ts b/apps/meteor/app/api/server/v1/subscriptions.ts
index 9d81fe6bef6..b92d9ba572f 100644
--- a/apps/meteor/app/api/server/v1/subscriptions.ts
+++ b/apps/meteor/app/api/server/v1/subscriptions.ts
@@ -82,6 +82,7 @@ API.v1.addRoute(
 		async post() {
 			const { readThreads = false } = this.bodyParams;
 			const roomId = 'rid' in this.bodyParams ? this.bodyParams.rid : this.bodyParams.roomId;
+
 			await readMessages(roomId, this.userId, readThreads);
 
 			return API.v1.success();
diff --git a/apps/meteor/app/autotranslate/server/methods/saveSettings.ts b/apps/meteor/app/autotranslate/server/methods/saveSettings.ts
index 2f119c94826..3d4d15c0316 100644
--- a/apps/meteor/app/autotranslate/server/methods/saveSettings.ts
+++ b/apps/meteor/app/autotranslate/server/methods/saveSettings.ts
@@ -4,6 +4,7 @@ import { check } from 'meteor/check';
 import { Meteor } from 'meteor/meteor';
 
 import { hasPermissionAsync } from '../../../authorization/server/functions/hasPermission';
+import { notifyOnSubscriptionChangedById } from '../../../lib/server/lib/notifyListener';
 
 declare module '@rocket.chat/ddp-client' {
 	// eslint-disable-next-line @typescript-eslint/naming-convention
@@ -44,6 +45,8 @@ Meteor.methods<ServerMethods>({
 			});
 		}
 
+		let shouldNotifySubscriptionChanged = false;
+
 		switch (field) {
 			case 'autoTranslate':
 				const room = await Rooms.findE2ERoomById(rid, { projection: { _id: 1 } });
@@ -53,16 +56,34 @@ Meteor.methods<ServerMethods>({
 					});
 				}
 
-				await Subscriptions.updateAutoTranslateById(subscription._id, value === '1');
+				const updateAutoTranslateResponse = await Subscriptions.updateAutoTranslateById(subscription._id, value === '1');
+				if (updateAutoTranslateResponse.modifiedCount) {
+					shouldNotifySubscriptionChanged = true;
+				}
+
 				if (!subscription.autoTranslateLanguage && options.defaultLanguage) {
-					await Subscriptions.updateAutoTranslateLanguageById(subscription._id, options.defaultLanguage);
+					const updateAutoTranslateLanguageResponse = await Subscriptions.updateAutoTranslateLanguageById(
+						subscription._id,
+						options.defaultLanguage,
+					);
+					if (updateAutoTranslateLanguageResponse.modifiedCount) {
+						shouldNotifySubscriptionChanged = true;
+					}
 				}
+
 				break;
 			case 'autoTranslateLanguage':
-				await Subscriptions.updateAutoTranslateLanguageById(subscription._id, value);
+				const updateAutoTranslateLanguage = await Subscriptions.updateAutoTranslateLanguageById(subscription._id, value);
+				if (updateAutoTranslateLanguage.modifiedCount) {
+					shouldNotifySubscriptionChanged = true;
+				}
 				break;
 		}
 
+		if (shouldNotifySubscriptionChanged) {
+			void notifyOnSubscriptionChangedById(subscription._id);
+		}
+
 		return true;
 	},
 });
diff --git a/apps/meteor/app/channel-settings/server/functions/saveRoomCustomFields.ts b/apps/meteor/app/channel-settings/server/functions/saveRoomCustomFields.ts
index 55d40cf3d7e..ef70ff65c06 100644
--- a/apps/meteor/app/channel-settings/server/functions/saveRoomCustomFields.ts
+++ b/apps/meteor/app/channel-settings/server/functions/saveRoomCustomFields.ts
@@ -3,21 +3,28 @@ import { Match } from 'meteor/check';
 import { Meteor } from 'meteor/meteor';
 import type { UpdateResult } from 'mongodb';
 
+import { notifyOnSubscriptionChangedByRoomId } from '../../../lib/server/lib/notifyListener';
+
 export const saveRoomCustomFields = async function (rid: string, roomCustomFields: Record<string, any>): Promise<UpdateResult> {
 	if (!Match.test(rid, String)) {
 		throw new Meteor.Error('invalid-room', 'Invalid room', {
 			function: 'RocketChat.saveRoomCustomFields',
 		});
 	}
+
 	if (!Match.test(roomCustomFields, Object)) {
 		throw new Meteor.Error('invalid-roomCustomFields-type', 'Invalid roomCustomFields type', {
 			function: 'RocketChat.saveRoomCustomFields',
 		});
 	}
+
 	const ret = await Rooms.setCustomFieldsById(rid, roomCustomFields);
 
 	// Update customFields of any user's Subscription related with this rid
-	await Subscriptions.updateCustomFieldsByRoomId(rid, roomCustomFields);
+	const { modifiedCount } = await Subscriptions.updateCustomFieldsByRoomId(rid, roomCustomFields);
+	if (modifiedCount) {
+		void notifyOnSubscriptionChangedByRoomId(rid);
+	}
 
 	return ret;
 };
diff --git a/apps/meteor/app/channel-settings/server/functions/saveRoomEncrypted.ts b/apps/meteor/app/channel-settings/server/functions/saveRoomEncrypted.ts
index ed07540ba2b..c1a441463a9 100644
--- a/apps/meteor/app/channel-settings/server/functions/saveRoomEncrypted.ts
+++ b/apps/meteor/app/channel-settings/server/functions/saveRoomEncrypted.ts
@@ -6,6 +6,8 @@ import { Match } from 'meteor/check';
 import { Meteor } from 'meteor/meteor';
 import type { UpdateResult } from 'mongodb';
 
+import { notifyOnSubscriptionChangedByRoomId } from '../../../lib/server/lib/notifyListener';
+
 export const saveRoomEncrypted = async function (rid: string, encrypted: boolean, user: IUser, sendMessage = true): Promise<UpdateResult> {
 	if (!Match.test(rid, String)) {
 		throw new Meteor.Error('invalid-room', 'Invalid room', {
@@ -27,7 +29,10 @@ export const saveRoomEncrypted = async function (rid: string, encrypted: boolean
 	}
 
 	if (encrypted) {
-		await Subscriptions.disableAutoTranslateByRoomId(rid);
+		const { modifiedCount } = await Subscriptions.disableAutoTranslateByRoomId(rid);
+		if (modifiedCount) {
+			void notifyOnSubscriptionChangedByRoomId(rid);
+		}
 	}
 	return update;
 };
diff --git a/apps/meteor/app/channel-settings/server/functions/saveRoomName.ts b/apps/meteor/app/channel-settings/server/functions/saveRoomName.ts
index c2af750ffa1..f4a5afbb638 100644
--- a/apps/meteor/app/channel-settings/server/functions/saveRoomName.ts
+++ b/apps/meteor/app/channel-settings/server/functions/saveRoomName.ts
@@ -8,11 +8,17 @@ import type { Document, UpdateResult } from 'mongodb';
 import { callbacks } from '../../../../lib/callbacks';
 import { roomCoordinator } from '../../../../server/lib/rooms/roomCoordinator';
 import { checkUsernameAvailability } from '../../../lib/server/functions/checkUsernameAvailability';
-import { notifyOnIntegrationChangedByChannels } from '../../../lib/server/lib/notifyListener';
+import { notifyOnIntegrationChangedByChannels, notifyOnSubscriptionChangedByRoomId } from '../../../lib/server/lib/notifyListener';
 import { getValidRoomName } from '../../../utils/server/lib/getValidRoomName';
 
 const updateFName = async (rid: string, displayName: string): Promise<(UpdateResult | Document)[]> => {
-	return Promise.all([Rooms.setFnameById(rid, displayName), Subscriptions.updateFnameByRoomId(rid, displayName)]);
+	const responses = await Promise.all([Rooms.setFnameById(rid, displayName), Subscriptions.updateFnameByRoomId(rid, displayName)]);
+
+	if (responses[1]?.modifiedCount) {
+		void notifyOnSubscriptionChangedByRoomId(rid);
+	}
+
+	return responses;
 };
 
 const updateRoomName = async (rid: string, displayName: string, slugifiedRoomName: string) => {
@@ -24,10 +30,16 @@ const updateRoomName = async (rid: string, displayName: string, slugifiedRoomNam
 		});
 	}
 
-	return Promise.all([
+	const responses = await Promise.all([
 		Rooms.setNameById(rid, slugifiedRoomName, displayName),
 		Subscriptions.updateNameAndAlertByRoomId(rid, slugifiedRoomName, displayName),
 	]);
+
+	if (responses[1]?.modifiedCount) {
+		void notifyOnSubscriptionChangedByRoomId(rid);
+	}
+
+	return responses;
 };
 
 export async function saveRoomName(
diff --git a/apps/meteor/app/channel-settings/server/functions/saveRoomType.ts b/apps/meteor/app/channel-settings/server/functions/saveRoomType.ts
index e8a60d1ea0e..4600d1d46a8 100644
--- a/apps/meteor/app/channel-settings/server/functions/saveRoomType.ts
+++ b/apps/meteor/app/channel-settings/server/functions/saveRoomType.ts
@@ -8,6 +8,7 @@ import type { UpdateResult, Document } from 'mongodb';
 import { RoomSettingsEnum } from '../../../../definition/IRoomTypeConfig';
 import { i18n } from '../../../../server/lib/i18n';
 import { roomCoordinator } from '../../../../server/lib/rooms/roomCoordinator';
+import { notifyOnSubscriptionChangedByRoomId } from '../../../lib/server/lib/notifyListener';
 import { settings } from '../../../settings/server';
 
 export const saveRoomType = async function (
@@ -41,11 +42,16 @@ export const saveRoomType = async function (
 		});
 	}
 
-	const result = (await Rooms.setTypeById(rid, roomType)) && (await Subscriptions.updateTypeByRoomId(rid, roomType));
+	const result = await Promise.all([Rooms.setTypeById(rid, roomType), Subscriptions.updateTypeByRoomId(rid, roomType)]);
+
 	if (!result) {
 		return result;
 	}
 
+	if (result[1]?.modifiedCount) {
+		void notifyOnSubscriptionChangedByRoomId(rid);
+	}
+
 	if (sendMessage) {
 		let message;
 		if (roomType === 'c') {
@@ -59,5 +65,6 @@ export const saveRoomType = async function (
 		}
 		await Message.saveSystemMessage('room_changed_privacy', rid, message, user);
 	}
+
 	return result;
 };
diff --git a/apps/meteor/app/e2e/server/functions/handleSuggestedGroupKey.ts b/apps/meteor/app/e2e/server/functions/handleSuggestedGroupKey.ts
index 860051c04d4..22eccf03f40 100644
--- a/apps/meteor/app/e2e/server/functions/handleSuggestedGroupKey.ts
+++ b/apps/meteor/app/e2e/server/functions/handleSuggestedGroupKey.ts
@@ -1,6 +1,8 @@
 import { Rooms, Subscriptions } from '@rocket.chat/models';
 import { Meteor } from 'meteor/meteor';
 
+import { notifyOnSubscriptionChangedById } from '../../../lib/server/lib/notifyListener';
+
 export async function handleSuggestedGroupKey(
 	handle: 'accept' | 'reject',
 	rid: string,
@@ -30,5 +32,8 @@ export async function handleSuggestedGroupKey(
 		await Rooms.addUserIdToE2EEQueueByRoomIds([sub.rid], userId);
 	}
 
-	await Subscriptions.unsetGroupE2ESuggestedKey(sub._id);
+	const { modifiedCount } = await Subscriptions.unsetGroupE2ESuggestedKey(sub._id);
+	if (modifiedCount) {
+		void notifyOnSubscriptionChangedById(sub._id);
+	}
 }
diff --git a/apps/meteor/app/e2e/server/methods/updateGroupKey.ts b/apps/meteor/app/e2e/server/methods/updateGroupKey.ts
index 5764a021f54..87182f723e7 100644
--- a/apps/meteor/app/e2e/server/methods/updateGroupKey.ts
+++ b/apps/meteor/app/e2e/server/methods/updateGroupKey.ts
@@ -3,6 +3,7 @@ import { Subscriptions } from '@rocket.chat/models';
 import { Meteor } from 'meteor/meteor';
 
 import { methodDeprecationLogger } from '../../../lib/server/lib/deprecationWarningLogger';
+import { notifyOnSubscriptionChangedById, notifyOnSubscriptionChangedByRoomIdAndUserId } from '../../../lib/server/lib/notifyListener';
 
 declare module '@rocket.chat/ddp-client' {
 	// eslint-disable-next-line @typescript-eslint/naming-convention
@@ -25,12 +26,18 @@ Meteor.methods<ServerMethods>({
 		if (mySub) {
 			// Setting the key to myself, can set directly to the final field
 			if (userId === uid) {
-				await Subscriptions.setGroupE2EKey(mySub._id, key);
+				const setGroupE2EKeyResponse = await Subscriptions.setGroupE2EKey(mySub._id, key);
+				if (setGroupE2EKeyResponse.modifiedCount) {
+					void notifyOnSubscriptionChangedById(mySub._id);
+				}
 				return;
 			}
 
 			// uid also has subscription to this room
-			await Subscriptions.setGroupE2ESuggestedKey(uid, rid, key);
+			const { modifiedCount } = await Subscriptions.setGroupE2ESuggestedKey(uid, rid, key);
+			if (modifiedCount) {
+				void notifyOnSubscriptionChangedByRoomIdAndUserId(rid, uid);
+			}
 		}
 	},
 });
diff --git a/apps/meteor/app/federation/server/endpoints/dispatch.js b/apps/meteor/app/federation/server/endpoints/dispatch.js
index 7090f053a22..4f2a197b25e 100644
--- a/apps/meteor/app/federation/server/endpoints/dispatch.js
+++ b/apps/meteor/app/federation/server/endpoints/dispatch.js
@@ -6,7 +6,13 @@ import EJSON from 'ejson';
 import { API } from '../../../api/server';
 import { FileUpload } from '../../../file-upload/server';
 import { deleteRoom } from '../../../lib/server/functions/deleteRoom';
-import { notifyOnMessageChange, notifyOnRoomChanged, notifyOnRoomChangedById } from '../../../lib/server/lib/notifyListener';
+import {
+	notifyOnMessageChange,
+	notifyOnRoomChanged,
+	notifyOnRoomChangedById,
+	notifyOnSubscriptionChanged,
+	notifyOnSubscriptionChangedById,
+} from '../../../lib/server/lib/notifyListener';
 import { notifyUsersOnMessage } from '../../../lib/server/lib/notifyUsersOnMessage';
 import { sendAllNotifications } from '../../../lib/server/lib/sendNotificationsOnMessage';
 import { processThreads } from '../../../threads/server/hooks/aftersavemessage';
@@ -141,7 +147,10 @@ const eventHandlers = {
 					const denormalizedSubscription = normalizers.denormalizeSubscription(subscription);
 
 					// Create the subscription
-					await Subscriptions.insertOne(denormalizedSubscription);
+					const { insertedId } = await Subscriptions.insertOne(denormalizedSubscription);
+					if (insertedId) {
+						void notifyOnSubscriptionChangedById(insertedId);
+					}
 					federationAltered = true;
 				}
 			} catch (ex) {
@@ -176,7 +185,10 @@ const eventHandlers = {
 			} = event;
 
 			// Remove the user's subscription
-			await Subscriptions.removeByRoomIdAndUserId(roomId, user._id);
+			const deletedSubscription = await Subscriptions.removeByRoomIdAndUserId(roomId, user._id);
+			if (deletedSubscription) {
+				void notifyOnSubscriptionChanged(deletedSubscription, 'removed');
+			}
 
 			// Refresh the servers list
 			await FederationServers.refreshServers();
@@ -204,7 +216,10 @@ const eventHandlers = {
 			} = event;
 
 			// Remove the user's subscription
-			await Subscriptions.removeByRoomIdAndUserId(roomId, user._id);
+			const deletedSubscription = await Subscriptions.removeByRoomIdAndUserId(roomId, user._id);
+			if (deletedSubscription) {
+				void notifyOnSubscriptionChanged(deletedSubscription, 'removed');
+			}
 
 			// Refresh the servers list
 			await FederationServers.refreshServers();
diff --git a/apps/meteor/app/importer/server/classes/ImportDataConverter.ts b/apps/meteor/app/importer/server/classes/ImportDataConverter.ts
index 7b1e71eaa0f..6de47e33b2b 100644
--- a/apps/meteor/app/importer/server/classes/ImportDataConverter.ts
+++ b/apps/meteor/app/importer/server/classes/ImportDataConverter.ts
@@ -28,7 +28,7 @@ import { generateUsernameSuggestion } from '../../../lib/server/functions/getUse
 import { insertMessage } from '../../../lib/server/functions/insertMessage';
 import { saveUserIdentity } from '../../../lib/server/functions/saveUserIdentity';
 import { setUserActiveStatus } from '../../../lib/server/functions/setUserActiveStatus';
-import { notifyOnUserChange } from '../../../lib/server/lib/notifyListener';
+import { notifyOnSubscriptionChangedByRoomId, notifyOnUserChange } from '../../../lib/server/lib/notifyListener';
 import { createChannelMethod } from '../../../lib/server/methods/createChannel';
 import { createPrivateGroupMethod } from '../../../lib/server/methods/createPrivateGroup';
 import { getValidRoomName } from '../../../utils/server/lib/getValidRoomName';
@@ -1161,8 +1161,11 @@ export class ImportDataConverter {
 	}
 
 	async archiveRoomById(rid: string) {
-		await Rooms.archiveById(rid);
-		await Subscriptions.archiveByRoomId(rid);
+		const responses = await Promise.all([Rooms.archiveById(rid), Subscriptions.archiveByRoomId(rid)]);
+
+		if (responses[1]?.modifiedCount) {
+			void notifyOnSubscriptionChangedByRoomId(rid);
+		}
 	}
 
 	async convertData(startedByUserId: string, callbacks: IConversionCallbacks = {}): Promise<void> {
diff --git a/apps/meteor/app/lib/server/functions/addUserToDefaultChannels.ts b/apps/meteor/app/lib/server/functions/addUserToDefaultChannels.ts
index b6d977dc36e..3fb9c419aa5 100644
--- a/apps/meteor/app/lib/server/functions/addUserToDefaultChannels.ts
+++ b/apps/meteor/app/lib/server/functions/addUserToDefaultChannels.ts
@@ -5,6 +5,7 @@ import { Subscriptions } from '@rocket.chat/models';
 import { callbacks } from '../../../../lib/callbacks';
 import { getSubscriptionAutotranslateDefaultConfig } from '../../../../server/lib/getSubscriptionAutotranslateDefaultConfig';
 import { getDefaultSubscriptionPref } from '../../../utils/lib/getDefaultSubscriptionPref';
+import { notifyOnSubscriptionChangedById } from '../lib/notifyListener';
 import { getDefaultChannels } from './getDefaultChannels';
 
 export const addUserToDefaultChannels = async function (user: IUser, silenced?: boolean): Promise<void> {
@@ -14,8 +15,9 @@ export const addUserToDefaultChannels = async function (user: IUser, silenced?:
 	for await (const room of defaultRooms) {
 		if (!(await Subscriptions.findOneByRoomIdAndUserId(room._id, user._id, { projection: { _id: 1 } }))) {
 			const autoTranslateConfig = getSubscriptionAutotranslateDefaultConfig(user);
+
 			// Add a subscription to this user
-			await Subscriptions.createWithRoomAndUser(room, user, {
+			const { insertedId } = await Subscriptions.createWithRoomAndUser(room, user, {
 				ts: new Date(),
 				open: true,
 				alert: true,
@@ -27,6 +29,10 @@ export const addUserToDefaultChannels = async function (user: IUser, silenced?:
 				...getDefaultSubscriptionPref(user),
 			});
 
+			if (insertedId) {
+				void notifyOnSubscriptionChangedById(insertedId, 'inserted');
+			}
+
 			// Insert user joined message
 			if (!silenced) {
 				await Message.saveSystemMessage('uj', room._id, user.username || '', user);
diff --git a/apps/meteor/app/lib/server/functions/addUserToRoom.ts b/apps/meteor/app/lib/server/functions/addUserToRoom.ts
index b6ffc0ca462..e6ca7b2a8b4 100644
--- a/apps/meteor/app/lib/server/functions/addUserToRoom.ts
+++ b/apps/meteor/app/lib/server/functions/addUserToRoom.ts
@@ -11,7 +11,7 @@ import { getSubscriptionAutotranslateDefaultConfig } from '../../../../server/li
 import { roomCoordinator } from '../../../../server/lib/rooms/roomCoordinator';
 import { settings } from '../../../settings/server';
 import { getDefaultSubscriptionPref } from '../../../utils/lib/getDefaultSubscriptionPref';
-import { notifyOnRoomChangedById } from '../lib/notifyListener';
+import { notifyOnRoomChangedById, notifyOnSubscriptionChangedById } from '../lib/notifyListener';
 
 export const addUserToRoom = async function (
 	rid: string,
@@ -82,7 +82,7 @@ export const addUserToRoom = async function (
 
 	const autoTranslateConfig = getSubscriptionAutotranslateDefaultConfig(userToBeAdded);
 
-	await Subscriptions.createWithRoomAndUser(room, userToBeAdded as IUser, {
+	const { insertedId } = await Subscriptions.createWithRoomAndUser(room, userToBeAdded as IUser, {
 		ts: now,
 		open: true,
 		alert: !skipAlertSound,
@@ -93,6 +93,10 @@ export const addUserToRoom = async function (
 		...getDefaultSubscriptionPref(userToBeAdded as IUser),
 	});
 
+	if (insertedId) {
+		void notifyOnSubscriptionChangedById(insertedId, 'inserted');
+	}
+
 	void notifyOnRoomChangedById(rid);
 
 	if (!userToBeAdded.username) {
diff --git a/apps/meteor/app/lib/server/functions/archiveRoom.ts b/apps/meteor/app/lib/server/functions/archiveRoom.ts
index 3378d69f99f..46fd7a1ac35 100644
--- a/apps/meteor/app/lib/server/functions/archiveRoom.ts
+++ b/apps/meteor/app/lib/server/functions/archiveRoom.ts
@@ -3,11 +3,16 @@ import type { IMessage } from '@rocket.chat/core-typings';
 import { Rooms, Subscriptions } from '@rocket.chat/models';
 
 import { callbacks } from '../../../../lib/callbacks';
-import { notifyOnRoomChanged } from '../lib/notifyListener';
+import { notifyOnRoomChanged, notifyOnSubscriptionChangedByRoomId } from '../lib/notifyListener';
 
 export const archiveRoom = async function (rid: string, user: IMessage['u']): Promise<void> {
 	await Rooms.archiveById(rid);
-	await Subscriptions.archiveByRoomId(rid);
+
+	const archiveResponse = await Subscriptions.archiveByRoomId(rid);
+	if (archiveResponse.modifiedCount) {
+		void notifyOnSubscriptionChangedByRoomId(rid);
+	}
+
 	await Message.saveSystemMessage('room-archived', rid, '', user);
 
 	const room = await Rooms.findOneById(rid);
diff --git a/apps/meteor/app/lib/server/functions/cleanRoomHistory.ts b/apps/meteor/app/lib/server/functions/cleanRoomHistory.ts
index 2bfb1086c63..765a03cad87 100644
--- a/apps/meteor/app/lib/server/functions/cleanRoomHistory.ts
+++ b/apps/meteor/app/lib/server/functions/cleanRoomHistory.ts
@@ -4,7 +4,7 @@ import { Messages, Rooms, Subscriptions, ReadReceipts, Users } from '@rocket.cha
 
 import { i18n } from '../../../../server/lib/i18n';
 import { FileUpload } from '../../../file-upload/server';
-import { notifyOnRoomChangedById } from '../lib/notifyListener';
+import { notifyOnRoomChangedById, notifyOnSubscriptionChangedById } from '../lib/notifyListener';
 import { deleteRoom } from './deleteRoom';
 
 export async function cleanRoomHistory({
@@ -75,6 +75,7 @@ export async function cleanRoomHistory({
 
 	if (!ignoreThreads) {
 		const threads = new Set<string>();
+
 		await Messages.findThreadsByRoomIdPinnedTimestampAndUsers(
 			{ rid, pinned: excludePinned, ignoreDiscussion, ts, users: fromUsers },
 			{ projection: { _id: 1 } },
@@ -83,7 +84,14 @@ export async function cleanRoomHistory({
 		});
 
 		if (threads.size > 0) {
-			await Subscriptions.removeUnreadThreadsByRoomId(rid, [...threads]);
+			const subscriptionIds: string[] = (
+				await Subscriptions.findUnreadThreadsByRoomId(rid, [...threads], { projection: { _id: 1 } }).toArray()
+			).map(({ _id }) => _id);
+
+			const { modifiedCount } = await Subscriptions.removeUnreadThreadsByRoomId(rid, [...threads]);
+			if (modifiedCount) {
+				subscriptionIds.forEach((id) => notifyOnSubscriptionChangedById(id));
+			}
 		}
 	}
 
diff --git a/apps/meteor/app/lib/server/functions/closeLivechatRoom.ts b/apps/meteor/app/lib/server/functions/closeLivechatRoom.ts
index b716be044d5..263b137ae00 100644
--- a/apps/meteor/app/lib/server/functions/closeLivechatRoom.ts
+++ b/apps/meteor/app/lib/server/functions/closeLivechatRoom.ts
@@ -5,6 +5,7 @@ import { LivechatRooms, Subscriptions } from '@rocket.chat/models';
 import { hasPermissionAsync } from '../../../authorization/server/functions/hasPermission';
 import type { CloseRoomParams } from '../../../livechat/server/lib/LivechatTyped';
 import { Livechat } from '../../../livechat/server/lib/LivechatTyped';
+import { notifyOnSubscriptionChanged } from '../lib/notifyListener';
 
 export const closeLivechatRoom = async (
 	user: IUser,
@@ -34,9 +35,12 @@ export const closeLivechatRoom = async (
 	}
 
 	if (!room.open) {
-		const subscriptionsLeft = await Subscriptions.countByRoomId(roomId);
-		if (subscriptionsLeft) {
-			await Subscriptions.removeByRoomId(roomId);
+		const { deletedCount } = await Subscriptions.removeByRoomId(roomId, {
+			async onTrash(doc) {
+				void notifyOnSubscriptionChanged(doc, 'removed');
+			},
+		});
+		if (deletedCount) {
 			return;
 		}
 		throw new Error('error-room-already-closed');
diff --git a/apps/meteor/app/lib/server/functions/createDirectRoom.ts b/apps/meteor/app/lib/server/functions/createDirectRoom.ts
index 67c6328f38f..f77ee1f5590 100644
--- a/apps/meteor/app/lib/server/functions/createDirectRoom.ts
+++ b/apps/meteor/app/lib/server/functions/createDirectRoom.ts
@@ -11,7 +11,7 @@ import { callbacks } from '../../../../lib/callbacks';
 import { isTruthy } from '../../../../lib/isTruthy';
 import { settings } from '../../../settings/server';
 import { getDefaultSubscriptionPref } from '../../../utils/lib/getDefaultSubscriptionPref';
-import { notifyOnRoomChangedById } from '../lib/notifyListener';
+import { notifyOnRoomChangedById, notifyOnSubscriptionChangedByRoomIdAndUserId } from '../lib/notifyListener';
 
 const generateSubscription = (
 	fname: string,
@@ -135,7 +135,7 @@ export async function createDirectRoom(
 
 	if (roomMembers.length === 1) {
 		// dm to yourself
-		await Subscriptions.updateOne(
+		const { modifiedCount, upsertedCount } = await Subscriptions.updateOne(
 			{ rid, 'u._id': roomMembers[0]._id },
 			{
 				$set: { open: true },
@@ -146,6 +146,9 @@ export async function createDirectRoom(
 			},
 			{ upsert: true },
 		);
+		if (modifiedCount || upsertedCount) {
+			void notifyOnSubscriptionChangedByRoomIdAndUserId(rid, roomMembers[0]._id, modifiedCount ? 'updated' : 'inserted');
+		}
 	} else {
 		const memberIds = roomMembers.map((member) => member._id);
 		const membersWithPreferences: IUser[] = await Users.find(
@@ -155,7 +158,7 @@ export async function createDirectRoom(
 
 		for await (const member of membersWithPreferences) {
 			const otherMembers = sortedMembers.filter(({ _id }) => _id !== member._id);
-			await Subscriptions.updateOne(
+			const { modifiedCount, upsertedCount } = await Subscriptions.updateOne(
 				{ rid, 'u._id': member._id },
 				{
 					...(options?.creator === member._id && { $set: { open: true } }),
@@ -166,6 +169,9 @@ export async function createDirectRoom(
 				},
 				{ upsert: true },
 			);
+			if (modifiedCount || upsertedCount) {
+				void notifyOnSubscriptionChangedByRoomIdAndUserId(rid, member._id, modifiedCount ? 'updated' : 'inserted');
+			}
 		}
 	}
 
diff --git a/apps/meteor/app/lib/server/functions/createRoom.ts b/apps/meteor/app/lib/server/functions/createRoom.ts
index b339155775e..769155b66b6 100644
--- a/apps/meteor/app/lib/server/functions/createRoom.ts
+++ b/apps/meteor/app/lib/server/functions/createRoom.ts
@@ -12,7 +12,7 @@ import { beforeCreateRoomCallback } from '../../../../lib/callbacks/beforeCreate
 import { getSubscriptionAutotranslateDefaultConfig } from '../../../../server/lib/getSubscriptionAutotranslateDefaultConfig';
 import { getDefaultSubscriptionPref } from '../../../utils/lib/getDefaultSubscriptionPref';
 import { getValidRoomName } from '../../../utils/server/lib/getValidRoomName';
-import { notifyOnRoomChanged } from '../lib/notifyListener';
+import { notifyOnRoomChanged, notifyOnSubscriptionChangedById } from '../lib/notifyListener';
 import { createDirectRoom } from './createDirectRoom';
 
 const isValidName = (name: unknown): name is string => {
@@ -47,7 +47,11 @@ async function createUsersSubscriptions({
 			...getDefaultSubscriptionPref(owner),
 		};
 
-		await Subscriptions.createWithRoomAndUser(room, owner, extra);
+		const { insertedId } = await Subscriptions.createWithRoomAndUser(room, owner, extra);
+
+		if (insertedId) {
+			await notifyOnRoomChanged(room, 'inserted');
+		}
 
 		return;
 	}
@@ -98,7 +102,9 @@ async function createUsersSubscriptions({
 		await Users.addRoomByUserIds(memberIds, room._id);
 	}
 
-	await Subscriptions.createWithRoomAndManyUsers(room, subs);
+	const { insertedIds } = await Subscriptions.createWithRoomAndManyUsers(room, subs);
+
+	Object.values(insertedIds).forEach((subId) => notifyOnSubscriptionChangedById(subId, 'inserted'));
 
 	await Rooms.incUsersCountById(room._id, subs.length);
 }
diff --git a/apps/meteor/app/lib/server/functions/deleteRoom.ts b/apps/meteor/app/lib/server/functions/deleteRoom.ts
index a605d82b08c..386ba8da8b9 100644
--- a/apps/meteor/app/lib/server/functions/deleteRoom.ts
+++ b/apps/meteor/app/lib/server/functions/deleteRoom.ts
@@ -2,16 +2,27 @@ import { Messages, Rooms, Subscriptions } from '@rocket.chat/models';
 
 import { callbacks } from '../../../../lib/callbacks';
 import { FileUpload } from '../../../file-upload/server';
-import { notifyOnRoomChangedById } from '../lib/notifyListener';
+import { notifyOnRoomChangedById, notifyOnSubscriptionChanged } from '../lib/notifyListener';
 
 export const deleteRoom = async function (rid: string): Promise<void> {
 	await FileUpload.removeFilesByRoomId(rid);
+
 	await Messages.removeByRoomId(rid);
+
 	await callbacks.run('beforeDeleteRoom', rid);
-	await Subscriptions.removeByRoomId(rid);
+
+	await Subscriptions.removeByRoomId(rid, {
+		async onTrash(doc) {
+			void notifyOnSubscriptionChanged(doc, 'removed');
+		},
+	});
+
 	await FileUpload.getStore('Avatars').deleteByRoomId(rid);
+
 	await callbacks.run('afterDeleteRoom', rid);
-	await Rooms.removeById(rid);
 
-	void notifyOnRoomChangedById(rid, 'removed');
+	const { deletedCount } = await Rooms.removeById(rid);
+	if (deletedCount) {
+		void notifyOnRoomChangedById(rid, 'removed');
+	}
 };
diff --git a/apps/meteor/app/lib/server/functions/deleteUser.ts b/apps/meteor/app/lib/server/functions/deleteUser.ts
index e66c8c2d5ee..483085d4081 100644
--- a/apps/meteor/app/lib/server/functions/deleteUser.ts
+++ b/apps/meteor/app/lib/server/functions/deleteUser.ts
@@ -112,7 +112,7 @@ export async function deleteUser(userId: string, confirmRelinquish = false, dele
 		const rids = subscribedRooms.map((room) => room.rid);
 		void notifyOnRoomChangedById(rids);
 
-		await Subscriptions.removeByUserId(userId); // Remove user subscriptions
+		await Subscriptions.removeByUserId(userId);
 
 		// Remove user as livechat agent
 		if (user.roles.includes('livechat-agent')) {
diff --git a/apps/meteor/app/lib/server/functions/relinquishRoomOwnerships.ts b/apps/meteor/app/lib/server/functions/relinquishRoomOwnerships.ts
index 75b23246207..8f1981ca386 100644
--- a/apps/meteor/app/lib/server/functions/relinquishRoomOwnerships.ts
+++ b/apps/meteor/app/lib/server/functions/relinquishRoomOwnerships.ts
@@ -1,6 +1,7 @@
 import { Messages, Roles, Rooms, Subscriptions, ReadReceipts } from '@rocket.chat/models';
 
 import { FileUpload } from '../../../file-upload/server';
+import { notifyOnSubscriptionChanged } from '../lib/notifyListener';
 import type { SubscribedRoomsForUserWithDetails } from './getRoomsWithSingleOwner';
 
 const bulkRoomCleanUp = async (rids: string[]): Promise<unknown> => {
@@ -8,7 +9,11 @@ const bulkRoomCleanUp = async (rids: string[]): Promise<unknown> => {
 	await Promise.all(rids.map((rid) => FileUpload.removeFilesByRoomId(rid)));
 
 	return Promise.all([
-		Subscriptions.removeByRoomIds(rids),
+		Subscriptions.removeByRoomIds(rids, {
+			async onTrash(doc) {
+				void notifyOnSubscriptionChanged(doc, 'removed');
+			},
+		}),
 		Messages.removeByRoomIds(rids),
 		ReadReceipts.removeByRoomIds(rids),
 		Rooms.removeByIds(rids),
diff --git a/apps/meteor/app/lib/server/functions/removeUserFromRoom.ts b/apps/meteor/app/lib/server/functions/removeUserFromRoom.ts
index 686fdd9c031..5800cb68af8 100644
--- a/apps/meteor/app/lib/server/functions/removeUserFromRoom.ts
+++ b/apps/meteor/app/lib/server/functions/removeUserFromRoom.ts
@@ -8,7 +8,7 @@ import { Meteor } from 'meteor/meteor';
 import { afterLeaveRoomCallback } from '../../../../lib/callbacks/afterLeaveRoomCallback';
 import { beforeLeaveRoomCallback } from '../../../../lib/callbacks/beforeLeaveRoomCallback';
 import { settings } from '../../../settings/server';
-import { notifyOnRoomChangedById } from '../lib/notifyListener';
+import { notifyOnRoomChangedById, notifyOnSubscriptionChanged } from '../lib/notifyListener';
 
 export const removeUserFromRoom = async function (rid: string, user: IUser, options?: { byUser: IUser }): Promise<void> {
 	const room = await Rooms.findOneById(rid);
@@ -59,7 +59,10 @@ export const removeUserFromRoom = async function (rid: string, user: IUser, opti
 		await Message.saveSystemMessage('command', rid, 'survey', user);
 	}
 
-	await Subscriptions.removeByRoomIdAndUserId(rid, user._id);
+	const deletedSubscription = await Subscriptions.removeByRoomIdAndUserId(rid, user._id);
+	if (deletedSubscription) {
+		void notifyOnSubscriptionChanged(deletedSubscription, 'removed');
+	}
 
 	if (room.teamId && room.teamMain) {
 		await Team.removeMember(room.teamId, user._id);
diff --git a/apps/meteor/app/lib/server/functions/saveCustomFieldsWithoutValidation.ts b/apps/meteor/app/lib/server/functions/saveCustomFieldsWithoutValidation.ts
index 4a0ac005e55..5383048f13b 100644
--- a/apps/meteor/app/lib/server/functions/saveCustomFieldsWithoutValidation.ts
+++ b/apps/meteor/app/lib/server/functions/saveCustomFieldsWithoutValidation.ts
@@ -5,6 +5,7 @@ import type { UpdateFilter } from 'mongodb';
 
 import { trim } from '../../../../lib/utils/stringUtils';
 import { settings } from '../../../settings/server';
+import { notifyOnSubscriptionChangedByUserIdAndRoomType } from '../lib/notifyListener';
 
 export const saveCustomFieldsWithoutValidation = async function (userId: string, formData: Record<string, any>): Promise<void> {
 	if (trim(settings.get('Accounts_CustomFields')) !== '') {
@@ -22,7 +23,10 @@ export const saveCustomFieldsWithoutValidation = async function (userId: string,
 		await Users.setCustomFields(userId, customFields);
 
 		// Update customFields of all Direct Messages' Rooms for userId
-		await Subscriptions.setCustomFieldsDirectMessagesByUserId(userId, customFields);
+		const setCustomFieldsResponse = await Subscriptions.setCustomFieldsDirectMessagesByUserId(userId, customFields);
+		if (setCustomFieldsResponse.modifiedCount) {
+			void notifyOnSubscriptionChangedByUserIdAndRoomType(userId, 'd');
+		}
 
 		for await (const fieldName of Object.keys(customFields)) {
 			if (!customFieldsMeta[fieldName].modifyRecordField) {
diff --git a/apps/meteor/app/lib/server/functions/saveUserIdentity.ts b/apps/meteor/app/lib/server/functions/saveUserIdentity.ts
index 0b9ff21e53e..1729a1ba8ab 100644
--- a/apps/meteor/app/lib/server/functions/saveUserIdentity.ts
+++ b/apps/meteor/app/lib/server/functions/saveUserIdentity.ts
@@ -3,7 +3,11 @@ import { Messages, VideoConference, LivechatDepartmentAgents, Rooms, Subscriptio
 
 import { SystemLogger } from '../../../../server/lib/logger/system';
 import { FileUpload } from '../../../file-upload/server';
-import { notifyOnRoomChangedByUsernamesOrUids } from '../lib/notifyListener';
+import {
+	notifyOnRoomChangedByUsernamesOrUids,
+	notifyOnSubscriptionChangedByUserId,
+	notifyOnSubscriptionChangedByNameAndRoomType,
+} from '../lib/notifyListener';
 import { _setRealName } from './setRealName';
 import { _setUsername } from './setUsername';
 import { updateGroupDMsName } from './updateGroupDMsName';
@@ -129,20 +133,38 @@ async function updateUsernameReferences({
 			await Messages.updateUsernameAndMessageOfMentionByIdAndOldUsername(msg._id, previousUsername, username, updatedMsg);
 		}
 
-		await Rooms.replaceUsername(previousUsername, username);
-		await Rooms.replaceMutedUsername(previousUsername, username);
-		await Rooms.replaceUsernameOfUserByUserId(user._id, username);
-		await Subscriptions.setUserUsernameByUserId(user._id, username);
+		const responses = await Promise.all([
+			Rooms.replaceUsername(previousUsername, username),
+			Rooms.replaceMutedUsername(previousUsername, username),
+			Rooms.replaceUsernameOfUserByUserId(user._id, username),
+			Subscriptions.setUserUsernameByUserId(user._id, username),
+			LivechatDepartmentAgents.replaceUsernameOfAgentByUserId(user._id, username),
+		]);
 
-		await LivechatDepartmentAgents.replaceUsernameOfAgentByUserId(user._id, username);
+		if (responses[3]?.modifiedCount) {
+			void notifyOnSubscriptionChangedByUserId(user._id);
+		}
 
-		void notifyOnRoomChangedByUsernamesOrUids([user._id], [previousUsername, username]);
+		if (responses[0]?.modifiedCount || responses[1]?.modifiedCount || responses[2]?.modifiedCount) {
+			void notifyOnRoomChangedByUsernamesOrUids([user._id], [previousUsername, username]);
+		}
 	}
 
 	// update other references if either the name or username has changed
 	if (usernameChanged || nameChanged) {
 		// update name and fname of 1-on-1 direct messages
-		await Subscriptions.updateDirectNameAndFnameByName(previousUsername, rawUsername && username, rawName && name);
+		const updateDirectNameResponse = await Subscriptions.updateDirectNameAndFnameByName(
+			previousUsername,
+			rawUsername && username,
+			rawName && name,
+		);
+
+		if (updateDirectNameResponse?.modifiedCount) {
+			void notifyOnSubscriptionChangedByNameAndRoomType({
+				t: 'd',
+				name: username,
+			});
+		}
 
 		// update name and fname of group direct messages
 		await updateGroupDMsName(user);
diff --git a/apps/meteor/app/lib/server/functions/setUserActiveStatus.ts b/apps/meteor/app/lib/server/functions/setUserActiveStatus.ts
index 9d7a3e113fc..929c24210d2 100644
--- a/apps/meteor/app/lib/server/functions/setUserActiveStatus.ts
+++ b/apps/meteor/app/lib/server/functions/setUserActiveStatus.ts
@@ -9,7 +9,12 @@ import { Meteor } from 'meteor/meteor';
 import { callbacks } from '../../../../lib/callbacks';
 import * as Mailer from '../../../mailer/server/api';
 import { settings } from '../../../settings/server';
-import { notifyOnRoomChangedById, notifyOnRoomChangedByUserDM, notifyOnUserChange } from '../lib/notifyListener';
+import {
+	notifyOnRoomChangedById,
+	notifyOnRoomChangedByUserDM,
+	notifyOnSubscriptionChangedByNameAndRoomType,
+	notifyOnUserChange,
+} from '../lib/notifyListener';
 import { closeOmnichannelConversations } from './closeOmnichannelConversations';
 import { shouldRemoveOrChangeOwner, getSubscribedRoomsForUserWithDetails } from './getRoomsWithSingleOwner';
 import { getUserSingleOwnedRooms } from './getUserSingleOwnedRooms';
@@ -39,8 +44,10 @@ async function reactivateDirectConversations(userId: string) {
 		return acc;
 	}, []);
 
-	await Rooms.setDmReadOnlyByUserId(userId, roomsToReactivate, false, false);
-	void notifyOnRoomChangedById(roomsToReactivate);
+	const setDmReadOnlyResponse = await Rooms.setDmReadOnlyByUserId(userId, roomsToReactivate, false, false);
+	if (setDmReadOnlyResponse.modifiedCount) {
+		void notifyOnRoomChangedById(roomsToReactivate);
+	}
 }
 
 export async function setUserActiveStatus(userId: string, active: boolean, confirmRelinquish = false): Promise<boolean | undefined> {
@@ -118,7 +125,10 @@ export async function setUserActiveStatus(userId: string, active: boolean, confi
 	}
 
 	if (user.username) {
-		await Subscriptions.setArchivedByUsername(user.username, !active);
+		const { modifiedCount } = await Subscriptions.setArchivedByUsername(user.username, !active);
+		if (modifiedCount) {
+			void notifyOnSubscriptionChangedByNameAndRoomType({ t: 'd', name: user.username });
+		}
 	}
 
 	if (active === false) {
diff --git a/apps/meteor/app/lib/server/functions/unarchiveRoom.ts b/apps/meteor/app/lib/server/functions/unarchiveRoom.ts
index 7db86ed933a..699f9c3701b 100644
--- a/apps/meteor/app/lib/server/functions/unarchiveRoom.ts
+++ b/apps/meteor/app/lib/server/functions/unarchiveRoom.ts
@@ -2,11 +2,16 @@ import { Message } from '@rocket.chat/core-services';
 import type { IMessage } from '@rocket.chat/core-typings';
 import { Rooms, Subscriptions } from '@rocket.chat/models';
 
-import { notifyOnRoomChangedById } from '../lib/notifyListener';
+import { notifyOnRoomChangedById, notifyOnSubscriptionChangedByRoomId } from '../lib/notifyListener';
 
 export const unarchiveRoom = async function (rid: string, user: IMessage['u']): Promise<void> {
 	await Rooms.unarchiveById(rid);
-	await Subscriptions.unarchiveByRoomId(rid);
+
+	const unarchiveResponse = await Subscriptions.unarchiveByRoomId(rid);
+	if (unarchiveResponse.modifiedCount) {
+		void notifyOnSubscriptionChangedByRoomId(rid);
+	}
+
 	await Message.saveSystemMessage('room-unarchived', rid, '', user);
 
 	void notifyOnRoomChangedById(rid);
diff --git a/apps/meteor/app/lib/server/functions/updateGroupDMsName.ts b/apps/meteor/app/lib/server/functions/updateGroupDMsName.ts
index a0ad2eedcf5..feb26ce6a1b 100644
--- a/apps/meteor/app/lib/server/functions/updateGroupDMsName.ts
+++ b/apps/meteor/app/lib/server/functions/updateGroupDMsName.ts
@@ -1,6 +1,8 @@
 import type { IUser } from '@rocket.chat/core-typings';
 import { Rooms, Subscriptions, Users } from '@rocket.chat/models';
 
+import { notifyOnSubscriptionChangedByRoomId } from '../lib/notifyListener';
+
 const getFname = (members: IUser[]): string => members.map(({ name, username }) => name || username).join(', ');
 const getName = (members: IUser[]): string => members.map(({ username }) => username).join(',');
 
@@ -63,7 +65,10 @@ export const updateGroupDMsName = async (userThatChangedName: IUser): Promise<vo
 		const subs = Subscriptions.findByRoomId(room._id, { projection: { '_id': 1, 'u._id': 1 } });
 		for await (const sub of subs) {
 			const otherMembers = sortedMembers.filter(({ _id }) => _id !== sub.u._id);
-			await Subscriptions.updateNameAndFnameById(sub._id, getName(otherMembers), getFname(otherMembers));
+			const updateNameRespose = await Subscriptions.updateNameAndFnameById(sub._id, getName(otherMembers), getFname(otherMembers));
+			if (updateNameRespose.modifiedCount) {
+				void notifyOnSubscriptionChangedByRoomId(room._id);
+			}
 		}
 	}
 };
diff --git a/apps/meteor/app/lib/server/lib/notifyListener.ts b/apps/meteor/app/lib/server/lib/notifyListener.ts
index 934742945f2..778fe89dbbf 100644
--- a/apps/meteor/app/lib/server/lib/notifyListener.ts
+++ b/apps/meteor/app/lib/server/lib/notifyListener.ts
@@ -15,6 +15,7 @@ import type {
 	IEmailInbox,
 	IIntegrationHistory,
 	AtLeast,
+	ISubscription,
 	ISettingColor,
 	IUser,
 	IMessage,
@@ -30,6 +31,7 @@ import {
 	Integrations,
 	LoginServiceConfiguration,
 	IntegrationHistory,
+	Subscriptions,
 	LivechatInquiry,
 	LivechatDepartmentAgents,
 	Users,
@@ -37,6 +39,7 @@ import {
 } from '@rocket.chat/models';
 import mem from 'mem';
 
+import { subscriptionFields } from '../../../../lib/publishFields';
 import { shouldHideSystemMessage } from '../../../../server/lib/systemMessage/hideSystemMessage';
 
 type ClientAction = 'inserted' | 'updated' | 'removed';
@@ -467,3 +470,119 @@ export const notifyOnMessageChange = withDbWatcherCheck(async ({ id, data }: { i
 	}
 	void api.broadcast('watch.messages', { message });
 });
+
+export const notifyOnSubscriptionChanged = withDbWatcherCheck(
+	async (subscription: ISubscription, clientAction: ClientAction = 'updated'): Promise<void> => {
+		void api.broadcast('watch.subscriptions', { clientAction, subscription });
+	},
+);
+
+export const notifyOnSubscriptionChangedByRoomIdAndUserId = withDbWatcherCheck(
+	async (
+		rid: ISubscription['rid'],
+		uid: ISubscription['u']['_id'],
+		clientAction: Exclude<ClientAction, 'removed'> = 'updated',
+	): Promise<void> => {
+		const cursor = Subscriptions.findByUserIdAndRoomIds(uid, [rid], { projection: subscriptionFields });
+
+		void cursor.forEach((subscription) => {
+			void api.broadcast('watch.subscriptions', { clientAction, subscription });
+		});
+	},
+);
+
+export const notifyOnSubscriptionChangedById = withDbWatcherCheck(
+	async (id: ISubscription['_id'], clientAction: Exclude<ClientAction, 'removed'> = 'updated'): Promise<void> => {
+		const subscription = await Subscriptions.findOneById(id);
+		if (!subscription) {
+			return;
+		}
+
+		void api.broadcast('watch.subscriptions', { clientAction, subscription });
+	},
+);
+
+export const notifyOnSubscriptionChangedByUserPreferences = withDbWatcherCheck(
+	async (
+		uid: ISubscription['u']['_id'],
+		notificationOriginField: keyof ISubscription,
+		originFieldNotEqualValue: 'user' | 'subscription',
+		clientAction: Exclude<ClientAction, 'removed'> = 'updated',
+	): Promise<void> => {
+		const cursor = Subscriptions.findByUserPreferences(uid, notificationOriginField, originFieldNotEqualValue, {
+			projection: subscriptionFields,
+		});
+
+		void cursor.forEach((subscription) => {
+			void api.broadcast('watch.subscriptions', { clientAction, subscription });
+		});
+	},
+);
+
+export const notifyOnSubscriptionChangedByRoomId = withDbWatcherCheck(
+	async (rid: ISubscription['rid'], clientAction: Exclude<ClientAction, 'removed'> = 'updated'): Promise<void> => {
+		const cursor = Subscriptions.findByRoomId(rid, { projection: subscriptionFields });
+
+		void cursor.forEach((subscription) => {
+			void api.broadcast('watch.subscriptions', { clientAction, subscription });
+		});
+	},
+);
+
+export const notifyOnSubscriptionChangedByAutoTranslateAndUserId = withDbWatcherCheck(
+	async (uid: ISubscription['u']['_id'], clientAction: Exclude<ClientAction, 'removed'> = 'updated'): Promise<void> => {
+		const cursor = Subscriptions.findByAutoTranslateAndUserId(uid, true, { projection: subscriptionFields });
+
+		void cursor.forEach((subscription) => {
+			void api.broadcast('watch.subscriptions', { clientAction, subscription });
+		});
+	},
+);
+
+export const notifyOnSubscriptionChangedByUserIdAndRoomType = withDbWatcherCheck(
+	async (
+		uid: ISubscription['u']['_id'],
+		t: ISubscription['t'],
+		clientAction: Exclude<ClientAction, 'removed'> = 'updated',
+	): Promise<void> => {
+		const cursor = Subscriptions.findByUserIdAndRoomType(uid, t, { projection: subscriptionFields });
+
+		void cursor.forEach((subscription) => {
+			void api.broadcast('watch.subscriptions', { clientAction, subscription });
+		});
+	},
+);
+
+export const notifyOnSubscriptionChangedByNameAndRoomType = withDbWatcherCheck(
+	async (filter: Partial<Pick<ISubscription, 'name' | 't'>>, clientAction: Exclude<ClientAction, 'removed'> = 'updated'): Promise<void> => {
+		const cursor = Subscriptions.findByNameAndRoomType(filter, { projection: subscriptionFields });
+
+		void cursor.forEach((subscription) => {
+			void api.broadcast('watch.subscriptions', { clientAction, subscription });
+		});
+	},
+);
+
+export const notifyOnSubscriptionChangedByUserId = withDbWatcherCheck(
+	async (uid: ISubscription['u']['_id'], clientAction: Exclude<ClientAction, 'removed'> = 'updated'): Promise<void> => {
+		const cursor = Subscriptions.findByUserId(uid, { projection: subscriptionFields });
+
+		void cursor.forEach((subscription) => {
+			void api.broadcast('watch.subscriptions', { clientAction, subscription });
+		});
+	},
+);
+
+export const notifyOnSubscriptionChangedByRoomIdAndUserIds = withDbWatcherCheck(
+	async (
+		rid: ISubscription['rid'],
+		uids: ISubscription['u']['_id'][],
+		clientAction: Exclude<ClientAction, 'removed'> = 'updated',
+	): Promise<void> => {
+		const cursor = Subscriptions.findByRoomIdAndUserIds(rid, uids, { projection: subscriptionFields });
+
+		void cursor.forEach((subscription) => {
+			void api.broadcast('watch.subscriptions', { clientAction, subscription });
+		});
+	},
+);
diff --git a/apps/meteor/app/lib/server/lib/notifyUsersOnMessage.ts b/apps/meteor/app/lib/server/lib/notifyUsersOnMessage.ts
index 85f2ac52b70..7551cabb6e6 100644
--- a/apps/meteor/app/lib/server/lib/notifyUsersOnMessage.ts
+++ b/apps/meteor/app/lib/server/lib/notifyUsersOnMessage.ts
@@ -7,6 +7,11 @@ import moment from 'moment';
 
 import { callbacks } from '../../../../lib/callbacks';
 import { settings } from '../../../settings/server';
+import {
+	notifyOnSubscriptionChanged,
+	notifyOnSubscriptionChangedByRoomIdAndUserId,
+	notifyOnSubscriptionChangedByRoomIdAndUserIds,
+} from './notifyListener';
 
 function messageContainsHighlight(message: IMessage, highlights: string[]): boolean {
 	if (!highlights || highlights.length === 0) return false;
@@ -51,26 +56,14 @@ export async function getMentions(message: IMessage): Promise<{ toAll: boolean;
 
 type UnreadCountType = 'all_messages' | 'user_mentions_only' | 'group_mentions_only' | 'user_and_group_mentions_only';
 
-const incGroupMentions = async (
-	rid: IRoom['_id'],
-	roomType: RoomType,
-	excludeUserId: IUser['_id'],
-	unreadCount: Exclude<UnreadCountType, 'user_mentions_only'>,
-): Promise<void> => {
+const getGroupMentions = (roomType: RoomType, unreadCount: Exclude<UnreadCountType, 'user_mentions_only'>): number => {
 	const incUnreadByGroup = ['all_messages', 'group_mentions_only', 'user_and_group_mentions_only'].includes(unreadCount);
-	const incUnread = roomType === 'd' || roomType === 'l' || incUnreadByGroup ? 1 : 0;
-	await Subscriptions.incGroupMentionsAndUnreadForRoomIdExcludingUserId(rid, excludeUserId, 1, incUnread);
+	return roomType === 'd' || roomType === 'l' || incUnreadByGroup ? 1 : 0;
 };
 
-const incUserMentions = async (
-	rid: IRoom['_id'],
-	roomType: RoomType,
-	uids: IUser['_id'][],
-	unreadCount: Exclude<UnreadCountType, 'group_mentions_only'>,
-): Promise<void> => {
-	const incUnreadByUser = new Set(['all_messages', 'user_mentions_only', 'user_and_group_mentions_only']).has(unreadCount);
-	const incUnread = roomType === 'd' || roomType === 'l' || incUnreadByUser ? 1 : 0;
-	await Subscriptions.incUserMentionsAndUnreadForRoomIdAndUserIds(rid, uids, 1, incUnread);
+const getUserMentions = (roomType: RoomType, unreadCount: Exclude<UnreadCountType, 'group_mentions_only'>): number => {
+	const incUnreadByUser = ['all_messages', 'user_mentions_only', 'user_and_group_mentions_only'].includes(unreadCount);
+	return roomType === 'd' || roomType === 'l' || incUnreadByUser ? 1 : 0;
 };
 
 export const getUserIdsFromHighlights = async (rid: IRoom['_id'], message: IMessage): Promise<string[]> => {
@@ -101,45 +94,77 @@ const getUnreadSettingCount = (roomType: RoomType): UnreadCountType => {
 };
 
 async function updateUsersSubscriptions(message: IMessage, room: IRoom): Promise<void> {
-	// Don't increase unread counter on thread messages
-	if (room != null && !message.tmid) {
-		const { toAll, toHere, mentionIds } = await getMentions(message);
-
-		const userIds = new Set(mentionIds);
-
-		const unreadCount = getUnreadSettingCount(room.t);
+	if (!room || message.tmid) {
+		return;
+	}
 
-		(await getUserIdsFromHighlights(room._id, message)).forEach((uid) => userIds.add(uid));
+	const [mentions, highlightIds] = await Promise.all([getMentions(message), getUserIdsFromHighlights(room._id, message)]);
+
+	const { toAll, toHere, mentionIds } = mentions;
+	const userIds = [...new Set([...mentionIds, ...highlightIds])];
+	const unreadCount = getUnreadSettingCount(room.t);
+
+	const userMentionInc = getUserMentions(room.t, unreadCount as Exclude<UnreadCountType, 'group_mentions_only'>);
+	const groupMentionInc = getGroupMentions(room.t, unreadCount as Exclude<UnreadCountType, 'user_mentions_only'>);
+
+	void Subscriptions.findByRoomIdAndNotAlertOrOpenExcludingUserIds({
+		roomId: room._id,
+		uidsExclude: [message.u._id],
+		uidsInclude: userIds,
+		onlyRead: !toAll && !toHere,
+	}).forEach((sub) => {
+		const hasUserMention = userIds.includes(sub.u._id);
+		const shouldIncUnread = hasUserMention || toAll || toHere || unreadCount === 'all_messages';
+		void notifyOnSubscriptionChanged(
+			{
+				...sub,
+				alert: true,
+				open: true,
+				...(shouldIncUnread && { unread: sub.unread + 1 }),
+				...(hasUserMention && { userMentions: sub.userMentions + 1 }),
+				...((toAll || toHere) && { groupMentions: sub.groupMentions + 1 }),
+			},
+			'updated',
+		);
+	});
 
-		// Give priority to user mentions over group mentions
-		if (userIds.size > 0) {
-			await incUserMentions(room._id, room.t, [...userIds], unreadCount as Exclude<UnreadCountType, 'group_mentions_only'>);
-		} else if (toAll || toHere) {
-			await incGroupMentions(room._id, room.t, message.u._id, unreadCount as Exclude<UnreadCountType, 'user_mentions_only'>);
-		}
+	// Give priority to user mentions over group mentions
+	if (userIds.length) {
+		await Subscriptions.incUserMentionsAndUnreadForRoomIdAndUserIds(room._id, userIds, 1, userMentionInc);
+	} else if (toAll || toHere) {
+		await Subscriptions.incGroupMentionsAndUnreadForRoomIdExcludingUserId(room._id, message.u._id, 1, groupMentionInc);
+	}
 
-		// this shouldn't run only if has group mentions because it will already exclude mentioned users from the query
-		if (!toAll && !toHere && unreadCount === 'all_messages') {
-			await Subscriptions.incUnreadForRoomIdExcludingUserIds(room._id, [...userIds, message.u._id], 1);
-		}
+	if (!toAll && !toHere && unreadCount === 'all_messages') {
+		await Subscriptions.incUnreadForRoomIdExcludingUserIds(room._id, [...userIds, message.u._id], 1);
 	}
 
-	// Update all other subscriptions to alert their owners but without incrementing
-	// the unread counter, as it is only for mentions and direct messages
-	// We now set alert and open properties in two separate update commands. This proved to be more efficient on MongoDB - because it uses a more efficient index.
+	// update subscriptions of other members of the room
 	await Promise.all([
 		Subscriptions.setAlertForRoomIdExcludingUserId(message.rid, message.u._id),
 		Subscriptions.setOpenForRoomIdExcludingUserId(message.rid, message.u._id),
 	]);
+
+	// update subscription of the message sender
+	await Subscriptions.setAsReadByRoomIdAndUserId(message.rid, message.u._id);
+	const setAsReadResponse = await Subscriptions.setAsReadByRoomIdAndUserId(message.rid, message.u._id);
+	if (setAsReadResponse.modifiedCount) {
+		void notifyOnSubscriptionChangedByRoomIdAndUserId(message.rid, message.u._id);
+	}
 }
 
 export async function updateThreadUsersSubscriptions(message: IMessage, replies: IUser['_id'][]): Promise<void> {
 	// Don't increase unread counter on thread messages
-
-	await Subscriptions.setAlertForRoomIdAndUserIds(message.rid, replies);
 	const repliesPlusSender = [...new Set([message.u._id, ...replies])];
-	await Subscriptions.setOpenForRoomIdAndUserIds(message.rid, repliesPlusSender);
-	await Subscriptions.setLastReplyForRoomIdAndUserIds(message.rid, repliesPlusSender, new Date());
+
+	const responses = await Promise.all([
+		Subscriptions.setAlertForRoomIdAndUserIds(message.rid, replies),
+		Subscriptions.setOpenForRoomIdAndUserIds(message.rid, repliesPlusSender),
+		Subscriptions.setLastReplyForRoomIdAndUserIds(message.rid, repliesPlusSender, new Date()),
+	]);
+
+	responses.some((response) => response?.modifiedCount) &&
+		void notifyOnSubscriptionChangedByRoomIdAndUserIds(message.rid, repliesPlusSender);
 }
 
 export async function notifyUsersOnMessage(message: IMessage, room: IRoom, roomUpdater: Updater<IRoom>): Promise<IMessage> {
diff --git a/apps/meteor/app/lib/server/methods/blockUser.ts b/apps/meteor/app/lib/server/methods/blockUser.ts
index b967e35d7bc..7fe6ec803dd 100644
--- a/apps/meteor/app/lib/server/methods/blockUser.ts
+++ b/apps/meteor/app/lib/server/methods/blockUser.ts
@@ -5,6 +5,7 @@ import { Meteor } from 'meteor/meteor';
 
 import { RoomMemberActions } from '../../../../definition/IRoomTypeConfig';
 import { roomCoordinator } from '../../../../server/lib/rooms/roomCoordinator';
+import { notifyOnSubscriptionChangedByRoomIdAndUserIds } from '../lib/notifyListener';
 
 declare module '@rocket.chat/ddp-client' {
 	// eslint-disable-next-line @typescript-eslint/naming-convention
@@ -33,14 +34,22 @@ Meteor.methods<ServerMethods>({
 			throw new Meteor.Error('error-invalid-room', 'Invalid room', { method: 'blockUser' });
 		}
 
-		const subscription = await Subscriptions.findOneByRoomIdAndUserId(rid, userId);
-		const subscription2 = await Subscriptions.findOneByRoomIdAndUserId(rid, blocked);
+		const [blockedUser, blockerUser] = await Promise.all([
+			Subscriptions.findOneByRoomIdAndUserId(rid, blocked, { projection: { _id: 1 } }),
+			Subscriptions.findOneByRoomIdAndUserId(rid, userId, { projection: { _id: 1 } }),
+		]);
 
-		if (!subscription || !subscription2) {
+		if (!blockedUser || !blockerUser) {
 			throw new Meteor.Error('error-invalid-room', 'Invalid room', { method: 'blockUser' });
 		}
 
-		await Subscriptions.setBlockedByRoomId(rid, blocked, userId);
+		const [blockedResponse, blockerResponse] = await Subscriptions.setBlockedByRoomId(rid, blocked, userId);
+
+		const listenerUsers = [...(blockedResponse?.modifiedCount ? [blocked] : []), ...(blockerResponse?.modifiedCount ? [userId] : [])];
+
+		if (listenerUsers.length) {
+			void notifyOnSubscriptionChangedByRoomIdAndUserIds(rid, listenerUsers);
+		}
 
 		return true;
 	},
diff --git a/apps/meteor/app/lib/server/methods/unblockUser.ts b/apps/meteor/app/lib/server/methods/unblockUser.ts
index 2eec5a08210..7b4bc566001 100644
--- a/apps/meteor/app/lib/server/methods/unblockUser.ts
+++ b/apps/meteor/app/lib/server/methods/unblockUser.ts
@@ -3,6 +3,8 @@ import { Subscriptions } from '@rocket.chat/models';
 import { check } from 'meteor/check';
 import { Meteor } from 'meteor/meteor';
 
+import { notifyOnSubscriptionChangedByRoomIdAndUserIds } from '../lib/notifyListener';
+
 declare module '@rocket.chat/ddp-client' {
 	// eslint-disable-next-line @typescript-eslint/naming-convention
 	interface ServerMethods {
@@ -20,14 +22,22 @@ Meteor.methods<ServerMethods>({
 			throw new Meteor.Error('error-invalid-user', 'Invalid user', { method: 'blockUser' });
 		}
 
-		const subscription = await Subscriptions.findOneByRoomIdAndUserId(rid, userId);
-		const subscription2 = await Subscriptions.findOneByRoomIdAndUserId(rid, blocked);
+		const [blockedUser, blockerUser] = await Promise.all([
+			Subscriptions.findOneByRoomIdAndUserId(rid, blocked, { projection: { _id: 1 } }),
+			Subscriptions.findOneByRoomIdAndUserId(rid, userId, { projection: { _id: 1 } }),
+		]);
 
-		if (!subscription || !subscription2) {
+		if (!blockedUser || !blockerUser) {
 			throw new Meteor.Error('error-invalid-room', 'Invalid room', { method: 'blockUser' });
 		}
 
-		await Subscriptions.unsetBlockedByRoomId(rid, blocked, userId);
+		const [blockedResponse, blockerResponse] = await Subscriptions.unsetBlockedByRoomId(rid, blocked, userId);
+
+		const listenerUsers = [...(blockedResponse?.modifiedCount ? [blocked] : []), ...(blockerResponse?.modifiedCount ? [userId] : [])];
+
+		if (listenerUsers.length) {
+			void notifyOnSubscriptionChangedByRoomIdAndUserIds(rid, listenerUsers);
+		}
 
 		return true;
 	},
diff --git a/apps/meteor/app/livechat/server/lib/Contacts.ts b/apps/meteor/app/livechat/server/lib/Contacts.ts
index 2e648b02f5d..c20b5dbdb66 100644
--- a/apps/meteor/app/livechat/server/lib/Contacts.ts
+++ b/apps/meteor/app/livechat/server/lib/Contacts.ts
@@ -6,7 +6,11 @@ import type { MatchKeysAndValues, OnlyFieldsOfType } from 'mongodb';
 
 import { callbacks } from '../../../../lib/callbacks';
 import { trim } from '../../../../lib/utils/stringUtils';
-import { notifyOnRoomChangedById, notifyOnLivechatInquiryChangedByRoom } from '../../../lib/server/lib/notifyListener';
+import {
+	notifyOnRoomChangedById,
+	notifyOnSubscriptionChangedByRoomId,
+	notifyOnLivechatInquiryChangedByRoom,
+} from '../../../lib/server/lib/notifyListener';
 import { i18n } from '../../../utils/lib/i18n';
 
 type RegisterContactProps = {
@@ -138,14 +142,23 @@ export const Contacts = {
 			for await (const room of rooms) {
 				const { _id: rid } = room;
 
-				await Promise.all([
+				const responses = await Promise.all([
 					Rooms.setFnameById(rid, name),
 					LivechatInquiry.setNameByRoomId(rid, name),
 					Subscriptions.updateDisplayNameByRoomId(rid, name),
 				]);
 
-				void notifyOnLivechatInquiryChangedByRoom(rid, 'updated', { name });
-				void notifyOnRoomChangedById(rid);
+				if (responses[0]?.modifiedCount) {
+					void notifyOnRoomChangedById(rid);
+				}
+
+				if (responses[1]?.modifiedCount) {
+					void notifyOnLivechatInquiryChangedByRoom(rid, 'updated', { name });
+				}
+
+				if (responses[2]?.modifiedCount) {
+					void notifyOnSubscriptionChangedByRoomId(rid);
+				}
 			}
 		}
 
diff --git a/apps/meteor/app/livechat/server/lib/Helper.ts b/apps/meteor/app/livechat/server/lib/Helper.ts
index 1ef572df306..17f21d8d7b0 100644
--- a/apps/meteor/app/livechat/server/lib/Helper.ts
+++ b/apps/meteor/app/livechat/server/lib/Helper.ts
@@ -39,6 +39,9 @@ import { sendNotification } from '../../../lib/server';
 import {
 	notifyOnLivechatDepartmentAgentChanged,
 	notifyOnLivechatDepartmentAgentChangedByAgentsAndDepartmentId,
+	notifyOnSubscriptionChangedById,
+	notifyOnSubscriptionChangedByRoomId,
+	notifyOnSubscriptionChanged,
 } from '../../../lib/server/lib/notifyListener';
 import { settings } from '../../../settings/server';
 import { Livechat as LivechatTyped } from './LivechatTyped';
@@ -285,7 +288,13 @@ export const createLivechatSubscription = async (
 		...(department && { department }),
 	} as InsertionModel<ISubscription>;
 
-	return Subscriptions.insertOne(subscriptionData);
+	const response = await Subscriptions.insertOne(subscriptionData);
+
+	if (response?.insertedId) {
+		void notifyOnSubscriptionChangedById(response.insertedId, 'inserted');
+	}
+
+	return response;
 };
 
 export const removeAgentFromSubscription = async (rid: string, { _id, username }: Pick<IUser, '_id' | 'username'>) => {
@@ -296,7 +305,11 @@ export const removeAgentFromSubscription = async (rid: string, { _id, username }
 		return;
 	}
 
-	await Subscriptions.removeByRoomIdAndUserId(rid, _id);
+	const deletedSubscription = await Subscriptions.removeByRoomIdAndUserId(rid, _id);
+	if (deletedSubscription) {
+		void notifyOnSubscriptionChanged(deletedSubscription, 'removed');
+	}
+
 	await Message.saveSystemMessage('ul', rid, username || '', { _id: user._id, username: user.username, name: user.name });
 
 	setImmediate(() => {
@@ -513,12 +526,16 @@ export const updateChatDepartment = async ({
 	newDepartmentId: string;
 	oldDepartmentId?: string;
 }) => {
-	await Promise.all([
+	const responses = await Promise.all([
 		LivechatRooms.changeDepartmentIdByRoomId(rid, newDepartmentId),
 		LivechatInquiry.changeDepartmentIdByRoomId(rid, newDepartmentId),
 		Subscriptions.changeDepartmentByRoomId(rid, newDepartmentId),
 	]);
 
+	if (responses[2].modifiedCount) {
+		void notifyOnSubscriptionChangedByRoomId(rid);
+	}
+
 	setImmediate(() => {
 		void Apps.self?.triggerEvent(AppEvents.IPostLivechatRoomTransferred, {
 			type: LivechatTransferEventType.DEPARTMENT,
diff --git a/apps/meteor/app/livechat/server/lib/LivechatTyped.ts b/apps/meteor/app/livechat/server/lib/LivechatTyped.ts
index bb8a3fd77ba..2745b37d150 100644
--- a/apps/meteor/app/livechat/server/lib/LivechatTyped.ts
+++ b/apps/meteor/app/livechat/server/lib/LivechatTyped.ts
@@ -60,8 +60,10 @@ import {
 	notifyOnLivechatInquiryChangedByRoom,
 	notifyOnRoomChangedById,
 	notifyOnLivechatInquiryChangedByToken,
-	notifyOnLivechatDepartmentAgentChangedByDepartmentId,
 	notifyOnUserChange,
+	notifyOnLivechatDepartmentAgentChangedByDepartmentId,
+	notifyOnSubscriptionChangedByRoomId,
+	notifyOnSubscriptionChanged,
 } from '../../../lib/server/lib/notifyListener';
 import * as Mailer from '../../../mailer/server/api';
 import { metrics } from '../../../metrics/server';
@@ -289,7 +291,11 @@ class LivechatClass {
 			throw new Error('Error closing room');
 		}
 
-		await Subscriptions.removeByRoomId(rid);
+		await Subscriptions.removeByRoomId(rid, {
+			async onTrash(doc) {
+				void notifyOnSubscriptionChanged(doc, 'removed');
+			},
+		});
 
 		this.logger.debug(`DB updated for room ${room._id}`);
 
@@ -515,12 +521,16 @@ class LivechatClass {
 		const result = await Promise.allSettled([
 			Messages.removeByRoomId(rid),
 			ReadReceipts.removeByRoomId(rid),
-			Subscriptions.removeByRoomId(rid),
+			Subscriptions.removeByRoomId(rid, {
+				async onTrash(doc) {
+					void notifyOnSubscriptionChanged(doc, 'removed');
+				},
+			}),
 			LivechatInquiry.removeByRoomId(rid),
 			LivechatRooms.removeById(rid),
 		]);
 
-		if (inquiry) {
+		if (result[3]?.status === 'fulfilled' && result[3].value?.deletedCount && inquiry) {
 			void notifyOnLivechatInquiryChanged(inquiry, 'removed');
 		}
 
@@ -1143,13 +1153,18 @@ class LivechatClass {
 		const cursor = LivechatRooms.findByVisitorToken(token);
 		for await (const room of cursor) {
 			await Promise.all([
+				Subscriptions.removeByRoomId(room._id, {
+					async onTrash(doc) {
+						void notifyOnSubscriptionChanged(doc, 'removed');
+					},
+				}),
 				FileUpload.removeFilesByRoomId(room._id),
 				Messages.removeByRoomId(room._id),
 				ReadReceipts.removeByRoomId(room._id),
 			]);
 		}
 
-		await Promise.all([Subscriptions.removeByVisitorToken(token), LivechatRooms.removeByVisitorToken(token)]);
+		await LivechatRooms.removeByVisitorToken(token);
 
 		const livechatInquiries = await LivechatInquiry.findIdsByVisitorToken(token).toArray();
 		await LivechatInquiry.removeByIds(livechatInquiries.map(({ _id }) => _id));
@@ -1701,13 +1716,19 @@ class LivechatClass {
 			const { _id: rid } = roomData;
 			const { name } = guestData;
 
-			await Promise.all([
+			const responses = await Promise.all([
 				Rooms.setFnameById(rid, name),
 				LivechatInquiry.setNameByRoomId(rid, name),
 				Subscriptions.updateDisplayNameByRoomId(rid, name),
 			]);
 
-			void notifyOnLivechatInquiryChangedByRoom(rid, 'updated', { name });
+			if (responses[1]?.modifiedCount) {
+				void notifyOnLivechatInquiryChangedByRoom(rid, 'updated', { name });
+			}
+
+			if (responses[2]?.modifiedCount) {
+				await notifyOnSubscriptionChangedByRoomId(rid);
+			}
 		}
 
 		void notifyOnRoomChangedById(roomData._id);
diff --git a/apps/meteor/app/message-mark-as-unread/server/unreadMessages.ts b/apps/meteor/app/message-mark-as-unread/server/unreadMessages.ts
index 6ef1f5567a2..f213ae4b724 100644
--- a/apps/meteor/app/message-mark-as-unread/server/unreadMessages.ts
+++ b/apps/meteor/app/message-mark-as-unread/server/unreadMessages.ts
@@ -3,6 +3,7 @@ import type { ServerMethods } from '@rocket.chat/ddp-client';
 import { Messages, Subscriptions } from '@rocket.chat/models';
 import { Meteor } from 'meteor/meteor';
 
+import { notifyOnSubscriptionChangedByRoomIdAndUserId } from '../../lib/server/lib/notifyListener';
 import logger from './logger';
 
 declare module '@rocket.chat/ddp-client' {
@@ -36,7 +37,11 @@ Meteor.methods<ServerMethods>({
 				});
 			}
 
-			await Subscriptions.setAsUnreadByRoomIdAndUserId(lastMessage.rid, userId, lastMessage.ts);
+			const setAsUnreadResponse = await Subscriptions.setAsUnreadByRoomIdAndUserId(lastMessage.rid, userId, lastMessage.ts);
+			if (setAsUnreadResponse.modifiedCount) {
+				void notifyOnSubscriptionChangedByRoomIdAndUserId(lastMessage.rid, userId);
+			}
+
 			return;
 		}
 
@@ -72,7 +77,11 @@ Meteor.methods<ServerMethods>({
 		if (firstUnreadMessage.ts >= lastSeen) {
 			return logger.debug('Provided message is already marked as unread');
 		}
-		logger.debug(`Updating unread  message of ${originalMessage.ts} as the first unread`);
-		await Subscriptions.setAsUnreadByRoomIdAndUserId(originalMessage.rid, userId, originalMessage.ts);
+
+		logger.debug(`Updating unread message of ${originalMessage.ts} as the first unread`);
+		const setAsUnreadResponse = await Subscriptions.setAsUnreadByRoomIdAndUserId(originalMessage.rid, userId, originalMessage.ts);
+		if (setAsUnreadResponse.modifiedCount) {
+			void notifyOnSubscriptionChangedByRoomIdAndUserId(originalMessage.rid, userId);
+		}
 	},
 });
diff --git a/apps/meteor/app/push-notifications/server/methods/saveNotificationSettings.ts b/apps/meteor/app/push-notifications/server/methods/saveNotificationSettings.ts
index a86ded6f24e..ddac51b4eca 100644
--- a/apps/meteor/app/push-notifications/server/methods/saveNotificationSettings.ts
+++ b/apps/meteor/app/push-notifications/server/methods/saveNotificationSettings.ts
@@ -4,6 +4,7 @@ import { Subscriptions } from '@rocket.chat/models';
 import { check } from 'meteor/check';
 import { Meteor } from 'meteor/meteor';
 
+import { notifyOnSubscriptionChangedById } from '../../../lib/server/lib/notifyListener';
 import { getUserNotificationPreference } from '../../../utils/server/getUserNotificationPreference';
 
 const saveAudioNotificationValue = (subId: ISubscription['_id'], value: string) =>
@@ -132,7 +133,10 @@ Meteor.methods<ServerMethods>({
 			});
 		}
 
-		await notifications[field].updateMethod(subscription, value);
+		const updateResponse = await notifications[field].updateMethod(subscription, value);
+		if (updateResponse.modifiedCount) {
+			void notifyOnSubscriptionChangedById(subscription._id);
+		}
 
 		return true;
 	},
@@ -144,13 +148,19 @@ Meteor.methods<ServerMethods>({
 				method: 'saveAudioNotificationValue',
 			});
 		}
+
 		const subscription = await Subscriptions.findOneByRoomIdAndUserId(rid, userId);
 		if (!subscription) {
 			throw new Meteor.Error('error-invalid-subscription', 'Invalid subscription', {
 				method: 'saveAudioNotificationValue',
 			});
 		}
-		await saveAudioNotificationValue(subscription._id, value);
+
+		const saveAudioNotificationResponse = await saveAudioNotificationValue(subscription._id, value);
+		if (saveAudioNotificationResponse.modifiedCount) {
+			void notifyOnSubscriptionChangedById(subscription._id);
+		}
+
 		return true;
 	},
 });
diff --git a/apps/meteor/app/threads/server/functions.ts b/apps/meteor/app/threads/server/functions.ts
index 30daef8b8b9..194e482c54a 100644
--- a/apps/meteor/app/threads/server/functions.ts
+++ b/apps/meteor/app/threads/server/functions.ts
@@ -2,51 +2,57 @@ import type { IMessage } from '@rocket.chat/core-typings';
 import { isEditedMessage } from '@rocket.chat/core-typings';
 import { Messages, Subscriptions, ReadReceipts, NotificationQueue } from '@rocket.chat/models';
 
+import {
+	notifyOnSubscriptionChangedByRoomIdAndUserIds,
+	notifyOnSubscriptionChangedByRoomIdAndUserId,
+} from '../../lib/server/lib/notifyListener';
 import { getMentions, getUserIdsFromHighlights } from '../../lib/server/lib/notifyUsersOnMessage';
 
 export async function reply({ tmid }: { tmid?: string }, message: IMessage, parentMessage: IMessage, followers: string[]) {
-	const { rid, ts, u } = message;
 	if (!tmid || isEditedMessage(message)) {
 		return false;
 	}
 
-	const { toAll, toHere, mentionIds } = await getMentions(message);
+	const { rid, ts, u } = message;
+
+	const [highlightsUids, threadFollowers, { toAll, toHere, mentionIds }] = await Promise.all([
+		getUserIdsFromHighlights(rid, message),
+		Messages.getThreadFollowsByThreadId(tmid),
+		getMentions(message),
+	]);
 
 	const addToReplies = [
-		...new Set([
-			...followers,
-			...mentionIds,
-			...(Array.isArray(parentMessage.replies) && parentMessage.replies.length ? [u._id] : [parentMessage.u._id, u._id]),
-		]),
+		...new Set([...followers, ...mentionIds, ...(parentMessage.replies?.length ? [u._id] : [parentMessage.u._id, u._id])]),
 	];
-	const highlightedUserIds = new Set<string>();
 
-	(await getUserIdsFromHighlights(rid, message)).forEach((uid) => highlightedUserIds.add(uid));
-	await Messages.updateRepliesByThreadId(tmid, addToReplies, ts);
-	await ReadReceipts.setAsThreadById(tmid);
+	const threadFollowersUids = threadFollowers?.filter((userId) => userId !== u._id && !mentionIds.includes(userId)) || [];
 
-	const replies = await Messages.getThreadFollowsByThreadId(tmid);
+	// Notify everyone involved in the thread
+	const notifyOptions = toAll || toHere ? { groupMention: true } : {};
 
-	const repliesFiltered = (replies || []).filter((userId) => userId !== u._id).filter((userId) => !mentionIds.includes(userId));
+	// Notify message mentioned users and highlights
+	const mentionedUsers = [...new Set([...mentionIds, ...highlightsUids])];
 
-	if (toAll || toHere) {
-		await Subscriptions.addUnreadThreadByRoomIdAndUserIds(rid, repliesFiltered, tmid, {
-			groupMention: true,
-		});
-	} else {
-		await Subscriptions.addUnreadThreadByRoomIdAndUserIds(rid, repliesFiltered, tmid, {});
-	}
+	const promises = [
+		Messages.updateRepliesByThreadId(tmid, addToReplies, ts),
+		ReadReceipts.setAsThreadById(tmid),
+		Subscriptions.addUnreadThreadByRoomIdAndUserIds(rid, threadFollowersUids, tmid, notifyOptions),
+	];
 
-	const mentionedUsers = new Set<string>([...mentionIds, ...highlightedUserIds]);
-	for await (const userId of mentionedUsers) {
-		await Subscriptions.addUnreadThreadByRoomIdAndUserIds(rid, [userId], tmid, { userMention: true });
+	if (mentionedUsers.length) {
+		promises.push(Subscriptions.addUnreadThreadByRoomIdAndUserIds(rid, mentionedUsers, tmid, { userMention: true }));
 	}
 
-	const highlightIds = Array.from(highlightedUserIds);
-	if (highlightIds.length) {
-		await Subscriptions.setAlertForRoomIdAndUserIds(rid, highlightIds);
-		await Subscriptions.setOpenForRoomIdAndUserIds(rid, highlightIds);
+	if (highlightsUids.length) {
+		promises.push(
+			Subscriptions.setAlertForRoomIdAndUserIds(rid, highlightsUids),
+			Subscriptions.setOpenForRoomIdAndUserIds(rid, highlightsUids),
+		);
 	}
+
+	await Promise.allSettled(promises);
+
+	void notifyOnSubscriptionChangedByRoomIdAndUserIds(rid, [...threadFollowersUids, ...mentionedUsers, ...highlightsUids]);
 }
 
 export async function follow({ tmid, uid }: { tmid: string; uid: string }) {
@@ -62,20 +68,27 @@ export async function unfollow({ tmid, rid, uid }: { tmid: string; rid: string;
 		return false;
 	}
 
-	await Subscriptions.removeUnreadThreadByRoomIdAndUserId(rid, uid, tmid);
+	const removeUnreadThreadResponse = await Subscriptions.removeUnreadThreadByRoomIdAndUserId(rid, uid, tmid);
+	if (removeUnreadThreadResponse.modifiedCount) {
+		void notifyOnSubscriptionChangedByRoomIdAndUserId(rid, uid);
+	}
 
 	await Messages.removeThreadFollowerByThreadId(tmid, uid);
 }
 
 export const readThread = async ({ userId, rid, tmid }: { userId: string; rid: string; tmid: string }) => {
-	const projection = { tunread: 1 };
-	const sub = await Subscriptions.findOneByRoomIdAndUserId(rid, userId, { projection });
+	const sub = await Subscriptions.findOneByRoomIdAndUserId(rid, userId, { projection: { tunread: 1 } });
 	if (!sub) {
 		return;
 	}
+
 	// if the thread being marked as read is the last one unread also clear the unread subscription flag
 	const clearAlert = sub.tunread && sub.tunread?.length <= 1 && sub.tunread.includes(tmid);
 
-	await Subscriptions.removeUnreadThreadByRoomIdAndUserId(rid, userId, tmid, clearAlert);
+	const removeUnreadThreadResponse = await Subscriptions.removeUnreadThreadByRoomIdAndUserId(rid, userId, tmid, clearAlert);
+	if (removeUnreadThreadResponse.modifiedCount) {
+		void notifyOnSubscriptionChangedByRoomIdAndUserId(rid, userId);
+	}
+
 	await NotificationQueue.clearQueueByUserId(userId);
 };
diff --git a/apps/meteor/ee/app/livechat-enterprise/server/hooks/onCloseLivechat.ts b/apps/meteor/ee/app/livechat-enterprise/server/hooks/onCloseLivechat.ts
index 4e76f396617..bc2d4fb6a3f 100644
--- a/apps/meteor/ee/app/livechat-enterprise/server/hooks/onCloseLivechat.ts
+++ b/apps/meteor/ee/app/livechat-enterprise/server/hooks/onCloseLivechat.ts
@@ -1,6 +1,7 @@
 import type { IOmnichannelRoom } from '@rocket.chat/core-typings';
 import { LivechatRooms, Subscriptions } from '@rocket.chat/models';
 
+import { notifyOnSubscriptionChangedByRoomId } from '../../../../../app/lib/server/lib/notifyListener';
 import { settings } from '../../../../../app/settings/server';
 import { callbacks } from '../../../../../lib/callbacks';
 import { AutoCloseOnHoldScheduler } from '../lib/AutoCloseOnHoldScheduler';
@@ -16,12 +17,16 @@ const onCloseLivechat = async (params: LivechatCloseCallbackParams) => {
 		room: { _id: roomId },
 	} = params;
 
-	await Promise.all([
+	const responses = await Promise.all([
 		LivechatRooms.unsetOnHoldByRoomId(roomId),
 		Subscriptions.unsetOnHoldByRoomId(roomId),
 		AutoCloseOnHoldScheduler.unscheduleRoom(roomId),
 	]);
 
+	if (responses[1].modifiedCount) {
+		void notifyOnSubscriptionChangedByRoomId(roomId);
+	}
+
 	if (!settings.get('Livechat_waiting_queue')) {
 		return params;
 	}
diff --git a/apps/meteor/ee/app/livechat-enterprise/server/services/omnichannel.internalService.ts b/apps/meteor/ee/app/livechat-enterprise/server/services/omnichannel.internalService.ts
index c3133387865..f2db61ddbc9 100644
--- a/apps/meteor/ee/app/livechat-enterprise/server/services/omnichannel.internalService.ts
+++ b/apps/meteor/ee/app/livechat-enterprise/server/services/omnichannel.internalService.ts
@@ -5,7 +5,11 @@ import type { IOmnichannelRoom, IUser, ILivechatInquiryRecord, IOmnichannelSyste
 import { Logger } from '@rocket.chat/logger';
 import { LivechatRooms, Subscriptions, LivechatInquiry } from '@rocket.chat/models';
 
-import { notifyOnLivechatInquiryChangedById, notifyOnRoomChangedById } from '../../../../../app/lib/server/lib/notifyListener';
+import {
+	notifyOnSubscriptionChangedByRoomId,
+	notifyOnLivechatInquiryChangedById,
+	notifyOnRoomChangedById,
+} from '../../../../../app/lib/server/lib/notifyListener';
 import { dispatchAgentDelegated } from '../../../../../app/livechat/server/lib/Helper';
 import { queueInquiry } from '../../../../../app/livechat/server/lib/QueueManager';
 import { RoutingManager } from '../../../../../app/livechat/server/lib/RoutingManager';
@@ -53,15 +57,21 @@ export class OmnichannelEE extends ServiceClassInternal implements IOmnichannelE
 			throw new Error('error-unserved-rooms-cannot-be-placed-onhold');
 		}
 
-		await Promise.all([
+		const [roomResult, subsResult] = await Promise.all([
 			LivechatRooms.setOnHoldByRoomId(roomId),
 			Subscriptions.setOnHoldByRoomId(roomId),
 			Message.saveSystemMessage<IOmnichannelSystemMessage>('omnichannel_placed_chat_on_hold', roomId, '', onHoldBy, { comment }),
 		]);
 
-		await callbacks.run('livechat:afterOnHold', room);
+		if (roomResult.modifiedCount) {
+			void notifyOnRoomChangedById(roomId);
+		}
 
-		void notifyOnRoomChangedById(roomId);
+		if (subsResult.modifiedCount) {
+			void notifyOnSubscriptionChangedByRoomId(roomId);
+		}
+
+		await callbacks.run('livechat:afterOnHold', room);
 	}
 
 	async resumeRoomOnHold(
@@ -104,15 +114,21 @@ export class OmnichannelEE extends ServiceClassInternal implements IOmnichannelE
 			clientAction,
 		});
 
-		await Promise.all([
+		const [roomResult, subsResult] = await Promise.all([
 			LivechatRooms.unsetOnHoldByRoomId(roomId),
 			Subscriptions.unsetOnHoldByRoomId(roomId),
 			Message.saveSystemMessage<IOmnichannelSystemMessage>('omnichannel_on_hold_chat_resumed', roomId, '', resumeBy, { comment }),
 		]);
 
-		await callbacks.run('livechat:afterOnHoldChatResumed', room);
+		if (roomResult.modifiedCount) {
+			void notifyOnRoomChangedById(roomId);
+		}
 
-		void notifyOnRoomChangedById(roomId);
+		if (subsResult.modifiedCount) {
+			void notifyOnSubscriptionChangedByRoomId(roomId);
+		}
+
+		await callbacks.run('livechat:afterOnHoldChatResumed', room);
 	}
 
 	private async attemptToAssignRoomToServingAgentElseQueueIt({
diff --git a/apps/meteor/ee/app/message-read-receipt/server/hooks/afterSaveMessage.ts b/apps/meteor/ee/app/message-read-receipt/server/hooks/afterSaveMessage.ts
index 9180632768a..62623b1a4a1 100644
--- a/apps/meteor/ee/app/message-read-receipt/server/hooks/afterSaveMessage.ts
+++ b/apps/meteor/ee/app/message-read-receipt/server/hooks/afterSaveMessage.ts
@@ -1,5 +1,4 @@
-import { isEditedMessage, isOmnichannelRoom } from '@rocket.chat/core-typings';
-import { Subscriptions } from '@rocket.chat/models';
+import { isEditedMessage } from '@rocket.chat/core-typings';
 
 import { callbacks } from '../../../../../lib/callbacks';
 import { ReadReceipt } from '../../../../server/lib/message-read-receipt/ReadReceipt';
@@ -12,11 +11,6 @@ callbacks.add(
 			return message;
 		}
 
-		if (!isOmnichannelRoom(room) || !room.closedAt) {
-			// set subscription as read right after message was sent
-			await Subscriptions.setAsReadByRoomIdAndUserId(room._id, message.u._id);
-		}
-
 		// mark message as read as well
 		await ReadReceipt.markMessageAsReadBySender(message, room, message.u._id);
 
diff --git a/apps/meteor/server/database/watchCollections.ts b/apps/meteor/server/database/watchCollections.ts
index 5cd56af62fd..6dd173d5d32 100644
--- a/apps/meteor/server/database/watchCollections.ts
+++ b/apps/meteor/server/database/watchCollections.ts
@@ -29,7 +29,7 @@ const onlyCollections = DBWATCHER_ONLY_COLLECTIONS.split(',')
 	.filter(Boolean);
 
 export function getWatchCollections(): string[] {
-	const collections = [InstanceStatus.getCollectionName(), Subscriptions.getCollectionName()];
+	const collections = [InstanceStatus.getCollectionName()];
 
 	// add back to the list of collections in case db watchers are enabled
 	if (!dbWatchersDisabled) {
@@ -45,6 +45,7 @@ export function getWatchCollections(): string[] {
 		collections.push(LoginServiceConfiguration.getCollectionName());
 		collections.push(EmailInbox.getCollectionName());
 		collections.push(IntegrationHistory.getCollectionName());
+		collections.push(Subscriptions.getCollectionName());
 		collections.push(Settings.getCollectionName());
 		collections.push(LivechatDepartmentAgents.getCollectionName());
 	}
diff --git a/apps/meteor/server/lib/readMessages.ts b/apps/meteor/server/lib/readMessages.ts
index d7c8cf55928..3be43a875fa 100644
--- a/apps/meteor/server/lib/readMessages.ts
+++ b/apps/meteor/server/lib/readMessages.ts
@@ -1,6 +1,7 @@
 import type { IRoom, IUser } from '@rocket.chat/core-typings';
 import { NotificationQueue, Subscriptions } from '@rocket.chat/models';
 
+import { notifyOnSubscriptionChangedByRoomIdAndUserId } from '../../app/lib/server/lib/notifyListener';
 import { callbacks } from '../../lib/callbacks';
 
 export async function readMessages(rid: IRoom['_id'], uid: IUser['_id'], readThreads: boolean): Promise<void> {
@@ -15,7 +16,10 @@ export async function readMessages(rid: IRoom['_id'], uid: IUser['_id'], readThr
 	// do not mark room as read if there are still unread threads
 	const alert = !!(sub.alert && !readThreads && sub.tunread && sub.tunread.length > 0);
 
-	await Subscriptions.setAsReadByRoomIdAndUserId(rid, uid, readThreads, alert);
+	const setAsReadResponse = await Subscriptions.setAsReadByRoomIdAndUserId(rid, uid, readThreads, alert);
+	if (setAsReadResponse.modifiedCount) {
+		void notifyOnSubscriptionChangedByRoomIdAndUserId(rid, uid);
+	}
 
 	await NotificationQueue.clearQueueByUserId(uid);
 
diff --git a/apps/meteor/server/lib/resetUserE2EKey.ts b/apps/meteor/server/lib/resetUserE2EKey.ts
index 8535eee9a2c..85da6e59cf6 100644
--- a/apps/meteor/server/lib/resetUserE2EKey.ts
+++ b/apps/meteor/server/lib/resetUserE2EKey.ts
@@ -2,7 +2,7 @@ import { api } from '@rocket.chat/core-services';
 import { Subscriptions, Users, Rooms } from '@rocket.chat/models';
 import { Meteor } from 'meteor/meteor';
 
-import { notifyOnUserChange } from '../../app/lib/server/lib/notifyListener';
+import { notifyOnUserChange, notifyOnSubscriptionChangedByUserId } from '../../app/lib/server/lib/notifyListener';
 import * as Mailer from '../../app/mailer/server/api';
 import { settings } from '../../app/settings/server';
 import { i18n } from './i18n';
@@ -67,12 +67,13 @@ export async function resetUserE2EEncriptionKey(uid: string, notifyUser: boolean
 	}
 
 	// force logout the live sessions
-
 	await api.broadcast('user.forceLogout', uid);
 
-	await Users.resetE2EKey(uid);
-	await Subscriptions.resetUserE2EKey(uid);
-	await Rooms.removeUserFromE2EEQueue(uid);
+	const responses = await Promise.all([Users.resetE2EKey(uid), Subscriptions.resetUserE2EKey(uid), Rooms.removeUserFromE2EEQueue(uid)]);
+
+	if (responses[1]?.modifiedCount) {
+		void notifyOnSubscriptionChangedByUserId(uid);
+	}
 
 	// Force the user to logout, so that the keys can be generated again
 	await Users.unsetLoginTokens(uid);
diff --git a/apps/meteor/server/methods/addAllUserToRoom.ts b/apps/meteor/server/methods/addAllUserToRoom.ts
index c07bdc48040..6b1b690b4bf 100644
--- a/apps/meteor/server/methods/addAllUserToRoom.ts
+++ b/apps/meteor/server/methods/addAllUserToRoom.ts
@@ -6,6 +6,7 @@ import { check } from 'meteor/check';
 import { Meteor } from 'meteor/meteor';
 
 import { hasPermissionAsync } from '../../app/authorization/server/functions/hasPermission';
+import { notifyOnSubscriptionChangedById } from '../../app/lib/server/lib/notifyListener';
 import { settings } from '../../app/settings/server';
 import { getDefaultSubscriptionPref } from '../../app/utils/lib/getDefaultSubscriptionPref';
 import { callbacks } from '../../lib/callbacks';
@@ -58,7 +59,7 @@ Meteor.methods<ServerMethods>({
 			}
 			await callbacks.run('beforeJoinRoom', user, room);
 			const autoTranslateConfig = getSubscriptionAutotranslateDefaultConfig(user);
-			await Subscriptions.createWithRoomAndUser(room, user, {
+			const { insertedId } = await Subscriptions.createWithRoomAndUser(room, user, {
 				ts: now,
 				open: true,
 				alert: true,
@@ -68,6 +69,9 @@ Meteor.methods<ServerMethods>({
 				...autoTranslateConfig,
 				...getDefaultSubscriptionPref(user),
 			});
+			if (insertedId) {
+				void notifyOnSubscriptionChangedById(insertedId, 'inserted');
+			}
 			await Message.saveSystemMessage('uj', rid, user.username || '', user, { ts: now });
 			await callbacks.run('afterJoinRoom', user, room);
 		}
diff --git a/apps/meteor/server/methods/addRoomLeader.ts b/apps/meteor/server/methods/addRoomLeader.ts
index b8e09b44065..64240bff65f 100644
--- a/apps/meteor/server/methods/addRoomLeader.ts
+++ b/apps/meteor/server/methods/addRoomLeader.ts
@@ -6,6 +6,7 @@ import { check } from 'meteor/check';
 import { Meteor } from 'meteor/meteor';
 
 import { hasPermissionAsync } from '../../app/authorization/server/functions/hasPermission';
+import { notifyOnSubscriptionChangedById } from '../../app/lib/server/lib/notifyListener';
 import { settings } from '../../app/settings/server';
 
 declare module '@rocket.chat/ddp-client' {
@@ -56,7 +57,10 @@ Meteor.methods<ServerMethods>({
 			});
 		}
 
-		await Subscriptions.addRoleById(subscription._id, 'leader');
+		const addRoleResponse = await Subscriptions.addRoleById(subscription._id, 'leader');
+		if (addRoleResponse.modifiedCount) {
+			void notifyOnSubscriptionChangedById(subscription._id);
+		}
 
 		const fromUser = await Users.findOneById(uid);
 
diff --git a/apps/meteor/server/methods/addRoomModerator.ts b/apps/meteor/server/methods/addRoomModerator.ts
index a9cc21f30e0..da75038a368 100644
--- a/apps/meteor/server/methods/addRoomModerator.ts
+++ b/apps/meteor/server/methods/addRoomModerator.ts
@@ -7,6 +7,7 @@ import { check } from 'meteor/check';
 import { Meteor } from 'meteor/meteor';
 
 import { hasPermissionAsync } from '../../app/authorization/server/functions/hasPermission';
+import { notifyOnSubscriptionChangedById } from '../../app/lib/server/lib/notifyListener';
 import { settings } from '../../app/settings/server';
 import { isFederationEnabled, isFederationReady, FederationMatrixInvalidConfigurationError } from '../services/federation/utils';
 
@@ -71,7 +72,10 @@ Meteor.methods<ServerMethods>({
 			});
 		}
 
-		await Subscriptions.addRoleById(subscription._id, 'moderator');
+		const addRoleResponse = await Subscriptions.addRoleById(subscription._id, 'moderator');
+		if (addRoleResponse.modifiedCount) {
+			void notifyOnSubscriptionChangedById(subscription._id);
+		}
 
 		const fromUser = await Users.findOneById(uid);
 		if (!fromUser) {
diff --git a/apps/meteor/server/methods/addRoomOwner.ts b/apps/meteor/server/methods/addRoomOwner.ts
index f59267f6719..d0a23efea02 100644
--- a/apps/meteor/server/methods/addRoomOwner.ts
+++ b/apps/meteor/server/methods/addRoomOwner.ts
@@ -7,6 +7,7 @@ import { check } from 'meteor/check';
 import { Meteor } from 'meteor/meteor';
 
 import { hasPermissionAsync } from '../../app/authorization/server/functions/hasPermission';
+import { notifyOnSubscriptionChangedById } from '../../app/lib/server/lib/notifyListener';
 import { settings } from '../../app/settings/server';
 import { isFederationReady, isFederationEnabled, FederationMatrixInvalidConfigurationError } from '../services/federation/utils';
 
@@ -71,7 +72,10 @@ Meteor.methods<ServerMethods>({
 			});
 		}
 
-		await Subscriptions.addRoleById(subscription._id, 'owner');
+		const addRoleResponse = await Subscriptions.addRoleById(subscription._id, 'owner');
+		if (addRoleResponse.modifiedCount) {
+			void notifyOnSubscriptionChangedById(subscription._id);
+		}
 
 		const fromUser = await Users.findOneById(uid);
 		if (!fromUser) {
diff --git a/apps/meteor/server/methods/hideRoom.ts b/apps/meteor/server/methods/hideRoom.ts
index a53a328d549..1fd4c6b4657 100644
--- a/apps/meteor/server/methods/hideRoom.ts
+++ b/apps/meteor/server/methods/hideRoom.ts
@@ -3,6 +3,8 @@ import { Subscriptions } from '@rocket.chat/models';
 import { check } from 'meteor/check';
 import { Meteor } from 'meteor/meteor';
 
+import { notifyOnSubscriptionChangedByRoomIdAndUserId } from '../../app/lib/server/lib/notifyListener';
+
 declare module '@rocket.chat/ddp-client' {
 	// eslint-disable-next-line @typescript-eslint/naming-convention
 	interface ServerMethods {
@@ -19,7 +21,13 @@ export const hideRoomMethod = async (userId: string, rid: string): Promise<numbe
 		});
 	}
 
-	return (await Subscriptions.hideByRoomIdAndUserId(rid, userId)).modifiedCount;
+	const { modifiedCount } = await Subscriptions.hideByRoomIdAndUserId(rid, userId);
+
+	if (modifiedCount) {
+		void notifyOnSubscriptionChangedByRoomIdAndUserId(rid, userId);
+	}
+
+	return modifiedCount;
 };
 
 Meteor.methods<ServerMethods>({
diff --git a/apps/meteor/server/methods/ignoreUser.ts b/apps/meteor/server/methods/ignoreUser.ts
index 358fc3be3d8..a8739a910b3 100644
--- a/apps/meteor/server/methods/ignoreUser.ts
+++ b/apps/meteor/server/methods/ignoreUser.ts
@@ -3,6 +3,8 @@ import { Subscriptions } from '@rocket.chat/models';
 import { check } from 'meteor/check';
 import { Meteor } from 'meteor/meteor';
 
+import { notifyOnSubscriptionChangedById } from '../../app/lib/server/lib/notifyListener';
+
 declare module '@rocket.chat/ddp-client' {
 	// eslint-disable-next-line @typescript-eslint/naming-convention
 	interface ServerMethods {
@@ -23,7 +25,10 @@ Meteor.methods<ServerMethods>({
 			});
 		}
 
-		const subscription = await Subscriptions.findOneByRoomIdAndUserId(rid, userId);
+		const [subscription, subscriptionIgnoredUser] = await Promise.all([
+			Subscriptions.findOneByRoomIdAndUserId(rid, userId),
+			Subscriptions.findOneByRoomIdAndUserId(rid, ignoredUser),
+		]);
 
 		if (!subscription) {
 			throw new Meteor.Error('error-invalid-subscription', 'Invalid subscription', {
@@ -31,14 +36,18 @@ Meteor.methods<ServerMethods>({
 			});
 		}
 
-		const subscriptionIgnoredUser = await Subscriptions.findOneByRoomIdAndUserId(rid, ignoredUser);
-
 		if (!subscriptionIgnoredUser) {
 			throw new Meteor.Error('error-invalid-subscription', 'Invalid subscription', {
 				method: 'ignoreUser',
 			});
 		}
 
-		return !!(await Subscriptions.ignoreUser({ _id: subscription._id, ignoredUser, ignore }));
+		const result = await Subscriptions.ignoreUser({ _id: subscription._id, ignoredUser, ignore });
+
+		if (result.modifiedCount) {
+			void notifyOnSubscriptionChangedById(subscription._id);
+		}
+
+		return !!result;
 	},
 });
diff --git a/apps/meteor/server/methods/openRoom.ts b/apps/meteor/server/methods/openRoom.ts
index b2957768f23..440de52b87f 100644
--- a/apps/meteor/server/methods/openRoom.ts
+++ b/apps/meteor/server/methods/openRoom.ts
@@ -4,6 +4,8 @@ import { Subscriptions } from '@rocket.chat/models';
 import { check } from 'meteor/check';
 import { Meteor } from 'meteor/meteor';
 
+import { notifyOnSubscriptionChangedByRoomIdAndUserId } from '../../app/lib/server/lib/notifyListener';
+
 declare module '@rocket.chat/ddp-client' {
 	// eslint-disable-next-line @typescript-eslint/naming-convention
 	interface ServerMethods {
@@ -23,6 +25,12 @@ Meteor.methods<ServerMethods>({
 			});
 		}
 
-		return (await Subscriptions.openByRoomIdAndUserId(rid, uid)).modifiedCount;
+		const openByRoomResponse = await Subscriptions.openByRoomIdAndUserId(rid, uid);
+
+		if (openByRoomResponse.modifiedCount) {
+			void notifyOnSubscriptionChangedByRoomIdAndUserId(rid, uid);
+		}
+
+		return openByRoomResponse.modifiedCount;
 	},
 });
diff --git a/apps/meteor/server/methods/removeRoomLeader.ts b/apps/meteor/server/methods/removeRoomLeader.ts
index 754d68960a4..8a8f92d08fa 100644
--- a/apps/meteor/server/methods/removeRoomLeader.ts
+++ b/apps/meteor/server/methods/removeRoomLeader.ts
@@ -6,6 +6,7 @@ import { check } from 'meteor/check';
 import { Meteor } from 'meteor/meteor';
 
 import { hasPermissionAsync } from '../../app/authorization/server/functions/hasPermission';
+import { notifyOnSubscriptionChangedById } from '../../app/lib/server/lib/notifyListener';
 import { settings } from '../../app/settings/server';
 
 declare module '@rocket.chat/ddp-client' {
@@ -56,7 +57,10 @@ Meteor.methods<ServerMethods>({
 			});
 		}
 
-		await Subscriptions.removeRoleById(subscription._id, 'leader');
+		const removeRoleResponse = await Subscriptions.removeRoleById(subscription._id, 'leader');
+		if (removeRoleResponse.modifiedCount) {
+			void notifyOnSubscriptionChangedById(subscription._id);
+		}
 
 		const fromUser = await Users.findOneById(uid);
 		if (!fromUser) {
diff --git a/apps/meteor/server/methods/removeRoomModerator.ts b/apps/meteor/server/methods/removeRoomModerator.ts
index 291cc294a5f..bcb50076c83 100644
--- a/apps/meteor/server/methods/removeRoomModerator.ts
+++ b/apps/meteor/server/methods/removeRoomModerator.ts
@@ -7,6 +7,7 @@ import { check } from 'meteor/check';
 import { Meteor } from 'meteor/meteor';
 
 import { hasPermissionAsync } from '../../app/authorization/server/functions/hasPermission';
+import { notifyOnSubscriptionChangedById } from '../../app/lib/server/lib/notifyListener';
 import { settings } from '../../app/settings/server';
 
 declare module '@rocket.chat/ddp-client' {
@@ -64,7 +65,10 @@ Meteor.methods<ServerMethods>({
 			});
 		}
 
-		await Subscriptions.removeRoleById(subscription._id, 'moderator');
+		const removeRoleResponse = await Subscriptions.removeRoleById(subscription._id, 'moderator');
+		if (removeRoleResponse.modifiedCount) {
+			void notifyOnSubscriptionChangedById(subscription._id);
+		}
 
 		const fromUser = await Users.findOneById(uid);
 		if (!fromUser) {
diff --git a/apps/meteor/server/methods/removeRoomOwner.ts b/apps/meteor/server/methods/removeRoomOwner.ts
index 82ee2c37f9b..91046655a4a 100644
--- a/apps/meteor/server/methods/removeRoomOwner.ts
+++ b/apps/meteor/server/methods/removeRoomOwner.ts
@@ -7,6 +7,7 @@ import { Meteor } from 'meteor/meteor';
 
 import { getUsersInRole } from '../../app/authorization/server';
 import { hasPermissionAsync } from '../../app/authorization/server/functions/hasPermission';
+import { notifyOnSubscriptionChangedById } from '../../app/lib/server/lib/notifyListener';
 import { settings } from '../../app/settings/server';
 
 declare module '@rocket.chat/ddp-client' {
@@ -71,7 +72,10 @@ Meteor.methods<ServerMethods>({
 			});
 		}
 
-		await Subscriptions.removeRoleById(subscription._id, 'owner');
+		const removeRoleResponse = await Subscriptions.removeRoleById(subscription._id, 'owner');
+		if (removeRoleResponse.modifiedCount) {
+			void notifyOnSubscriptionChangedById(subscription._id);
+		}
 
 		const fromUser = await Users.findOneById(uid);
 		if (!fromUser) {
diff --git a/apps/meteor/server/methods/removeUserFromRoom.ts b/apps/meteor/server/methods/removeUserFromRoom.ts
index b039beb7ce6..781ffe3a267 100644
--- a/apps/meteor/server/methods/removeUserFromRoom.ts
+++ b/apps/meteor/server/methods/removeUserFromRoom.ts
@@ -9,7 +9,7 @@ import { Meteor } from 'meteor/meteor';
 import { canAccessRoomAsync, getUsersInRole } from '../../app/authorization/server';
 import { hasPermissionAsync } from '../../app/authorization/server/functions/hasPermission';
 import { hasRoleAsync } from '../../app/authorization/server/functions/hasRole';
-import { notifyOnRoomChanged } from '../../app/lib/server/lib/notifyListener';
+import { notifyOnRoomChanged, notifyOnSubscriptionChanged } from '../../app/lib/server/lib/notifyListener';
 import { settings } from '../../app/settings/server';
 import { RoomMemberActions } from '../../definition/IRoomTypeConfig';
 import { callbacks } from '../../lib/callbacks';
@@ -91,7 +91,10 @@ export const removeUserFromRoomMethod = async (fromId: string, data: { rid: stri
 
 	await callbacks.run('beforeRemoveFromRoom', { removedUser, userWhoRemoved: fromUser }, room);
 
-	await Subscriptions.removeByRoomIdAndUserId(data.rid, removedUser._id);
+	const deletedSubscription = await Subscriptions.removeByRoomIdAndUserId(data.rid, removedUser._id);
+	if (deletedSubscription) {
+		void notifyOnSubscriptionChanged(deletedSubscription, 'removed');
+	}
 
 	if (['c', 'p'].includes(room.t) === true) {
 		await removeUserFromRolesAsync(removedUser._id, ['moderator', 'owner'], data.rid);
diff --git a/apps/meteor/server/methods/saveUserPreferences.ts b/apps/meteor/server/methods/saveUserPreferences.ts
index f19e653f7cc..70e3a5cb9ea 100644
--- a/apps/meteor/server/methods/saveUserPreferences.ts
+++ b/apps/meteor/server/methods/saveUserPreferences.ts
@@ -1,11 +1,16 @@
-import type { ThemePreference } from '@rocket.chat/core-typings';
+import type { ISubscription, ThemePreference } from '@rocket.chat/core-typings';
 import type { ServerMethods } from '@rocket.chat/ddp-client';
 import { Subscriptions, Users } from '@rocket.chat/models';
 import type { FontSize } from '@rocket.chat/rest-typings';
 import { Match, check } from 'meteor/check';
 import { Meteor } from 'meteor/meteor';
 
-import { notifyOnUserChange } from '../../app/lib/server/lib/notifyListener';
+import {
+	notifyOnSubscriptionChangedByAutoTranslateAndUserId,
+	notifyOnSubscriptionChangedByUserId,
+	notifyOnSubscriptionChangedByUserPreferences,
+	notifyOnUserChange,
+} from '../../app/lib/server/lib/notifyListener';
 import { settings as rcSettings } from '../../app/settings/server';
 
 type UserPreferences = {
@@ -54,6 +59,31 @@ declare module '@rocket.chat/ddp-client' {
 	}
 }
 
+async function updateNotificationPreferences(
+	userId: ISubscription['u']['_id'],
+	setting: keyof ISubscription,
+	newValue: string,
+	oldValue: string,
+	preferenceType: keyof ISubscription,
+) {
+	if (newValue === oldValue) {
+		return;
+	}
+
+	if (newValue === 'default') {
+		const clearNotificationResponse = await Subscriptions.clearNotificationUserPreferences(userId, setting, preferenceType);
+		if (clearNotificationResponse.modifiedCount) {
+			void notifyOnSubscriptionChangedByUserPreferences(userId, preferenceType, 'user');
+		}
+		return;
+	}
+
+	const updateNotificationResponse = await Subscriptions.updateNotificationUserPreferences(userId, newValue, setting, preferenceType);
+	if (updateNotificationResponse.modifiedCount) {
+		void notifyOnSubscriptionChangedByUserPreferences(userId, preferenceType, 'subscription');
+	}
+}
+
 export const saveUserPreferences = async (settings: Partial<UserPreferences>, userId: string): Promise<void> => {
 	const keys = {
 		language: Match.Optional(String),
@@ -146,51 +176,41 @@ export const saveUserPreferences = async (settings: Partial<UserPreferences>, us
 
 	// propagate changed notification preferences
 	setImmediate(async () => {
-		if (settings.desktopNotifications && oldDesktopNotifications !== settings.desktopNotifications) {
-			if (settings.desktopNotifications === 'default') {
-				await Subscriptions.clearNotificationUserPreferences(user._id, 'desktopNotifications', 'desktopPrefOrigin');
-			} else {
-				await Subscriptions.updateNotificationUserPreferences(
-					user._id,
-					settings.desktopNotifications,
-					'desktopNotifications',
-					'desktopPrefOrigin',
-				);
-			}
+		const { desktopNotifications, pushNotifications, emailNotificationMode, highlights, language } = settings;
+		const promises = [];
+
+		if (desktopNotifications) {
+			promises.push(
+				updateNotificationPreferences(user._id, 'desktopNotifications', desktopNotifications, oldDesktopNotifications, 'desktopPrefOrigin'),
+			);
 		}
 
-		if (settings.pushNotifications && oldMobileNotifications !== settings.pushNotifications) {
-			if (settings.pushNotifications === 'default') {
-				await Subscriptions.clearNotificationUserPreferences(user._id, 'mobilePushNotifications', 'mobilePrefOrigin');
-			} else {
-				await Subscriptions.updateNotificationUserPreferences(
-					user._id,
-					settings.pushNotifications,
-					'mobilePushNotifications',
-					'mobilePrefOrigin',
-				);
-			}
+		if (pushNotifications) {
+			promises.push(
+				updateNotificationPreferences(user._id, 'mobilePushNotifications', pushNotifications, oldMobileNotifications, 'mobilePrefOrigin'),
+			);
 		}
 
-		if (settings.emailNotificationMode && oldEmailNotifications !== settings.emailNotificationMode) {
-			if (settings.emailNotificationMode === 'default') {
-				await Subscriptions.clearNotificationUserPreferences(user._id, 'emailNotifications', 'emailPrefOrigin');
-			} else {
-				await Subscriptions.updateNotificationUserPreferences(
-					user._id,
-					settings.emailNotificationMode,
-					'emailNotifications',
-					'emailPrefOrigin',
-				);
-			}
+		if (emailNotificationMode) {
+			promises.push(
+				updateNotificationPreferences(user._id, 'emailNotifications', emailNotificationMode, oldEmailNotifications, 'emailPrefOrigin'),
+			);
 		}
 
-		if (Array.isArray(settings.highlights)) {
-			await Subscriptions.updateUserHighlights(user._id, settings.highlights);
+		await Promise.allSettled(promises);
+
+		if (Array.isArray(highlights)) {
+			const response = await Subscriptions.updateUserHighlights(user._id, highlights);
+			if (response.modifiedCount) {
+				void notifyOnSubscriptionChangedByUserId(user._id);
+			}
 		}
 
-		if (settings.language && oldLanguage !== settings.language && rcSettings.get('AutoTranslate_AutoEnableOnJoinRoom')) {
-			await Subscriptions.updateAllAutoTranslateLanguagesByUserId(user._id, settings.language);
+		if (language && oldLanguage !== language && rcSettings.get('AutoTranslate_AutoEnableOnJoinRoom')) {
+			const response = await Subscriptions.updateAllAutoTranslateLanguagesByUserId(user._id, language);
+			if (response.modifiedCount) {
+				void notifyOnSubscriptionChangedByAutoTranslateAndUserId(user._id);
+			}
 		}
 	});
 };
diff --git a/apps/meteor/server/methods/toggleFavorite.ts b/apps/meteor/server/methods/toggleFavorite.ts
index 36555a4566d..912b9a8f3e5 100644
--- a/apps/meteor/server/methods/toggleFavorite.ts
+++ b/apps/meteor/server/methods/toggleFavorite.ts
@@ -4,6 +4,8 @@ import { Subscriptions } from '@rocket.chat/models';
 import { Match, check } from 'meteor/check';
 import { Meteor } from 'meteor/meteor';
 
+import { notifyOnSubscriptionChangedByRoomIdAndUserId } from '../../app/lib/server/lib/notifyListener';
+
 declare module '@rocket.chat/ddp-client' {
 	// eslint-disable-next-line @typescript-eslint/naming-convention
 	interface ServerMethods {
@@ -28,6 +30,12 @@ Meteor.methods<ServerMethods>({
 			throw new Meteor.Error('error-invalid-subscription', 'You must be part of a room to favorite it', { method: 'toggleFavorite' });
 		}
 
-		return (await Subscriptions.setFavoriteByRoomIdAndUserId(rid, userId, f)).modifiedCount;
+		const { modifiedCount } = await Subscriptions.setFavoriteByRoomIdAndUserId(rid, userId, f);
+
+		if (modifiedCount) {
+			void notifyOnSubscriptionChangedByRoomIdAndUserId(rid, userId);
+		}
+
+		return modifiedCount;
 	},
 });
diff --git a/apps/meteor/server/models/dummy/BaseDummy.ts b/apps/meteor/server/models/dummy/BaseDummy.ts
index c3052ede948..049295c1a28 100644
--- a/apps/meteor/server/models/dummy/BaseDummy.ts
+++ b/apps/meteor/server/models/dummy/BaseDummy.ts
@@ -53,6 +53,13 @@ export class BaseDummy<
 		return this.collectionName;
 	}
 
+	async findOneAndDelete(): Promise<ModifyResult<T>> {
+		return {
+			value: null,
+			ok: 1,
+		};
+	}
+
 	async findOneAndUpdate(): Promise<ModifyResult<T>> {
 		return {
 			value: null,
diff --git a/apps/meteor/server/models/raw/BaseRaw.ts b/apps/meteor/server/models/raw/BaseRaw.ts
index 1a3dd1a3eb4..fae43f2dbfb 100644
--- a/apps/meteor/server/models/raw/BaseRaw.ts
+++ b/apps/meteor/server/models/raw/BaseRaw.ts
@@ -26,6 +26,7 @@ import type {
 	InsertOneResult,
 	DeleteResult,
 	DeleteOptions,
+	FindOneAndDeleteOptions,
 } from 'mongodb';
 
 import { setUpdatedAt } from './setUpdatedAt';
@@ -315,7 +316,38 @@ export abstract class BaseRaw<
 		return this.col.deleteOne(filter);
 	}
 
-	async deleteMany(filter: Filter<T>, options?: DeleteOptions): Promise<DeleteResult> {
+	async findOneAndDelete(filter: Filter<T>, options?: FindOneAndDeleteOptions): Promise<ModifyResult<T>> {
+		if (!this.trash) {
+			if (options) {
+				return this.col.findOneAndDelete(filter, options);
+			}
+			return this.col.findOneAndDelete(filter);
+		}
+
+		const result = await this.col.findOneAndDelete(filter);
+
+		const { value: doc } = result;
+		if (!doc) {
+			return result;
+		}
+
+		const { _id, ...record } = doc;
+
+		const trash: TDeleted = {
+			...record,
+			_deletedAt: new Date(),
+			__collection__: this.name,
+		} as unknown as TDeleted;
+
+		// since the operation is not atomic, we need to make sure that the record is not already deleted/inserted
+		await this.trash?.updateOne({ _id } as Filter<TDeleted>, { $set: trash } as UpdateFilter<TDeleted>, {
+			upsert: true,
+		});
+
+		return result;
+	}
+
+	async deleteMany(filter: Filter<T>, options?: DeleteOptions & { onTrash?: (record: ResultFields<T, C>) => void }): Promise<DeleteResult> {
 		if (!this.trash) {
 			if (options) {
 				return this.col.deleteMany(filter, options);
@@ -341,6 +373,8 @@ export abstract class BaseRaw<
 			await this.trash?.updateOne({ _id } as Filter<TDeleted>, { $set: trash } as UpdateFilter<TDeleted>, {
 				upsert: true,
 			});
+
+			void options?.onTrash?.(doc);
 		}
 
 		if (options) {
diff --git a/apps/meteor/server/models/raw/Roles.ts b/apps/meteor/server/models/raw/Roles.ts
index 84a5b088ea3..4e1cb09348c 100644
--- a/apps/meteor/server/models/raw/Roles.ts
+++ b/apps/meteor/server/models/raw/Roles.ts
@@ -3,6 +3,7 @@ import type { IRolesModel } from '@rocket.chat/model-typings';
 import { Subscriptions, Users } from '@rocket.chat/models';
 import type { Collection, FindCursor, Db, Filter, FindOptions, Document } from 'mongodb';
 
+import { notifyOnSubscriptionChangedByRoomIdAndUserId } from '../../../app/lib/server/lib/notifyListener';
 import { BaseRaw } from './BaseRaw';
 
 export class RolesRaw extends BaseRaw<IRole> implements IRolesModel {
@@ -35,14 +36,15 @@ export class RolesRaw extends BaseRaw<IRole> implements IRolesModel {
 				process.env.NODE_ENV === 'development' && console.warn(`[WARN] RolesRaw.addUserRoles: role: ${roleId} not found`);
 				continue;
 			}
-			switch (role.scope) {
-				case 'Subscriptions':
-					// TODO remove dependency from other models - this logic should be inside a function/service
-					await Subscriptions.addRolesByUserId(userId, [role._id], scope);
-					break;
-				case 'Users':
-				default:
-					await Users.addRolesByUserId(userId, [role._id]);
+
+			if (role.scope === 'Subscriptions' && scope) {
+				// TODO remove dependency from other models - this logic should be inside a function/service
+				const addRolesResponse = await Subscriptions.addRolesByUserId(userId, [role._id], scope);
+				if (addRolesResponse.modifiedCount) {
+					void notifyOnSubscriptionChangedByRoomIdAndUserId(scope, userId);
+				}
+			} else {
+				await Users.addRolesByUserId(userId, [role._id]);
 			}
 		}
 		return true;
@@ -88,13 +90,13 @@ export class RolesRaw extends BaseRaw<IRole> implements IRolesModel {
 				continue;
 			}
 
-			switch (role.scope) {
-				case 'Subscriptions':
-					scope && (await Subscriptions.removeRolesByUserId(userId, [roleId], scope));
-					break;
-				case 'Users':
-				default:
-					await Users.removeRolesByUserId(userId, [roleId]);
+			if (role.scope === 'Subscriptions' && scope) {
+				const removeRolesResponse = await Subscriptions.removeRolesByUserId(userId, [roleId], scope);
+				if (removeRolesResponse.modifiedCount) {
+					void notifyOnSubscriptionChangedByRoomIdAndUserId(scope, userId);
+				}
+			} else {
+				await Users.removeRolesByUserId(userId, [roleId]);
 			}
 		}
 		return true;
diff --git a/apps/meteor/server/models/raw/Subscriptions.ts b/apps/meteor/server/models/raw/Subscriptions.ts
index 01440a179c7..efb75ed3d17 100644
--- a/apps/meteor/server/models/raw/Subscriptions.ts
+++ b/apps/meteor/server/models/raw/Subscriptions.ts
@@ -191,7 +191,7 @@ export class SubscriptionsRaw extends BaseRaw<ISubscription> implements ISubscri
 		readThreads = false,
 		alert = false,
 		options: FindOptions<ISubscription> = {},
-	): ReturnType<BaseRaw<ISubscription>['update']> {
+	): ReturnType<BaseRaw<ISubscription>['updateOne']> {
 		const query: Filter<ISubscription> = {
 			rid,
 			'u._id': uid,
@@ -327,20 +327,62 @@ export class SubscriptionsRaw extends BaseRaw<ISubscription> implements ISubscri
 		return this.find(query, options || {});
 	}
 
-	async removeByRoomId(roomId: string): Promise<number> {
+	findByRoomIdAndNotAlertOrOpenExcludingUserIds(
+		{
+			roomId,
+			uidsExclude,
+			uidsInclude,
+			onlyRead,
+		}: {
+			roomId: ISubscription['rid'];
+			uidsExclude?: ISubscription['u']['_id'][];
+			uidsInclude?: ISubscription['u']['_id'][];
+			onlyRead: boolean;
+		},
+		options?: FindOptions<ISubscription>,
+	) {
 		const query = {
 			rid: roomId,
+			...(uidsExclude?.length && {
+				'u._id': { $nin: uidsExclude },
+			}),
+			...(onlyRead && {
+				$or: [...(uidsInclude?.length ? [{ 'u._id': { $in: uidsInclude } }] : []), { alert: { $ne: true } }, { open: { $ne: true } }],
+			}),
 		};
 
-		const result = (await this.deleteMany(query)).deletedCount;
+		return this.find(query, options || {});
+	}
 
-		if (typeof result === 'number' && result > 0) {
-			await Rooms.incUsersCountByIds([roomId], -result);
+	async removeByRoomId(roomId: ISubscription['rid'], options?: { onTrash: (doc: ISubscription) => void }): Promise<DeleteResult> {
+		const query = {
+			rid: roomId,
+		};
+
+		const deleteResult = await this.deleteMany(query, options);
+
+		if (deleteResult?.deletedCount) {
+			await Rooms.incUsersCountByIds([roomId], -deleteResult.deletedCount);
 		}
 
 		await Users.removeRoomByRoomId(roomId);
 
-		return result;
+		return deleteResult;
+	}
+
+	findByRoomIdExcludingUserIds(
+		roomId: ISubscription['rid'],
+		userIds: ISubscription['u']['_id'][],
+		options: FindOptions<ISubscription> = {},
+	): FindCursor<ISubscription> {
+		const query = {
+			'rid': roomId,
+			'u._id': {
+				$nin: userIds,
+			},
+		};
+
+		return this.find(query, options);
 	}
 
 	async findConnectedUsersExcept(
@@ -532,11 +574,10 @@ export class SubscriptionsRaw extends BaseRaw<ISubscription> implements ISubscri
 		return this.updateMany(query, update);
 	}
 
-	async setGroupE2EKey(_id: string, key: string): Promise<ISubscription | null> {
+	async setGroupE2EKey(_id: string, key: string): Promise<UpdateResult> {
 		const query = { _id };
 		const update = { $set: { E2EKey: key } };
-		await this.updateOne(query, update);
-		return this.findOneById(_id);
+		return this.updateOne(query, update);
 	}
 
 	setGroupE2ESuggestedKey(uid: string, rid: string, key: string): Promise<UpdateResult> {
@@ -558,18 +599,12 @@ export class SubscriptionsRaw extends BaseRaw<ISubscription> implements ISubscri
 		return this.updateOne({ rid }, { $unset: { onHold: 1 } });
 	}
 
-	findByRoomIds(roomIds: string[]): FindCursor<ISubscription> {
+	findByRoomIds(roomIds: ISubscription['u']['_id'][], options?: FindOptions<ISubscription>): FindCursor<ISubscription> {
 		const query = {
 			rid: {
 				$in: roomIds,
 			},
 		};
-		const options = {
-			projection: {
-				'u._id': 1,
-				'rid': 1,
-			},
-		};
 
 		return this.find(query, options);
 	}
@@ -582,6 +617,14 @@ export class SubscriptionsRaw extends BaseRaw<ISubscription> implements ISubscri
 		return this.deleteMany(query);
 	}
 
+	findByToken(token: string, options?: FindOptions): FindCursor<ISubscription> {
+		const query = {
+			'v.token': token,
+		};
+
+		return this.find<ISubscription>(query, options);
+	}
+
 	updateAutoTranslateById(_id: string, autoTranslate: boolean): Promise<UpdateResult> {
 		const query = {
 			_id,
@@ -620,6 +663,19 @@ export class SubscriptionsRaw extends BaseRaw<ISubscription> implements ISubscri
 		return this.updateMany(query, update);
 	}
 
+	findByAutoTranslateAndUserId(
+		userId: ISubscription['u']['_id'],
+		autoTranslate: ISubscription['autoTranslate'] = true,
+		options?: FindOptions<ISubscription>,
+	): FindCursor<ISubscription> {
+		const query = {
+			'u._id': userId,
+			autoTranslate,
+		};
+
+		return this.find(query, options);
+	}
+
 	disableAutoTranslateByRoomId(roomId: IRoom['_id']): Promise<UpdateResult | Document> {
 		const query = {
 			rid: roomId,
@@ -1092,7 +1148,11 @@ export class SubscriptionsRaw extends BaseRaw<ISubscription> implements ISubscri
 		return subscription?.ls;
 	}
 
-	findByRoomIdAndUserIds(roomId: string, userIds: string[], options?: FindOptions<ISubscription>): FindCursor<ISubscription> {
+	findByRoomIdAndUserIds(
+		roomId: ISubscription['rid'],
+		userIds: ISubscription['u']['_id'][],
+		options?: FindOptions<ISubscription>,
+	): FindCursor<ISubscription> {
 		const query = {
 			'rid': roomId,
 			'u._id': {
@@ -1230,6 +1290,33 @@ export class SubscriptionsRaw extends BaseRaw<ISubscription> implements ISubscri
 		return this.updateMany(query, update);
 	}
 
+	findByUserIdAndRoomType(
+		userId: ISubscription['u']['_id'],
+		type: ISubscription['t'],
+		options?: FindOptions<ISubscription>,
+	): FindCursor<ISubscription> {
+		const query = {
+			'u._id': userId,
+			't': type,
+		};
+
+		return this.find(query, options);
+	}
+
+	findByNameAndRoomType(
+		filter: Partial<Pick<ISubscription, 'name' | 't'>>,
+		options?: FindOptions<ISubscription>,
+	): FindCursor<ISubscription> {
+		if (!filter.name && !filter.t) {
+			throw new Error('invalid filter');
+		}
+		const query: Filter<ISubscription> = {
+			...(filter.name && { name: filter.name }),
+			...(filter.t && { t: filter.t }),
+		};
+		return this.find(query, options);
+	}
+
 	setFavoriteByRoomIdAndUserId(roomId: string, userId: string, favorite?: boolean): Promise<UpdateResult> {
 		if (favorite == null) {
 			favorite = true;
@@ -1411,7 +1498,7 @@ export class SubscriptionsRaw extends BaseRaw<ISubscription> implements ISubscri
 		return this.updateOne(query, update);
 	}
 
-	setAlertForRoomIdAndUserIds(roomId: string, uids: string[]): Promise<UpdateResult | Document> {
+	setAlertForRoomIdAndUserIds(roomId: ISubscription['rid'], uids: ISubscription['u']['_id'][]): Promise<UpdateResult | Document> {
 		const query = {
 			'rid': roomId,
 			'u._id': { $in: uids },
@@ -1423,6 +1510,7 @@ export class SubscriptionsRaw extends BaseRaw<ISubscription> implements ISubscri
 				alert: true,
 			},
 		};
+
 		return this.updateMany(query, update);
 	}
 
@@ -1621,6 +1709,22 @@ export class SubscriptionsRaw extends BaseRaw<ISubscription> implements ISubscri
 		return this.updateMany(query, update);
 	}
 
+	findByUserPreferences(
+		userId: string,
+		notificationOriginField: keyof ISubscription,
+		notificationOriginValue: 'user' | 'subscription',
+		options?: FindOptions<ISubscription>,
+	): FindCursor<ISubscription> {
+		const value = notificationOriginValue === 'user' ? 'user' : { $ne: 'subscription' };
+
+		const query: Filter<ISubscription> = {
+			'u._id': userId,
+			[notificationOriginField]: value,
+		};
+
+		return this.find(query, options);
+	}
+
 	updateUserHighlights(userId: string, userHighlights: any): Promise<UpdateResult | Document> {
 		const query: Filter<ISubscription> = {
 			'u._id': userId,
@@ -1722,9 +1826,7 @@ export class SubscriptionsRaw extends BaseRaw<ISubscription> implements ISubscri
 		}));
 
 		// @ts-expect-error - types not good :(
-		const result = await this.insertMany(subscriptions);
-
-		return result;
+		return this.insertMany(subscriptions);
 	}
 
 	// REMOVE
@@ -1746,25 +1848,25 @@ export class SubscriptionsRaw extends BaseRaw<ISubscription> implements ISubscri
 		return result;
 	}
 
-	async removeByRoomIdAndUserId(roomId: string, userId: string): Promise<number> {
+	async removeByRoomIdAndUserId(roomId: string, userId: string): Promise<ISubscription | null> {
 		const query = {
 			'rid': roomId,
 			'u._id': userId,
 		};
 
-		const result = (await this.deleteMany(query)).deletedCount;
+		const { value: doc } = await this.findOneAndDelete(query);
 
-		if (typeof result === 'number' && result > 0) {
-			await Rooms.incUsersCountById(roomId, -result);
+		if (doc) {
+			await Rooms.incUsersCountById(roomId, -1);
 		}
 
 		await Users.removeRoomByUserId(userId, roomId);
 
-		return result;
+		return doc;
 	}
 
-	async removeByRoomIds(rids: string[]): Promise<DeleteResult> {
-		const result = await this.deleteMany({ rid: { $in: rids } });
+	async removeByRoomIds(rids: string[], options?: { onTrash: (doc: ISubscription) => void }): Promise<DeleteResult> {
+		const result = await this.deleteMany({ rid: { $in: rids } }, options);
 
 		await Users.removeRoomByRoomIds(rids);
 
@@ -1850,6 +1952,19 @@ export class SubscriptionsRaw extends BaseRaw<ISubscription> implements ISubscri
 		return this.updateMany(query, update);
 	}
 
+	findUnreadThreadsByRoomId(
+		rid: ISubscription['rid'],
+		tunread: ISubscription['tunread'],
+		options?: FindOptions<ISubscription>,
+	): FindCursor<ISubscription> {
+		const query = {
+			rid,
+			tunread: { $in: tunread },
+		};
+
+		return this.find(query, options);
+	}
+
 	openByRoomIdAndUserId(roomId: string, userId: string): Promise<UpdateResult> {
 		const query = {
 			'rid': roomId,
diff --git a/apps/meteor/server/services/federation/infrastructure/rocket-chat/adapters/Room.ts b/apps/meteor/server/services/federation/infrastructure/rocket-chat/adapters/Room.ts
index 91f5a6e66b4..9deabb53006 100644
--- a/apps/meteor/server/services/federation/infrastructure/rocket-chat/adapters/Room.ts
+++ b/apps/meteor/server/services/federation/infrastructure/rocket-chat/adapters/Room.ts
@@ -7,6 +7,12 @@ import { saveRoomTopic } from '../../../../../../app/channel-settings/server';
 import { addUserToRoom } from '../../../../../../app/lib/server/functions/addUserToRoom';
 import { createRoom } from '../../../../../../app/lib/server/functions/createRoom';
 import { removeUserFromRoom } from '../../../../../../app/lib/server/functions/removeUserFromRoom';
+import {
+	notifyOnSubscriptionChanged,
+	notifyOnSubscriptionChangedById,
+	notifyOnSubscriptionChangedByRoomId,
+	notifyOnSubscriptionChangedByRoomIdAndUserId,
+} from '../../../../../../app/lib/server/lib/notifyListener';
 import { settings } from '../../../../../../app/settings/server';
 import { getDefaultSubscriptionPref } from '../../../../../../app/utils/lib/getDefaultSubscriptionPref';
 import { getValidRoomName } from '../../../../../../app/utils/server/lib/getValidRoomName';
@@ -78,9 +84,16 @@ export class RocketChatRoomAdapter {
 
 	public async removeDirectMessageRoom(federatedRoom: FederatedRoom): Promise<void> {
 		const roomId = federatedRoom.getInternalId();
-		await Rooms.removeById(roomId);
-		await Subscriptions.removeByRoomId(roomId);
-		await MatrixBridgedRoom.removeByLocalRoomId(roomId);
+
+		await Promise.all([
+			Rooms.removeById(roomId),
+			Subscriptions.removeByRoomId(roomId, {
+				async onTrash(doc) {
+					void notifyOnSubscriptionChanged(doc, 'removed');
+				},
+			}),
+			MatrixBridgedRoom.removeByLocalRoomId(roomId),
+		]);
 	}
 
 	public async createFederatedRoomForDirectMessage(federatedRoom: DirectMessageFederatedRoom): Promise<string> {
@@ -160,10 +173,13 @@ export class RocketChatRoomAdapter {
 					}
 
 					const user = federatedUser.getInternalReference();
-					return Subscriptions.createWithRoomAndUser(room, user, {
+					const { insertedId } = await Subscriptions.createWithRoomAndUser(room, user, {
 						ts: new Date(),
 						...getDefaultSubscriptionPref(user),
 					});
+					if (insertedId) {
+						void notifyOnSubscriptionChangedById(insertedId, 'inserted');
+					}
 				})
 				.filter(Boolean),
 		);
@@ -182,33 +198,37 @@ export class RocketChatRoomAdapter {
 	}
 
 	public async updateRoomType(federatedRoom: FederatedRoom): Promise<void> {
-		await Rooms.setRoomTypeById(federatedRoom.getInternalId(), federatedRoom.getRoomType());
-		await Subscriptions.updateAllRoomTypesByRoomId(federatedRoom.getRoomType(), federatedRoom.getRoomType());
+		const rid = federatedRoom.getInternalId();
+		const roomType = federatedRoom.getRoomType();
+
+		await Rooms.setRoomTypeById(rid, roomType);
+		await Subscriptions.updateAllRoomTypesByRoomId(rid, roomType);
+
+		void notifyOnSubscriptionChangedByRoomId(rid);
 	}
 
 	public async updateDisplayRoomName(federatedRoom: FederatedRoom, federatedUser: FederatedUser): Promise<void> {
-		await Rooms.setFnameById(federatedRoom.getInternalId(), federatedRoom.getDisplayName());
-		await Subscriptions.updateNameAndFnameByRoomId(
-			federatedRoom.getInternalId(),
-			federatedRoom.getName() || '',
-			federatedRoom.getDisplayName() || '',
-		);
+		const rid = federatedRoom.getInternalId();
+		const roomName = federatedRoom.getName() || '';
+		const displayName = federatedRoom.getDisplayName() || '';
+		const internalReference = federatedUser.getInternalReference();
 
-		await Message.saveSystemMessage(
-			'r',
-			federatedRoom.getInternalId(),
-			federatedRoom.getDisplayName() || '',
-			federatedUser.getInternalReference() as unknown as Required<IUser>, // TODO fix type
-		);
+		await Rooms.setFnameById(rid, displayName);
+		await Subscriptions.updateNameAndFnameByRoomId(rid, roomName, displayName);
+		await Message.saveSystemMessage('r', rid, displayName, internalReference);
+
+		void notifyOnSubscriptionChangedByRoomId(rid);
 	}
 
 	public async updateRoomName(federatedRoom: FederatedRoom): Promise<void> {
-		await Rooms.setRoomNameById(federatedRoom.getInternalId(), federatedRoom.getName());
-		await Subscriptions.updateNameAndFnameByRoomId(
-			federatedRoom.getInternalId(),
-			federatedRoom.getName() || '',
-			federatedRoom.getDisplayName() || '',
-		);
+		const rid = federatedRoom.getInternalId();
+		const roomName = federatedRoom.getName() || '';
+		const displayName = federatedRoom.getDisplayName() || '';
+
+		await Rooms.setRoomNameById(rid, roomName);
+		await Subscriptions.updateNameAndFnameByRoomId(rid, roomName, displayName);
+
+		void notifyOnSubscriptionChangedByRoomId(rid);
 	}
 
 	public async updateRoomTopic(federatedRoom: FederatedRoom, federatedUser: FederatedUser): Promise<void> {
@@ -262,12 +282,15 @@ export class RocketChatRoomAdapter {
 		rolesToRemove: ROCKET_CHAT_FEDERATION_ROLES[];
 		notifyChannel: boolean;
 	}): Promise<void> {
-		const subscription = await Subscriptions.findOneByRoomIdAndUserId(federatedRoom.getInternalId(), targetFederatedUser.getInternalId(), {
-			projection: { roles: 1 },
-		});
+		const uid = targetFederatedUser.getInternalId();
+		const rid = federatedRoom.getInternalId();
+
+		const subscription = await Subscriptions.findOneByRoomIdAndUserId(rid, uid, { projection: { roles: 1 } });
+
 		if (!subscription) {
 			return;
 		}
+
 		const { roles: currentRoles = [] } = subscription;
 		const toAdd = rolesToAdd.filter((role) => !currentRoles.includes(role));
 		const toRemove = rolesToRemove.filter((role) => currentRoles.includes(role));
@@ -275,14 +298,19 @@ export class RocketChatRoomAdapter {
 			_id: fromUser.getInternalId(),
 			username: fromUser.getUsername(),
 		};
+
 		if (toAdd.length > 0) {
-			await Subscriptions.addRolesByUserId(targetFederatedUser.getInternalId(), toAdd, federatedRoom.getInternalId());
+			const addRolesResponse = await Subscriptions.addRolesByUserId(uid, toAdd, rid);
+			if (addRolesResponse.modifiedCount) {
+				void notifyOnSubscriptionChangedByRoomIdAndUserId(rid, uid);
+			}
+
 			if (notifyChannel) {
 				await Promise.all(
 					toAdd.map((role) =>
 						Message.saveSystemMessage(
 							'subscription-role-added',
-							federatedRoom.getInternalId(),
+							rid,
 							targetFederatedUser.getInternalReference().username || '',
 							whoDidTheChange,
 							{ role },
@@ -291,14 +319,19 @@ export class RocketChatRoomAdapter {
 				);
 			}
 		}
+
 		if (toRemove.length > 0) {
-			await Subscriptions.removeRolesByUserId(targetFederatedUser.getInternalId(), toRemove, federatedRoom.getInternalId());
+			const removeRolesResponse = await Subscriptions.removeRolesByUserId(uid, toRemove, rid);
+			if (removeRolesResponse.modifiedCount) {
+				void notifyOnSubscriptionChangedByRoomIdAndUserId(rid, uid);
+			}
+
 			if (notifyChannel) {
 				await Promise.all(
 					toRemove.map((role) =>
 						Message.saveSystemMessage(
 							'subscription-role-removed',
-							federatedRoom.getInternalId(),
+							rid,
 							targetFederatedUser.getInternalReference().username || '',
 							whoDidTheChange,
 							{ role },
@@ -307,6 +340,7 @@ export class RocketChatRoomAdapter {
 				);
 			}
 		}
+
 		if (settings.get('UI_DisplayRoles')) {
 			this.notifyUIAboutRoomRolesChange(targetFederatedUser, federatedRoom, toAdd, toRemove);
 		}
diff --git a/apps/meteor/server/services/team/service.ts b/apps/meteor/server/services/team/service.ts
index bc4211322b6..5ccf32ef779 100644
--- a/apps/meteor/server/services/team/service.ts
+++ b/apps/meteor/server/services/team/service.ts
@@ -32,6 +32,7 @@ import { addUserToRoom } from '../../../app/lib/server/functions/addUserToRoom';
 import { checkUsernameAvailability } from '../../../app/lib/server/functions/checkUsernameAvailability';
 import { getSubscribedRoomsForUserWithDetails } from '../../../app/lib/server/functions/getRoomsWithSingleOwner';
 import { removeUserFromRoom } from '../../../app/lib/server/functions/removeUserFromRoom';
+import { notifyOnSubscriptionChangedByRoomIdAndUserId } from '../../../app/lib/server/lib/notifyListener';
 import { settings } from '../../../app/settings/server';
 
 export class TeamService extends ServiceClassInternal implements ITeamService {
@@ -745,7 +746,7 @@ export class TeamService extends ServiceClassInternal implements ITeamService {
 			throw new Error('invalid-team');
 		}
 
-		await Promise.all([
+		const responses = await Promise.all([
 			TeamMember.updateOneByUserIdAndTeamId(member.userId, teamId, memberUpdate),
 			Subscriptions.updateOne(
 				{
@@ -757,6 +758,10 @@ export class TeamService extends ServiceClassInternal implements ITeamService {
 				},
 			),
 		]);
+
+		if (responses[1].modifiedCount) {
+			void notifyOnSubscriptionChangedByRoomIdAndUserId(team.roomId, member.userId);
+		}
 	}
 
 	async removeMember(teamId: string, userId: string): Promise<void> {
diff --git a/apps/meteor/tests/unit/app/lib/server/functions/closeLivechatRoom.tests.ts b/apps/meteor/tests/unit/app/lib/server/functions/closeLivechatRoom.tests.ts
index 07ee437832d..b40b971128b 100644
--- a/apps/meteor/tests/unit/app/lib/server/functions/closeLivechatRoom.tests.ts
+++ b/apps/meteor/tests/unit/app/lib/server/functions/closeLivechatRoom.tests.ts
@@ -73,7 +73,7 @@ describe('closeLivechatRoom', () => {
 
 	it('should not perform any operation when a closed room with no subscriptions is provided and the caller is not subscribed to it', async () => {
 		livechatRoomsStub.findOneById.resolves({ ...room, open: false });
-		subscriptionsStub.countByRoomId.resolves(0);
+		subscriptionsStub.removeByRoomId.resolves({ deletedCount: 0 });
 		subscriptionsStub.findOneByRoomIdAndUserId.resolves(null);
 		hasPermissionStub.resolves(true);
 
@@ -81,13 +81,12 @@ describe('closeLivechatRoom', () => {
 		expect(livechatStub.closeRoom.notCalled).to.be.true;
 		expect(livechatRoomsStub.findOneById.calledOnceWith(room._id)).to.be.true;
 		expect(subscriptionsStub.findOneByRoomIdAndUserId.notCalled).to.be.true;
-		expect(subscriptionsStub.countByRoomId.calledOnceWith(room._id)).to.be.true;
-		expect(subscriptionsStub.removeByRoomId.notCalled).to.be.true;
+		expect(subscriptionsStub.removeByRoomId.calledOnceWith(room._id)).to.be.true;
 	});
 
 	it('should remove dangling subscription when a closed room with subscriptions is provided and the caller is not subscribed to it', async () => {
 		livechatRoomsStub.findOneById.resolves({ ...room, open: false });
-		subscriptionsStub.countByRoomId.resolves(1);
+		subscriptionsStub.removeByRoomId.resolves({ deletedCount: 1 });
 		subscriptionsStub.findOneByRoomIdAndUserId.resolves(null);
 		hasPermissionStub.resolves(true);
 
@@ -95,28 +94,25 @@ describe('closeLivechatRoom', () => {
 		expect(livechatStub.closeRoom.notCalled).to.be.true;
 		expect(livechatRoomsStub.findOneById.calledOnceWith(room._id)).to.be.true;
 		expect(subscriptionsStub.findOneByRoomIdAndUserId.notCalled).to.be.true;
-		expect(subscriptionsStub.countByRoomId.calledOnceWith(room._id)).to.be.true;
 		expect(subscriptionsStub.removeByRoomId.calledOnceWith(room._id)).to.be.true;
 	});
 
 	it('should remove dangling subscription when a closed room is provided but the user is still subscribed to it', async () => {
 		livechatRoomsStub.findOneById.resolves({ ...room, open: false });
 		subscriptionsStub.findOneByRoomIdAndUserId.resolves(subscription);
-		subscriptionsStub.countByRoomId.resolves(1);
+		subscriptionsStub.removeByRoomId.resolves({ deletedCount: 1 });
 		hasPermissionStub.resolves(true);
 
 		await closeLivechatRoom(user, room._id, {});
 		expect(livechatStub.closeRoom.notCalled).to.be.true;
 		expect(livechatRoomsStub.findOneById.calledOnceWith(room._id)).to.be.true;
 		expect(subscriptionsStub.findOneByRoomIdAndUserId.notCalled).to.be.true;
-		expect(subscriptionsStub.countByRoomId.calledOnceWith(room._id)).to.be.true;
 		expect(subscriptionsStub.removeByRoomId.calledOnceWith(room._id)).to.be.true;
 	});
 
 	it('should not perform any operation when the caller is not subscribed to an open room and does not have the permission to close others rooms', async () => {
 		livechatRoomsStub.findOneById.resolves(room);
 		subscriptionsStub.findOneByRoomIdAndUserId.resolves(null);
-		subscriptionsStub.countByRoomId.resolves(1);
 		hasPermissionStub.resolves(false);
 
 		await expect(closeLivechatRoom(user, room._id, {})).to.be.rejectedWith('error-not-authorized');
@@ -129,7 +125,6 @@ describe('closeLivechatRoom', () => {
 	it('should close the room when the caller is not subscribed to it but has the permission to close others rooms', async () => {
 		livechatRoomsStub.findOneById.resolves(room);
 		subscriptionsStub.findOneByRoomIdAndUserId.resolves(null);
-		subscriptionsStub.countByRoomId.resolves(1);
 		hasPermissionStub.resolves(true);
 
 		await closeLivechatRoom(user, room._id, {});
@@ -142,7 +137,6 @@ describe('closeLivechatRoom', () => {
 	it('should close the room when the caller is subscribed to it and does not have the permission to close others rooms', async () => {
 		livechatRoomsStub.findOneById.resolves(room);
 		subscriptionsStub.findOneByRoomIdAndUserId.resolves(subscription);
-		subscriptionsStub.countByRoomId.resolves(1);
 		hasPermissionStub.resolves(false);
 
 		await closeLivechatRoom(user, room._id, {});
diff --git a/packages/model-typings/src/models/IBaseModel.ts b/packages/model-typings/src/models/IBaseModel.ts
index 246c3ae253d..626f91385a0 100644
--- a/packages/model-typings/src/models/IBaseModel.ts
+++ b/packages/model-typings/src/models/IBaseModel.ts
@@ -9,6 +9,7 @@ import type {
 	EnhancedOmit,
 	Filter,
 	FindCursor,
+	FindOneAndDeleteOptions,
 	FindOneAndUpdateOptions,
 	FindOptions,
 	InsertManyResult,
@@ -53,6 +54,7 @@ export interface IBaseModel<
 	getUpdater(): Updater<T>;
 	updateFromUpdater(query: Filter<T>, updater: Updater<T>): Promise<UpdateResult>;
 
+	findOneAndDelete(filter: Filter<T>, options?: FindOneAndDeleteOptions): Promise<ModifyResult<T>>;
 	findOneAndUpdate(query: Filter<T>, update: UpdateFilter<T> | T, options?: FindOneAndUpdateOptions): Promise<ModifyResult<T>>;
 
 	findOneById(_id: T['_id'], options?: FindOptions<T> | undefined): Promise<T | null>;
@@ -93,7 +95,7 @@ export interface IBaseModel<
 
 	deleteOne(filter: Filter<T>, options?: DeleteOptions & { bypassDocumentValidation?: boolean }): Promise<DeleteResult>;
 
-	deleteMany(filter: Filter<T>, options?: DeleteOptions): Promise<DeleteResult>;
+	deleteMany(filter: Filter<T>, options?: DeleteOptions & { onTrash?: (record: ResultFields<T, C>) => void }): Promise<DeleteResult>;
 
 	// Trash
 	trashFind<P extends TDeleted>(
diff --git a/packages/model-typings/src/models/ISubscriptionsModel.ts b/packages/model-typings/src/models/ISubscriptionsModel.ts
index 91398e77ebe..3703996898f 100644
--- a/packages/model-typings/src/models/ISubscriptionsModel.ts
+++ b/packages/model-typings/src/models/ISubscriptionsModel.ts
@@ -41,7 +41,7 @@ export interface ISubscriptionsModel extends IBaseModel<ISubscription> {
 		readThreads?: boolean,
 		alert?: boolean,
 		options?: FindOptions<ISubscription>,
-	): ReturnType<IBaseModel<ISubscription>['update']>;
+	): ReturnType<IBaseModel<ISubscription>['updateOne']>;
 
 	removeRolesByUserId(uid: IUser['_id'], roles: IRole['_id'][], rid: IRoom['_id']): Promise<UpdateResult>;
 
@@ -73,7 +73,23 @@ export interface ISubscriptionsModel extends IBaseModel<ISubscription> {
 
 	findByUserIdAndTypes(userId: string, types: ISubscription['t'][], options?: FindOptions<ISubscription>): FindCursor<ISubscription>;
 
-	removeByRoomId(roomId: string): Promise<number>;
+	findByRoomIdAndNotAlertOrOpenExcludingUserIds(
+		filter: {
+			roomId: ISubscription['rid'];
+			uidsExclude?: ISubscription['u']['_id'][];
+			uidsInclude?: ISubscription['u']['_id'][];
+			onlyRead: boolean;
+		},
+		options?: FindOptions<ISubscription>,
+	): FindCursor<ISubscription>;
+
+	removeByRoomId(roomId: ISubscription['rid'], options?: { onTrash: (doc: ISubscription) => void }): Promise<DeleteResult>;
+
+	findByRoomIdExcludingUserIds(
+		roomId: ISubscription['rid'],
+		userIds: ISubscription['u']['_id'][],
+		options?: FindOptions<ISubscription>,
+	): FindCursor<ISubscription>;
 
 	findConnectedUsersExcept(
 		userId: string,
@@ -95,7 +111,7 @@ export interface ISubscriptionsModel extends IBaseModel<ISubscription> {
 
 	updateNameAndFnameByRoomId(roomId: string, name: string, fname: string): Promise<UpdateResult | Document>;
 
-	setGroupE2EKey(_id: string, key: string): Promise<ISubscription | null>;
+	setGroupE2EKey(_id: string, key: string): Promise<UpdateResult>;
 
 	setGroupE2ESuggestedKey(uid: string, rid: string, key: string): Promise<UpdateResult>;
 
@@ -119,9 +135,10 @@ export interface ISubscriptionsModel extends IBaseModel<ISubscription> {
 	updateAutoTranslateLanguageById(_id: string, autoTranslateLanguage: string): Promise<UpdateResult>;
 
 	removeByVisitorToken(token: string): Promise<DeleteResult>;
+	findByToken(token: string, options?: FindOptions): FindCursor<ISubscription>;
 
 	updateMuteGroupMentions(_id: string, muteGroupMentions: boolean): Promise<UpdateResult>;
-	findByRoomIds(roomIds: string[]): FindCursor<ISubscription>;
+	findByRoomIds(roomIds: ISubscription['u']['_id'][], options?: FindOptions<ISubscription>): FindCursor<ISubscription>;
 	changeDepartmentByRoomId(rid: string, department: string): Promise<UpdateResult>;
 
 	roleBaseQuery(userId: string, scope?: string): Filter<ISubscription> | void;
@@ -137,7 +154,24 @@ export interface ISubscriptionsModel extends IBaseModel<ISubscription> {
 	findByUserId(userId: string, options?: FindOptions<ISubscription>): FindCursor<ISubscription>;
 	cachedFindByUserId(userId: string, options?: FindOptions<ISubscription>): FindCursor<ISubscription>;
 	updateAutoTranslateById(_id: string, autoTranslate: boolean): Promise<UpdateResult>;
+
 	updateAllAutoTranslateLanguagesByUserId(userId: IUser['_id'], language: string): Promise<UpdateResult | Document>;
+	findByAutoTranslateAndUserId(
+		userId: ISubscription['u']['_id'],
+		autoTranslate?: ISubscription['autoTranslate'],
+		options?: FindOptions<ISubscription>,
+	): FindCursor<ISubscription>;
+
+	findByUserIdAndRoomType(
+		userId: ISubscription['u']['_id'],
+		type: ISubscription['t'],
+		options?: FindOptions<ISubscription>,
+	): FindCursor<ISubscription>;
+	findByNameAndRoomType(
+		filter: Partial<Pick<ISubscription, 'name' | 't'>>,
+		options?: FindOptions<ISubscription>,
+	): FindCursor<ISubscription>;
+
 	disableAutoTranslateByRoomId(roomId: IRoom['_id']): Promise<UpdateResult | Document>;
 	findAlwaysNotifyDesktopUsersByRoomId(roomId: string): FindCursor<ISubscription>;
 
@@ -166,7 +200,11 @@ export interface ISubscriptionsModel extends IBaseModel<ISubscription> {
 		options?: FindOptions<ISubscription>,
 	): FindCursor<ISubscription>;
 	findByRoomIdAndRoles(roomId: string, roles: string[], options?: FindOptions<ISubscription>): FindCursor<ISubscription>;
-	findByRoomIdAndUserIds(roomId: string, userIds: string[], options?: FindOptions<ISubscription>): FindCursor<ISubscription>;
+	findByRoomIdAndUserIds(
+		roomId: ISubscription['rid'],
+		userIds: ISubscription['u']['_id'][],
+		options?: FindOptions<ISubscription>,
+	): FindCursor<ISubscription>;
 	findByUserIdUpdatedAfter(userId: string, updatedAt: Date, options?: FindOptions<ISubscription>): FindCursor<ISubscription>;
 
 	findByRoomIdAndUserIdsOrAllMessages(roomId: string, userIds: string[]): FindCursor<ISubscription>;
@@ -203,7 +241,7 @@ export interface ISubscriptionsModel extends IBaseModel<ISubscription> {
 	updateCustomFieldsByRoomId(rid: string, cfields: Record<string, any>): Promise<UpdateResult | Document>;
 	setOpenForRoomIdAndUserIds(roomId: string, uids: string[]): Promise<UpdateResult | Document>;
 
-	setAlertForRoomIdAndUserIds(roomId: string, uids: string[]): Promise<UpdateResult | Document>;
+	setAlertForRoomIdAndUserIds(roomId: ISubscription['rid'], uids: ISubscription['u']['_id'][]): Promise<UpdateResult | Document>;
 	updateTypeByRoomId(roomId: string, type: ISubscription['t']): Promise<UpdateResult | Document>;
 	setBlockedByRoomId(rid: string, blocked: string, blocker: string): Promise<UpdateResult[]>;
 	incUserMentionsAndUnreadForRoomIdAndUserIds(
@@ -227,6 +265,12 @@ export interface ISubscriptionsModel extends IBaseModel<ISubscription> {
 		notificationField: keyof ISubscription,
 		notificationOriginField: keyof ISubscription,
 	): Promise<UpdateResult | Document>;
+	findByUserPreferences(
+		userId: string,
+		notificationOriginField: keyof ISubscription,
+		originFieldNotEqualValue: 'user' | 'subscription',
+		options?: FindOptions<ISubscription>,
+	): FindCursor<ISubscription>;
 	clearNotificationUserPreferences(
 		userId: string,
 		notificationField: string,
@@ -239,9 +283,9 @@ export interface ISubscriptionsModel extends IBaseModel<ISubscription> {
 		users: { user: AtLeast<IUser, '_id' | 'username' | 'name' | 'settings'>; extraData: Record<string, any> }[],
 	): Promise<InsertManyResult<ISubscription>>;
 	removeByRoomIdsAndUserId(rids: string[], userId: string): Promise<number>;
-	removeByRoomIdAndUserId(roomId: string, userId: string): Promise<number>;
+	removeByRoomIdAndUserId(roomId: string, userId: string): Promise<ISubscription | null>;
 
-	removeByRoomIds(rids: string[]): Promise<DeleteResult>;
+	removeByRoomIds(rids: string[], options?: { onTrash: (doc: ISubscription) => void }): Promise<DeleteResult>;
 
 	addUnreadThreadByRoomIdAndUserIds(
 		rid: string,
@@ -252,6 +296,12 @@ export interface ISubscriptionsModel extends IBaseModel<ISubscription> {
 	removeUnreadThreadByRoomIdAndUserId(rid: string, userId: string, tmid: string, clearAlert?: boolean): Promise<UpdateResult>;
 
 	removeUnreadThreadsByRoomId(rid: string, tunread: string[]): Promise<UpdateResult | Document>;
+	findUnreadThreadsByRoomId(
+		rid: ISubscription['rid'],
+		tunread: ISubscription['tunread'],
+		options?: FindOptions<ISubscription>,
+	): FindCursor<ISubscription>;
+
 	countByRoomIdAndRoles(roomId: string, roles: string[]): Promise<number>;
 	countByRoomId(roomId: string): Promise<number>;
 	countByUserId(userId: string): Promise<number>;
-- 
GitLab