Skip to main content

mail_list/
mailer.rs

1use std::borrow::Cow;
2
3use lettre::{
4    Address, AsyncSmtpTransport, AsyncTransport, Message, message::Mailbox,
5    transport::smtp::response::Severity,
6};
7
8use crate::{EmailEnvelopeDetails, MailListError, MailListResult};
9
10#[derive(Debug)]
11pub struct Smtps {
12    pub(crate) domain: Cow<'static, str>,
13    pub(crate) from: Mailbox,
14    pub(crate) reply_to: Option<Mailbox>,
15    pub(crate) ehlo_name: Option<Cow<'static, str>>,
16    #[cfg(feature = "tokio-runtime")]
17    pub(crate) mailer: AsyncSmtpTransport<lettre::Tokio1Executor>,
18}
19
20impl Smtps {
21    pub fn set_reply_to(
22        &mut self,
23        reply_to_name: &str,
24        reply_to_address: &str,
25    ) -> MailListResult<&mut Self> {
26        self.reply_to.replace(Mailbox::new(
27            Some(reply_to_name.to_string()),
28            reply_to_address
29                .parse::<Address>()
30                .map_err(|error| MailListError::Mailer(error.to_string()))?,
31        ));
32
33        Ok(self)
34    }
35
36    pub fn domain(&self) -> &str {
37        self.domain.as_ref()
38    }
39
40    pub fn ehlo_name(&self) -> Option<&Cow<'_, str>> {
41        self.ehlo_name.as_ref()
42    }
43
44    pub fn from(&self) -> &Mailbox {
45        &self.from
46    }
47
48    pub fn reply_to(&self) -> Option<&Mailbox> {
49        self.reply_to.as_ref()
50    }
51
52    #[cfg(feature = "tokio-runtime")]
53    pub fn mailer(&self) -> &AsyncSmtpTransport<lettre::Tokio1Executor> {
54        &self.mailer
55    }
56
57    pub async fn send(&self, message: &EmailEnvelopeDetails) -> MailListResult<()> {
58        let email = Message::builder().from(self.from().clone());
59
60        let email = if let Some(reply_to) = self.reply_to() {
61            email.reply_to(reply_to.clone())
62        } else {
63            email
64        };
65
66        let email = email
67            .to(message
68                .to
69                .parse::<Mailbox>()
70                .map_err(|error| MailListError::Mailer(error.to_string()))?)
71            .subject(message.subject.as_str())
72            .header(lettre::message::header::ContentType::TEXT_HTML)
73            .body(message.body.to_string())
74            .map_err(|error| MailListError::Mailer(error.to_string()))?;
75
76        let response = self
77            .mailer
78            .send(email)
79            .await
80            .map_err(|error| MailListError::MailDelivery(error.to_string()))?;
81
82        if response.code().severity == Severity::PositiveCompletion {
83            Ok(())
84        } else {
85            Err(MailListError::Smtps(
86                response.message().map(|msg| msg.to_string()).collect(),
87            ))
88        }
89    }
90
91    pub async fn test_connection(&self) -> MailListResult<bool> {
92        self.mailer
93            .test_connection()
94            .await
95            .map_err(|error| MailListError::Mailer(error.to_string()))
96    }
97}