87 lines
2.5 KiB
Rust
87 lines
2.5 KiB
Rust
use lettre::{
|
|
AsyncSmtpTransport, AsyncTransport, Message, Tokio1Executor,
|
|
message::Mailbox,
|
|
transport::smtp::{authentication::Credentials, extension::ClientId},
|
|
};
|
|
|
|
use crate::{
|
|
config::AppConfig,
|
|
error::{AppError, AppResult},
|
|
};
|
|
|
|
fn transport(config: &AppConfig) -> AppResult<AsyncSmtpTransport<Tokio1Executor>> {
|
|
let smtp = &config.smtp;
|
|
let mut builder = match smtp.kind.as_str() {
|
|
"starttls" => AsyncSmtpTransport::<Tokio1Executor>::starttls_relay(&smtp.host)
|
|
.map_err(|e| AppError::Mail(e.to_string()))?,
|
|
"tls" => AsyncSmtpTransport::<Tokio1Executor>::relay(&smtp.host)
|
|
.map_err(|e| AppError::Mail(e.to_string()))?,
|
|
_ => AsyncSmtpTransport::<Tokio1Executor>::builder_dangerous(&smtp.host),
|
|
}
|
|
.port(smtp.port);
|
|
|
|
if !smtp.client_name.trim().is_empty() {
|
|
builder = builder.hello_name(ClientId::Domain(smtp.client_name.trim().to_string()));
|
|
}
|
|
if !smtp.username.is_empty() {
|
|
builder = builder.credentials(Credentials::new(
|
|
smtp.username.clone(),
|
|
smtp.password.clone(),
|
|
));
|
|
}
|
|
Ok(builder.build())
|
|
}
|
|
|
|
pub fn sender_named(
|
|
config: &AppConfig,
|
|
fallback: &str,
|
|
display_name: Option<&str>,
|
|
) -> AppResult<Mailbox> {
|
|
let address = if config.smtp.admin_email.is_empty() {
|
|
fallback
|
|
} else {
|
|
&config.smtp.admin_email
|
|
};
|
|
let parsed = address
|
|
.parse()
|
|
.map_err(|e| AppError::bad_request(format!("invalid sender address: {e}")))?;
|
|
let name = display_name
|
|
.filter(|value| !value.trim().is_empty())
|
|
.unwrap_or(&config.smtp.admin_name);
|
|
Ok(Mailbox::new(Some(name.to_string()), parsed))
|
|
}
|
|
|
|
pub async fn send_text(
|
|
config: &AppConfig,
|
|
from_fallback: &str,
|
|
to: &str,
|
|
subject: &str,
|
|
body: &str,
|
|
) -> AppResult<()> {
|
|
send_text_named(config, from_fallback, None, to, subject, body).await
|
|
}
|
|
|
|
pub async fn send_text_named(
|
|
config: &AppConfig,
|
|
from_fallback: &str,
|
|
from_name: Option<&str>,
|
|
to: &str,
|
|
subject: &str,
|
|
body: &str,
|
|
) -> AppResult<()> {
|
|
let to: Mailbox = to
|
|
.parse()
|
|
.map_err(|e| AppError::bad_request(format!("invalid recipient: {e}")))?;
|
|
let message = Message::builder()
|
|
.from(sender_named(config, from_fallback, from_name)?)
|
|
.to(to)
|
|
.subject(subject)
|
|
.body(body.to_owned())
|
|
.map_err(|e| AppError::Mail(e.to_string()))?;
|
|
transport(config)?
|
|
.send(message)
|
|
.await
|
|
.map_err(|e| AppError::Mail(e.to_string()))?;
|
|
Ok(())
|
|
}
|