diff --git a/Docs/XXMessengerClient.md b/Docs/XXMessengerClient.md
index c8fc3e7dd8b6ba45738f1e0914fa758f6b5a0d28..05afa4aaa33cb74d114d0548faf8c157e00678ce 100644
--- a/Docs/XXMessengerClient.md
+++ b/Docs/XXMessengerClient.md
@@ -27,8 +27,9 @@ let messenger: Messenger = .live(environment)
 Example:
 
 ```swift
-// allow cancellation of auth callbacks registration:
+// allow cancellation of callbacks:
 var authCallbacksCancellable: Cancellable?
+var messageListenerCancellable: Cancellable?
 
 func start(messenger: Messenger) throws {
   // check if messenger is loaded:
@@ -51,11 +52,20 @@ func start(messenger: Messenger) throws {
       // implement auth callbacks handling
     })
   )
+  
+  // register message listener before connecting:
+  messageListenerCancellable = messenger.registerMessageListener(
+  	Listener(handle: { message in
+  	  // handle incoming message
+  	})
+  )
 
   // check if messenger is connected:
   if messenger.isConnected() == false {
     // start end-to-end connection:
     try messenger.connect()
+    // start listening for messanges:
+    try messener.listenForMessages()
   }
 
   // check if messenger is logged in with user-discovery:
diff --git a/Sources/XXMessengerClient/Messenger/Functions/MessengerListenForMessages.swift b/Sources/XXMessengerClient/Messenger/Functions/MessengerListenForMessages.swift
new file mode 100644
index 0000000000000000000000000000000000000000..90fa02dab3cf84d85e50e3d696fe0a5a226d4884
--- /dev/null
+++ b/Sources/XXMessengerClient/Messenger/Functions/MessengerListenForMessages.swift
@@ -0,0 +1,35 @@
+import XCTestDynamicOverlay
+import XXClient
+
+public struct MessengerListenForMessages {
+  public enum Error: Swift.Error {
+    case notConnected
+  }
+
+  public var run: () throws -> Void
+
+  public func callAsFunction() throws -> Void {
+    try run()
+  }
+}
+
+extension MessengerListenForMessages {
+  public static func live(_ env: MessengerEnvironment) -> MessengerListenForMessages {
+    MessengerListenForMessages {
+      guard let e2e = env.e2e() else {
+        throw Error.notConnected
+      }
+      try e2e.registerListener(
+        senderId: nil,
+        messageType: 2,
+        callback: env.messageListeners.registered()
+      )
+    }
+  }
+}
+
+extension MessengerListenForMessages {
+  public static let unimplemented = MessengerListenForMessages(
+    run: XCTUnimplemented("\(Self.self)")
+  )
+}
diff --git a/Sources/XXMessengerClient/Messenger/Functions/MessengerRegisterMessageListener.swift b/Sources/XXMessengerClient/Messenger/Functions/MessengerRegisterMessageListener.swift
new file mode 100644
index 0000000000000000000000000000000000000000..666e26345e03b3be620959a37333bba566d991ad
--- /dev/null
+++ b/Sources/XXMessengerClient/Messenger/Functions/MessengerRegisterMessageListener.swift
@@ -0,0 +1,24 @@
+import XCTestDynamicOverlay
+import XXClient
+
+public struct MessengerRegisterMessageListener {
+  public var run: (Listener) -> Cancellable
+
+  public func callAsFunction(_ listener: Listener) -> Cancellable {
+    run(listener)
+  }
+}
+
+extension MessengerRegisterMessageListener {
+  public static func live(_ env: MessengerEnvironment) -> MessengerRegisterMessageListener {
+    MessengerRegisterMessageListener { listener in
+      env.messageListeners.register(listener)
+    }
+  }
+}
+
+extension MessengerRegisterMessageListener {
+  public static let unimplemented = MessengerRegisterMessageListener(
+    run: XCTUnimplemented("\(Self.self)", placeholder: Cancellable {})
+  )
+}
diff --git a/Sources/XXMessengerClient/Messenger/Functions/MessengerSendMessage.swift b/Sources/XXMessengerClient/Messenger/Functions/MessengerSendMessage.swift
new file mode 100644
index 0000000000000000000000000000000000000000..118ca046b341bd4699be6e0066c327243c9ffb37
--- /dev/null
+++ b/Sources/XXMessengerClient/Messenger/Functions/MessengerSendMessage.swift
@@ -0,0 +1,85 @@
+import Foundation
+import XCTestDynamicOverlay
+import XXClient
+
+public struct MessengerSendMessage {
+  public struct DeliveryReport: Equatable {
+    public enum Result: Equatable {
+      case delivered
+      case notDelivered(timedOut: Bool)
+      case failure(NSError)
+    }
+
+    public init(
+      report: E2ESendReport,
+      result: Result
+    ) {
+      self.report = report
+      self.result = result
+    }
+
+    public var report: E2ESendReport
+    public var result: Result
+  }
+
+  public typealias DeliveryCallback = (DeliveryReport) -> Void
+
+  public enum Error: Swift.Error, Equatable {
+    case notLoaded
+    case notConnected
+  }
+
+  public var run: (Data, Data, DeliveryCallback?) throws -> E2ESendReport
+
+  public func callAsFunction(
+    recipientId: Data,
+    payload: Data,
+    deliveryCallback: DeliveryCallback?
+  ) throws -> E2ESendReport {
+    try run(recipientId, payload, deliveryCallback)
+  }
+}
+
+extension MessengerSendMessage {
+  public static func live(_ env: MessengerEnvironment) -> MessengerSendMessage {
+    MessengerSendMessage { recipientId, payload, deliveryCallback in
+      guard let cMix = env.cMix() else {
+        throw Error.notLoaded
+      }
+      guard let e2e = env.e2e() else {
+        throw Error.notConnected
+      }
+      let report = try e2e.send(
+        messageType: 2,
+        recipientId: recipientId,
+        payload: payload,
+        e2eParams: env.getE2EParams()
+      )
+      if let deliveryCallback = deliveryCallback {
+        do {
+          try cMix.waitForRoundResult(
+            roundList: try report.encode(),
+            timeoutMS: 30_000,
+            callback: .init { result in
+              switch result {
+              case .delivered(_):
+                deliveryCallback(.init(report: report, result: .delivered))
+              case .notDelivered(let timedOut):
+                deliveryCallback(.init(report: report, result: .notDelivered(timedOut: timedOut)))
+              }
+            }
+          )
+        } catch {
+          deliveryCallback(.init(report: report, result: .failure(error as NSError)))
+        }
+      }
+      return report
+    }
+  }
+}
+
+extension MessengerSendMessage {
+  public static let unimplemented = MessengerSendMessage(
+    run: XCTUnimplemented("\(Self.self)")
+  )
+}
diff --git a/Sources/XXMessengerClient/Messenger/Messenger.swift b/Sources/XXMessengerClient/Messenger/Messenger.swift
index e2d31f69350c83022d18d8b5dd3698eddff59255..4995f9a14c02e1b1b0c5fe8181d02cb0f689704c 100644
--- a/Sources/XXMessengerClient/Messenger/Messenger.swift
+++ b/Sources/XXMessengerClient/Messenger/Messenger.swift
@@ -9,9 +9,11 @@ public struct Messenger {
   public var isLoaded: MessengerIsLoaded
   public var load: MessengerLoad
   public var registerAuthCallbacks: MessengerRegisterAuthCallbacks
+  public var registerMessageListener: MessengerRegisterMessageListener
   public var start: MessengerStart
   public var isConnected: MessengerIsConnected
   public var connect: MessengerConnect
+  public var listenForMessages: MessengerListenForMessages
   public var isRegistered: MessengerIsRegistered
   public var register: MessengerRegister
   public var isLoggedIn: MessengerIsLoggedIn
@@ -22,6 +24,7 @@ public struct Messenger {
   public var searchUsers: MessengerSearchUsers
   public var registerForNotifications: MessengerRegisterForNotifications
   public var verifyContact: MessengerVerifyContact
+  public var sendMessage: MessengerSendMessage
 }
 
 extension Messenger {
@@ -35,9 +38,11 @@ extension Messenger {
       isLoaded: .live(env),
       load: .live(env),
       registerAuthCallbacks: .live(env),
+      registerMessageListener: .live(env),
       start: .live(env),
       isConnected: .live(env),
       connect: .live(env),
+      listenForMessages: .live(env),
       isRegistered: .live(env),
       register: .live(env),
       isLoggedIn: .live(env),
@@ -47,7 +52,8 @@ extension Messenger {
       destroy: .live(env),
       searchUsers: .live(env),
       registerForNotifications: .live(env),
-      verifyContact: .live(env)
+      verifyContact: .live(env),
+      sendMessage: .live(env)
     )
   }
 }
@@ -62,9 +68,11 @@ extension Messenger {
     isLoaded: .unimplemented,
     load: .unimplemented,
     registerAuthCallbacks: .unimplemented,
+    registerMessageListener: .unimplemented,
     start: .unimplemented,
     isConnected: .unimplemented,
     connect: .unimplemented,
+    listenForMessages: .unimplemented,
     isRegistered: .unimplemented,
     register: .unimplemented,
     isLoggedIn: .unimplemented,
@@ -74,6 +82,7 @@ extension Messenger {
     destroy: .unimplemented,
     searchUsers: .unimplemented,
     registerForNotifications: .unimplemented,
-    verifyContact: .unimplemented
+    verifyContact: .unimplemented,
+    sendMessage: .unimplemented
   )
 }
diff --git a/Sources/XXMessengerClient/Messenger/MessengerEnvironment.swift b/Sources/XXMessengerClient/Messenger/MessengerEnvironment.swift
index 9c4837748fa35628fdfd87bdbcc0e746d745f77e..b4ff607ed6238450cdbf05aec918376c833060cb 100644
--- a/Sources/XXMessengerClient/Messenger/MessengerEnvironment.swift
+++ b/Sources/XXMessengerClient/Messenger/MessengerEnvironment.swift
@@ -16,6 +16,7 @@ public struct MessengerEnvironment {
   public var loadCMix: LoadCMix
   public var login: Login
   public var lookupUD: LookupUD
+  public var messageListeners: ListenersRegistry
   public var ndfEnvironment: NDFEnvironment
   public var newCMix: NewCMix
   public var newOrLoadUd: NewOrLoadUd
@@ -52,6 +53,7 @@ extension MessengerEnvironment {
       loadCMix: .live,
       login: .live,
       lookupUD: .live,
+      messageListeners: .live(),
       ndfEnvironment: .mainnet,
       newCMix: .live,
       newOrLoadUd: .live,
@@ -83,6 +85,7 @@ extension MessengerEnvironment {
     loadCMix: .unimplemented,
     login: .unimplemented,
     lookupUD: .unimplemented,
+    messageListeners: .unimplemented,
     ndfEnvironment: .unimplemented,
     newCMix: .unimplemented,
     newOrLoadUd: .unimplemented,
diff --git a/Sources/XXMessengerClient/Utils/AuthCallbacksRegistry.swift b/Sources/XXMessengerClient/Utils/AuthCallbacksRegistry.swift
index d0c50b8149c77ecd54e208858d895285c6d7408c..1f85ad1763201ce553281ac65ff5b56af03d128e 100644
--- a/Sources/XXMessengerClient/Utils/AuthCallbacksRegistry.swift
+++ b/Sources/XXMessengerClient/Utils/AuthCallbacksRegistry.swift
@@ -1,6 +1,6 @@
-import XXClient
-import XCTestDynamicOverlay
 import Foundation
+import XCTestDynamicOverlay
+import XXClient
 
 public struct AuthCallbacksRegistry {
   public var register: (AuthCallbacks) -> Cancellable
diff --git a/Sources/XXMessengerClient/Utils/ListenersRegistry.swift b/Sources/XXMessengerClient/Utils/ListenersRegistry.swift
new file mode 100644
index 0000000000000000000000000000000000000000..6378e3b00295483ec3cc14607ae0a90d8e7e632d
--- /dev/null
+++ b/Sources/XXMessengerClient/Utils/ListenersRegistry.swift
@@ -0,0 +1,39 @@
+import Foundation
+import XCTestDynamicOverlay
+import XXClient
+
+public struct ListenersRegistry {
+  public var register: (Listener) -> Cancellable
+  public var registered: () -> Listener
+}
+
+extension ListenersRegistry {
+  public static func live() -> ListenersRegistry {
+    class Registry {
+      var listeners: [UUID: Listener] = [:]
+    }
+    let registry = Registry()
+    return ListenersRegistry(
+      register: { listener in
+        let id = UUID()
+        registry.listeners[id] = listener
+        return Cancellable { registry.listeners[id] = nil }
+      },
+      registered: {
+        Listener(name: "listeners-registry") { message in
+          registry.listeners.values.forEach { $0.handle(message) }
+        }
+      }
+    )
+  }
+}
+
+extension ListenersRegistry {
+  public static let unimplemented = ListenersRegistry(
+    register: XCTUnimplemented("\(Self.self).register", placeholder: Cancellable {}),
+    registered: XCTUnimplemented("\(Self.self).registered", placeholder: Listener(
+      name: "unimplemented",
+      handle: { _ in }
+    ))
+  )
+}
diff --git a/Tests/XXMessengerClientTests/Messenger/Functions/MessengerListenForMessagesTests.swift b/Tests/XXMessengerClientTests/Messenger/Functions/MessengerListenForMessagesTests.swift
new file mode 100644
index 0000000000000000000000000000000000000000..948ee33e3d668fdb0eff4a4cfad72f82b908c395
--- /dev/null
+++ b/Tests/XXMessengerClientTests/Messenger/Functions/MessengerListenForMessagesTests.swift
@@ -0,0 +1,69 @@
+import CustomDump
+import XCTest
+import XXClient
+@testable import XXMessengerClient
+
+final class MessengerListenForMessagesTests: XCTestCase {
+  func testListen() throws {
+    struct RegisterListenerParams: Equatable {
+      var senderId: Data?
+      var messageType: Int
+    }
+    var didRegisterListenerWithParams: [RegisterListenerParams] = []
+    var didRegisterListenerWithCallback: [Listener] = []
+    var didHandleMessage: [Message] = []
+
+    var env: MessengerEnvironment = .unimplemented
+    env.e2e.get = {
+      var e2e: E2E = .unimplemented
+      e2e.registerListener.run = { senderId, messageType, callback in
+        didRegisterListenerWithParams.append(.init(senderId: senderId, messageType: messageType))
+        didRegisterListenerWithCallback.append(callback)
+      }
+      return e2e
+    }
+    env.messageListeners.registered = {
+      Listener { message in didHandleMessage.append(message) }
+    }
+    let listen: MessengerListenForMessages = .live(env)
+
+    try listen()
+
+    XCTAssertNoDifference(didRegisterListenerWithParams, [
+      .init(senderId: nil, messageType: 2)
+    ])
+
+    let message = Message.stub(123)
+    didRegisterListenerWithCallback.first?.handle(message)
+
+    XCTAssertNoDifference(didHandleMessage, [message])
+  }
+
+  func testListenWhenNotLoggedIn() {
+    var env: MessengerEnvironment = .unimplemented
+    env.e2e.get = { nil }
+    let listen: MessengerListenForMessages = .live(env)
+
+    XCTAssertThrowsError(try listen()) { error in
+      XCTAssertNoDifference(error as? MessengerListenForMessages.Error, .notConnected)
+    }
+  }
+
+  func testListenFailure() {
+    struct Failure: Error, Equatable {}
+    let error = Failure()
+
+    var env: MessengerEnvironment = .unimplemented
+    env.e2e.get = {
+      var e2e: E2E = .unimplemented
+      e2e.registerListener.run = { _, _, _ in throw error }
+      return e2e
+    }
+    env.messageListeners.registered = { Listener.unimplemented }
+    let listen: MessengerListenForMessages = .live(env)
+
+    XCTAssertThrowsError(try listen()) { err in
+      XCTAssertNoDifference(err as? Failure, error)
+    }
+  }
+}
diff --git a/Tests/XXMessengerClientTests/Messenger/Functions/MessengerRegisterAuthCallbacksTests.swift b/Tests/XXMessengerClientTests/Messenger/Functions/MessengerRegisterAuthCallbacksTests.swift
index d7903e2f1b73e03dbe8268561c1ef586bf8a4583..17b1b94dd4e77347f7959960f1cc286d3d9198c7 100644
--- a/Tests/XXMessengerClientTests/Messenger/Functions/MessengerRegisterAuthCallbacksTests.swift
+++ b/Tests/XXMessengerClientTests/Messenger/Functions/MessengerRegisterAuthCallbacksTests.swift
@@ -1,7 +1,7 @@
+import CustomDump
 import XCTest
 import XXClient
 @testable import XXMessengerClient
-import CustomDump
 
 final class MessengerRegisterAuthCallbacksTests: XCTestCase {
   func testRegisterAuthCallbacks() {
diff --git a/Tests/XXMessengerClientTests/Messenger/Functions/MessengerRegisterMessageListenerTests.swift b/Tests/XXMessengerClientTests/Messenger/Functions/MessengerRegisterMessageListenerTests.swift
new file mode 100644
index 0000000000000000000000000000000000000000..0deedefba66819555f25a883185f52856d7aead1
--- /dev/null
+++ b/Tests/XXMessengerClientTests/Messenger/Functions/MessengerRegisterMessageListenerTests.swift
@@ -0,0 +1,34 @@
+import CustomDump
+import XCTest
+import XXClient
+@testable import XXMessengerClient
+
+final class MessengerRegisterMessageListenerTests: XCTestCase {
+  func testRegisterAuthCallbacks() {
+    var registeredListeners: [Listener] = []
+    var didHandleMessage: [Message] = []
+    var didCancelRegisteredListener = 0
+
+    var env: MessengerEnvironment = .unimplemented
+    env.messageListeners.register = { listener in
+      registeredListeners.append(listener)
+      return Cancellable { didCancelRegisteredListener += 1 }
+    }
+    let registerMessageListener: MessengerRegisterMessageListener = .live(env)
+    let cancellable = registerMessageListener(Listener { message in
+      didHandleMessage.append(message)
+    })
+
+    XCTAssertEqual(registeredListeners.count, 1)
+
+    registeredListeners.forEach { listener in
+      listener.handle(Message.stub(123))
+    }
+
+    XCTAssertNoDifference(didHandleMessage, [Message.stub(123)])
+
+    cancellable.cancel()
+
+    XCTAssertEqual(didCancelRegisteredListener, 1)
+  }
+}
diff --git a/Tests/XXMessengerClientTests/Messenger/Functions/MessengerSendMessageTests.swift b/Tests/XXMessengerClientTests/Messenger/Functions/MessengerSendMessageTests.swift
new file mode 100644
index 0000000000000000000000000000000000000000..c429c74218d28bfd69c2c58bb6cb3e8d623c6f87
--- /dev/null
+++ b/Tests/XXMessengerClientTests/Messenger/Functions/MessengerSendMessageTests.swift
@@ -0,0 +1,233 @@
+import CustomDump
+import XCTest
+import XXClient
+@testable import XXMessengerClient
+
+final class MessengerSendMessageTests: XCTestCase {
+  func testSend() throws {
+    struct E2ESendParams: Equatable {
+      var messageType: Int
+      var recipientId: Data
+      var payload: Data
+      var e2eParams: Data
+    }
+    var e2eDidSendWithParams: [E2ESendParams] = []
+
+    let e2eSendReport = E2ESendReport(
+      rounds: [1, 2, 3],
+      roundURL: "round-url",
+      messageId: "message-id".data(using: .utf8)!,
+      timestamp: 123,
+      keyResidue: "key-residue".data(using: .utf8)!
+    )
+
+    var env: MessengerEnvironment = .unimplemented
+    env.getE2EParams.run = { "e2e-params".data(using: .utf8)! }
+    env.cMix.get = { .unimplemented }
+    env.e2e.get = {
+      var e2e: E2E = .unimplemented
+      e2e.send.run = { messageType, recipientId, payload, e2eParams in
+        e2eDidSendWithParams.append(.init(
+          messageType: messageType,
+          recipientId: recipientId,
+          payload: payload,
+          e2eParams: e2eParams
+        ))
+        return e2eSendReport
+      }
+      return e2e
+    }
+    let send: MessengerSendMessage = .live(env)
+
+    let report = try send(
+      recipientId: "recipient-id".data(using: .utf8)!,
+      payload: "payload".data(using: .utf8)!,
+      deliveryCallback: nil
+    )
+
+    XCTAssertNoDifference(e2eDidSendWithParams, [.init(
+      messageType: 2,
+      recipientId: "recipient-id".data(using: .utf8)!,
+      payload: "payload".data(using: .utf8)!,
+      e2eParams: "e2e-params".data(using: .utf8)!
+    )])
+
+    XCTAssertNoDifference(report, e2eSendReport)
+  }
+
+  func testSendWithDeliveryCallback() throws {
+    struct WaitForRoundResultsParams: Equatable {
+      var roundList: Data
+      var timeoutMS: Int
+    }
+    var didWaitForRoundResultsWithParams: [WaitForRoundResultsParams] = []
+    var didWaitForRoundResultsWithCallback: [MessageDeliveryCallback] = []
+    var didReceiveDeliveryReport: [MessengerSendMessage.DeliveryReport] = []
+
+    let e2eSendReport = E2ESendReport(
+      rounds: [1, 2, 3],
+      roundURL: "round-url",
+      messageId: "message-id".data(using: .utf8)!,
+      timestamp: 123,
+      keyResidue: "key-residue".data(using: .utf8)!
+    )
+
+    var env: MessengerEnvironment = .unimplemented
+    env.getE2EParams.run = { "e2e-params".data(using: .utf8)! }
+    env.cMix.get = {
+      var cMix: CMix = .unimplemented
+      cMix.waitForRoundResult.run = { roundList, timeoutMS, callback in
+        didWaitForRoundResultsWithParams.append(.init(roundList: roundList, timeoutMS: timeoutMS))
+        didWaitForRoundResultsWithCallback.append(callback)
+      }
+      return cMix
+    }
+    env.e2e.get = {
+      var e2e: E2E = .unimplemented
+      e2e.send.run = { _, _, _, _ in e2eSendReport }
+      return e2e
+    }
+    let send: MessengerSendMessage = .live(env)
+
+    let report = try send(
+      recipientId: "recipient-id".data(using: .utf8)!,
+      payload: "payload".data(using: .utf8)!,
+      deliveryCallback: .init { deliveryReport in
+        didReceiveDeliveryReport.append(deliveryReport)
+      }
+    )
+
+    XCTAssertNoDifference(report, e2eSendReport)
+
+    XCTAssertNoDifference(didWaitForRoundResultsWithParams, [
+      .init(roundList: try! e2eSendReport.encode(), timeoutMS: 30_000),
+    ])
+
+    didWaitForRoundResultsWithCallback.first?.handle(.delivered(roundResults: [1, 2, 3]))
+
+    XCTAssertNoDifference(didReceiveDeliveryReport, [
+      .init(report: report, result: .delivered)
+    ])
+
+    didReceiveDeliveryReport.removeAll()
+    didWaitForRoundResultsWithCallback.first?.handle(.notDelivered(timedOut: false))
+
+    XCTAssertNoDifference(didReceiveDeliveryReport, [
+      .init(report: report, result: .notDelivered(timedOut: false))
+    ])
+
+    didReceiveDeliveryReport.removeAll()
+    didWaitForRoundResultsWithCallback.first?.handle(.notDelivered(timedOut: true))
+
+    XCTAssertNoDifference(didReceiveDeliveryReport, [
+      .init(report: report, result: .notDelivered(timedOut: true))
+    ])
+  }
+
+  func testSendWhenNotLoaded() {
+    var env: MessengerEnvironment = .unimplemented
+    env.cMix.get = { nil }
+    let send: MessengerSendMessage = .live(env)
+
+    XCTAssertThrowsError(
+      try send(
+        recipientId: Data(),
+        payload: Data(),
+        deliveryCallback: nil
+      )
+    ) { error in
+      XCTAssertNoDifference(
+        error as? MessengerSendMessage.Error,
+        .notLoaded
+      )
+    }
+  }
+
+  func testSendWhenNotConnected() {
+    var env: MessengerEnvironment = .unimplemented
+    env.cMix.get = { .unimplemented }
+    env.e2e.get = { nil }
+    let send: MessengerSendMessage = .live(env)
+
+    XCTAssertThrowsError(
+      try send(
+        recipientId: Data(),
+        payload: Data(),
+        deliveryCallback: nil
+      )
+    ) { error in
+      XCTAssertNoDifference(
+        error as? MessengerSendMessage.Error,
+        .notConnected
+      )
+    }
+  }
+
+  func testSendFailure() {
+    struct Failure: Error, Equatable {}
+    let error = Failure()
+
+    var env: MessengerEnvironment = .unimplemented
+    env.getE2EParams.run = { "e2e-params".data(using: .utf8)! }
+    env.cMix.get = { .unimplemented }
+    env.e2e.get = {
+      var e2e: E2E = .unimplemented
+      e2e.send.run = { _, _, _, _ in throw error }
+      return e2e
+    }
+    let send: MessengerSendMessage = .live(env)
+
+    XCTAssertThrowsError(
+      try send(
+        recipientId: "recipient-id".data(using: .utf8)!,
+        payload: "payload".data(using: .utf8)!,
+        deliveryCallback: nil
+      )
+    ) { err in
+      XCTAssertNoDifference(err as? Failure, error)
+    }
+  }
+
+  func testSendDeliveryFailure() throws {
+    let e2eSendReport = E2ESendReport(
+      rounds: [1, 2, 3],
+      roundURL: "round-url",
+      messageId: "message-id".data(using: .utf8)!,
+      timestamp: 123,
+      keyResidue: "key-residue".data(using: .utf8)!
+    )
+
+    struct Failure: Error {}
+    let error = Failure()
+
+    var didReceiveDeliveryReport: [MessengerSendMessage.DeliveryReport] = []
+
+    var env: MessengerEnvironment = .unimplemented
+    env.getE2EParams.run = { "e2e-params".data(using: .utf8)! }
+    env.cMix.get = {
+      var cMix: CMix = .unimplemented
+      cMix.waitForRoundResult.run = { _, _, _ in throw error }
+      return cMix
+    }
+    env.e2e.get = {
+      var e2e: E2E = .unimplemented
+      e2e.send.run = { _, _, _, _ in e2eSendReport }
+      return e2e
+    }
+    let send: MessengerSendMessage = .live(env)
+
+    let report = try send(
+      recipientId: "recipient-id".data(using: .utf8)!,
+      payload: "payload".data(using: .utf8)!,
+      deliveryCallback: .init { deliveryReport in
+        didReceiveDeliveryReport.append(deliveryReport)
+      }
+    )
+
+    XCTAssertNoDifference(report, e2eSendReport)
+
+    XCTAssertNoDifference(didReceiveDeliveryReport, [
+      .init(report: report, result: .failure(error as NSError))
+    ])
+  }
+}
diff --git a/Tests/XXMessengerClientTests/TestHelpers/Message+stubs.swift b/Tests/XXMessengerClientTests/TestHelpers/Message+stubs.swift
new file mode 100644
index 0000000000000000000000000000000000000000..e10bea4a6298544c2e4c4d01ba267a00d2dcc167
--- /dev/null
+++ b/Tests/XXMessengerClientTests/TestHelpers/Message+stubs.swift
@@ -0,0 +1,18 @@
+import XXClient
+
+extension Message {
+  static func stub(_ stubId: Int) -> Message {
+    .init(
+      messageType: stubId,
+      id: "id-\(stubId)".data(using: .utf8)!,
+      payload: "payload-\(stubId)".data(using: .utf8)!,
+      sender: "sender-\(stubId)".data(using: .utf8)!,
+      recipientId: "recipientId-\(stubId)".data(using: .utf8)!,
+      ephemeralId: stubId,
+      timestamp: stubId,
+      encrypted: stubId % 2 == 0,
+      roundId: stubId,
+      roundURL: "roundURL-\(stubId)"
+    )
+  }
+}
diff --git a/Tests/XXMessengerClientTests/Utils/AuthCallbacksRegistryTests.swift b/Tests/XXMessengerClientTests/Utils/AuthCallbacksRegistryTests.swift
index 1d1ba7b2f6226e9283fc60d8656dbe0b317113a1..d2a72c351ca269576b8e2e8eb5a714d84a18e514 100644
--- a/Tests/XXMessengerClientTests/Utils/AuthCallbacksRegistryTests.swift
+++ b/Tests/XXMessengerClientTests/Utils/AuthCallbacksRegistryTests.swift
@@ -1,7 +1,7 @@
+import CustomDump
 import XCTest
 import XXClient
 @testable import XXMessengerClient
-import CustomDump
 
 final class AuthCallbacksRegistryTests: XCTestCase {
   func testRegistry() {
diff --git a/Tests/XXMessengerClientTests/Utils/ListenersRegistryTests.swift b/Tests/XXMessengerClientTests/Utils/ListenersRegistryTests.swift
new file mode 100644
index 0000000000000000000000000000000000000000..763f92b69aea1b2be78c47a31250ab8a9229fe22
--- /dev/null
+++ b/Tests/XXMessengerClientTests/Utils/ListenersRegistryTests.swift
@@ -0,0 +1,43 @@
+import CustomDump
+import XCTest
+import XXClient
+@testable import XXMessengerClient
+
+final class ListenersRegistryTestsTests: XCTestCase {
+  func testRegistry() {
+    var firstListenerDidHandle: [Message] = []
+    var secondListenerDidHandle: [Message] = []
+
+    let firstListener = Listener { message in
+      firstListenerDidHandle.append(message)
+    }
+    let secondListener = Listener { message in
+      secondListenerDidHandle.append(message)
+    }
+    let listenersRegistry: ListenersRegistry = .live()
+    let registeredListeners = listenersRegistry.registered()
+    let firstListenerCancellable = listenersRegistry.register(firstListener)
+    let secondListenerCancellable = listenersRegistry.register(secondListener)
+
+    let firstMessage = Message.stub(1)
+    registeredListeners.handle(firstMessage)
+
+    XCTAssertNoDifference(firstListenerDidHandle, [firstMessage])
+    XCTAssertNoDifference(secondListenerDidHandle, [firstMessage])
+
+    firstListenerCancellable.cancel()
+    let secondMessage = Message.stub(2)
+    registeredListeners.handle(secondMessage)
+
+    XCTAssertNoDifference(firstListenerDidHandle, [firstMessage])
+    XCTAssertNoDifference(secondListenerDidHandle, [firstMessage, secondMessage])
+
+    secondListenerCancellable.cancel()
+
+    let thirdMessage = Message.stub(3)
+    registeredListeners.handle(thirdMessage)
+
+    XCTAssertNoDifference(firstListenerDidHandle, [firstMessage])
+    XCTAssertNoDifference(secondListenerDidHandle, [firstMessage, secondMessage])
+  }
+}