diff --git a/Sources/XXClient/CMix/CMix.swift b/Sources/XXClient/CMix/CMix.swift
index 4571f0dbf1017fcb8ee7366439add0a7e8b752f4..9ec8007ec8fa3a555988c6fd749913eabc08b398 100644
--- a/Sources/XXClient/CMix/CMix.swift
+++ b/Sources/XXClient/CMix/CMix.swift
@@ -19,6 +19,8 @@ public struct CMix {
   public var addHealthCallback: CMixAddHealthCallback
   public var waitForRoundResult: CMixWaitForRoundResult
   public var connect: CMixConnect
+  public var trackServices: CMixTrackServices
+  public var trackServicesWithIdentity: CMixTrackServicesWithIdentity
 }
 
 extension CMix {
@@ -41,7 +43,9 @@ extension CMix {
       registerClientErrorCallback: .live(bindingsCMix),
       addHealthCallback: .live(bindingsCMix),
       waitForRoundResult: .live(bindingsCMix),
-      connect: .live(bindingsCMix)
+      connect: .live(bindingsCMix),
+      trackServices: .live(bindingsCMix),
+      trackServicesWithIdentity: .live(bindingsCMix)
     )
   }
 }
@@ -65,6 +69,8 @@ extension CMix {
     registerClientErrorCallback: .unimplemented,
     addHealthCallback: .unimplemented,
     waitForRoundResult: .unimplemented,
-    connect: .unimplemented
+    connect: .unimplemented,
+    trackServices: .unimplemented,
+    trackServicesWithIdentity: .unimplemented
   )
 }
