diff --git a/Examples/xx-messenger/Sources/HomeFeature/HomeFeature.swift b/Examples/xx-messenger/Sources/HomeFeature/HomeFeature.swift
index 8116fde02b42f465d8e12b2c52d63b4db9bc9514..e054e6ac8bf07bd9ade9d0837d1c6e0408362348 100644
--- a/Examples/xx-messenger/Sources/HomeFeature/HomeFeature.swift
+++ b/Examples/xx-messenger/Sources/HomeFeature/HomeFeature.swift
@@ -10,20 +10,23 @@ import XXMessengerClient
 public struct HomeState: Equatable {
   public init(
     failure: String? = nil,
-    register: RegisterState? = nil,
+    isNetworkHealthy: Bool? = nil,
+    isDeletingAccount: Bool = false,
     alert: AlertState<HomeAction>? = nil,
-    isDeletingAccount: Bool = false
+    register: RegisterState? = nil
   ) {
     self.failure = failure
-    self.register = register
-    self.alert = alert
+    self.isNetworkHealthy = isNetworkHealthy
     self.isDeletingAccount = isDeletingAccount
+    self.alert = alert
+    self.register = register
   }
 
   public var failure: String?
-  public var register: RegisterState?
-  public var alert: AlertState<HomeAction>?
+  public var isNetworkHealthy: Bool?
   public var isDeletingAccount: Bool
+  public var alert: AlertState<HomeAction>?
+  public var register: RegisterState?
 }
 
 public enum HomeAction: Equatable {
@@ -34,6 +37,12 @@ public enum HomeAction: Equatable {
     case failure(NSError)
   }
 
+  public enum NetworkMonitor: Equatable {
+    case start
+    case stop
+    case health(Bool)
+  }
+
   public enum DeleteAccount: Equatable {
     case buttonTapped
     case confirmed
@@ -42,6 +51,7 @@ public enum HomeAction: Equatable {
   }
 
   case messenger(Messenger)
+  case networkMonitor(NetworkMonitor)
   case deleteAccount(DeleteAccount)
   case didDismissAlert
   case didDismissRegister
@@ -82,28 +92,33 @@ extension HomeEnvironment {
 
 public let homeReducer = Reducer<HomeState, HomeAction, HomeEnvironment>
 { state, action, env in
+  enum NetworkHealthEffectId {}
+
   switch action {
   case .messenger(.start):
-    return .result {
-      do {
-        try env.messenger.start()
-
-        if env.messenger.isConnected() == false {
-          try env.messenger.connect()
-        }
+    return .merge(
+      Effect(value: .networkMonitor(.stop)),
+      Effect.result {
+        do {
+          try env.messenger.start()
+
+          if env.messenger.isConnected() == false {
+            try env.messenger.connect()
+          }
 
-        if env.messenger.isLoggedIn() == false {
-          if try env.messenger.isRegistered() == false {
-            return .success(.messenger(.didStartUnregistered))
+          if env.messenger.isLoggedIn() == false {
+            if try env.messenger.isRegistered() == false {
+              return .success(.messenger(.didStartUnregistered))
+            }
+            try env.messenger.logIn()
           }
-          try env.messenger.logIn()
-        }
 
-        return .success(.messenger(.didStartRegistered))
-      } catch {
-        return .success(.messenger(.failure(error as NSError)))
+          return .success(.messenger(.didStartRegistered))
+        } catch {
+          return .success(.messenger(.failure(error as NSError)))
+        }
       }
-    }
+    )
     .subscribe(on: env.bgQueue)
     .receive(on: env.mainQueue)
     .eraseToEffect()
@@ -113,12 +128,33 @@ public let homeReducer = Reducer<HomeState, HomeAction, HomeEnvironment>
     return .none
 
   case .messenger(.didStartRegistered):
-    return .none
+    return Effect(value: .networkMonitor(.start))
 
   case .messenger(.failure(let error)):
     state.failure = error.localizedDescription
     return .none
 
+  case .networkMonitor(.start):
+    return .run { subscriber in
+      let callback = HealthCallback { isHealthy in
+        subscriber.send(.networkMonitor(.health(isHealthy)))
+      }
+      let cancellable = env.messenger.cMix()?.addHealthCallback(callback)
+      return AnyCancellable { cancellable?.cancel() }
+    }
+    .cancellable(id: NetworkHealthEffectId.self, cancelInFlight: true)
+    .subscribe(on: env.bgQueue)
+    .receive(on: env.mainQueue)
+    .eraseToEffect()
+
+  case .networkMonitor(.stop):
+    state.isNetworkHealthy = nil
+    return .cancel(id: NetworkHealthEffectId.self)
+
+  case .networkMonitor(.health(let isHealthy)):
+    state.isNetworkHealthy = isHealthy
+    return .none
+
   case .deleteAccount(.buttonTapped):
     state.alert = .confirmAccountDeletion()
     return .none
diff --git a/Examples/xx-messenger/Sources/HomeFeature/HomeView.swift b/Examples/xx-messenger/Sources/HomeFeature/HomeView.swift
index b2c9e660d1f1849cf9d26ea8d9d4113e05bf502c..d105e0bd6c400c18217d17cb6b791d1ab391359f 100644
--- a/Examples/xx-messenger/Sources/HomeFeature/HomeView.swift
+++ b/Examples/xx-messenger/Sources/HomeFeature/HomeView.swift
@@ -12,10 +12,12 @@ public struct HomeView: View {
 
   struct ViewState: Equatable {
     var failure: String?
+    var isNetworkHealthy: Bool?
     var isDeletingAccount: Bool
 
     init(state: HomeState) {
       failure = state.failure
+      isNetworkHealthy = state.isNetworkHealthy
       isDeletingAccount = state.isDeletingAccount
     }
   }
@@ -37,6 +39,28 @@ public struct HomeView: View {
             }
           }
 
+          Section {
+            HStack {
+              Text("Health")
+              Spacer()
+              switch viewStore.isNetworkHealthy {
+              case .some(true):
+                Image(systemName: "checkmark.circle.fill")
+                  .foregroundColor(.green)
+
+              case .some(false):
+                Image(systemName: "xmark.diamond.fill")
+                  .foregroundColor(.red)
+
+              case .none:
+                Image(systemName: "questionmark.circle")
+                  .foregroundColor(.gray)
+              }
+            }
+          } header: {
+            Text("Network")
+          }
+
           Section {
             Button(role: .destructive) {
               viewStore.send(.deleteAccount(.buttonTapped))
diff --git a/Examples/xx-messenger/Tests/HomeFeatureTests/HomeFeatureTests.swift b/Examples/xx-messenger/Tests/HomeFeatureTests/HomeFeatureTests.swift
index 60c7e305de613a980ba700b8deb10ea5dd0ad2d8..97ac8900fff0624421ed26fee9232fce36f939b9 100644
--- a/Examples/xx-messenger/Tests/HomeFeatureTests/HomeFeatureTests.swift
+++ b/Examples/xx-messenger/Tests/HomeFeatureTests/HomeFeatureTests.swift
@@ -14,13 +14,11 @@ final class HomeFeatureTests: XCTestCase {
       environment: .unimplemented
     )
 
-    let bgQueue = DispatchQueue.test
-    let mainQueue = DispatchQueue.test
     var messengerDidStartWithTimeout: [Int] = []
     var messengerDidConnect = 0
 
-    store.environment.bgQueue = bgQueue.eraseToAnyScheduler()
-    store.environment.mainQueue = mainQueue.eraseToAnyScheduler()
+    store.environment.bgQueue = .immediate
+    store.environment.mainQueue = .immediate
     store.environment.messenger.start.run = { messengerDidStartWithTimeout.append($0) }
     store.environment.messenger.isConnected.run = { false }
     store.environment.messenger.connect.run = { messengerDidConnect += 1 }
@@ -29,13 +27,10 @@ final class HomeFeatureTests: XCTestCase {
 
     store.send(.messenger(.start))
 
-    bgQueue.advance()
-
     XCTAssertNoDifference(messengerDidStartWithTimeout, [30_000])
     XCTAssertNoDifference(messengerDidConnect, 1)
 
-    mainQueue.advance()
-
+    store.receive(.networkMonitor(.stop))
     store.receive(.messenger(.didStartUnregistered)) {
       $0.register = RegisterState()
     }
@@ -48,32 +43,35 @@ final class HomeFeatureTests: XCTestCase {
       environment: .unimplemented
     )
 
-    let bgQueue = DispatchQueue.test
-    let mainQueue = DispatchQueue.test
     var messengerDidStartWithTimeout: [Int] = []
     var messengerDidConnect = 0
     var messengerDidLogIn = 0
 
-    store.environment.bgQueue = bgQueue.eraseToAnyScheduler()
-    store.environment.mainQueue = mainQueue.eraseToAnyScheduler()
+    store.environment.bgQueue = .immediate
+    store.environment.mainQueue = .immediate
     store.environment.messenger.start.run = { messengerDidStartWithTimeout.append($0) }
     store.environment.messenger.isConnected.run = { false }
     store.environment.messenger.connect.run = { messengerDidConnect += 1 }
     store.environment.messenger.isLoggedIn.run = { false }
     store.environment.messenger.isRegistered.run = { true }
     store.environment.messenger.logIn.run = { messengerDidLogIn += 1 }
+    store.environment.messenger.cMix.get = {
+      var cMix: CMix = .unimplemented
+      cMix.addHealthCallback.run = { _ in Cancellable {} }
+      return cMix
+    }
 
     store.send(.messenger(.start))
 
-    bgQueue.advance()
-
     XCTAssertNoDifference(messengerDidStartWithTimeout, [30_000])
     XCTAssertNoDifference(messengerDidConnect, 1)
     XCTAssertNoDifference(messengerDidLogIn, 1)
 
-    mainQueue.advance()
-
+    store.receive(.networkMonitor(.stop))
     store.receive(.messenger(.didStartRegistered))
+    store.receive(.networkMonitor(.start))
+
+    store.send(.networkMonitor(.stop))
   }
 
   func testRegisterFinished() {
@@ -85,18 +83,21 @@ final class HomeFeatureTests: XCTestCase {
       environment: .unimplemented
     )
 
-    let bgQueue = DispatchQueue.test
-    let mainQueue = DispatchQueue.test
     var messengerDidStartWithTimeout: [Int] = []
     var messengerDidLogIn = 0
 
-    store.environment.bgQueue = bgQueue.eraseToAnyScheduler()
-    store.environment.mainQueue = mainQueue.eraseToAnyScheduler()
+    store.environment.bgQueue = .immediate
+    store.environment.mainQueue = .immediate
     store.environment.messenger.start.run = { messengerDidStartWithTimeout.append($0) }
     store.environment.messenger.isConnected.run = { true }
     store.environment.messenger.isLoggedIn.run = { false }
     store.environment.messenger.isRegistered.run = { true }
     store.environment.messenger.logIn.run = { messengerDidLogIn += 1 }
+    store.environment.messenger.cMix.get = {
+      var cMix: CMix = .unimplemented
+      cMix.addHealthCallback.run = { _ in Cancellable {} }
+      return cMix
+    }
 
     store.send(.register(.finished)) {
       $0.register = nil
@@ -104,14 +105,14 @@ final class HomeFeatureTests: XCTestCase {
 
     store.receive(.messenger(.start))
 
-    bgQueue.advance()
-
     XCTAssertNoDifference(messengerDidStartWithTimeout, [30_000])
     XCTAssertNoDifference(messengerDidLogIn, 1)
 
-    mainQueue.advance()
-
+    store.receive(.networkMonitor(.stop))
     store.receive(.messenger(.didStartRegistered))
+    store.receive(.networkMonitor(.start))
+
+    store.send(.networkMonitor(.stop))
   }
 
   func testMessengerStartFailure() {
@@ -130,6 +131,7 @@ final class HomeFeatureTests: XCTestCase {
 
     store.send(.messenger(.start))
 
+    store.receive(.networkMonitor(.stop))
     store.receive(.messenger(.failure(error as NSError))) {
       $0.failure = error.localizedDescription
     }
@@ -153,6 +155,7 @@ final class HomeFeatureTests: XCTestCase {
 
     store.send(.messenger(.start))
 
+    store.receive(.networkMonitor(.stop))
     store.receive(.messenger(.failure(error as NSError))) {
       $0.failure = error.localizedDescription
     }
@@ -177,6 +180,7 @@ final class HomeFeatureTests: XCTestCase {
 
     store.send(.messenger(.start))
 
+    store.receive(.networkMonitor(.stop))
     store.receive(.messenger(.failure(error as NSError))) {
       $0.failure = error.localizedDescription
     }
@@ -202,11 +206,56 @@ final class HomeFeatureTests: XCTestCase {
 
     store.send(.messenger(.start))
 
+    store.receive(.networkMonitor(.stop))
     store.receive(.messenger(.failure(error as NSError))) {
       $0.failure = error.localizedDescription
     }
   }
 
+  func testNetworkMonitorStart() {
+    let store = TestStore(
+      initialState: HomeState(),
+      reducer: homeReducer,
+      environment: .unimplemented
+    )
+
+    var cMixDidAddHealthCallback: [HealthCallback] = []
+    var healthCallbackDidCancel = 0
+
+    store.environment.bgQueue = .immediate
+    store.environment.mainQueue = .immediate
+    store.environment.messenger.cMix.get = {
+      var cMix: CMix = .unimplemented
+      cMix.addHealthCallback.run = { callback in
+        cMixDidAddHealthCallback.append(callback)
+        return Cancellable { healthCallbackDidCancel += 1 }
+      }
+      return cMix
+    }
+
+    store.send(.networkMonitor(.start))
+
+    XCTAssertNoDifference(cMixDidAddHealthCallback.count, 1)
+
+    cMixDidAddHealthCallback.first?.handle(true)
+
+    store.receive(.networkMonitor(.health(true))) {
+      $0.isNetworkHealthy = true
+    }
+
+    cMixDidAddHealthCallback.first?.handle(false)
+
+    store.receive(.networkMonitor(.health(false))) {
+      $0.isNetworkHealthy = false
+    }
+
+    store.send(.networkMonitor(.stop)) {
+      $0.isNetworkHealthy = nil
+    }
+
+    XCTAssertNoDifference(healthCallbackDidCancel, 1)
+  }
+
   func testAccountDeletion() {
     let store = TestStore(
       initialState: HomeState(),