diff --git a/bridges/relays/messages/src/message_race_delivery.rs b/bridges/relays/messages/src/message_race_delivery.rs
index 8a667fdbdb8902c11a5a3bf3a0eae837f4971333..06e02a06017b1d69c2a8b761bfa2b88bce69eb48 100644
--- a/bridges/relays/messages/src/message_race_delivery.rs
+++ b/bridges/relays/messages/src/message_race_delivery.rs
@@ -343,31 +343,6 @@ where
 		// There's additional condition in the message delivery race: target would reject messages
 		// if there are too much unconfirmed messages at the inbound lane.
 
-		// The receiving race is responsible to deliver confirmations back to the source chain. So
-		// if there's a lot of unconfirmed messages, let's wait until it'll be able to do its job.
-		let latest_received_nonce_at_target = target_nonces.latest_nonce;
-		let confirmations_missing =
-			latest_received_nonce_at_target.checked_sub(latest_confirmed_nonce_at_source);
-		match confirmations_missing {
-			Some(confirmations_missing)
-				if confirmations_missing >= self.max_unconfirmed_nonces_at_target =>
-			{
-				log::debug!(
-					target: "bridge",
-					"Cannot deliver any more messages from {} to {}. Too many unconfirmed nonces \
-					at target: target.latest_received={:?}, source.latest_confirmed={:?}, max={:?}",
-					MessageDeliveryRace::<P>::source_name(),
-					MessageDeliveryRace::<P>::target_name(),
-					latest_received_nonce_at_target,
-					latest_confirmed_nonce_at_source,
-					self.max_unconfirmed_nonces_at_target,
-				);
-
-				return None
-			},
-			_ => (),
-		}
-
 		// Ok - we may have new nonces to deliver. But target may still reject new messages, because
 		// we haven't notified it that (some) messages have been confirmed. So we may want to
 		// include updated `source.latest_confirmed` in the proof.
@@ -375,6 +350,7 @@ where
 		// Important note: we're including outbound state lane proof whenever there are unconfirmed
 		// nonces on the target chain. Other strategy is to include it only if it's absolutely
 		// necessary.
+		let latest_received_nonce_at_target = target_nonces.latest_nonce;
 		let latest_confirmed_nonce_at_target = target_nonces.nonces_data.confirmed_nonce;
 		let outbound_state_proof_required =
 			latest_confirmed_nonce_at_target < latest_confirmed_nonce_at_source;
@@ -585,6 +561,11 @@ where
 		self.strategy.source_nonces_updated(at_block, nonces)
 	}
 
+	fn reset_best_target_nonces(&mut self) {
+		self.target_nonces = None;
+		self.strategy.reset_best_target_nonces();
+	}
+
 	fn best_target_nonces_updated<RS: RaceState<SourceHeaderIdOf<P>, TargetHeaderIdOf<P>>>(
 		&mut self,
 		nonces: TargetClientNonces<DeliveryRaceTargetNoncesData>,
@@ -808,22 +789,6 @@ mod tests {
 		);
 	}
 
-	#[async_std::test]
-	async fn message_delivery_strategy_selects_nothing_if_too_many_confirmations_missing() {
-		let (state, mut strategy) = prepare_strategy();
-
-		// if there are already `max_unconfirmed_nonces_at_target` messages on target,
-		// we need to wait until confirmations will be delivered by receiving race
-		strategy.latest_confirmed_nonces_at_source = vec![(
-			header_id(1),
-			strategy.target_nonces.as_ref().unwrap().latest_nonce -
-				strategy.max_unconfirmed_nonces_at_target,
-		)]
-		.into_iter()
-		.collect();
-		assert_eq!(strategy.select_nonces_to_deliver(state).await, None);
-	}
-
 	#[async_std::test]
 	async fn message_delivery_strategy_includes_outbound_state_proof_when_new_nonces_are_available()
 	{
diff --git a/bridges/relays/messages/src/message_race_loop.rs b/bridges/relays/messages/src/message_race_loop.rs
index be7d5b4675659dfe80890526e40e329e92b0155b..3df9eef496016d934a73251967842903043e769f 100644
--- a/bridges/relays/messages/src/message_race_loop.rs
+++ b/bridges/relays/messages/src/message_race_loop.rs
@@ -195,6 +195,9 @@ pub trait RaceStrategy<SourceHeaderId, TargetHeaderId, Proof>: Debug {
 		at_block: SourceHeaderId,
 		nonces: SourceClientNonces<Self::SourceNoncesRange>,
 	);
+	/// Called when we want to wait until next `best_target_nonces_updated` before selecting
+	/// any nonces for delivery.
+	fn reset_best_target_nonces(&mut self);
 	/// Called when best nonces are updated at target node of the race.
 	fn best_target_nonces_updated<RS: RaceState<SourceHeaderId, TargetHeaderId>>(
 		&mut self,
@@ -542,14 +545,22 @@ pub async fn run<P: MessageRace, SC: SourceClient<P>, TC: TargetClient<P>>(
 							P::target_name(),
 						);
 
-						race_state.reset_nonces_to_submit();
 						race_state.nonces_submitted = Some(artifacts.nonces);
 						target_tx_tracker.set(artifacts.tx_tracker.wait().fuse());
 					},
 					&mut target_go_offline_future,
 					async_std::task::sleep,
 					|| format!("Error submitting proof {}", P::target_name()),
-				).fail_if_error(FailedClient::Target).map(|_| true)?;
+				).fail_if_connection_error(FailedClient::Target)?;
+
+				// in any case - we don't need to retry submitting the same nonces again until
+				// we read nonces from the target client
+				race_state.reset_nonces_to_submit();
+				// if we have failed to submit transaction AND that is not the connection issue,
+				// then we need to read best target nonces before selecting nonces again
+				if !target_client_is_online {
+					strategy.reset_best_target_nonces();
+				}
 			},
 			target_transaction_status = target_tx_tracker => {
 				match (target_transaction_status, race_state.nonces_submitted.as_ref()) {
@@ -699,7 +710,13 @@ pub async fn run<P: MessageRace, SC: SourceClient<P>, TC: TargetClient<P>>(
 						.fuse(),
 				);
 			} else if let Some(source_required_header) = source_required_header.clone() {
-				log::debug!(target: "bridge", "Going to require {} header {:?} at {}", P::source_name(), source_required_header, P::target_name());
+				log::debug!(
+					target: "bridge",
+					"Going to require {} header {:?} at {}",
+					P::source_name(),
+					source_required_header,
+					P::target_name(),
+				);
 				target_require_source_header
 					.set(race_target.require_source_header(source_required_header).fuse());
 			} else if target_best_nonces_required {
diff --git a/bridges/relays/messages/src/message_race_strategy.rs b/bridges/relays/messages/src/message_race_strategy.rs
index 718c296391c5c24abde8cb30445aaba3583e7b85..5c8f9a162b4a23b043b5566d27834a08ea4aaa23 100644
--- a/bridges/relays/messages/src/message_race_strategy.rs
+++ b/bridges/relays/messages/src/message_race_strategy.rs
@@ -254,6 +254,10 @@ impl<
 		)
 	}
 
