Skip to main content

mailbridge/error/
kind.rs

1pub type Result<T> = std::result::Result<T, MailError>;
2
3#[derive(Debug, Clone, PartialEq, thiserror::Error)]
4pub enum MailError {
5    #[error("configuration error: {0}")]
6    Config(String),
7
8    #[error("invalid email message: {0}")]
9    Validation(String),
10
11    #[error("sender domain is not allowed: {domain}")]
12    SenderDomainNotAllowed { domain: String },
13
14    #[error("request rate limited")]
15    RateLimited,
16
17    #[error("relay authentication failed")]
18    Authentication,
19
20    #[error("relay rejected request: status={status}, message={message}")]
21    RelayRejected { status: u16, message: String },
22
23    #[error("temporary delivery failure: {0}")]
24    Temporary(String),
25
26    #[error("queue error: {0}")]
27    Queue(String),
28}
29
30impl MailError {
31    #[must_use]
32    pub const fn is_retryable(&self) -> bool {
33        match self {
34            Self::RateLimited | Self::Temporary(_) => true,
35            Self::RelayRejected { status, .. } => *status == 429 || *status >= 500,
36            Self::Config(_)
37            | Self::Validation(_)
38            | Self::SenderDomainNotAllowed { .. }
39            | Self::Authentication
40            | Self::Queue(_) => false,
41        }
42    }
43}
44
45#[cfg(test)]
46mod tests {
47    use super::*;
48
49    #[test]
50    fn retry_classification_marks_temporary_errors_retryable() {
51        let error = MailError::Temporary("timeout".to_owned());
52
53        assert!(error.is_retryable());
54    }
55
56    #[test]
57    fn retry_classification_marks_validation_errors_permanent() {
58        let error = MailError::Validation("missing body".to_owned());
59
60        assert!(!error.is_retryable());
61    }
62}