use crate::{MailConfig, MailError, Mailable};
#[cfg(feature = "postmark")]
pub(crate) async fn send_postmark(
mailable: &dyn Mailable,
to: &str,
config: &MailConfig,
) -> Result<(), MailError> {
let api_key = config
.postmark_api_key
.as_deref()
.ok_or_else(|| MailError::MissingApiKey("postmark".into()))?;
let from = format!("{} <{}>", config.from_name, config.from_address);
let mut body = serde_json::json!({
"From": from,
"To": to,
"Subject": mailable.subject(),
"TextBody": mailable.body(),
});
if let Some(html) = mailable.html_body() {
body["HtmlBody"] = serde_json::Value::String(html);
}
let client = reqwest::Client::new();
let resp = client
.post("https://api.postmarkapp.com/email")
.header("X-Postmark-Server-Token", api_key)
.header("Content-Type", "application/json")
.json(&body)
.send()
.await
.map_err(|e| MailError::Http(e.to_string()))?;
if !resp.status().is_success() {
let status = resp.status();
let text = resp.text().await.unwrap_or_default();
return Err(MailError::Http(format!("Postmark {status}: {text}")));
}
Ok(())
}
#[cfg(feature = "resend")]
pub(crate) async fn send_resend(
mailable: &dyn Mailable,
to: &str,
config: &MailConfig,
) -> Result<(), MailError> {
let api_key = config
.resend_api_key
.as_deref()
.ok_or_else(|| MailError::MissingApiKey("resend".into()))?;
let from = format!("{} <{}>", config.from_name, config.from_address);
let mut body = serde_json::json!({
"from": from,
"to": [to],
"subject": mailable.subject(),
"text": mailable.body(),
});
if let Some(html) = mailable.html_body() {
body["html"] = serde_json::Value::String(html);
}
let client = reqwest::Client::new();
let resp = client
.post("https://api.resend.com/emails")
.header("Authorization", format!("Bearer {api_key}"))
.json(&body)
.send()
.await
.map_err(|e| MailError::Http(e.to_string()))?;
if !resp.status().is_success() {
let status = resp.status();
let text = resp.text().await.unwrap_or_default();
return Err(MailError::Http(format!("Resend {status}: {text}")));
}
Ok(())
}
pub(crate) async fn send_log(
mailable: &dyn Mailable,
to: &str,
config: &MailConfig,
) -> Result<(), MailError> {
println!(
"[rok-mail:log]\n To: {to}\n From: {} <{}>\n Subject: {}\n Body:\n{}",
config.from_name,
config.from_address,
mailable.subject(),
mailable.body(),
);
Ok(())
}
#[cfg(feature = "smtp")]
pub(crate) async fn send_smtp(
mailable: &dyn Mailable,
to: &str,
config: &MailConfig,
) -> Result<(), MailError> {
use lettre::{
message::{header::ContentType, Mailbox, MultiPart, SinglePart},
transport::smtp::authentication::Credentials,
AsyncTransport, Message, Tokio1Executor,
};
let host = config
.smtp_host
.as_deref()
.ok_or(MailError::MissingSmtpHost)?;
let from: Mailbox = format!("{} <{}>", config.from_name, config.from_address)
.parse()
.map_err(|e: lettre::address::AddressError| MailError::Smtp(e.to_string()))?;
let recipient: Mailbox = to
.parse()
.map_err(|e: lettre::address::AddressError| MailError::Smtp(e.to_string()))?;
let builder = Message::builder()
.from(from)
.to(recipient)
.subject(mailable.subject());
let email = match mailable.html_body() {
Some(html) => builder
.multipart(
MultiPart::alternative()
.singlepart(
SinglePart::builder()
.header(ContentType::TEXT_PLAIN)
.body(mailable.body()),
)
.singlepart(
SinglePart::builder()
.header(ContentType::TEXT_HTML)
.body(html),
),
)
.map_err(|e| MailError::Smtp(e.to_string()))?,
None => builder
.header(ContentType::TEXT_PLAIN)
.body(mailable.body())
.map_err(|e| MailError::Smtp(e.to_string()))?,
};
use lettre::AsyncSmtpTransport;
let transport: AsyncSmtpTransport<Tokio1Executor> = match config.smtp_encryption.as_str() {
"tls" => {
let mut b = AsyncSmtpTransport::<Tokio1Executor>::relay(host)
.map_err(|e| MailError::Smtp(e.to_string()))?
.port(config.smtp_port);
if let (Some(user), Some(pass)) = (&config.smtp_username, &config.smtp_password) {
b = b.credentials(Credentials::new(user.clone(), pass.clone()));
}
b.build()
}
"none" => {
let mut b = AsyncSmtpTransport::<Tokio1Executor>::builder_dangerous(host)
.port(config.smtp_port);
if let (Some(user), Some(pass)) = (&config.smtp_username, &config.smtp_password) {
b = b.credentials(Credentials::new(user.clone(), pass.clone()));
}
b.build()
}
_ => {
let mut b = AsyncSmtpTransport::<Tokio1Executor>::starttls_relay(host)
.map_err(|e| MailError::Smtp(e.to_string()))?
.port(config.smtp_port);
if let (Some(user), Some(pass)) = (&config.smtp_username, &config.smtp_password) {
b = b.credentials(Credentials::new(user.clone(), pass.clone()));
}
b.build()
}
};
transport
.send(email)
.await
.map_err(|e| MailError::Smtp(e.to_string()))?;
Ok(())
}