refactor: use async email sending

This commit is contained in:
Max Hohlfeld 2025-01-24 15:51:22 +01:00
parent 8b470f2d4f
commit b696aa4fe0
9 changed files with 86 additions and 182 deletions

161
Cargo.lock generated
View File

@ -1038,16 +1038,6 @@ dependencies = [
"version_check",
]
[[package]]
name = "core-foundation"
version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f"
dependencies = [
"core-foundation-sys",
"libc",
]
[[package]]
name = "core-foundation-sys"
version = "0.8.7"
@ -1447,21 +1437,6 @@ version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a0d2fde1f7b3d48b8395d5f2de76c18a528bd6a9cdde438df747bfcba3e05d6f"
[[package]]
name = "foreign-types"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1"
dependencies = [
"foreign-types-shared",
]
[[package]]
name = "foreign-types-shared"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
[[package]]
name = "form_urlencoded"
version = "1.2.1"
@ -1554,6 +1529,17 @@ dependencies = [
"syn 2.0.96",
]
[[package]]
name = "futures-rustls"
version = "0.26.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a8f2f12607f92c69b12ed746fabf9ca4f5c482cba46679c1a75b874ed7c26adb"
dependencies = [
"futures-io",
"rustls",
"rustls-pki-types",
]
[[package]]
name = "futures-sink"
version = "0.3.31"
@ -1769,17 +1755,6 @@ dependencies = [
"windows-sys 0.59.0",
]
[[package]]
name = "hostname"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f9c7c7c8ac16c798734b8a24560c1362120597c40d5e1459f09498f8f6c8f2ba"
dependencies = [
"cfg-if",
"libc",
"windows",
]
[[package]]
name = "http"
version = "0.2.12"
@ -2115,23 +2090,29 @@ version = "0.11.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ab4c9a167ff73df98a5ecc07e8bf5ce90b583665da3d1762eb1f775ad4d0d6f5"
dependencies = [
"async-std",
"async-trait",
"base64 0.22.1",
"chumsky",
"email-encoding",
"email_address",
"fastrand 2.3.0",
"futures-io",
"futures-rustls",
"futures-util",
"hostname",
"httpdate",
"idna",
"mime",
"native-tls",
"nom",
"percent-encoding",
"quoted_printable",
"rustls",
"rustls-pemfile",
"rustls-pki-types",
"socket2 0.5.8",
"tokio",
"url",
"webpki-roots",
]
[[package]]
@ -2306,23 +2287,6 @@ dependencies = [
"windows-sys 0.52.0",
]
[[package]]
name = "native-tls"
version = "0.2.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a8614eb2c83d59d1c8cc974dd3f920198647674a0a035e1af1fa58707e317466"
dependencies = [
"libc",
"log",
"openssl",
"openssl-probe",
"openssl-sys",
"schannel",
"security-framework",
"security-framework-sys",
"tempfile",
]
[[package]]
name = "nom"
version = "7.1.3"
@ -2413,50 +2377,6 @@ version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381"
[[package]]
name = "openssl"
version = "0.10.68"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6174bc48f102d208783c2c84bf931bb75927a617866870de8a4ea85597f871f5"
dependencies = [
"bitflags 2.8.0",
"cfg-if",
"foreign-types",
"libc",
"once_cell",
"openssl-macros",
"openssl-sys",
]
[[package]]
name = "openssl-macros"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.96",
]
[[package]]
name = "openssl-probe"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf"
[[package]]
name = "openssl-sys"
version = "0.9.104"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "45abf306cbf99debc8195b66b7346498d7b10c210de50418b5ccd7ceba08c741"
dependencies = [
"cc",
"libc",
"pkg-config",
"vcpkg",
]
[[package]]
name = "parking"
version = "2.2.1"
@ -2955,6 +2875,7 @@ version = "0.23.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f287924602bf649d949c63dc8ac8b235fa5387d394020705b80c4eb597ce5b8"
dependencies = [
"log",
"once_cell",
"ring",
"rustls-pki-types",
@ -3001,44 +2922,12 @@ version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f"
[[package]]
name = "schannel"
version = "0.1.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d"
dependencies = [
"windows-sys 0.59.0",
]
[[package]]
name = "scopeguard"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]]
name = "security-framework"
version = "2.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02"
dependencies = [
"bitflags 2.8.0",
"core-foundation",
"core-foundation-sys",
"libc",
"security-framework-sys",
]
[[package]]
name = "security-framework-sys"
version = "2.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49db231d56a190491cb4aeda9527f1ad45345af50b0851622a7adb8c03b01c32"
dependencies = [
"core-foundation-sys",
"libc",
]
[[package]]
name = "semver"
version = "1.0.25"
@ -3939,16 +3828,6 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windows"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e48a53791691ab099e5e2ad123536d0fff50652600abaf43bbf952894110d0be"
dependencies = [
"windows-core",
"windows-targets 0.52.6",
]
[[package]]
name = "windows-core"
version = "0.52.0"

