diff --git a/bridges/relays/substrate/src/cli.rs b/bridges/relays/substrate/src/cli.rs
index 9914cd2e3a5db9a32baf1f0747af2e65847c44e3..d9bec5774740e2296e9793ff64ec735380cd9c3b 100644
--- a/bridges/relays/substrate/src/cli.rs
+++ b/bridges/relays/substrate/src/cli.rs
@@ -176,7 +176,7 @@ pub enum SendMessage {
 		/// Dispatch weight of the message. If not passed, determined automatically.
 		#[structopt(long)]
 		dispatch_weight: Option<ExplicitOrMaximal<Weight>>,
-		/// Delivery and dispatch fee. If not passed, determined automatically.
+		/// Delivery and dispatch fee in source chain base currency units. If not passed, determined automatically.
 		#[structopt(long)]
 		fee: Option<bp_millau::Balance>,
 		/// Message type.
@@ -201,7 +201,7 @@ pub enum SendMessage {
 		/// Dispatch weight of the message. If not passed, determined automatically.
 		#[structopt(long)]
 		dispatch_weight: Option<ExplicitOrMaximal<Weight>>,
-		/// Delivery and dispatch fee. If not passed, determined automatically.
+		/// Delivery and dispatch fee in source chain base currency units. If not passed, determined automatically.
 		#[structopt(long)]
 		fee: Option<bp_rialto::Balance>,
 		/// Message type.
@@ -291,7 +291,6 @@ pub enum MillauToRialtoMessagePayload {
 	/// Raw, SCALE-encoded `MessagePayload`.
 	Raw {
 		/// Hex-encoded SCALE data.
-		#[structopt(long)]
 		data: Bytes,
 	},
 	/// Construct message to send over the bridge.
@@ -311,7 +310,6 @@ pub enum RialtoToMillauMessagePayload {
 	/// Raw, SCALE-encoded `MessagePayload`.
 	Raw {
 		/// Hex-encoded SCALE data.
-		#[structopt(long)]
 		data: Bytes,
 	},
 	/// Construct message to send over the bridge.
@@ -319,7 +317,6 @@ pub enum RialtoToMillauMessagePayload {
 		/// Message details.
 		#[structopt(flatten)]
 		message: ToMillauMessage,
-
 		/// SS58 encoded account that will send the payload (must have SS58Prefix = 42)
 		#[structopt(long)]
 		sender: AccountId,
@@ -345,10 +342,22 @@ pub enum ToRialtoMessage {
 		/// SS58 encoded account that will receive the transfer (must have SS58Prefix = 42)
 		#[structopt(long)]
 		recipient: AccountId,
-		/// Amount of target tokens to send.
+		/// Amount of target tokens to send in target chain base currency units.
 		#[structopt(long)]
 		amount: bp_rialto::Balance,
 	},
+	/// A call to the Millau Bridge Message Lane pallet to send a message over the bridge.
+	MillauSendMessage {
+		/// Hex-encoded lane id that should be served by the relay. Defaults to `00000000`.
+		#[structopt(long, default_value = "00000000")]
+		lane: HexLaneId,
+		/// Raw SCALE-encoded Message Payload to submit to the message lane pallet.
+		#[structopt(long)]
+		payload: Bytes,
+		/// Declared delivery and dispatch fee in base source-chain currency units.
+		#[structopt(long)]
+		fee: bp_rialto::Balance,
+	},
 }
 
 /// All possible messages that may be delivered to the Millau chain.
@@ -370,10 +379,22 @@ pub enum ToMillauMessage {
 		/// SS58 encoded account that will receive the transfer (must have SS58Prefix = 42)
 		#[structopt(long)]
 		recipient: AccountId,
-		/// Amount of target tokens to send.
+		/// Amount of target tokens to send in target chain base currency units.
 		#[structopt(long)]
 		amount: bp_millau::Balance,
 	},
+	/// A call to the Rialto Bridge Message Lane pallet to send a message over the bridge.
+	RialtoSendMessage {
+		/// Hex-encoded lane id that should be served by the relay. Defaults to `00000000`.
+		#[structopt(long, default_value = "00000000")]
+		lane: HexLaneId,
+		/// Raw SCALE-encoded Message Payload to submit to the message lane pallet.
+		#[structopt(long)]
+		payload: Bytes,
+		/// Declared delivery and dispatch fee in base source-chain currency units.
+		#[structopt(long)]
+		fee: bp_millau::Balance,
+	},
 }
 
 arg_enum! {
diff --git a/bridges/relays/substrate/src/main.rs b/bridges/relays/substrate/src/main.rs
index f5c8c6a1e4366b9e62558f3d0b0e3fcbab2e66a9..3a3c3620aeb2587bc72f6c4a48a88219c0c018f3 100644
--- a/bridges/relays/substrate/src/main.rs
+++ b/bridges/relays/substrate/src/main.rs
@@ -283,6 +283,7 @@ async fn run_send_message(command: cli::SendMessage) -> Result<(), String> {
 				dispatch_weight,
 				fee,
 			);
+			log::info!(target: "bridge", "Signed Millau Call: {:?}", HexBytes::encode(&signed_millau_call));
 
 			millau_client.submit_extrinsic(Bytes(signed_millau_call)).await?;
 		}
@@ -338,6 +339,7 @@ async fn run_send_message(command: cli::SendMessage) -> Result<(), String> {
 				dispatch_weight,
 				fee,
 			);
+			log::info!(target: "bridge", "Signed Rialto Call: {:?}", HexBytes::encode(&signed_rialto_call));
 
 			rialto_client.submit_extrinsic(Bytes(signed_rialto_call)).await?;
 		}
@@ -728,6 +730,13 @@ impl crate::cli::ToRialtoMessage {
 				let recipient = recipient.into_rialto();
 				rialto_runtime::Call::Balances(rialto_runtime::BalancesCall::transfer(recipient, amount))
 			}
+			cli::ToRialtoMessage::MillauSendMessage { lane, payload, fee } => {
+				let payload = cli::RialtoToMillauMessagePayload::Raw { data: payload }.into_payload()?;
+				let lane = lane.into();
+				rialto_runtime::Call::BridgeMillauMessageLane(rialto_runtime::MessageLaneCall::send_message(
+					lane, payload, fee,
+				))
+			}
 		};
 
 		log::info!(target: "bridge", "Generated Rialto call: {:#?}", call);
@@ -758,6 +767,13 @@ impl crate::cli::ToMillauMessage {
 				let recipient = recipient.into_millau();
 				millau_runtime::Call::Balances(millau_runtime::BalancesCall::transfer(recipient, amount))
 			}
+			cli::ToMillauMessage::RialtoSendMessage { lane, payload, fee } => {
+				let payload = cli::MillauToRialtoMessagePayload::Raw { data: payload }.into_payload()?;
+				let lane = lane.into();
+				millau_runtime::Call::BridgeRialtoMessageLane(millau_runtime::MessageLaneCall::send_message(
+					lane, payload, fee,
+				))
+			}
 		};
 
 		log::info!(target: "bridge", "Generated Millau call: {:#?}", call);