1use serde::{Deserialize, Serialize};
2use std::collections::HashMap;
3
4#[derive(Debug, Clone, Serialize, Deserialize)]
6pub struct EmailConfig {
7 pub default_from: String,
9 pub default_provider: String,
11 pub providers: HashMap<String, ProviderConfig>,
13 pub templates: TemplateConfig,
15 pub tracking: GlobalTrackingConfig,
17 pub attachments: AttachmentConfig,
19}
20
21#[derive(Debug, Clone, Serialize, Deserialize)]
23#[serde(tag = "type")]
24pub enum ProviderConfig {
25 #[serde(rename = "smtp")]
26 Smtp(SmtpConfig),
27 #[serde(rename = "sendgrid")]
28 SendGrid(SendGridConfig),
29 #[serde(rename = "mailgun")]
30 Mailgun(MailgunConfig),
31}
32
33#[derive(Debug, Clone, Serialize, Deserialize)]
35pub enum SmtpAuthMethod {
36 #[serde(rename = "plain")]
37 Plain,
38 #[serde(rename = "login")]
39 Login,
40 #[serde(rename = "xoauth2")]
41 XOAuth2,
42}
43
44#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
46pub enum SmtpTlsConfig {
47 #[serde(rename = "none")]
48 None,
49 #[serde(rename = "tls")]
50 Tls,
51 #[serde(rename = "starttls")]
52 StartTls,
53 #[serde(rename = "starttls_required")]
54 StartTlsRequired,
55}
56
57#[derive(Debug, Clone, Serialize, Deserialize)]
59pub struct SmtpConfig {
60 pub host: String,
62 pub port: u16,
64 pub username: String,
66 pub password: String,
68 pub tls: SmtpTlsConfig,
70 pub auth_method: SmtpAuthMethod,
72 pub timeout: Option<u64>,
74 pub pool_size: Option<u32>,
76 pub keepalive: bool,
78 pub max_retries: u32,
80 pub retry_delay: u64,
82
83 #[serde(skip_serializing_if = "Option::is_none")]
85 pub use_tls: Option<bool>,
86 #[serde(skip_serializing_if = "Option::is_none")]
87 pub use_starttls: Option<bool>,
88}
89
90#[derive(Debug, Clone, Serialize, Deserialize)]
92pub struct SendGridConfig {
93 pub api_key: String,
95 pub endpoint: Option<String>,
97 pub timeout: Option<u64>,
99}
100
101#[derive(Debug, Clone, Serialize, Deserialize)]
103pub struct MailgunConfig {
104 pub api_key: String,
106 pub domain: String,
108 pub region: Option<String>,
110 pub timeout: Option<u64>,
112}
113
114#[derive(Debug, Clone, Serialize, Deserialize)]
116pub struct TemplateConfig {
117 pub templates_dir: String,
119 pub layouts_dir: String,
121 pub partials_dir: String,
123 pub enable_cache: bool,
125 pub template_extension: String,
127 pub cache_size: Option<u64>,
129 pub watch_files: bool,
131}
132
133#[derive(Debug, Clone, Serialize, Deserialize)]
135pub struct QueueConfig {
136 pub enabled: bool,
138 pub queue_name: String,
140 pub max_retries: u32,
142 pub retry_delay: u64,
144 pub batch_size: usize,
146}
147
148#[derive(Debug, Clone, Serialize, Deserialize)]
150pub struct GlobalTrackingConfig {
151 pub enabled: bool,
153 pub base_url: Option<String>,
155 pub pixel_endpoint: String,
157 pub link_endpoint: String,
159}
160
161#[derive(Debug, Clone, Serialize, Deserialize)]
163pub struct AttachmentConfig {
164 pub max_size: usize,
166 pub max_total_size: usize,
168 pub max_count: usize,
170 pub allowed_types: Vec<String>,
172 pub blocked_types: Vec<String>,
174 pub auto_compress: bool,
176 pub compression_quality: Option<u8>,
178}
179
180impl Default for EmailConfig {
181 fn default() -> Self {
182 Self {
183 default_from: "noreply@example.com".to_string(),
184 default_provider: "smtp".to_string(),
185 providers: HashMap::new(),
186 templates: TemplateConfig::default(),
187 tracking: GlobalTrackingConfig::default(),
188 attachments: AttachmentConfig::default(),
189 }
190 }
191}
192
193impl Default for TemplateConfig {
194 fn default() -> Self {
195 Self {
196 templates_dir: "templates/emails".to_string(),
197 layouts_dir: "templates/emails/layouts".to_string(),
198 partials_dir: "templates/emails/partials".to_string(),
199 enable_cache: true,
200 template_extension: ".html".to_string(),
201 cache_size: None,
202 watch_files: false,
203 }
204 }
205}
206
207impl Default for QueueConfig {
208 fn default() -> Self {
209 Self {
210 enabled: true,
211 queue_name: "emails".to_string(),
212 max_retries: 3,
213 retry_delay: 60,
214 batch_size: 10,
215 }
216 }
217}
218
219impl Default for GlobalTrackingConfig {
220 fn default() -> Self {
221 Self {
222 enabled: false,
223 base_url: None,
224 pixel_endpoint: "/email/track/open".to_string(),
225 link_endpoint: "/email/track/click".to_string(),
226 }
227 }
228}
229
230impl Default for AttachmentConfig {
231 fn default() -> Self {
232 Self {
233 max_size: 25 * 1024 * 1024, max_total_size: 50 * 1024 * 1024, max_count: 10,
236 allowed_types: vec![], blocked_types: vec![
238 "application/x-executable".to_string(),
239 "application/x-dosexec".to_string(),
240 "application/x-msdownload".to_string(),
241 ],
242 auto_compress: false,
243 compression_quality: Some(85),
244 }
245 }
246}
247
248impl Default for SmtpAuthMethod {
249 fn default() -> Self {
250 SmtpAuthMethod::Plain
251 }
252}
253
254impl Default for SmtpTlsConfig {
255 fn default() -> Self {
256 SmtpTlsConfig::StartTls
257 }
258}
259
260impl SmtpConfig {
261 pub fn new(
263 host: impl Into<String>,
264 port: u16,
265 username: impl Into<String>,
266 password: impl Into<String>,
267 ) -> Self {
268 Self {
269 host: host.into(),
270 port,
271 username: username.into(),
272 password: password.into(),
273 tls: SmtpTlsConfig::StartTls,
274 auth_method: SmtpAuthMethod::Plain,
275 timeout: Some(30),
276 pool_size: Some(10),
277 keepalive: true,
278 max_retries: 3,
279 retry_delay: 5,
280 use_tls: None,
281 use_starttls: None,
282 }
283 }
284
285 pub fn effective_tls_config(&self) -> SmtpTlsConfig {
287 if self.use_tls.is_some() || self.use_starttls.is_some() {
289 let use_tls = self.use_tls.unwrap_or(false);
290 let use_starttls = self.use_starttls.unwrap_or(false);
291 match (use_tls, use_starttls) {
292 (true, false) => SmtpTlsConfig::Tls,
293 (false, true) => SmtpTlsConfig::StartTls,
294 (false, false) => SmtpTlsConfig::None,
295 (true, true) => SmtpTlsConfig::StartTls, }
297 } else {
298 self.tls.clone()
299 }
300 }
301}
302
303impl SendGridConfig {
304 pub fn new(api_key: impl Into<String>) -> Self {
306 Self {
307 api_key: api_key.into(),
308 endpoint: None,
309 timeout: Some(30),
310 }
311 }
312}
313
314impl MailgunConfig {
315 pub fn new(api_key: impl Into<String>, domain: impl Into<String>) -> Self {
317 Self {
318 api_key: api_key.into(),
319 domain: domain.into(),
320 region: None,
321 timeout: Some(30),
322 }
323 }
324}
325
326#[cfg(test)]
327mod tests {
328 use super::*;
329
330 #[test]
331 fn test_effective_tls_config_new_field() {
332 let mut config = SmtpConfig::new("smtp.gmail.com", 587, "user", "pass");
333 config.tls = SmtpTlsConfig::Tls;
334
335 assert!(matches!(config.effective_tls_config(), SmtpTlsConfig::Tls));
336 }
337
338 #[test]
339 fn test_effective_tls_config_legacy_both() {
340 let mut config = SmtpConfig::new("smtp.gmail.com", 587, "user", "pass");
341 config.use_tls = Some(true);
342 config.use_starttls = Some(false);
343
344 assert!(matches!(config.effective_tls_config(), SmtpTlsConfig::Tls));
345 }
346
347 #[test]
348 fn test_effective_tls_config_legacy_only_tls() {
349 let mut config = SmtpConfig::new("smtp.gmail.com", 587, "user", "pass");
350 config.use_tls = Some(true);
351 assert!(matches!(config.effective_tls_config(), SmtpTlsConfig::Tls));
354 }
355
356 #[test]
357 fn test_effective_tls_config_legacy_only_starttls() {
358 let mut config = SmtpConfig::new("smtp.gmail.com", 587, "user", "pass");
359 config.use_starttls = Some(true);
360 assert!(matches!(config.effective_tls_config(), SmtpTlsConfig::StartTls));
363 }
364
365 #[test]
366 fn test_effective_tls_config_legacy_both_false() {
367 let mut config = SmtpConfig::new("smtp.gmail.com", 587, "user", "pass");
368 config.use_tls = Some(false);
369 config.use_starttls = Some(false);
370
371 assert!(matches!(config.effective_tls_config(), SmtpTlsConfig::None));
372 }
373
374 #[test]
375 fn test_effective_tls_config_legacy_both_true() {
376 let mut config = SmtpConfig::new("smtp.gmail.com", 587, "user", "pass");
377 config.use_tls = Some(true);
378 config.use_starttls = Some(true);
379
380 assert!(matches!(config.effective_tls_config(), SmtpTlsConfig::StartTls));
382 }
383
384 #[test]
385 fn test_effective_tls_config_legacy_false_none() {
386 let mut config = SmtpConfig::new("smtp.gmail.com", 587, "user", "pass");
387 config.use_tls = Some(false);
388 assert!(matches!(config.effective_tls_config(), SmtpTlsConfig::None));
391 }
392}