+	fn reset_best_target_nonces(&mut self) {
+		self.best_target_nonce = None;
+	}
+
 	fn best_target_nonces_updated<
 		RS: RaceState<
 			HeaderId<SourceHeaderHash, SourceHeaderNumber>,
@@ -266,19 +270,37 @@ impl<
 	) {
 		let nonce = nonces.latest_nonce;
 
+		// if **some** of nonces that we have selected to submit already present at the
+		// target chain => select new nonces
 		let need_to_select_new_nonces = race_state
 			.nonces_to_submit()
-			.map(|nonces| *nonces.end() <= nonce)
+			.map(|nonces| nonce >= *nonces.start())
 			.unwrap_or(false);
 		if need_to_select_new_nonces {
+			log::trace!(
+				target: "bridge",
+				"Latest nonce at target is {}. Clearing nonces to submit: {:?}",
+				nonce,
+				race_state.nonces_to_submit(),
+			);
+
 			race_state.reset_nonces_to_submit();
 		}
 
+		// if **some** of nonces that we have submitted already present at the
+		// target chain => select new nonces
 		let need_new_nonces_to_submit = race_state
 			.nonces_submitted()
-			.map(|nonces| *nonces.end() <= nonce)
+			.map(|nonces| nonce >= *nonces.start())
 			.unwrap_or(false);
 		if need_new_nonces_to_submit {
+			log::trace!(
+				target: "bridge",
+				"Latest nonce at target is {}. Clearing submitted nonces: {:?}",
+				nonce,
+				race_state.nonces_submitted(),
+			);
+
 			race_state.reset_nonces_submitted();
 		}
 
@@ -415,10 +437,15 @@ mod tests {
 		let mut state = TestRaceStateImpl::default();
 		let mut strategy = BasicStrategy::<TestMessageLane>::new();
 		state.nonces_to_submit = Some((header_id(1), 5..=10, (5..=10, None)));
-		strategy.best_target_nonces_updated(target_nonces(7), &mut state);
+		// we are going to submit 5..=10, so having latest nonce 4 at target is fine
+		strategy.best_target_nonces_updated(target_nonces(4), &mut state);
 		assert!(state.nonces_to_submit.is_some());
-		strategy.best_target_nonces_updated(target_nonces(10), &mut state);
-		assert!(state.nonces_to_submit.is_none());
+		// any nonce larger than 4 invalidates the `nonces_to_submit`
+		for nonce in 5..=11 {
+			state.nonces_to_submit = Some((header_id(1), 5..=10, (5..=10, None)));
+			strategy.best_target_nonces_updated(target_nonces(nonce), &mut state);
+			assert!(state.nonces_to_submit.is_none());
+		}
 	}
 
 	#[test]
@@ -426,10 +453,15 @@ mod tests {
 		let mut state = TestRaceStateImpl::default();
 		let mut strategy = BasicStrategy::<TestMessageLane>::new();
 		state.nonces_submitted = Some(5..=10);
-		strategy.best_target_nonces_updated(target_nonces(7), &mut state);
+		// we have submitted 5..=10, so having latest nonce 4 at target is fine
+		strategy.best_target_nonces_updated(target_nonces(4), &mut state);
 		assert!(state.nonces_submitted.is_some());
-		strategy.best_target_nonces_updated(target_nonces(10), &mut state);
-		assert!(state.nonces_submitted.is_none());
+		// any nonce larger than 4 invalidates the `nonces_submitted`
+		for nonce in 5..=11 {
+			state.nonces_submitted = Some(5..=10);
+			strategy.best_target_nonces_updated(target_nonces(nonce), &mut state);
+			assert!(state.nonces_submitted.is_none());
+		}
 	}
 
 	#[async_std::test]