diff --git a/crates/messaging/src/controller.rs b/crates/messaging/src/controller.rs
new file mode 100644
index 000000000..62a749e88
--- /dev/null
+++ b/crates/messaging/src/controller.rs
@@ -0,0 +1,255 @@
+//! RPC-facing checkpoint controller for the messaging service.
+//!
+//! Encapsulates DB-side checkpoint operations and signalling the running drain
+//! task to live-rewind the in-memory cursor without restarting the node.
+
+use anyhow::Context;
+use katana_provider::api::messaging::{
+ MessagingCheckpoint, MessagingCheckpointProvider, MessagingL1ToL2IndexWriter,
+};
+use katana_provider::{MutableProvider, ProviderFactory, ProviderRW, ProviderResult};
+use tokio::sync::mpsc;
+use tracing::warn;
+
+use crate::LOG_TARGET;
+
+/// Signal sent from the controller to the drain task: rewind the in-memory
+/// cursor to `(from_block, from_tx_index)`.
+#[derive(Debug, Clone, Copy)]
+pub struct RewindSignal {
+ pub from_block: u64,
+ pub from_tx_index: u64,
+}
+
+/// Operator-facing handle to the messaging checkpoint.
+///
+/// Reads/writes the persisted DB checkpoint and signals the running drain
+/// task to rewind its in-memory cursor.
+#[derive(Debug, Clone)]
+pub struct MessagingController
{
+ provider: P,
+ default_from_block: u64,
+ rewind_tx: mpsc::Sender,
+}
+
+impl MessagingController
{
+ pub(crate) fn new(
+ provider: P,
+ default_from_block: u64,
+ rewind_tx: mpsc::Sender,
+ ) -> Self {
+ Self { provider, default_from_block, rewind_tx }
+ }
+}
+
+impl MessagingController
+where
+ P: ProviderFactory + Clone + Send + Sync + 'static,
+
::ProviderMut:
+ ProviderRW + MessagingCheckpointProvider + MessagingL1ToL2IndexWriter + MutableProvider,
+{
+ /// Read the last *committed* checkpoint — the same value `resume_cursor`
+ /// reads on boot.
+ pub fn get_checkpoint(&self) -> ProviderResult