use crate::{EmailMessage, EmailResult, backends::EmailBackend};
use reinhardt_conf::settings::email::EmailSettings;
use reinhardt_conf::settings::fragment::HasSettings;
pub async fn send_mail<S: HasSettings<EmailSettings> + ?Sized>(
settings: &S,
subject: impl Into<String>,
message: impl Into<String>,
recipient_list: Vec<impl Into<String>>,
html_message: Option<String>,
) -> EmailResult<()> {
let backend = crate::backends::backend_from_settings(settings)?;
let email_settings = settings.get_settings();
let recipients: Vec<String> = recipient_list.into_iter().map(|r| r.into()).collect();
let mut email_builder = EmailMessage::builder()
.subject(subject)
.body(message)
.from(&email_settings.from_email)
.to(recipients);
if let Some(html) = html_message {
email_builder = email_builder.html(html);
}
let email = email_builder.build()?;
backend.send_messages(&[email]).await?;
Ok(())
}
pub async fn send_mail_with_backend(
subject: impl Into<String>,
message: impl Into<String>,
from_email: impl Into<String>,
recipient_list: Vec<impl Into<String>>,
html_message: Option<String>,
backend: &dyn EmailBackend,
) -> EmailResult<()> {
let recipients: Vec<String> = recipient_list.into_iter().map(|r| r.into()).collect();
let mut email_builder = EmailMessage::builder()
.subject(subject)
.body(message)
.from(from_email)
.to(recipients);
if let Some(html) = html_message {
email_builder = email_builder.html(html);
}
let email = email_builder.build()?;
backend.send_messages(&[email]).await?;
Ok(())
}
pub async fn send_mass_mail(
messages: Vec<EmailMessage>,
backend: &dyn EmailBackend,
) -> EmailResult<usize> {
backend.send_messages(&messages).await
}
pub async fn mail_admins<S: HasSettings<EmailSettings> + ?Sized>(
settings: &S,
subject: impl Into<String>,
message: impl Into<String>,
fail_silently: bool,
backend: &dyn EmailBackend,
) -> EmailResult<()> {
let email_settings = settings.get_settings();
send_to_role(
&email_settings.admins,
"admins",
settings,
subject,
message,
fail_silently,
backend,
)
.await
}
pub async fn mail_managers<S: HasSettings<EmailSettings> + ?Sized>(
settings: &S,
subject: impl Into<String>,
message: impl Into<String>,
fail_silently: bool,
backend: &dyn EmailBackend,
) -> EmailResult<()> {
let email_settings = settings.get_settings();
send_to_role(
&email_settings.managers,
"managers",
settings,
subject,
message,
fail_silently,
backend,
)
.await
}
async fn send_to_role(
recipients: &[(String, String)],
role_name: &str,
settings: &(impl HasSettings<EmailSettings> + ?Sized),
subject: impl Into<String>,
message: impl Into<String>,
fail_silently: bool,
backend: &dyn EmailBackend,
) -> EmailResult<()> {
let email_settings = settings.get_settings();
if recipients.is_empty() {
if fail_silently {
return Ok(());
} else {
return Err(crate::EmailError::MissingField(role_name.to_string()));
}
}
let emails: Vec<String> = recipients.iter().map(|(_, email)| email.clone()).collect();
let subject_str = subject.into();
let final_subject = if !email_settings.subject_prefix.is_empty() {
format!("{} {}", email_settings.subject_prefix, subject_str)
} else {
subject_str
};
let from_email = if !email_settings.server_email.is_empty() {
email_settings.server_email.clone()
} else {
email_settings.from_email.clone()
};
let result =
send_mail_with_backend(final_subject, message, from_email, emails, None, backend).await;
match result {
Ok(()) => Ok(()),
Err(e) if fail_silently && e.is_transient() => {
eprintln!("Email to {} failed (fail_silently=true): {}", role_name, e);
Ok(())
}
Err(e) => Err(e),
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::backends::MemoryBackend;
#[tokio::test]
async fn test_send_mail() {
let backend = MemoryBackend::new();
let result = send_mail_with_backend(
"Test Subject",
"Test Message",
"from@example.com",
vec!["to@example.com"],
None,
&backend,
)
.await;
assert!(result.is_ok());
assert_eq!(backend.count().await, 1);
}
#[tokio::test]
async fn test_send_mail_with_html() {
let backend = MemoryBackend::new();
let result = send_mail_with_backend(
"Test Subject",
"Test Message",
"from@example.com",
vec!["to@example.com"],
Some("<h1>Test HTML</h1>".to_string()),
&backend,
)
.await;
assert!(result.is_ok());
let messages = backend.get_messages().await;
assert!(messages[0].html_body().is_some());
}
#[tokio::test]
async fn test_send_mass_mail() {
let backend = MemoryBackend::new();
let messages = vec![
EmailMessage::builder()
.subject("Test 1")
.body("Body 1")
.from("from@example.com")
.to(vec!["to1@example.com".to_string()])
.build()
.unwrap(),
EmailMessage::builder()
.subject("Test 2")
.body("Body 2")
.from("from@example.com")
.to(vec!["to2@example.com".to_string()])
.build()
.unwrap(),
];
let results = send_mass_mail(messages, &backend).await.unwrap();
assert_eq!(results, 2);
assert_eq!(backend.count().await, 2);
}
}