1use thiserror::Error;
4
5pub type Result<T> = std::result::Result<T, MailError>;
7
8#[derive(Debug, Error)]
10pub enum MailError {
11 #[error("SMTP error: {0}")]
13 Smtp(String),
14
15 #[error("Invalid email address: {0}")]
17 InvalidAddress(String),
18
19 #[error("Missing required field: {0}")]
21 MissingField(&'static str),
22
23 #[error("Template error: {0}")]
25 Template(String),
26
27 #[error("Template not found: {0}")]
29 TemplateNotFound(String),
30
31 #[error("Attachment error: {0}")]
33 Attachment(String),
34
35 #[error("Configuration error: {0}")]
37 Config(String),
38
39 #[error("Provider error: {0}")]
41 Provider(String),
42
43 #[error("Authentication failed: {0}")]
45 Auth(String),
46
47 #[error("Rate limited, retry after {0} seconds")]
49 RateLimited(u64),
50
51 #[error("I/O error: {0}")]
53 Io(#[from] std::io::Error),
54
55 #[error("Serialization error: {0}")]
57 Serialization(String),
58
59 #[error("Network error: {0}")]
61 Network(String),
62
63 #[error("Operation timed out")]
65 Timeout,
66
67 #[error("Queue error: {0}")]
69 Queue(String),
70}
71
72impl MailError {
73 pub fn is_retryable(&self) -> bool {
75 matches!(
76 self,
77 Self::Smtp(_) | Self::Network(_) | Self::Timeout | Self::RateLimited(_)
78 )
79 }
80
81 pub fn retry_after(&self) -> Option<std::time::Duration> {
83 if let Self::RateLimited(secs) = self {
84 Some(std::time::Duration::from_secs(*secs))
85 } else {
86 None
87 }
88 }
89}
90
91impl From<lettre::transport::smtp::Error> for MailError {
92 fn from(err: lettre::transport::smtp::Error) -> Self {
93 Self::Smtp(err.to_string())
94 }
95}
96
97impl From<lettre::address::AddressError> for MailError {
98 fn from(err: lettre::address::AddressError) -> Self {
99 Self::InvalidAddress(err.to_string())
100 }
101}
102
103impl From<lettre::error::Error> for MailError {
104 fn from(err: lettre::error::Error) -> Self {
105 Self::Smtp(err.to_string())
106 }
107}
108
109impl From<serde_json::Error> for MailError {
110 fn from(err: serde_json::Error) -> Self {
111 Self::Serialization(err.to_string())
112 }
113}
114
115#[cfg(feature = "handlebars")]
116impl From<handlebars::RenderError> for MailError {
117 fn from(err: handlebars::RenderError) -> Self {
118 Self::Template(err.to_string())
119 }
120}
121
122#[cfg(feature = "handlebars")]
123impl From<handlebars::TemplateError> for MailError {
124 fn from(err: handlebars::TemplateError) -> Self {
125 Self::Template(err.to_string())
126 }
127}
128
129#[cfg(feature = "tera")]
130impl From<tera::Error> for MailError {
131 fn from(err: tera::Error) -> Self {
132 Self::Template(err.to_string())
133 }
134}