View File

@ -22,7 +22,7 @@ serde_json = "1.0.114"
pico-args = "0.5.0"
rand = { version = "0.8.5", features = ["getrandom"] }
async-trait = "0.1.79"
lettre = "0.11.11"
lettre = { version = "0.11.11", default-features = false, features = ["builder", "smtp-transport", "async-std1-rustls-tls"] }
quick-xml = { version = "0.37", features = ["serde", "serialize"] }
actix-web-static-files = "4.0"
static-files = "0.2.1"

View File

@ -49,7 +49,7 @@ pub async fn post_new(
let registration = Registration::insert_new_for_user(pool.get_ref(), id).await?;
let new_user = User::read_by_id(pool.get_ref(), id).await?.unwrap();
mailer.send_registration_mail(&new_user, &registration.token)?;
mailer.send_registration_mail(&new_user, &registration.token).await?;
Ok(HttpResponse::Found()
.insert_header((LOCATION, "/users"))

View File

@ -31,7 +31,7 @@ async fn post(
{
if let Ok(user) = User::read_for_login(pool.get_ref(), form.email.as_ref().unwrap()).await {
let reset = PasswordReset::insert_new_for_user(pool.get_ref(), user.id).await?;
mailer.send_forgot_password_mail(&user, &reset.token)?;
mailer.send_forgot_password_mail(&user, &reset.token).await?;
}
return Ok(HttpResponse::Ok().body("E-Mail versandt!"));

View File

@ -1,6 +1,6 @@
use lettre::{
message::{Mailbox, MultiPart, SinglePart},
Message, Transport,
AsyncTransport, Message,
};
use rinja::Template;
@ -9,13 +9,13 @@ use crate::{models::User, utils::ApplicationError};
use super::Mailer;
impl Mailer {
pub fn send_forgot_password_mail(
pub async fn send_forgot_password_mail(
&self,
user: &User,
token: &str,
) -> Result<(), ApplicationError> {
let message = build(&self.hostname, &user.name, &user.email, token)?;
self.transport.send(&message)?;
self.transport.send(message).await?;
Ok(())
}
@ -74,7 +74,7 @@ fn build(
mod tests {
use super::*;
use crate::utils::test_helper::assert_mail_snapshot;
use lettre::transport::stub::StubTransport;
use lettre::{transport::stub::StubTransport, Transport};
#[test]
fn build_mail_snapshot() {

View File

@ -1,10 +1,11 @@
use brass_config::{Config, SmtpTlsType};
use lettre::{
address::Envelope,
transport::{
smtp::{authentication::Credentials, extension::ClientId},
stub::StubTransport,
stub::AsyncStubTransport,
},
SmtpTransport, Transport,
AsyncSmtpTransport, AsyncStd1Executor, AsyncTransport,
};
use crate::utils::ApplicationError;
@ -21,30 +22,46 @@ pub struct Mailer {
#[derive(Clone)]
enum Transports {
SmtpTransport(SmtpTransport),
SmtpTransport(AsyncSmtpTransport<AsyncStd1Executor>),
#[allow(unused)]
StubTransport(StubTransport),
StubTransport(AsyncStubTransport),
}
impl Transport for Transports {
impl AsyncTransport for Transports {
type Ok = ();
type Error = ApplicationError;
fn send_raw(
&self,
envelope: &lettre::address::Envelope,
email: &[u8],
) -> Result<Self::Ok, Self::Error> {
match self {
Transports::SmtpTransport(smtp_transport) => smtp_transport
.send_raw(envelope, email)
.map(|_| ())
.map_err(|err| ApplicationError::EmailTransport(err)),
Transports::StubTransport(stub_transport) => stub_transport
.send_raw(envelope, email)
.map(|_| ())
.map_err(|err| ApplicationError::EmailStubTransport(err)),
}
fn send_raw<'life0, 'life1, 'life2, 'async_trait>(
&'life0 self,
envelope: &'life1 Envelope,
email: &'life2 [u8],
) -> ::core::pin::Pin<
Box<
dyn ::core::future::Future<Output = Result<Self::Ok, Self::Error>>
+ ::core::marker::Send
+ 'async_trait,
>,
>
where
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
Self: 'async_trait,
{
return Box::pin(async move {
match self {
Transports::SmtpTransport(smtp_transport) => smtp_transport
.send_raw(envelope, email)
.await
.map(|_| ())
.map_err(|err| ApplicationError::EmailTransport(err)),
Transports::StubTransport(stub_transport) => stub_transport
.send_raw(envelope, email)
.await
.map(|_| ())
.map_err(|err| ApplicationError::EmailStubTransport(err)),
}
});
}
}
@ -52,11 +69,16 @@ impl Mailer {
pub fn new(config: &Config) -> anyhow::Result<Self> {
let mut builder = match config.smtp_tlstype {
SmtpTlsType::StartTLS => {
SmtpTransport::starttls_relay(&config.smtp_server)?.port(config.smtp_port)
AsyncSmtpTransport::<AsyncStd1Executor>::starttls_relay(&config.smtp_server)?
.port(config.smtp_port)
}
SmtpTlsType::TLS => {
AsyncSmtpTransport::<AsyncStd1Executor>::relay(&config.smtp_server)?
.port(config.smtp_port)
}
SmtpTlsType::TLS => SmtpTransport::relay(&config.smtp_server)?.port(config.smtp_port),
SmtpTlsType::NoTLS => {
SmtpTransport::builder_dangerous(&config.smtp_server).port(config.smtp_port)
AsyncSmtpTransport::<AsyncStd1Executor>::builder_dangerous(&config.smtp_server)
.port(config.smtp_port)
}
};
@ -80,13 +102,12 @@ impl Mailer {
}
}
#[cfg(test)]
impl Mailer {
pub fn new_stub() -> Self {
Mailer {
transport: Transports::StubTransport(StubTransport::new_ok()),
hostname: String::from("testhostname")
transport: Transports::StubTransport(AsyncStubTransport::new_ok()),
hostname: String::from("testhostname"),
}
}
}

View File

@ -1,6 +1,6 @@
use lettre::{
message::{Mailbox, MultiPart, SinglePart},
Message, Transport,
AsyncTransport, Message,
};
use rinja::Template;
@ -9,9 +9,13 @@ use crate::{models::User, utils::ApplicationError};
use super::Mailer;
impl Mailer {
pub fn send_registration_mail(&self, user: &User, token: &str) -> Result<(), ApplicationError> {
pub async fn send_registration_mail(
&self,
user: &User,
token: &str,
) -> Result<(), ApplicationError> {
let message = build(&self.hostname, &user.name, &user.email, token)?;
self.transport.send(&message)?;
self.transport.send(message).await?;
Ok(())
}
@ -73,7 +77,7 @@ fn build(
mod tests {
use super::*;
use crate::utils::test_helper::assert_mail_snapshot;
use lettre::transport::stub::StubTransport;
use lettre::{transport::stub::StubTransport, Transport};
#[test]
fn build_mail_snapshot() {

View File

@ -1,11 +1,11 @@
use lettre::{message::SinglePart, Message, Transport};
use lettre::{message::SinglePart, AsyncTransport, Message};
use crate::utils::ApplicationError;
use super::Mailer;
impl Mailer {
pub fn send_test_mail(&self, to: &str) -> Result<(), ApplicationError> {
pub async fn send_test_mail(&self, to: &str) -> Result<(), ApplicationError> {
let message = Message::builder()
.from("noreply <noreply@brasiwa-leipzig.de>".parse()?)
.reply_to("noreply <noreply@brasiwa-leipzig.de>".parse()?)
@ -15,7 +15,7 @@ impl Mailer {
"Testmail von Brass. E-Mail Versand funktioniert!".to_string(),
))?;
self.transport.send(&message)?;
self.transport.send(message).await?;
Ok(())
}
}

View File

@ -98,7 +98,7 @@ pub async fn handle_command(
exit(0);
}
Some(Command::TestMail(to)) => {
match mailer.send_test_mail(&to) {
match mailer.send_test_mail(&to).await {
Ok(_) => println!("Successfully sent mail to {to}."),
Err(e) => {
if let Some(source) = e.source() {