diff --git a/Docs/XXMessengerClient.md b/Docs/XXMessengerClient.md
index 05afa4aaa33cb74d114d0548faf8c157e00678ce..971aefd97c91f7b210ea1bdeb456a257cb89ae71 100644
--- a/Docs/XXMessengerClient.md
+++ b/Docs/XXMessengerClient.md
@@ -4,7 +4,7 @@
 
 ## ▶️ Instantiate messenger
 
-Example:
+### Example
 
 ```swift
 // setup environment:
@@ -24,7 +24,7 @@ let messenger: Messenger = .live(environment)
 
 ## 🚀 Start messenger
 
-Example:
+### Example
 
 ```swift
 // allow cancellation of callbacks:
@@ -84,7 +84,7 @@ func start(messenger: Messenger) throws {
 
 ## 🛠 Use client components directly
 
-Example:
+### Example
 
 ```swift
 // get cMix:
@@ -95,4 +95,60 @@ let e2e = messenger.e2e()
 
 // get UserDicovery:
 let ud = messenger.ud()
-```
\ No newline at end of file
+
+// get Backup:
+let backup = messenger.backup()
+```
+
+## 💾 Backup
+
+### Make backup
+
+```swift
+// start receiving backup data before starting or resuming backup:
+let cancellable = messenger.registerBackupCallback(.init { data in
+  // handle backup data, save on disk, upload to cloud, etc.
+})
+
+// check if backup is already running:
+if messenger.isBackupRunning() == false {
+  do {
+    // try to resume previous backup:
+    try messenger.resumeBackup()
+  } catch {
+    // try to start a new backup:
+    let params: BackupParams = ...
+    try messenger.startBackup(
+      password: "backup-passphrase",
+      params: params
+    )
+  }
+}
+
+// update params in the backup:
+let params: BackupParams = ...
+try messenger.backupParams(params)
+
+// stop the backup:
+try messenger.stopBackup()
+
+// optionally stop receiving backup data
+cancellable.cancel()
+```
+
+When starting a new backup you must provide `BackupParams` to prevent creating backups that does not contain it.
+
+The registered backup callback can be reused later when a new backup is started. There is no need to cancel it and register a new callback in such a case.
+
+### Restore from backup
+
+```swift
+let result = try messenger.restoreBackup(
+  backupData: ...,
+  backupPassphrase: "backup-passphrase"
+)
+
+// handle restoration result
+```
+
+If no error was thrown during restoration, the `Messenger` is already loaded, started, connected, and logged in.
\ No newline at end of file
diff --git a/Sources/XXMessengerClient/Messenger/Functions/MessengerBackupParams.swift b/Sources/XXMessengerClient/Messenger/Functions/MessengerBackupParams.swift
new file mode 100644
index 0000000000000000000000000000000000000000..f5ac71f04d82b27fd3585acfbfe89b4372034ac2
--- /dev/null
+++ b/Sources/XXMessengerClient/Messenger/Functions/MessengerBackupParams.swift
@@ -0,0 +1,33 @@
+import Bindings
+import XCTestDynamicOverlay
+
+public struct MessengerBackupParams {
+  public enum Error: Swift.Error, Equatable {
+    case notRunning
+  }
+
+  public var run: (BackupParams) throws -> Void
+
+  public func callAsFunction(_ params: BackupParams) throws {
+    try run(params)
+  }
+}
+
+extension MessengerBackupParams {
+  public static func live(_ env: MessengerEnvironment) -> MessengerBackupParams {
+    MessengerBackupParams { params in
+      guard let backup = env.backup(), backup.isRunning() else {
+        throw Error.notRunning
+      }
+      let paramsData = try params.encode()
+      let paramsString = String(data: paramsData, encoding: .utf8)!
+      backup.addJSON(paramsString)
+    }
+  }
+}
+
+extension MessengerBackupParams {
+  public static let unimplemented = MessengerBackupParams(
+    run: XCTUnimplemented("\(Self.self)")
+  )
+}
diff --git a/Sources/XXMessengerClient/Messenger/Functions/MessengerIsBackupRunning.swift b/Sources/XXMessengerClient/Messenger/Functions/MessengerIsBackupRunning.swift
new file mode 100644
index 0000000000000000000000000000000000000000..08453e23cb8eddf2190ac49b5870489980b88765
--- /dev/null
+++ b/Sources/XXMessengerClient/Messenger/Functions/MessengerIsBackupRunning.swift
@@ -0,0 +1,24 @@
+import XCTestDynamicOverlay
+import XXClient
+
+public struct MessengerIsBackupRunning {
+  public var run: () -> Bool
+
+  public func callAsFunction() -> Bool {
+    run()
+  }
+}
+
+extension MessengerIsBackupRunning {
+  public static func live(_ env: MessengerEnvironment) -> MessengerIsBackupRunning {
+    MessengerIsBackupRunning {
+      env.backup()?.isRunning() == true
+    }
+  }
+}
+
+extension MessengerIsBackupRunning {
+  public static let unimplemented = MessengerIsBackupRunning(
+    run: XCTUnimplemented("\(Self.self)")
+  )
+}
diff --git a/Sources/XXMessengerClient/Messenger/Functions/MessengerRegisterBackupCallback.swift b/Sources/XXMessengerClient/Messenger/Functions/MessengerRegisterBackupCallback.swift
new file mode 100644
index 0000000000000000000000000000000000000000..f7c9a251072b04f28f7c12f527e42be7174dce15
--- /dev/null
+++ b/Sources/XXMessengerClient/Messenger/Functions/MessengerRegisterBackupCallback.swift
@@ -0,0 +1,24 @@
+import XCTestDynamicOverlay
+import XXClient
+
+public struct MessengerRegisterBackupCallback {
+  public var run: (UpdateBackupFunc) -> Cancellable
+
+  public func callAsFunction(_ callback: UpdateBackupFunc) -> Cancellable {
+    run(callback)
+  }
+}
+
+extension MessengerRegisterBackupCallback {
+  public static func live(_ env: MessengerEnvironment) -> MessengerRegisterBackupCallback {
+    MessengerRegisterBackupCallback { callback in
+      env.backupCallbacks.register(callback)
+    }
+  }
+}
+
+extension MessengerRegisterBackupCallback {
+  public static let unimplemented = MessengerRegisterBackupCallback(
+    run: XCTUnimplemented("\(Self.self)", placeholder: Cancellable {})
+  )
+}
diff --git a/Sources/XXMessengerClient/Messenger/Functions/MessengerRestoreBackup.swift b/Sources/XXMessengerClient/Messenger/Functions/MessengerRestoreBackup.swift
index 66f50dd550930409dd164315095b7517fd34acca..b196faca4b578bf3ea9bc534073be015b24dc816 100644
--- a/Sources/XXMessengerClient/Messenger/Functions/MessengerRestoreBackup.swift
+++ b/Sources/XXMessengerClient/Messenger/Functions/MessengerRestoreBackup.swift
@@ -42,9 +42,8 @@ extension MessengerRestoreBackup {
         sessionPassword: password,
         backupFileContents: backupData
       )
-      let decoder = JSONDecoder()
       let paramsData = report.params.data(using: .utf8)!
-      let params = try decoder.decode(BackupParams.self, from: paramsData)
+      let params = try BackupParams.decode(paramsData)
       let cMix = try env.loadCMix(
         storageDir: storageDir,
         password: password,
diff --git a/Sources/XXMessengerClient/Messenger/Functions/MessengerResumeBackup.swift b/Sources/XXMessengerClient/Messenger/Functions/MessengerResumeBackup.swift
new file mode 100644
index 0000000000000000000000000000000000000000..9c4d62a09fe35d22c4afc7c1d6c7fed50d5b1951
--- /dev/null
+++ b/Sources/XXMessengerClient/Messenger/Functions/MessengerResumeBackup.swift
@@ -0,0 +1,44 @@
+import Bindings
+import XCTestDynamicOverlay
+
+public struct MessengerResumeBackup {
+  public enum Error: Swift.Error, Equatable {
+    case isRunning
+    case notConnected
+    case notLoggedIn
+  }
+
+  public var run: () throws -> Void
+
+  public func callAsFunction() throws {
+    try run()
+  }
+}
+
+extension MessengerResumeBackup {
+  public static func live(_ env: MessengerEnvironment) -> MessengerResumeBackup {
+    MessengerResumeBackup {
+      guard env.backup()?.isRunning() != true else {
+        throw Error.isRunning
+      }
+      guard let e2e = env.e2e() else {
+        throw Error.notConnected
+      }
+      guard let ud = env.ud() else {
+        throw Error.notLoggedIn
+      }
+      let backup = try env.resumeBackup(
+        e2eId: e2e.getId(),
+        udId: ud.getId(),
+        callback: env.backupCallbacks.registered()
+      )
+      env.backup.set(backup)
+    }
+  }
+}
+
+extension MessengerResumeBackup {
+  public static let unimplemented = MessengerResumeBackup(
+    run: XCTUnimplemented("\(Self.self)")
+  )
+}
diff --git a/Sources/XXMessengerClient/Messenger/Functions/MessengerStartBackup.swift b/Sources/XXMessengerClient/Messenger/Functions/MessengerStartBackup.swift
new file mode 100644
index 0000000000000000000000000000000000000000..254bc6b9268f03c9c01d6ed8086d2c1e55c1fd89
--- /dev/null
+++ b/Sources/XXMessengerClient/Messenger/Functions/MessengerStartBackup.swift
@@ -0,0 +1,62 @@
+import XCTestDynamicOverlay
+import XXClient
+
+public struct MessengerStartBackup {
+  public enum Error: Swift.Error, Equatable {
+    case isRunning
+    case notConnected
+    case notLoggedIn
+  }
+
+  public var run: (String, BackupParams) throws -> Void
+
+  public func callAsFunction(
+    password: String,
+    params: BackupParams
+  ) throws {
+    try run(password, params)
+  }
+}
+
+extension MessengerStartBackup {
+  public static func live(_ env: MessengerEnvironment) -> MessengerStartBackup {
+    MessengerStartBackup { password, params in
+      guard env.backup()?.isRunning() != true else {
+        throw Error.isRunning
+      }
+      guard let e2e = env.e2e() else {
+        throw Error.notConnected
+      }
+      guard let ud = env.ud() else {
+        throw Error.notLoggedIn
+      }
+      let paramsData = try params.encode()
+      let paramsString = String(data: paramsData, encoding: .utf8)!
+      var didAddParams = false
+      func addParams() {
+        guard let backup = env.backup() else { return }
+        backup.addJSON(paramsString)
+        didAddParams = true
+      }
+      let backup = try env.initializeBackup(
+        e2eId: e2e.getId(),
+        udId: ud.getId(),
+        password: password,
+        callback: .init { data in
+          if !didAddParams {
+            addParams()
+          } else {
+            env.backupCallbacks.registered().handle(data)
+          }
+        }
+      )
+      env.backup.set(backup)
+    }
+  }
+}
+
+extension MessengerStartBackup {
+  public static let unimplemented = MessengerStartBackup(
+    run: XCTUnimplemented("\(Self.self)")
+  )
+}
diff --git a/Sources/XXMessengerClient/Messenger/Functions/MessengerStopBackup.swift b/Sources/XXMessengerClient/Messenger/Functions/MessengerStopBackup.swift
new file mode 100644
index 0000000000000000000000000000000000000000..f5471166652f0d03488f70c74bf3a21aed8df6f8
--- /dev/null
+++ b/Sources/XXMessengerClient/Messenger/Functions/MessengerStopBackup.swift
@@ -0,0 +1,26 @@
+import Bindings
+import XCTestDynamicOverlay
+
+public struct MessengerStopBackup {
+  public var run: () throws -> Void
+
+  public func callAsFunction() throws {
+    try run()
+  }
+}
+
+extension MessengerStopBackup {
+  public static func live(_ env: MessengerEnvironment) -> MessengerStopBackup {
+    MessengerStopBackup {
+      guard let backup = env.backup() else { return }
+      try backup.stop()
+      env.backup.set(nil)
+    }
+  }
+}
+
+extension MessengerStopBackup {
+  public static let unimplemented = MessengerStopBackup(
+    run: XCTUnimplemented("\(Self.self)")
+  )
+}
diff --git a/Sources/XXMessengerClient/Messenger/Messenger.swift b/Sources/XXMessengerClient/Messenger/Messenger.swift
index ea85852c6752e30af60987264851d57af910e811..ce598c5451db8694e727055bba3ae087f08ae3fc 100644
--- a/Sources/XXMessengerClient/Messenger/Messenger.swift
+++ b/Sources/XXMessengerClient/Messenger/Messenger.swift
@@ -4,6 +4,7 @@ public struct Messenger {
   public var cMix: Stored<CMix?>
   public var e2e: Stored<E2E?>
   public var ud: Stored<UserDiscovery?>
+  public var backup: Stored<Backup?>
   public var isCreated: MessengerIsCreated
   public var create: MessengerCreate
   public var restoreBackup: MessengerRestoreBackup
@@ -29,6 +30,12 @@ public struct Messenger {
   public var registerForNotifications: MessengerRegisterForNotifications
   public var verifyContact: MessengerVerifyContact
   public var sendMessage: MessengerSendMessage
+  public var registerBackupCallback: MessengerRegisterBackupCallback
+  public var isBackupRunning: MessengerIsBackupRunning
+  public var startBackup: MessengerStartBackup
+  public var resumeBackup: MessengerResumeBackup
+  public var backupParams: MessengerBackupParams
+  public var stopBackup: MessengerStopBackup
 }
 
 extension Messenger {
@@ -37,6 +44,7 @@ extension Messenger {
       cMix: env.cMix,
       e2e: env.e2e,
       ud: env.ud,
+      backup: env.backup,
       isCreated: .live(env),
       create: .live(env),
       restoreBackup: .live(env),
@@ -61,7 +69,13 @@ extension Messenger {
       lookupContacts: .live(env),
       registerForNotifications: .live(env),
       verifyContact: .live(env),
-      sendMessage: .live(env)
+      sendMessage: .live(env),
+      registerBackupCallback: .live(env),
+      isBackupRunning: .live(env),
+      startBackup: .live(env),
+      resumeBackup: .live(env),
+      backupParams: .live(env),
+      stopBackup: .live(env)
     )
   }
 }
@@ -71,6 +85,7 @@ extension Messenger {
     cMix: .unimplemented(),
     e2e: .unimplemented(),
     ud: .unimplemented(),
+    backup: .unimplemented(),
     isCreated: .unimplemented,
     create: .unimplemented,
     restoreBackup: .unimplemented,
@@ -95,6 +110,12 @@ extension Messenger {
     lookupContacts: .unimplemented,
     registerForNotifications: .unimplemented,
     verifyContact: .unimplemented,
-    sendMessage: .unimplemented
+    sendMessage: .unimplemented,
+    registerBackupCallback: .unimplemented,
+    isBackupRunning: .unimplemented,
+    startBackup: .unimplemented,
+    resumeBackup: .unimplemented,
+    backupParams: .unimplemented,
+    stopBackup: .unimplemented
   )
 }
diff --git a/Sources/XXMessengerClient/Messenger/MessengerEnvironment.swift b/Sources/XXMessengerClient/Messenger/MessengerEnvironment.swift
index 641a8062d3f9ae136192af38b2814fe14cafccb9..f019df1618b0985f70936fdeb97e4558060a46eb 100644
--- a/Sources/XXMessengerClient/Messenger/MessengerEnvironment.swift
+++ b/Sources/XXMessengerClient/Messenger/MessengerEnvironment.swift
@@ -4,6 +4,8 @@ import XCTestDynamicOverlay
 
 public struct MessengerEnvironment {
   public var authCallbacks: AuthCallbacksRegistry
+  public var backup: Stored<Backup?>
+  public var backupCallbacks: BackupCallbacksRegistry
   public var cMix: Stored<CMix?>
   public var downloadNDF: DownloadAndVerifySignedNdf
   public var e2e: Stored<E2E?>
@@ -12,6 +14,7 @@ public struct MessengerEnvironment {
   public var getCMixParams: GetCMixParams
   public var getE2EParams: GetE2EParams
   public var getSingleUseParams: GetSingleUseParams
+  public var initializeBackup: InitializeBackup
   public var isListeningForMessages: Stored<Bool>
   public var isRegisteredWithUD: IsRegisteredWithUD
   public var loadCMix: LoadCMix
@@ -26,6 +29,7 @@ public struct MessengerEnvironment {
   public var newUdManagerFromBackup: NewUdManagerFromBackup
   public var passwordStorage: PasswordStorage
   public var registerForNotifications: RegisterForNotifications
+  public var resumeBackup: ResumeBackup
   public var searchUD: SearchUD
   public var sleep: (TimeInterval) -> Void
   public var storageDir: String
@@ -45,6 +49,8 @@ extension MessengerEnvironment {
   public static func live() -> MessengerEnvironment {
     MessengerEnvironment(
       authCallbacks: .live(),
+      backup: .inMemory(),
+      backupCallbacks: .live(),
       cMix: .inMemory(),
       downloadNDF: .live,
       e2e: .inMemory(),
@@ -53,6 +59,7 @@ extension MessengerEnvironment {
       getCMixParams: .liveDefault,
       getE2EParams: .liveDefault,
       getSingleUseParams: .liveDefault,
+      initializeBackup: .live,
       isListeningForMessages: .inMemory(false),
       isRegisteredWithUD: .live,
       loadCMix: .live,
@@ -67,6 +74,7 @@ extension MessengerEnvironment {
       newUdManagerFromBackup: .live,
       passwordStorage: .keychain,
       registerForNotifications: .live,
+      resumeBackup: .live,
       searchUD: .live,
       sleep: { Thread.sleep(forTimeInterval: $0) },
       storageDir: MessengerEnvironment.defaultStorageDir,
@@ -81,6 +89,8 @@ extension MessengerEnvironment {
 extension MessengerEnvironment {
   public static let unimplemented = MessengerEnvironment(
     authCallbacks: .unimplemented,
+    backup: .unimplemented(),
+    backupCallbacks: .unimplemented,
     cMix: .unimplemented(),
     downloadNDF: .unimplemented,
     e2e: .unimplemented(),
@@ -89,6 +99,7 @@ extension MessengerEnvironment {
     getCMixParams: .unimplemented,
     getE2EParams: .unimplemented,
     getSingleUseParams: .unimplemented,
+    initializeBackup: .unimplemented,
     isListeningForMessages: .unimplemented(placeholder: false),
     isRegisteredWithUD: .unimplemented,
     loadCMix: .unimplemented,
@@ -103,6 +114,7 @@ extension MessengerEnvironment {
     newUdManagerFromBackup: .unimplemented,
     passwordStorage: .unimplemented,
     registerForNotifications: .unimplemented,
+    resumeBackup: .unimplemented,
     searchUD: .unimplemented,
     sleep: XCTUnimplemented("\(Self.self).sleep"),
     storageDir: "unimplemented",
diff --git a/Sources/XXMessengerClient/Utils/BackupCallbackRegistry.swift b/Sources/XXMessengerClient/Utils/BackupCallbackRegistry.swift
new file mode 100644
index 0000000000000000000000000000000000000000..7c7fc9e8ae08a983f72a4240728610bab152f965
--- /dev/null
+++ b/Sources/XXMessengerClient/Utils/BackupCallbackRegistry.swift
@@ -0,0 +1,36 @@
+import Foundation
+import XCTestDynamicOverlay
+import XXClient
+
+public struct BackupCallbacksRegistry {
+  public var register: (UpdateBackupFunc) -> Cancellable
+  public var registered: () -> UpdateBackupFunc
+}
+
+extension BackupCallbacksRegistry {
+  public static func live() -> BackupCallbacksRegistry {
+    class Registry {
+      var callbacks: [UUID: UpdateBackupFunc] = [:]
+    }
+    let registry = Registry()
+    return BackupCallbacksRegistry(
+      register: { callback in
+        let id = UUID()
+        registry.callbacks[id] = callback
+        return Cancellable { registry.callbacks[id] = nil }
+      },
+      registered: {
+        UpdateBackupFunc { data in
+          registry.callbacks.values.forEach { $0.handle(data) }
+        }
+      }
+    )
+  }
+}
+
+extension BackupCallbacksRegistry {
+  public static let unimplemented = BackupCallbacksRegistry(
+    register: XCTUnimplemented("\(Self.self).register", placeholder: Cancellable {}),
+    registered: XCTUnimplemented("\(Self.self).registered", placeholder: UpdateBackupFunc { _ in })
+  )
+}
diff --git a/Sources/XXMessengerClient/Utils/BackupParams.swift b/Sources/XXMessengerClient/Utils/BackupParams.swift
index 02fdc595461cbcc14e617560b6f7b91b6b398fc2..c3bd8fc0f8e566348bb1d731bf8606c2f8bea5ce 100644
--- a/Sources/XXMessengerClient/Utils/BackupParams.swift
+++ b/Sources/XXMessengerClient/Utils/BackupParams.swift
@@ -1,6 +1,6 @@
 import Foundation
 
-public struct BackupParams: Equatable, Codable {
+public struct BackupParams: Equatable {
   public init(
     username: String,
     email: String?,
@@ -15,3 +15,13 @@ public struct BackupParams: Equatable, Codable {
   public var email: String?
   public var phone: String?
 }
+
+extension BackupParams: Codable {
+  public static func decode(_ data: Data) throws -> Self {
+    try JSONDecoder().decode(Self.self, from: data)
+  }
+
+  public func encode() throws -> Data {
+    try JSONEncoder().encode(self)
+  }
+}
diff --git a/Tests/XXMessengerClientTests/Messenger/Functions/MessengerBackupParamsTests.swift b/Tests/XXMessengerClientTests/Messenger/Functions/MessengerBackupParamsTests.swift
new file mode 100644
index 0000000000000000000000000000000000000000..e0af7129f1f82f9c5fbce7b67d2ea4c3ff05060e
--- /dev/null
+++ b/Tests/XXMessengerClientTests/Messenger/Functions/MessengerBackupParamsTests.swift
@@ -0,0 +1,62 @@
+import CustomDump
+import XCTest
+import XXClient
+@testable import XXMessengerClient
+
+final class MessengerBackupParamsTests: XCTestCase {
+  func testBackupParams() throws {
+    var didAddJSON: [String] = []
+
+    var env: MessengerEnvironment = .unimplemented
+    env.backup.get = {
+      var backup: Backup = .unimplemented
+      backup.isRunning.run = { true }
+      backup.addJSON.run = { didAddJSON.append($0) }
+      return backup
+    }
+    let backup: MessengerBackupParams = .live(env)
+    let params = BackupParams(
+      username: "test-username",
+      email: "test-email",
+      phone: "test-phone"
+    )
+
+    try backup(params)
+
+    XCTAssertNoDifference(didAddJSON, [
+      String(data: try params.encode(), encoding: .utf8)!
+    ])
+  }
+
+  func testBackupParamsWhenNoBackup() {
+    var env: MessengerEnvironment = .unimplemented
+    env.backup.get = { nil }
+    let backup: MessengerBackupParams = .live(env)
+    let params = BackupParams(username: "test", email: nil, phone: nil)
+
+    XCTAssertThrowsError(try backup(params)) { error in
+      XCTAssertNoDifference(
+        error as NSError,
+        MessengerBackupParams.Error.notRunning as NSError
+      )
+    }
+  }
+
+  func testBackupParamsWhenBackupNotRunning() {
+    var env: MessengerEnvironment = .unimplemented
+    env.backup.get = {
+      var backup: Backup = .unimplemented
+      backup.isRunning.run = { false }
+      return backup
+    }
+    let backup: MessengerBackupParams = .live(env)
+    let params = BackupParams(username: "test", email: nil, phone: nil)
+
+    XCTAssertThrowsError(try backup(params)) { error in
+      XCTAssertNoDifference(
+        error as NSError,
+        MessengerBackupParams.Error.notRunning as NSError
+      )
+    }
+  }
+}
diff --git a/Tests/XXMessengerClientTests/Messenger/Functions/MessengerIsBackupRunningTests.swift b/Tests/XXMessengerClientTests/Messenger/Functions/MessengerIsBackupRunningTests.swift
new file mode 100644
index 0000000000000000000000000000000000000000..7076aa648b4d287ece157cf56ae868000fdfe6ed
--- /dev/null
+++ b/Tests/XXMessengerClientTests/Messenger/Functions/MessengerIsBackupRunningTests.swift
@@ -0,0 +1,37 @@
+import XCTest
+import XXClient
+@testable import XXMessengerClient
+
+final class MessengerIsBackupRunningTests: XCTestCase {
+  func testWithoutBackup() {
+    var env: MessengerEnvironment = .unimplemented
+    env.backup.get = { nil }
+    let isRunning: MessengerIsBackupRunning = .live(env)
+
+    XCTAssertFalse(isRunning())
+  }
+
+  func testWithBackupRunning() {
+    var env: MessengerEnvironment = .unimplemented
+    env.backup.get = {
+      var backup: Backup = .unimplemented
+      backup.isRunning.run = { true }
+      return backup
+    }
+    let isRunning: MessengerIsBackupRunning = .live(env)
+
+    XCTAssertTrue(isRunning())
+  }
+
+  func testWithBackupNotRunning() {
+    var env: MessengerEnvironment = .unimplemented
+    env.backup.get = {
+      var backup: Backup = .unimplemented
+      backup.isRunning.run = { false }
+      return backup
+    }
+    let isRunning: MessengerIsBackupRunning = .live(env)
+
+    XCTAssertFalse(isRunning())
+  }
+}
diff --git a/Tests/XXMessengerClientTests/Messenger/Functions/MessengerRegisterBackupCallbackTests.swift b/Tests/XXMessengerClientTests/Messenger/Functions/MessengerRegisterBackupCallbackTests.swift
new file mode 100644
index 0000000000000000000000000000000000000000..44e7e21c2bbac9badafd94370a595fea209da2f9
--- /dev/null
+++ b/Tests/XXMessengerClientTests/Messenger/Functions/MessengerRegisterBackupCallbackTests.swift
@@ -0,0 +1,34 @@
+import CustomDump
+import XCTest
+import XXClient
+@testable import XXMessengerClient
+
+final class MessengerRegisterBackupCallbackTests: XCTestCase {
+  func testRegisterBackupCallback() {
+    var registeredCallbacks: [UpdateBackupFunc] = []
+    var didHandleData: [Data] = []
+    var didCancelRegisteredCallback = 0
+
+    var env: MessengerEnvironment = .unimplemented
+    env.backupCallbacks.register = { callback in
+      registeredCallbacks.append(callback)
+      return Cancellable { didCancelRegisteredCallback += 1 }
+    }
+    let registerBackupCallback: MessengerRegisterBackupCallback = .live(env)
+    let cancellable = registerBackupCallback(UpdateBackupFunc { data in
+      didHandleData.append(data)
+    })
+
+    XCTAssertEqual(registeredCallbacks.count, 1)
+
+    registeredCallbacks.forEach { callback in
+      callback.handle("test".data(using: .utf8)!)
+    }
+
+    XCTAssertNoDifference(didHandleData, ["test".data(using: .utf8)!])
+
+    cancellable.cancel()
+
+    XCTAssertEqual(didCancelRegisteredCallback, 1)
+  }
+}
diff --git a/Tests/XXMessengerClientTests/Messenger/Functions/MessengerResumeBackupTests.swift b/Tests/XXMessengerClientTests/Messenger/Functions/MessengerResumeBackupTests.swift
new file mode 100644
index 0000000000000000000000000000000000000000..7979ffbd7da561d8b3405e701d2bb84718e39ea1
--- /dev/null
+++ b/Tests/XXMessengerClientTests/Messenger/Functions/MessengerResumeBackupTests.swift
@@ -0,0 +1,126 @@
+import CustomDump
+import XCTest
+import XXClient
+@testable import XXMessengerClient
+
+final class MessengerResumeBackupTests: XCTestCase {
+  func testResume() throws {
+    struct ResumeBackupParams: Equatable {
+      var e2eId: Int
+      var udId: Int
+    }
+    var didResumeBackup: [ResumeBackupParams] = []
+    var backupCallbacks: [UpdateBackupFunc] = []
+    var didHandleCallback: [Data] = []
+    var didSetBackup: [Backup?] = []
+
+    let e2eId = 123
+    let udId = 321
+    let data = "test-data".data(using: .utf8)!
+
+    var env: MessengerEnvironment = .unimplemented
+    env.backup.get = { nil }
+    env.backup.set = { didSetBackup.append($0) }
+    env.e2e.get = {
+      var e2e: E2E = .unimplemented
+      e2e.getId.run = { e2eId }
+      return e2e
+    }
+    env.ud.get = {
+      var ud: UserDiscovery = .unimplemented
+      ud.getId.run = { udId }
+      return ud
+    }
+    env.backupCallbacks.registered = {
+      UpdateBackupFunc { didHandleCallback.append($0) }
+    }
+    env.resumeBackup.run = { e2eId, udId, callback in
+      didResumeBackup.append(.init(e2eId: e2eId, udId: udId))
+      backupCallbacks.append(callback)
+      return .unimplemented
+    }
+    let resume: MessengerResumeBackup = .live(env)
+
+    try resume()
+
+    XCTAssertNoDifference(didResumeBackup, [
+      .init(e2eId: e2eId, udId: udId)
+    ])
+    XCTAssertNoDifference(didSetBackup.map { $0 != nil }, [true])
+
+    backupCallbacks.forEach { $0.handle(data) }
+
+    XCTAssertNoDifference(didHandleCallback, [data])
+  }
+
+  func testResumeWhenRunning() {
+    var env: MessengerEnvironment = .unimplemented
+    env.backup.get = {
+      var backup: Backup = .unimplemented
+      backup.isRunning.run = { true }
+      return backup
+    }
+    let resume: MessengerResumeBackup = .live(env)
+
+    XCTAssertThrowsError(try resume()) { error in
+      XCTAssertNoDifference(
+        error as NSError,
+        MessengerResumeBackup.Error.isRunning as NSError
+      )
+    }
+  }
+
+  func testResumeWhenNotConnected() {
+    var env: MessengerEnvironment = .unimplemented
+    env.backup.get = { nil }
+    env.e2e.get = { nil }
+    let resume: MessengerResumeBackup = .live(env)
+
+    XCTAssertThrowsError(try resume()) { error in
+      XCTAssertNoDifference(
+        error as NSError,
+        MessengerResumeBackup.Error.notConnected as NSError
+      )
+    }
+  }
+
+  func testResumeWhenNotLoggedIn() {
+    var env: MessengerEnvironment = .unimplemented
+    env.backup.get = { nil }
+    env.e2e.get = { .unimplemented }
+    env.ud.get = { nil }
+    let resume: MessengerResumeBackup = .live(env)
+
+    XCTAssertThrowsError(try resume()) { error in
+      XCTAssertNoDifference(
+        error as NSError,
+        MessengerResumeBackup.Error.notLoggedIn as NSError
+      )
+    }
+  }
+
+  func testResumeFailure() {
+    struct Failure: Error, Equatable {}
+    let failure = Failure()
+
+    var env: MessengerEnvironment = .unimplemented
+    env.backup.get = { nil }
+    env.e2e.get = {
+      var e2e: E2E = .unimplemented
+      e2e.getId.run = { 123 }
+      return e2e
+    }
+    env.ud.get = {
+      var ud: UserDiscovery = .unimplemented
+      ud.getId.run = { 321 }
+      return ud
+    }
+    env.backupCallbacks.registered = { UpdateBackupFunc { _ in  } }
+    env.resumeBackup.run = { _, _ , _ in throw failure }
+    let resume: MessengerResumeBackup = .live(env)
+
+    XCTAssertThrowsError(try resume()) { error in
+      XCTAssertNoDifference(error as NSError, failure as NSError)
+    }
+  }
+}
diff --git a/Tests/XXMessengerClientTests/Messenger/Functions/MessengerStartBackupTests.swift b/Tests/XXMessengerClientTests/Messenger/Functions/MessengerStartBackupTests.swift
new file mode 100644
index 0000000000000000000000000000000000000000..b92bd1ee38948737453e973a8e4b4cd874797b84
--- /dev/null
+++ b/Tests/XXMessengerClientTests/Messenger/Functions/MessengerStartBackupTests.swift
@@ -0,0 +1,147 @@
+import CustomDump
+import XCTest
+import XXClient
+@testable import XXMessengerClient
+
+final class MessengerStartBackupTests: XCTestCase {
+  func testStart() throws {
+    struct InitBackupParams: Equatable {
+      var e2eId: Int
+      var udId: Int
+      var password: String
+    }
+    var didInitializeBackup: [InitBackupParams] = []
+    var backupCallbacks: [UpdateBackupFunc] = []
+    var didHandleCallback: [Data] = []
+    var didSetBackup: [Backup?] = []
+    var didAddJSON: [String] = []
+
+    let password = "test-password"
+    let e2eId = 123
+    let udId = 321
+    let dataWithoutParams = "backup-without-params".data(using: .utf8)!
+    let dataWithParams = "backup-with-params".data(using: .utf8)!
+
+    var env: MessengerEnvironment = .unimplemented
+    env.backup.get = { didSetBackup.last as? Backup }
+    env.backup.set = { didSetBackup.append($0) }
+    env.e2e.get = {
+      var e2e: E2E = .unimplemented
+      e2e.getId.run = { e2eId }
+      return e2e
+    }
+    env.ud.get = {
+      var ud: UserDiscovery = .unimplemented
+      ud.getId.run = { udId }
+      return ud
+    }
+    env.backupCallbacks.registered = {
+      UpdateBackupFunc { didHandleCallback.append($0) }
+    }
+    env.initializeBackup.run = { e2eId, udId, password, callback in
+      didInitializeBackup.append(.init(e2eId: e2eId, udId: udId, password: password))
+      backupCallbacks.append(callback)
+      var backup: Backup = .unimplemented
+      backup.addJSON.run = { string in
+        didAddJSON.append(string)
+      }
+      return backup
+    }
+    let start: MessengerStartBackup = .live(env)
+
+    try start(password: password, params: .stub)
+
+    XCTAssertNoDifference(didInitializeBackup, [
+      .init(e2eId: e2eId, udId: udId, password: password)
+    ])
+    XCTAssertNoDifference(didSetBackup.map { $0 != nil }, [true])
+
+    backupCallbacks.forEach { $0.handle(dataWithoutParams) }
+
+    XCTAssertNoDifference(
+      didHandleCallback.map(StringData.init),
+      [].map(StringData.init)
+    )
+    XCTAssertNoDifference(didAddJSON, [
+      String(data: try BackupParams.stub.encode(), encoding: .utf8)!
+    ])
+
+    backupCallbacks.forEach { $0.handle(dataWithParams) }
+
+    XCTAssertNoDifference(
+      didHandleCallback.map(StringData.init),
+      [dataWithParams].map(StringData.init)
+    )
+  }
+
+  func testStartWhenRunning() {
+    var env: MessengerEnvironment = .unimplemented
+    env.backup.get = {
+      var backup: Backup = .unimplemented
+      backup.isRunning.run = { true }
+      return backup
+    }
+    let start: MessengerStartBackup = .live(env)
+
+    XCTAssertThrowsError(try start(password: "", params: .stub)) { error in
+      XCTAssertNoDifference(
+        error as NSError,
+        MessengerStartBackup.Error.isRunning as NSError
+      )
+    }
+  }
+
+  func testStartWhenNotConnected() {
+    var env: MessengerEnvironment = .unimplemented
+    env.backup.get = { nil }
+    env.e2e.get = { nil }
+    let start: MessengerStartBackup = .live(env)
+
+    XCTAssertThrowsError(try start(password: "", params: .stub)) { error in
+      XCTAssertNoDifference(
+        error as NSError,
+        MessengerStartBackup.Error.notConnected as NSError
+      )
+    }
+  }
+
+  func testStartWhenNotLoggedIn() {
+    var env: MessengerEnvironment = .unimplemented
+    env.backup.get = { nil }
+    env.e2e.get = { .unimplemented }
+    env.ud.get = { nil }
+    let start: MessengerStartBackup = .live(env)
+
+    XCTAssertThrowsError(try start(password: "", params: .stub)) { error in
+      XCTAssertNoDifference(
+        error as NSError,
+        MessengerStartBackup.Error.notLoggedIn as NSError
+      )
+    }
+  }
+
+  func testStartFailure() {
+    struct Failure: Error, Equatable {}
+    let failure = Failure()
+
+    var env: MessengerEnvironment = .unimplemented
+    env.backup.get = { nil }
+    env.e2e.get = {
+      var e2e: E2E = .unimplemented
+      e2e.getId.run = { 123 }
+      return e2e
+    }
+    env.ud.get = {
+      var ud: UserDiscovery = .unimplemented
+      ud.getId.run = { 321 }
+      return ud
+    }
+    env.backupCallbacks.registered = { UpdateBackupFunc { _ in  } }
+    env.initializeBackup.run = { _, _, _, _ in throw failure }
+    let start: MessengerStartBackup = .live(env)
+
+    XCTAssertThrowsError(try start(password: "", params: .stub)) { error in
+      XCTAssertNoDifference(error as NSError, failure as NSError)
+    }
+  }
+}
diff --git a/Tests/XXMessengerClientTests/Messenger/Functions/MessengerStopBackupTests.swift b/Tests/XXMessengerClientTests/Messenger/Functions/MessengerStopBackupTests.swift
new file mode 100644
index 0000000000000000000000000000000000000000..306b6c2d1d6b6507cd0bd83071d54ad99fd2e8f7
--- /dev/null
+++ b/Tests/XXMessengerClientTests/Messenger/Functions/MessengerStopBackupTests.swift
@@ -0,0 +1,53 @@
+import CustomDump
+import XCTest
+import XXClient
+@testable import XXMessengerClient
+
+final class MessengerStopBackupTests: XCTestCase {
+  func testStop() throws {
+    var didStopBackup = 0
+    var didSetBackup: [Backup?] = []
+
+    var env: MessengerEnvironment = .unimplemented
+    env.backup.get = {
+      var backup: Backup = .unimplemented
+      backup.stop.run = { didStopBackup += 1 }
+      return backup
+    }
+    env.backup.set = { backup in
+      didSetBackup.append(backup)
+    }
+    let stop: MessengerStopBackup = .live(env)
+
+    try stop()
+
+    XCTAssertEqual(didStopBackup, 1)
+    XCTAssertEqual(didSetBackup.count, 1)
+    XCTAssertNil(didSetBackup.first as? Backup)
+  }
+
+  func testStopFailure() {
+    struct Failure: Error, Equatable {}
+    let failure = Failure()
+
+    var env: MessengerEnvironment = .unimplemented
+    env.backup.get = {
+      var backup: Backup = .unimplemented
+      backup.stop.run = { throw failure }
+      return backup
+    }
+    let stop: MessengerStopBackup = .live(env)
+
+    XCTAssertThrowsError(try stop()) { error in
+      XCTAssertNoDifference(error as NSError, failure as NSError)
+    }
+  }
+
+  func testStopWithoutBackup() throws {
+    var env: MessengerEnvironment = .unimplemented
+    env.backup.get = { nil }
+    let stop: MessengerStopBackup = .live(env)
+
+    try stop()
+  }
+}
diff --git a/Tests/XXMessengerClientTests/TestHelpers/StringData.swift b/Tests/XXMessengerClientTests/TestHelpers/StringData.swift
new file mode 100644
index 0000000000000000000000000000000000000000..6987a4ea385989f6823e701b0e00002b036f0f00
--- /dev/null
+++ b/Tests/XXMessengerClientTests/TestHelpers/StringData.swift
@@ -0,0 +1,14 @@
+import CustomDump
+import Foundation
+
+struct StringData: Equatable, CustomDumpStringConvertible {
+  var data: Data
+
+  var customDumpDescription: String {
+    if let string = String(data: data, encoding: .utf8) {
+      return #"Data(string: "\#(string)", encoding: .utf8)"#
+    } else {
+      return data.customDumpDescription
+    }
+  }
+}
diff --git a/Tests/XXMessengerClientTests/TestHelpers/Message+stubs.swift b/Tests/XXMessengerClientTests/TestHelpers/TestDoubles.swift
similarity index 74%
rename from Tests/XXMessengerClientTests/TestHelpers/Message+stubs.swift
rename to Tests/XXMessengerClientTests/TestHelpers/TestDoubles.swift
index e10bea4a6298544c2e4c4d01ba267a00d2dcc167..4d57e29363d3f22bdbb836622d7bfa92b014b683 100644
--- a/Tests/XXMessengerClientTests/TestHelpers/Message+stubs.swift
+++ b/Tests/XXMessengerClientTests/TestHelpers/TestDoubles.swift
@@ -1,4 +1,5 @@
 import XXClient
+import XXMessengerClient
 
 extension Message {
   static func stub(_ stubId: Int) -> Message {
@@ -16,3 +17,11 @@ extension Message {
     )
   }
 }
+
+extension BackupParams {
+  static let stub = BackupParams(
+    username: "stub-username",
+    email: "stub-email",
+    phone: "stub-phone"
+  )
+}
diff --git a/Tests/XXMessengerClientTests/Utils/BackupCallbackRegistryTests.swift b/Tests/XXMessengerClientTests/Utils/BackupCallbackRegistryTests.swift
new file mode 100644
index 0000000000000000000000000000000000000000..d3cba50cb81fe1efb2188985fed7ef9a8470290d
--- /dev/null
+++ b/Tests/XXMessengerClientTests/Utils/BackupCallbackRegistryTests.swift
@@ -0,0 +1,43 @@
+import CustomDump
+import XCTest
+import XXClient
+@testable import XXMessengerClient
+
+final class BackupCallbackRegistryTests: XCTestCase {
+  func testRegistry() {
+    var firstCallbackDidHandle: [Data] = []
+    var secondCallbackDidHandle: [Data] = []
+
+    let firstCallback = UpdateBackupFunc { data in
+      firstCallbackDidHandle.append(data)
+    }
+    let secondCallback = UpdateBackupFunc { data in
+      secondCallbackDidHandle.append(data)
+    }
+    let callbackRegistry: BackupCallbacksRegistry = .live()
+    let registeredCallbacks = callbackRegistry.registered()
+    let firstCallbackCancellable = callbackRegistry.register(firstCallback)
+    let secondCallbackCancellable = callbackRegistry.register(secondCallback)
+
+    let firstData = "1".data(using: .utf8)!
+    registeredCallbacks.handle(firstData)
+
+    XCTAssertNoDifference(firstCallbackDidHandle, [firstData])
+    XCTAssertNoDifference(secondCallbackDidHandle, [firstData])
+
+    firstCallbackCancellable.cancel()
+    let secondData = "2".data(using: .utf8)!
+    registeredCallbacks.handle(secondData)
+
+    XCTAssertNoDifference(firstCallbackDidHandle, [firstData])
+    XCTAssertNoDifference(secondCallbackDidHandle, [firstData, secondData])
+
+    secondCallbackCancellable.cancel()
+
+    let thirdData = "3".data(using: .utf8)!
+    registeredCallbacks.handle(thirdData)
+
+    XCTAssertNoDifference(firstCallbackDidHandle, [firstData])
+    XCTAssertNoDifference(secondCallbackDidHandle, [firstData, secondData])
+  }
+}