1use crate::{message::Email, smtp::SmtpClient};
4use avila_error::Result;
5use std::collections::VecDeque;
6
7#[derive(Debug, Clone, PartialEq)]
9pub enum QueueStatus {
10 Pending,
11 Sending,
12 Sent,
13 Failed,
14 Retry,
15}
16
17#[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
51pub 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 pub fn enqueue(&mut self, email: QueuedEmail) {
69 self.queue.push_back(email);
70 }
71
72 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 pub fn mark_sent(&mut self, mut email: QueuedEmail) {
86 email.status = QueueStatus::Sent;
87 self.sent.push(email);
88 }
89
90 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 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 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); assert_eq!(stats.failed, 0);
184 }
185}