1use async_trait::async_trait;
4use crate::{
5 EmailResult, EmailMessage, EmailDeliveryReport,
6 EmailTemplate, EmailStatus
7};
8use std::collections::HashMap;
9
10#[async_trait]
12pub trait EmailService: Send + Sync {
13 async fn send(&self, message: EmailMessage) -> EmailResult<EmailDeliveryReport>;
15
16 async fn send_bulk(&self, messages: Vec<EmailMessage>) -> EmailResult<Vec<EmailDeliveryReport>> {
18 let mut reports = Vec::with_capacity(messages.len());
19
20 for message in messages {
21 let report = self.send(message).await?;
22 reports.push(report);
23 }
24
25 Ok(reports)
26 }
27
28 async fn send_template(
30 &self,
31 template_name: &str,
32 recipients: Vec<String>,
33 template_data: HashMap<String, serde_json::Value>,
34 ) -> EmailResult<Vec<EmailDeliveryReport>>;
35
36 async fn get_delivery_status(&self, message_id: &str) -> EmailResult<Option<EmailDeliveryReport>>;
38
39 async fn cancel_scheduled(&self, message_id: &str) -> EmailResult<bool>;
41
42 async fn get_stats(&self) -> EmailResult<EmailServiceStats>;
44
45 async fn validate_config(&self) -> EmailResult<bool>;
47
48 async fn test_connection(&self) -> EmailResult<bool>;
50}
51
52#[async_trait]
54pub trait TemplateManager: Send + Sync {
55 async fn create_template(&self, template: EmailTemplate) -> EmailResult<String>;
57
58 async fn get_template(&self, name: &str) -> EmailResult<Option<EmailTemplate>>;
60
61 async fn update_template(&self, name: &str, template: EmailTemplate) -> EmailResult<bool>;
63
64 async fn delete_template(&self, name: &str) -> EmailResult<bool>;
66
67 async fn list_templates(&self) -> EmailResult<Vec<String>>;
69
70 async fn render_template(
72 &self,
73 template_name: &str,
74 data: &HashMap<String, serde_json::Value>,
75 ) -> EmailResult<TemplateRenderResult>;
76}
77
78#[derive(Debug, Clone)]
80pub struct TemplateRenderResult {
81 pub subject: String,
83
84 pub html: String,
86
87 pub text: Option<String>,
89
90 pub metadata: HashMap<String, String>,
92}
93
94#[derive(Debug, Clone, Default)]
96pub struct EmailServiceStats {
97 pub total_sent: u64,
99
100 pub total_delivered: u64,
102
103 pub total_failed: u64,
105
106 pub total_bounced: u64,
108
109 pub total_opened: u64,
111
112 pub total_clicked: u64,
114
115 pub avg_delivery_time_ms: Option<f64>,
117
118 pub queue_size: u64,
120
121 pub rate_limit_remaining: Option<u64>,
123
124 pub last_activity: Option<chrono::DateTime<chrono::Utc>>,
126
127 pub provider_stats: HashMap<String, serde_json::Value>,
129}
130
131impl EmailServiceStats {
132 pub fn delivery_rate(&self) -> f64 {
134 if self.total_sent > 0 {
135 self.total_delivered as f64 / self.total_sent as f64
136 } else {
137 0.0
138 }
139 }
140
141 pub fn failure_rate(&self) -> f64 {
143 if self.total_sent > 0 {
144 self.total_failed as f64 / self.total_sent as f64
145 } else {
146 0.0
147 }
148 }
149
150 pub fn bounce_rate(&self) -> f64 {
152 if self.total_delivered > 0 {
153 self.total_bounced as f64 / self.total_delivered as f64
154 } else {
155 0.0
156 }
157 }
158
159 pub fn open_rate(&self) -> f64 {
161 if self.total_delivered > 0 {
162 self.total_opened as f64 / self.total_delivered as f64
163 } else {
164 0.0
165 }
166 }
167
168 pub fn click_rate(&self) -> f64 {
170 if self.total_opened > 0 {
171 self.total_clicked as f64 / self.total_opened as f64
172 } else {
173 0.0
174 }
175 }
176
177 pub fn update_with_delivery_report(&mut self, report: &EmailDeliveryReport) {
179 match report.status {
180 EmailStatus::Sent => self.total_sent += 1,
181 EmailStatus::Failed => {
182 self.total_sent += 1;
183 self.total_failed += 1;
184 }
185 EmailStatus::Bounced => {
186 self.total_delivered += 1;
187 self.total_bounced += 1;
188 }
189 EmailStatus::Opened => self.total_opened += 1,
190 EmailStatus::Clicked => self.total_clicked += 1,
191 EmailStatus::Pending => {} }
193
194 use chrono::Utc;
196 self.last_activity = Some(Utc::now());
197 }
198}
199
200pub trait EmailProviderConfig {
202 fn provider_name(&self) -> &'static str;
204
205 fn validate(&self) -> Result<(), String>;
207
208 fn to_json(&self) -> Result<serde_json::Value, serde_json::Error>;
210}
211
212#[async_trait]
214pub trait WebhookHandler: Send + Sync {
215 async fn handle_delivery_webhook(
217 &self,
218 payload: serde_json::Value,
219 signature: Option<String>,
220 ) -> EmailResult<EmailDeliveryReport>;
221
222 async fn handle_bounce_webhook(
224 &self,
225 payload: serde_json::Value,
226 signature: Option<String>,
227 ) -> EmailResult<EmailDeliveryReport>;
228
229 async fn handle_open_webhook(
231 &self,
232 payload: serde_json::Value,
233 signature: Option<String>,
234 ) -> EmailResult<EmailDeliveryReport>;
235
236 async fn handle_click_webhook(
238 &self,
239 payload: serde_json::Value,
240 signature: Option<String>,
241 ) -> EmailResult<EmailDeliveryReport>;
242
243 fn verify_signature(&self, payload: &str, signature: &str) -> bool;
245}
246
247#[async_trait]
249pub trait EmailQueue: Send + Sync {
250 async fn enqueue(&self, message: EmailMessage, scheduled_at: Option<chrono::DateTime<chrono::Utc>>) -> EmailResult<String>;
252
253 async fn dequeue(&self) -> EmailResult<Option<EmailMessage>>;
255
256 async fn size(&self) -> EmailResult<usize>;
258
259 async fn clear(&self) -> EmailResult<()>;
261
262 async fn remove(&self, message_id: &str) -> EmailResult<bool>;
264
265 async fn get_scheduled(&self, limit: usize) -> EmailResult<Vec<EmailMessage>>;
267}