avila_cell/
queue.rs

1//! Email queue system for batch sending and retry logic
2
3use crate::{message::Email, smtp::SmtpClient};
4use avila_error::Result;
5use std::collections::VecDeque;
6
7/// Email queue status
8#[derive(Debug, Clone, PartialEq)]
9pub enum QueueStatus {
10    Pending,
11    Sending,
12    Sent,
13    Failed,
14    Retry,
15}
16
17/// Queued email with metadata
18#[derive(Debug, Clone)]
19pub struct QueuedEmail {
20    pub email: Email,
21    pub status: QueueStatus,
22    pub attempts: u32,
23    pub max_attempts: u32,
24    pub scheduled_time: Option<u64>,
25    pub last_error: Option<String>,
26}
27
28impl QueuedEmail {
29    pub fn new(email: Email) -> Self {
30        Self {
31            email,
32            status: QueueStatus::Pending,
33            attempts: 0,
34            max_attempts: 3,
35            scheduled_time: None,
36            last_error: None,
37        }
38    }
39
40    pub fn with_retry(mut self, max_attempts: u32) -> Self {
41        self.max_attempts = max_attempts;
42        self
43    }
44
45    pub fn schedule_at(mut self, timestamp: u64) -> Self {
46        self.scheduled_time = Some(timestamp);
47        self
48    }
49}
50
51/// Email queue manager
52pub struct EmailQueue {
53    queue: VecDeque<QueuedEmail>,
54    sent: Vec<QueuedEmail>,
55    failed: Vec<QueuedEmail>,
56}
57
58impl EmailQueue {
59    pub fn new() -> Self {
60        Self {
61            queue: VecDeque::new(),
62            sent: Vec::new(),
63            failed: Vec::new(),
64        }
65    }
66
67    /// Adds email to queue
68    pub fn enqueue(&mut self, email: QueuedEmail) {
69        self.queue.push_back(email);
70    }
71
72    /// Gets next pending email
73    pub fn next(&mut self) -> Option<QueuedEmail> {
74        for i in 0..self.queue.len() {
75            if let Some(email) = self.queue.get(i) {
76                if email.status == QueueStatus::Pending {
77                    return self.queue.remove(i);
78                }
79            }
80        }
81        None
82    }
83
84    /// Marks email as sent
85    pub fn mark_sent(&mut self, mut email: QueuedEmail) {
86        email.status = QueueStatus::Sent;
87        self.sent.push(email);
88    }
89
90    /// Marks email as failed and retry if attempts remaining
91    pub fn mark_failed(&mut self, mut email: QueuedEmail, error: String) {
92        email.attempts += 1;
93        email.last_error = Some(error);
94
95        if email.attempts < email.max_attempts {
96            email.status = QueueStatus::Retry;
97            self.queue.push_back(email);
98        } else {
99            email.status = QueueStatus::Failed;
100            self.failed.push(email);
101        }
102    }
103
104    /// Gets queue statistics
105    pub fn stats(&self) -> QueueStats {
106        QueueStats {
107            pending: self.queue.len(),
108            sent: self.sent.len(),
109            failed: self.failed.len(),
110        }
111    }
112
113    /// Processes queue with SMTP client
114    pub async fn process(&mut self, client: &mut SmtpClient) -> Result<()> {
115        while let Some(mut email) = self.next() {
116            email.status = QueueStatus::Sending;
117            
118            match client.send_email(&email.email).await {
119                Ok(_) => self.mark_sent(email),
120                Err(e) => self.mark_failed(email, e.to_string()),
121            }
122        }
123        Ok(())
124    }
125}
126
127impl Default for EmailQueue {
128    fn default() -> Self {
129        Self::new()
130    }
131}
132
133#[derive(Debug, Clone)]
134pub struct QueueStats {
135    pub pending: usize,
136    pub sent: usize,
137    pub failed: usize,
138}
139
140#[cfg(test)]
141mod tests {
142    use super::*;
143    use crate::EmailAddress;
144
145    #[test]
146    fn test_queue_operations() {
147        let mut queue = EmailQueue::new();
148        
149        let email = Email::new(
150            EmailAddress::new("from@test.com").unwrap(),
151            vec![EmailAddress::new("to@test.com").unwrap()],
152            "Test".to_string(),
153            "Body".to_string(),
154        );
155
156        queue.enqueue(QueuedEmail::new(email));
157        
158        let stats = queue.stats();
159        assert_eq!(stats.pending, 1);
160        assert_eq!(stats.sent, 0);
161    }
162
163    #[test]
164    fn test_retry_logic() {
165        let mut queue = EmailQueue::new();
166        
167        let email = Email::new(
168            EmailAddress::new("from@test.com").unwrap(),
169            vec![EmailAddress::new("to@test.com").unwrap()],
170            "Test".to_string(),
171            "Body".to_string(),
172        );
173
174        let queued = QueuedEmail::new(email).with_retry(3);
175        queue.enqueue(queued);
176
177        if let Some(email) = queue.next() {
178            queue.mark_failed(email, "Test error".to_string());
179        }
180
181        let stats = queue.stats();
182        assert_eq!(stats.pending, 1); // Moved back to queue for retry
183        assert_eq!(stats.failed, 0);
184    }
185}