1use std::{ffi::OsString, num::NonZeroU16, sync::Arc};
10
11use async_trait::async_trait;
12use lettre::{
13 AsyncTransport, Tokio1Executor,
14 address::Envelope,
15 transport::{
16 sendmail::AsyncSendmailTransport,
17 smtp::{AsyncSmtpTransport, authentication::Credentials},
18 },
19};
20use thiserror::Error;
21
22#[derive(Debug, Clone, Copy)]
24pub enum SmtpMode {
25 Plain,
27 StartTls,
29 Tls,
31}
32
33#[derive(Default, Clone)]
35pub struct Transport {
36 inner: Arc<TransportInner>,
37}
38
39enum TransportInner {
40 Blackhole,
41 Smtp(AsyncSmtpTransport<Tokio1Executor>),
42 Sendmail(AsyncSendmailTransport<Tokio1Executor>),
43}
44
45impl Transport {
46 fn new(inner: TransportInner) -> Self {
47 let inner = Arc::new(inner);
48 Self { inner }
49 }
50
51 #[must_use]
53 pub fn blackhole() -> Self {
54 Self::new(TransportInner::Blackhole)
55 }
56
57 pub fn smtp(
63 mode: SmtpMode,
64 hostname: &str,
65 port: Option<NonZeroU16>,
66 credentials: Option<Credentials>,
67 ) -> Result<Self, lettre::transport::smtp::Error> {
68 let mut t = match mode {
69 SmtpMode::Plain => AsyncSmtpTransport::<Tokio1Executor>::builder_dangerous(hostname),
70 SmtpMode::StartTls => AsyncSmtpTransport::<Tokio1Executor>::starttls_relay(hostname)?,
71 SmtpMode::Tls => AsyncSmtpTransport::<Tokio1Executor>::relay(hostname)?,
72 };
73
74 if let Some(credentials) = credentials {
75 t = t.credentials(credentials);
76 }
77
78 if let Some(port) = port {
79 t = t.port(port.into());
80 }
81
82 Ok(Self::new(TransportInner::Smtp(t.build())))
83 }
84
85 #[must_use]
87 pub fn sendmail(command: Option<impl Into<OsString>>) -> Self {
88 let transport = if let Some(command) = command {
89 AsyncSendmailTransport::new_with_command(command)
90 } else {
91 AsyncSendmailTransport::new()
92 };
93 Self::new(TransportInner::Sendmail(transport))
94 }
95}
96
97impl Transport {
98 pub async fn test_connection(&self) -> Result<(), Error> {
105 match self.inner.as_ref() {
106 TransportInner::Smtp(t) => {
107 t.test_connection().await?;
108 }
109 TransportInner::Blackhole | TransportInner::Sendmail(_) => {}
110 }
111
112 Ok(())
113 }
114}
115
116impl Default for TransportInner {
117 fn default() -> Self {
118 Self::Blackhole
119 }
120}
121
122#[derive(Debug, Error)]
123#[error(transparent)]
124pub enum Error {
125 Smtp(#[from] lettre::transport::smtp::Error),
126 Sendmail(#[from] lettre::transport::sendmail::Error),
127}
128
129#[async_trait]
130impl AsyncTransport for Transport {
131 type Ok = ();
132 type Error = Error;
133
134 async fn send_raw(&self, envelope: &Envelope, email: &[u8]) -> Result<Self::Ok, Self::Error> {
135 match self.inner.as_ref() {
136 TransportInner::Blackhole => {
137 tracing::warn!(
138 "An email was supposed to be sent but no email backend is configured"
139 );
140 }
141 TransportInner::Smtp(t) => {
142 t.send_raw(envelope, email).await?;
143 }
144 TransportInner::Sendmail(t) => {
145 t.send_raw(envelope, email).await?;
146 }
147 }
148
149 Ok(())
150 }
151}