mail-list 0.2.4-alpha

Email subscriptions list library. Unicast, narrowcast and broadcast lists
Documentation
use std::borrow::Cow;

use lettre::{AsyncSmtpTransport, message::Mailbox};
use percent_encoding::percent_decode_str;

use crate::{MailListError, MailListResult, Smtps};

#[derive(Debug, Default)]
pub struct SmtpsBuilder {
    from: Cow<'static, str>,
    reply_to: Option<Cow<'static, str>>,
    hello_name: Option<Cow<'static, str>>,
}

impl SmtpsBuilder {
    pub fn new() -> Self {
        Self::default()
    }

    pub fn set_from(&mut self, from: &str) -> &mut Self {
        self.from = from.to_string().into();

        self
    }
    pub fn set_reply_to(&mut self, reply_to: &str) -> &mut Self {
        self.reply_to.replace(reply_to.to_string().into());

        self
    }

    pub fn set_hello_name(&mut self, hello_name: &str) -> &mut Self {
        self.hello_name.replace(hello_name.to_string().into());

        self
    }

    pub fn build(self, smtps_uri: &str) -> MailListResult<Smtps> {
        use url::Url;

        let url =
            Url::parse(smtps_uri).or(Err(MailListError::Smtps("Invalid smtps URI".to_string())))?;

        if url.scheme() != "smtps" {
            return Err(MailListError::Smtps("Invalid SMTPs scheme".to_string()));
        };
        let domain = url
            .host_str()
            .ok_or(MailListError::Smtps(
                "Domain not found in SMTPs".to_string(),
            ))?
            .to_string();
        let domain = percent_decode_str(&domain)
            .decode_utf8()
            .or(Err(MailListError::Smtps(
                "Domain is not valid UTF-8".to_string(),
            )))?
            .to_string();

        let ehlo_name = url.path().trim_start_matches('/');
        let ehlo_name = percent_decode_str(&ehlo_name)
            .decode_utf8()
            .or(Err(MailListError::Smtps(
                "EHLO name is not valid UTF-8".to_string(),
            )))?
            .to_string();

        #[cfg(feature = "tokio-runtime")]
        let transport = AsyncSmtpTransport::<lettre::Tokio1Executor>::from_url(smtps_uri)
            .map_err(|error| MailListError::Mailer(error.to_string()))?;

        let mailer = if let Some(hello_name) = self.hello_name.as_ref() {
            transport.hello_name(lettre::transport::smtp::extension::ClientId::Domain(
                hello_name.to_string(),
            ))
        } else {
            transport
        };

        let mailer = mailer.build();

        let outcome = Smtps {
            domain: domain.into(),
            from: self
                .from
                .parse::<Mailbox>()
                .map_err(|error| MailListError::Mailer(error.to_string()))?,
            reply_to: self
                .reply_to
                .map(|value| {
                    value
                        .parse::<Mailbox>()
                        .map_err(|error| MailListError::Mailer(error.to_string()))
                })
                .transpose()?,
            ehlo_name: if ehlo_name.is_empty() {
                None
            } else {
                Some(ehlo_name.to_string().into())
            },
            mailer,
        };

        Ok(outcome)
    }

    pub async fn test_connection(self, smtps_uri: &str) -> MailListResult<()> {
        #[cfg(feature = "tokio-runtime")]
        let transport = AsyncSmtpTransport::<lettre::Tokio1Executor>::from_url(smtps_uri)
            .map_err(|error| MailListError::Mailer(error.to_string()))?;

        let mailer = if let Some(hello_name) = self.hello_name.as_ref() {
            transport.hello_name(lettre::transport::smtp::extension::ClientId::Domain(
                hello_name.to_string(),
            ))
        } else {
            transport
        };

        #[cfg(feature = "tokio-runtime")]
        let mailer: AsyncSmtpTransport<lettre::Tokio1Executor> = mailer.build();

        mailer
            .test_connection()
            .await
            .map_err(|error| MailListError::Mailer(error.to_string()))?;

        Ok(())
    }
}