ft_sys_shared/
email.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
/// add an email to the offline email queue, so that the email can be sent later. these emails
/// get picked up by the email worker.
///
/// # Arguments
///
/// * `from` - (name, email)
/// * `to` - Vec<(name, email)>
/// * `subject` - email subject
/// * `body_html` - email body in html format
/// * `body_text` - email body in text format
/// * `reply_to` - (name, email)
/// * `mkind` - mkind is any string, used for product analytics, etc. the value should be dot
///   separated, e.g. x.y.z to capture hierarchy. ideally you should use `marketing.` as the prefix
///   for all marketing related emails, and anything else for transaction mails, so your mailer can
///   use appropriate channels
/// * `cc`, `bcc` - Vec<(name, email)>
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct Email {
    pub from: EmailAddress,
    pub to: Vec<EmailAddress>,
    pub subject: String,
    pub body_html: String,
    pub body_text: String,
    pub reply_to: Option<Vec<EmailAddress>>,
    pub cc: Option<Vec<EmailAddress>>,
    pub bcc: Option<Vec<EmailAddress>>,
    pub mkind: String,
}

#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct EmailAddress {
    pub name: Option<String>,
    pub email: String,
}

impl From<EmailAddress> for String {
    fn from(x: EmailAddress) -> Self {
        let name = x.name.unwrap_or_default();
        format!("{} <{}>", name, x.email)
    }
}

impl From<(String, String)> for EmailAddress {
    fn from((name, email): (String, String)) -> Self {
        EmailAddress {
            name: Some(name),
            email,
        }
    }
}

impl From<String> for EmailAddress {
    fn from(email: String) -> Self {
        let email = email.trim().to_string();

        // handle both cases where the name is present and where its just email address
        if let Some(i) = email.find('<') {
            let name = email[..i].to_string();
            let email = email[i + 1..].to_string();
            EmailAddress {
                name: Some(name),
                email,
            }
        } else {
            EmailAddress { name: None, email }
        }
    }
}

/// `ft_sdk::send_mail()` returns an EmailHandle, which can be used to cancel the email during the
/// web request. this is useful in case you want to do a cleanup in case a transaction fails, etc.
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct EmailHandle(String);

// fn to_comma_separated_str(x: Vec<(&str, &str)>) -> String {
//     let len = x
//         .iter()
//         .fold(0, |acc, (name, email)| acc + name.len() + email.len() + 5);
//     x.iter()
//         .fold(String::with_capacity(len), |mut acc, (name, email)| {
//             if !acc.is_empty() {
//                 acc.push_str(", ");
//             };
//             acc.push_str(name);
//             acc.push_str(" <");
//             acc.push_str(email);
//             acc.push('>');
//             acc
//         })
// }
//
// #[cfg(test)]
// mod tests {
//     #[test]
//     fn to_comma_separated_str() {
//         assert_eq!(
//             super::to_comma_separated_str(vec![("Alice", "alice@a.com")]),
//             "Alice <alice@a.com>"
//         );
//         assert_eq!(
//             super::to_comma_separated_str(vec![("Alice", "alice@a.com"), ("Bob", "bob@a.com")]),
//             "Alice <alice@a.com>, Bob <bob@a.com>"
//         );
//     }
// }