diff --git a/Sources/XXClient/CMix/Functions/CMixTrackServicesWithIdentity.swift b/Sources/XXClient/CMix/Functions/CMixTrackServicesWithIdentity.swift
new file mode 100644
index 0000000000000000000000000000000000000000..a3393e6e02e8d93c697c21d85ed1997a1e006466
--- /dev/null
+++ b/Sources/XXClient/CMix/Functions/CMixTrackServicesWithIdentity.swift
@@ -0,0 +1,30 @@
+import Bindings
+import XCTestDynamicOverlay
+
+public struct CMixTrackServicesWithIdentity {
+  public var run: (Int, TrackServicesCallback) throws -> Void
+
+  public func callAsFunction(
+    e2eId: Int,
+    callback: TrackServicesCallback
+  ) throws -> Void {
+    try run(e2eId, callback)
+  }
+}
+
+extension CMixTrackServicesWithIdentity {
+  public static func live(_ bindingsCMix: BindingsCmix) -> CMixTrackServicesWithIdentity {
+    CMixTrackServicesWithIdentity { e2eId, callback in
+      try bindingsCMix.trackServices(
+        withIdentity: e2eId,
+        cb: callback.makeBindingsHealthCallback()
+      )
+    }
+  }
+}
+
+extension CMixTrackServicesWithIdentity {
+  public static let unimplemented = CMixTrackServicesWithIdentity(
+    run: XCTUnimplemented("\(Self.self)")
+  )
+}
diff --git a/Sources/XXClient/Models/NotificationReport.swift b/Sources/XXClient/Models/NotificationReport.swift
index 31c106a34f5a083478522acf542d8b608544a333..06eb1d89c273fcfec99e8d4615936a1d9b8ff237 100644
--- a/Sources/XXClient/Models/NotificationReport.swift
+++ b/Sources/XXClient/Models/NotificationReport.swift
@@ -1,7 +1,7 @@
 import Foundation
 
 public struct NotificationReport: Equatable {
-  public enum ReportType: String, Equatable {
+  public enum ReportType: String, Equatable, CaseIterable {
     case `default`
     case request
     case reset
diff --git a/Sources/XXMessengerClient/Messenger/Functions/MessengerDestroy.swift b/Sources/XXMessengerClient/Messenger/Functions/MessengerDestroy.swift
index c7ee476c543c7096c7bcf8dca8716c607775faff..b2b18a4dd28110e05e9490b3e1e8556fbf5e6b7d 100644
--- a/Sources/XXMessengerClient/Messenger/Functions/MessengerDestroy.swift
+++ b/Sources/XXMessengerClient/Messenger/Functions/MessengerDestroy.swift
@@ -25,6 +25,7 @@ extension MessengerDestroy {
       env.e2e.set(nil)
       env.cMix.set(nil)
       env.isListeningForMessages.set(false)
+      env.serviceList.set(nil)
       try env.fileManager.removeItem(env.storageDir)
       try env.passwordStorage.remove()
     }
diff --git a/Sources/XXMessengerClient/Messenger/Functions/MessengerGetNotificationReport.swift b/Sources/XXMessengerClient/Messenger/Functions/MessengerGetNotificationReport.swift
new file mode 100644
index 0000000000000000000000000000000000000000..a7f04ff91554e2982cfb082cb49a9c289985dd6f
--- /dev/null
+++ b/Sources/XXMessengerClient/Messenger/Functions/MessengerGetNotificationReport.swift
@@ -0,0 +1,34 @@
+import XXClient
+import XCTestDynamicOverlay
+
+public struct MessengerGetNotificationReport {
+  public enum Error: Swift.Error, Equatable {
+    case serviceListMissing
+  }
+
+  public var run: (String) throws -> NotificationReport
+
+  public func callAsFunction(notificationCSV: String) throws -> NotificationReport {
+    try run(notificationCSV)
+  }
+}
+
+extension MessengerGetNotificationReport {
+  public static func live(_ env: MessengerEnvironment) -> MessengerGetNotificationReport {
+    MessengerGetNotificationReport { notificationCSV in
+      guard let serviceList = env.serviceList() else {
+        throw Error.serviceListMissing
+      }
+      return try env.getNotificationsReport(
+        notificationCSV: notificationCSV,
+        services: serviceList
+      )
+    }
+  }
+}
+
+extension MessengerGetNotificationReport {
+  public static let unimplemented = MessengerGetNotificationReport(
+    run: XCTUnimplemented("\(Self.self)")
+  )
+}
diff --git a/Sources/XXMessengerClient/Messenger/Functions/MessengerListenForMessages.swift b/Sources/XXMessengerClient/Messenger/Functions/MessengerListenForMessages.swift
index 62b9a600a58eb7c279a518aff0b23d7110aaaaf5..e0d96caf7ce8744992a9a6f3f66e4dc725c777f5 100644
--- a/Sources/XXMessengerClient/Messenger/Functions/MessengerListenForMessages.swift
+++ b/Sources/XXMessengerClient/Messenger/Functions/MessengerListenForMessages.swift
@@ -2,7 +2,7 @@ import XCTestDynamicOverlay
 import XXClient
 
 public struct MessengerListenForMessages {
-  public enum Error: Swift.Error {
+  public enum Error: Swift.Error, Equatable {
     case notConnected
   }
 
diff --git a/Sources/XXMessengerClient/Messenger/Functions/MessengerMyContact.swift b/Sources/XXMessengerClient/Messenger/Functions/MessengerMyContact.swift
index bcedd164fb985724f90f743ce5d417180f39b067..47f4ed2469aca16838fec99c89b7021a84d78851 100644
--- a/Sources/XXMessengerClient/Messenger/Functions/MessengerMyContact.swift
+++ b/Sources/XXMessengerClient/Messenger/Functions/MessengerMyContact.swift
@@ -7,7 +7,7 @@ public struct MessengerMyContact {
     case types(Set<FactType>)
   }
 
-  public enum Error: Swift.Error {
+  public enum Error: Swift.Error, Equatable {
     case notConnected
     case notLoggedIn
   }
diff --git a/Sources/XXMessengerClient/Messenger/Functions/MessengerStart.swift b/Sources/XXMessengerClient/Messenger/Functions/MessengerStart.swift
index cc3363ec543ee2c3ed6c2414ac79d1ceaa0ccc33..d911fe665dc9fba901aa2defd26cac969e9f3098 100644
--- a/Sources/XXMessengerClient/Messenger/Functions/MessengerStart.swift
+++ b/Sources/XXMessengerClient/Messenger/Functions/MessengerStart.swift
@@ -2,7 +2,7 @@ import XXClient
 import XCTestDynamicOverlay
 
 public struct MessengerStart {
-  public enum Error: Swift.Error {
+  public enum Error: Swift.Error, Equatable {
     case notLoaded
   }
 
diff --git a/Sources/XXMessengerClient/Messenger/Functions/MessengerStop.swift b/Sources/XXMessengerClient/Messenger/Functions/MessengerStop.swift
index 6e49054fbf45c7feaced41449712da617680d9e9..249cea792241e78f2be4d9df2e4e4da82b5cd5ea 100644
--- a/Sources/XXMessengerClient/Messenger/Functions/MessengerStop.swift
+++ b/Sources/XXMessengerClient/Messenger/Functions/MessengerStop.swift
@@ -16,7 +16,7 @@ public struct MessengerStop {
     public var retries: Int
   }
 
-  public enum Error: Swift.Error {
+  public enum Error: Swift.Error, Equatable {
     case notLoaded
     case timedOut
   }
diff --git a/Sources/XXMessengerClient/Messenger/Functions/MessengerTrackServices.swift b/Sources/XXMessengerClient/Messenger/Functions/MessengerTrackServices.swift
new file mode 100644
index 0000000000000000000000000000000000000000..d16cd2b3125f6eea9c6e97026ac56798a4d47756
--- /dev/null
+++ b/Sources/XXMessengerClient/Messenger/Functions/MessengerTrackServices.swift
@@ -0,0 +1,49 @@
+import XCTestDynamicOverlay
+import XXClient
+
+public struct MessengerTrackServices {
+  public enum Error: Swift.Error, Equatable {
+    case notLoaded
+    case notConnected
+  }
+
+  public typealias OnError = (Swift.Error) -> Void
+
+  public var run: (@escaping OnError) throws -> Void
+
+  public func callAsFunction(onError: @escaping OnError) throws {
+    try run(onError)
+  }
+}
+
+extension MessengerTrackServices {
+  public static func live(_ env: MessengerEnvironment) -> MessengerTrackServices {
+    MessengerTrackServices { onError in
+      guard let cMix = env.cMix() else {
+        throw Error.notLoaded
+      }
+      guard let e2e = env.e2e() else {
+        throw Error.notConnected
+      }
+      let callback = TrackServicesCallback { result in
+        switch result {
+        case .success(let serviceList):
+          env.serviceList.set(serviceList)
+        case .failure(let error):
+          env.serviceList.set(nil)
+          onError(error)
+        }
+      }
+      try cMix.trackServicesWithIdentity(
+        e2eId: e2e.getId(),
+        callback: callback
+      )
+    }
+  }
+}
+
+extension MessengerTrackServices {
+  public static let unimplemented = MessengerTrackServices(
+    run: XCTUnimplemented("\(Self.self)")
+  )
+}
diff --git a/Sources/XXMessengerClient/Messenger/Functions/MessengerWaitForNetwork.swift b/Sources/XXMessengerClient/Messenger/Functions/MessengerWaitForNetwork.swift
index 8fe8cf3e4750f1d3bff8a7191574a143639466c7..d0ddb199170902f8dc0d38ff98a341e3f7bee974 100644
--- a/Sources/XXMessengerClient/Messenger/Functions/MessengerWaitForNetwork.swift
+++ b/Sources/XXMessengerClient/Messenger/Functions/MessengerWaitForNetwork.swift
@@ -2,7 +2,7 @@ import XXClient
 import XCTestDynamicOverlay
 
 public struct MessengerWaitForNetwork {
-  public enum Error: Swift.Error {
+  public enum Error: Swift.Error, Equatable {
     case notLoaded
     case timeout
   }
diff --git a/Sources/XXMessengerClient/Messenger/Functions/MessengerWaitForNodes.swift b/Sources/XXMessengerClient/Messenger/Functions/MessengerWaitForNodes.swift
index 3494df5038485e75f297f6e034859754c6b290ad..d4fd91a6a5ab282e103b9de5381c5282b4bcc5ff 100644
--- a/Sources/XXMessengerClient/Messenger/Functions/MessengerWaitForNodes.swift
+++ b/Sources/XXMessengerClient/Messenger/Functions/MessengerWaitForNodes.swift
@@ -5,7 +5,7 @@ import XCTestDynamicOverlay
 public struct MessengerWaitForNodes {
   public typealias Progress = (NodeRegistrationReport) -> Void
 
-  public enum Error: Swift.Error {
+  public enum Error: Swift.Error, Equatable {
     case notLoaded
     case timeout
   }
diff --git a/Sources/XXMessengerClient/Messenger/Messenger.swift b/Sources/XXMessengerClient/Messenger/Messenger.swift
index a6f1ee0d3eb409f6c18fca2c0c62860a97721dfb..d0bf6e46b814ce1c10a3194d627cd46707528530 100644
--- a/Sources/XXMessengerClient/Messenger/Messenger.swift
+++ b/Sources/XXMessengerClient/Messenger/Messenger.swift
@@ -46,6 +46,8 @@ public struct Messenger {
   public var startFileTransfer: MessengerStartFileTransfer
   public var sendFile: MessengerSendFile
   public var receiveFile: MessengerReceiveFile
+  public var trackServices: MessengerTrackServices
+  public var getNotificationReport: MessengerGetNotificationReport
 }
 
 extension Messenger {
@@ -95,7 +97,9 @@ extension Messenger {
       isFileTransferRunning: .live(env),
       startFileTransfer: .live(env),
       sendFile: .live(env),
-      receiveFile: .live(env)
+      receiveFile: .live(env),
+      trackServices: .live(env),
+      getNotificationReport: .live(env)
     )
   }
 }
@@ -146,6 +150,8 @@ extension Messenger {
     isFileTransferRunning: .unimplemented,
     startFileTransfer: .unimplemented,
     sendFile: .unimplemented,
-    receiveFile: .unimplemented
+    receiveFile: .unimplemented,
+    trackServices: .unimplemented,
+    getNotificationReport: .unimplemented
   )
 }
diff --git a/Sources/XXMessengerClient/Messenger/MessengerEnvironment.swift b/Sources/XXMessengerClient/Messenger/MessengerEnvironment.swift
index a1ab1517817248c93ce78bb435ceb621d5449fa6..76707e352ad472aed566118f10963e6bb8600177 100644
--- a/Sources/XXMessengerClient/Messenger/MessengerEnvironment.swift
+++ b/Sources/XXMessengerClient/Messenger/MessengerEnvironment.swift
@@ -16,6 +16,7 @@ public struct MessengerEnvironment {
   public var getE2EFileTransferParams: GetE2EFileTransferParams
   public var getE2EParams: GetE2EParams
   public var getFileTransferParams: GetFileTransferParams
+  public var getNotificationsReport: GetNotificationsReport
   public var getSingleUseParams: GetSingleUseParams
   public var initFileTransfer: InitFileTransfer
   public var initializeBackup: InitializeBackup
@@ -38,6 +39,7 @@ public struct MessengerEnvironment {
   public var registerLogWriter: RegisterLogWriter
   public var resumeBackup: ResumeBackup
   public var searchUD: SearchUD
+  public var serviceList: Stored<MessageServiceList?>
   public var setLogLevel: SetLogLevel
   public var sleep: (TimeInterval) -> Void
   public var storageDir: String
@@ -67,6 +69,7 @@ extension MessengerEnvironment {
       getE2EFileTransferParams: .liveDefault,
       getE2EParams: .liveDefault,
       getFileTransferParams: .liveDefault,
+      getNotificationsReport: .live,
       getSingleUseParams: .liveDefault,
       initFileTransfer: .live,
       initializeBackup: .live,
@@ -89,6 +92,7 @@ extension MessengerEnvironment {
       registerLogWriter: .live,
       resumeBackup: .live,
       searchUD: .live,
+      serviceList: .inMemory(),
       setLogLevel: .live,
       sleep: { Thread.sleep(forTimeInterval: $0) },
       storageDir: MessengerEnvironment.defaultStorageDir,
@@ -113,6 +117,7 @@ extension MessengerEnvironment {
     getE2EFileTransferParams: .unimplemented,
     getE2EParams: .unimplemented,
     getFileTransferParams: .unimplemented,
+    getNotificationsReport: .unimplemented,
     getSingleUseParams: .unimplemented,
     initFileTransfer: .unimplemented,
     initializeBackup: .unimplemented,
@@ -135,6 +140,7 @@ extension MessengerEnvironment {
     registerLogWriter: .unimplemented,
     resumeBackup: .unimplemented,
     searchUD: .unimplemented,
+    serviceList: .unimplemented(),
     setLogLevel: .unimplemented,
     sleep: XCTUnimplemented("\(Self.self).sleep"),
     storageDir: "unimplemented",
diff --git a/Sources/XXMessengerClient/Utils/Stored.swift b/Sources/XXMessengerClient/Utils/Stored.swift
index 052d638705c6dce4a619fbe18d0d55cf2159733c..ab0de7e1db138d121b2f4a9228f6bb61900d84c4 100644
--- a/Sources/XXMessengerClient/Utils/Stored.swift
+++ b/Sources/XXMessengerClient/Utils/Stored.swift
@@ -1,3 +1,4 @@
+import Foundation
 import XCTestDynamicOverlay
 
 public struct Stored<Value> {
@@ -31,6 +32,26 @@ private final class Memory<Value> {
   var value: Value
 }
 
+extension Stored {
+  public static func userDefaults<T>(
+    key: String,
+    userDefaults: UserDefaults = .standard
+  ) -> Stored<T?> where T: Codable {
+    let encoder = JSONEncoder()
+    let decoder = JSONDecoder()
+    return Stored<T?>(
+      get: {
+        guard let data = userDefaults.data(forKey: key) else { return nil }
+        return try? decoder.decode(T.self, from: data)
+      },
+      set: { newValue in
+        let data = try? encoder.encode(newValue)
+        userDefaults.set(data, forKey: key)
+      }
+    )
+  }
+}
+
 extension Stored {
   public struct MissingValueError: Error, Equatable {
     public init(typeDescription: String) {
diff --git a/Tests/XXMessengerClientTests/Messenger/Functions/MessengerDestroyTests.swift b/Tests/XXMessengerClientTests/Messenger/Functions/MessengerDestroyTests.swift
index 927bc9e3c247814be28172cb97407d4b7f843ad7..3f87cacb3204fd02488a76dfe2b07f219dba8095 100644
--- a/Tests/XXMessengerClientTests/Messenger/Functions/MessengerDestroyTests.swift
+++ b/Tests/XXMessengerClientTests/Messenger/Functions/MessengerDestroyTests.swift
@@ -17,6 +17,7 @@ final class MessengerDestroyTests: XCTestCase {
     var didSetCMix: [CMix?] = []
     var didRemovePassword = 0
     var didSetIsListeningForMessages: [Bool] = []
+    var didSetServiceList: [MessageServiceList?] = []
 
     var env: MessengerEnvironment = .unimplemented
     env.cMix.get = {
@@ -34,6 +35,7 @@ final class MessengerDestroyTests: XCTestCase {
     env.e2e.set = { didSetE2E.append($0) }
     env.cMix.set = { didSetCMix.append($0) }
     env.isListeningForMessages.set = { didSetIsListeningForMessages.append($0) }
+    env.serviceList.set = { didSetServiceList.append($0) }
     env.fileManager.removeItem = { didRemoveItem.append($0) }
     env.passwordStorage.remove = { didRemovePassword += 1 }
     let destroy: MessengerDestroy = .live(env)
@@ -48,6 +50,7 @@ final class MessengerDestroyTests: XCTestCase {
     XCTAssertNoDifference(didSetE2E.map { $0 == nil }, [true])
     XCTAssertNoDifference(didSetCMix.map { $0 == nil }, [true])
     XCTAssertNoDifference(didSetIsListeningForMessages, [false])
+    XCTAssertNoDifference(didSetServiceList.map { $0 == nil }, [true])
     XCTAssertNoDifference(didRemoveItem, [storageDir])
     XCTAssertNoDifference(didRemovePassword, 1)
   }
@@ -79,6 +82,7 @@ final class MessengerDestroyTests: XCTestCase {
     var didSetE2E: [E2E?] = []
     var didSetCMix: [CMix?] = []
     var didSetIsListeningForMessages: [Bool] = []
+    var didSetServiceList: [MessageServiceList?] = []
 
     var env: MessengerEnvironment = .unimplemented
     env.cMix.get = { nil }
@@ -88,6 +92,7 @@ final class MessengerDestroyTests: XCTestCase {
     env.e2e.set = { didSetE2E.append($0) }
     env.cMix.set = { didSetCMix.append($0) }
     env.isListeningForMessages.set = { didSetIsListeningForMessages.append($0) }
+    env.serviceList.set = { didSetServiceList.append($0) }
     env.fileManager.removeItem = { _ in throw error }
     let destroy: MessengerDestroy = .live(env)
 
@@ -100,6 +105,7 @@ final class MessengerDestroyTests: XCTestCase {
     XCTAssertNoDifference(didSetE2E.map { $0 == nil }, [true])
     XCTAssertNoDifference(didSetCMix.map { $0 == nil }, [true])
     XCTAssertNoDifference(didSetIsListeningForMessages, [false])
+    XCTAssertNoDifference(didSetServiceList.map { $0 == nil }, [true])
   }
 
   func testRemovePasswordFailure() {
@@ -113,6 +119,7 @@ final class MessengerDestroyTests: XCTestCase {
     var didSetE2E: [E2E?] = []
     var didSetCMix: [CMix?] = []
     var didSetIsListeningForMessages: [Bool] = []
+    var didSetServiceList: [MessageServiceList?] = []
 
     var env: MessengerEnvironment = .unimplemented
     env.cMix.get = { nil }
@@ -122,6 +129,7 @@ final class MessengerDestroyTests: XCTestCase {
     env.e2e.set = { didSetE2E.append($0) }
     env.cMix.set = { didSetCMix.append($0) }
     env.isListeningForMessages.set = { didSetIsListeningForMessages.append($0) }
+    env.serviceList.set = { didSetServiceList.append($0) }
     env.storageDir = storageDir
     env.fileManager.removeItem = { didRemoveItem.append($0) }
     env.passwordStorage.remove = { throw error }
@@ -136,6 +144,7 @@ final class MessengerDestroyTests: XCTestCase {
     XCTAssertNoDifference(didSetE2E.map { $0 == nil }, [true])
     XCTAssertNoDifference(didSetCMix.map { $0 == nil }, [true])
     XCTAssertNoDifference(didSetIsListeningForMessages, [false])
+    XCTAssertNoDifference(didSetServiceList.map { $0 == nil }, [true])
     XCTAssertNoDifference(didRemoveItem, [storageDir])
   }
 }
diff --git a/Tests/XXMessengerClientTests/Messenger/Functions/MessengerGetNotificationReportTests.swift b/Tests/XXMessengerClientTests/Messenger/Functions/MessengerGetNotificationReportTests.swift
new file mode 100644
index 0000000000000000000000000000000000000000..eed49be16789ec70d6179e9b5c0a8c5fc2e2e171
--- /dev/null
+++ b/Tests/XXMessengerClientTests/Messenger/Functions/MessengerGetNotificationReportTests.swift
@@ -0,0 +1,66 @@
+import CustomDump
+import XCTest
+import XCTestDynamicOverlay
+import XXClient
+@testable import XXMessengerClient
+
+final class MessengerGetNotificationReportTests: XCTestCase {
+  func testGetReport() throws {
+    let serviceList = MessageServiceList.stub()
+    let notificationCSV = "notification-csv"
+    let notificationReport = NotificationReport.stub()
+
+    struct GetNotificationsReportParams: Equatable {
+      var notificationCSV: String
+      var serviceList: MessageServiceList
+    }
+    var didGetNotificationsReport: [GetNotificationsReportParams] = []
+
+    var env: MessengerEnvironment = .unimplemented
+    env.serviceList.get = {
+      serviceList
+    }
+    env.getNotificationsReport.run = { notificationCSV, serviceList in
+      didGetNotificationsReport.append(.init(
+        notificationCSV: notificationCSV,
+        serviceList: serviceList
+      ))
+      return notificationReport
+    }
+    let getReport: MessengerGetNotificationReport = .live(env)
+
+    let report = try getReport(notificationCSV: notificationCSV)
+
+    XCTAssertNoDifference(didGetNotificationsReport, [
+      .init(
+        notificationCSV: notificationCSV,
+        serviceList: serviceList
+      )
+    ])
+    XCTAssertNoDifference(report, notificationReport)
+  }
+
+  func testGetReportWhenServiceListMissing() {
+    var env: MessengerEnvironment = .unimplemented
+    env.e2e.get = { .unimplemented }
+    env.serviceList.get = { nil }
+    let getReport: MessengerGetNotificationReport = .live(env)
+
+    XCTAssertThrowsError(try getReport(notificationCSV: "")) { error in
+      XCTAssertNoDifference(
+        error as? MessengerGetNotificationReport.Error,
+        .serviceListMissing
+      )
+    }
+  }
+}
+
+extension NotificationReport {
+  static func stub() -> NotificationReport {
+    NotificationReport(
+      forMe: .random(),
+      type: ReportType.allCases.randomElement()!,
+      source: "source-\(Int.random(in: 100...999))".data(using: .utf8)!
+    )
+  }
+}
diff --git a/Tests/XXMessengerClientTests/Messenger/Functions/MessengerTrackServicesTests.swift b/Tests/XXMessengerClientTests/Messenger/Functions/MessengerTrackServicesTests.swift
new file mode 100644
index 0000000000000000000000000000000000000000..55fc5e3156cf0dc880a094bf0a12e98da824cdff
--- /dev/null
+++ b/Tests/XXMessengerClientTests/Messenger/Functions/MessengerTrackServicesTests.swift
@@ -0,0 +1,87 @@
+import CustomDump
+import XCTest
+import XCTestDynamicOverlay
+import XXClient
+@testable import XXMessengerClient
+
+final class MessengerTrackServicesTests: XCTestCase {
+  func testTrack() throws {
+    struct Failure: Error, Equatable {}
+    let failure = Failure()
+    let serviceList = MessageServiceList.stub()
+    let e2eId = 123
+
+    var didTrackServicesWithIdentity: [Int] = []
+    var didSetServiceList: [MessageServiceList?] = []
+    var didReceiveError: [Error] = []
+    var callbacks: [TrackServicesCallback] = []
+
+    var env: MessengerEnvironment = .unimplemented
+    env.serviceList.set = { serviceList in
+      didSetServiceList.append(serviceList)
+    }
+    env.cMix.get = {
+      var cMix: CMix = .unimplemented
+      cMix.trackServicesWithIdentity.run = { e2eId, callback in
+        didTrackServicesWithIdentity.append(e2eId)
+        callbacks.append(callback)
+      }
+      return cMix
+    }
+    env.e2e.get = {
+      var e2e: E2E = .unimplemented
+      e2e.getId.run = { e2eId }
+      return e2e
+    }
+    let track: MessengerTrackServices = .live(env)
+
+    try track(onError: { error in
+      didReceiveError.append(error)
+    })
+
+    XCTAssertNoDifference(didTrackServicesWithIdentity, [e2eId])
+    XCTAssertEqual(callbacks.count, 1)
+
+    didSetServiceList = []
+    didReceiveError = []
+    callbacks.first?.handle(.success(serviceList))
+
+    XCTAssertNoDifference(didSetServiceList, [serviceList])
+    XCTAssertEqual(didReceiveError.count, 0)
+
+    didSetServiceList = []
+    didReceiveError = []
+    callbacks.first?.handle(.failure(failure))
+
+    XCTAssertNoDifference(didSetServiceList, [nil])
+    XCTAssertEqual(didReceiveError.count, 1)
+    XCTAssertNoDifference(didReceiveError.first as? Failure, failure)
+  }
+
+  func testTrackWhenNotLoaded() {
+    var env: MessengerEnvironment = .unimplemented
+    env.cMix.get = { nil }
+    let track: MessengerTrackServices = .live(env)
+
+    XCTAssertThrowsError(try track(onError: unimplemented())) { error in
+      XCTAssertNoDifference(
+        error as? MessengerTrackServices.Error,
+        .notLoaded
+      )
+    }
+  }
+
+  func testTrackWhenNotConnected() {
+    var env: MessengerEnvironment = .unimplemented
+    env.cMix.get = { .unimplemented }
+    env.e2e.get = { nil }
+    let track: MessengerTrackServices = .live(env)
+
+    XCTAssertThrowsError(try track(onError: unimplemented())) { error in
+      XCTAssertNoDifference(
+        error as? MessengerTrackServices.Error,
+        .notConnected
+      )
+    }
+  }
+}
diff --git a/Tests/XXMessengerClientTests/TestHelpers/TestDoubles.swift b/Tests/XXMessengerClientTests/TestHelpers/TestDoubles.swift
index e43d76accbb65a1f7f58528e4f5a6c443f48e394..d36ad479f5f865ad5ff2e06656938740054b5676 100644
--- a/Tests/XXMessengerClientTests/TestHelpers/TestDoubles.swift
+++ b/Tests/XXMessengerClientTests/TestHelpers/TestDoubles.swift
@@ -30,3 +30,28 @@ extension ReceivedFile {
     )
   }
 }
+
+extension MessageServiceList {
+  static func stub() -> MessageServiceList {
+    (1...3).map { .stub($0) }
+  }
+}
+
+extension MessageServiceListElement {
+  static func stub(_ id: Int) -> MessageServiceListElement {
+    MessageServiceListElement(
+      id: "id-\(id)".data(using: .utf8)!,
+      services: (1...3).map { $0 + 10 * id }.map { .stub($0) }
+    )
+  }
+}
+
+extension MessageService {
+  static func stub(_ id: Int) -> MessageService {
+    MessageService(
+      identifier: "identifier-\(id)".data(using: .utf8)!,
+      tag: "tag-\(id)",
+      metadata: "metadata-\(id)".data(using: .utf8)!
+    )
+  }
+}
diff --git a/Tests/XXMessengerClientTests/Utils/StoredTests.swift b/Tests/XXMessengerClientTests/Utils/StoredTests.swift
index 154f61967627500def1526f2944aeb074792c748..834097ef9e870d7b9e297d3131c246c6a068c48a 100644
--- a/Tests/XXMessengerClientTests/Utils/StoredTests.swift
+++ b/Tests/XXMessengerClientTests/Utils/StoredTests.swift
@@ -1,8 +1,9 @@
+import CustomDump
 import XCTest
 @testable import XXMessengerClient
 
 final class StoredTests: XCTestCase {
-  func testInMemory() throws {
+  func testInMemory() {
     let stored: Stored<String?> = .inMemory()
 
     XCTAssertNil(stored())
@@ -15,4 +16,35 @@ final class StoredTests: XCTestCase {
 
     XCTAssertNil(stored())
   }
+
+  func testUserDefaults() {
+    struct Value: Equatable, Codable {
+      var id = UUID()
+    }
+
+    let key = "key"
+    let userDefaults = UserDefaults(suiteName: "XXMessengerClient_StoredTests")!
+    userDefaults.removeObject(forKey: key)
+    let stored: Stored<Value?> = .userDefaults(
+      key: key,
+      userDefaults: userDefaults
+    )
+
+    XCTAssertNoDifference(stored.get(), nil)
+
+    let value = Value()
+    stored.set(value)
+
+    XCTAssertNoDifference(
+      userDefaults.data(forKey: key).map { data in
+        try? JSONDecoder().decode(Value.self, from: data)
+      },
+      value
+    )
+    XCTAssertNoDifference(stored.get(), value)
+
+    userDefaults.set(nil, forKey: key)
+
+    XCTAssertNoDifference(stored.get(), nil)
+  }
 }