//! Scheduled message dispatcher on `MessageService`. //! //! A background task that periodically scans for due scheduled messages //! and sends them through the normal message path. use std::time::Duration; use crate::repo::CreateMessageInput; use super::message::MessageService; impl MessageService { /// Start the background scheduled-message dispatcher. /// Scans every 30 seconds for pending messages whose `scheduled_at` has passed. pub fn start_scheduled_dispatcher(self: std::sync::Arc) { tokio::spawn(async move { tracing::info!("Scheduled message dispatcher started (interval: 30s)"); loop { tokio::time::sleep(Duration::from_secs(30)).await; match self.process_due_scheduled().await { Ok(count) => { if count > 0 { tracing::info!(count, "Dispatched scheduled messages"); } } Err(e) => { tracing::error!(error = %e, "Scheduled message dispatch failed"); } } } }); } /// Fetch and dispatch all due scheduled messages. async fn process_due_scheduled(&self) -> crate::ImksResult { let due = self.repo.get_due_scheduled().await?; let mut dispatched = 0; for scheduled in due { let input = CreateMessageInput { channel_id: scheduled.channel_id, author_id: scheduled.author_id, thread_id: scheduled.thread_id, reply_to_message_id: scheduled.reply_to_message_id, message_type: "text".into(), body: scheduled.body.clone(), metadata: scheduled.metadata.clone(), system: false, }; match self.repo.create(&input).await { Ok(message) => { self.repo .mark_scheduled_sent(scheduled.id, message.id) .await?; // Broadcast to channel if let Some(ns) = self.namespaces.get_namespace("/") { ns.emit_to_room( &scheduled.channel_id.to_string(), "message:new", serde_json::to_value(&message).unwrap_or_default(), ) .await; } dispatched += 1; } Err(e) => { tracing::error!(scheduled_id = %scheduled.id, error = %e, "Failed to send scheduled message"); self.repo .mark_scheduled_failed(scheduled.id, &e.to_string()) .await?; } } } Ok(dispatched) } }