| 1 |
|
| 2 |
|
| 3 |
|
| 4 |
|
| 5 |
use crate::Shared; |
| 6 |
|
| 7 |
|
| 8 |
pub fn enabled(app: &Shared) -> bool { |
| 9 |
let cfg = &app.config.email; |
| 10 |
!cfg.smtp.is_empty() && !cfg.from.is_empty() |
| 11 |
} |
| 12 |
|
| 13 |
|
| 14 |
pub async fn send(app: &Shared, to_name: &str, to_addr: &str, subject: &str, body: &str) -> Result<(), String> { |
| 15 |
if !enabled(app) { |
| 16 |
return Err("this hub does not send mail".into()); |
| 17 |
} |
| 18 |
let cfg = &app.config.email; |
| 19 |
let subject = format!("[{}] {subject}", app.config.site.name); |
| 20 |
if let Some(dir) = cfg.smtp.strip_prefix("file://") { |
| 21 |
let name = format!("{}-{}.eml", crate::db::now_ms(), crate::auth::random_hex(4)); |
| 22 |
let text = format!("From: {}\nTo: {to_name} <{to_addr}>\nSubject: {subject}\n\n{body}", cfg.from); |
| 23 |
return tokio::fs::write(std::path::Path::new(dir).join(name), text).await.map_err(|e| format!("write mail to {dir}: {e}")); |
| 24 |
} |
| 25 |
use lettre::message::header::ContentType; |
| 26 |
use lettre::{AsyncSmtpTransport, AsyncTransport, Message, Tokio1Executor}; |
| 27 |
let from = cfg.from.parse().map_err(|e| format!("[email] from: {e}"))?; |
| 28 |
let to = format!("{to_name} <{to_addr}>").parse().or_else(|_| to_addr.parse()).map_err(|e| format!("address {to_addr}: {e}"))?; |
| 29 |
let message = Message::builder() |
| 30 |
.from(from) |
| 31 |
.to(to) |
| 32 |
.subject(subject) |
| 33 |
.header(ContentType::TEXT_PLAIN) |
| 34 |
.body(body.to_string()) |
| 35 |
.map_err(|e| e.to_string())?; |
| 36 |
let transport = AsyncSmtpTransport::<Tokio1Executor>::from_url(&cfg.smtp).map_err(|e| format!("[email] smtp: {e}"))?.build(); |
| 37 |
transport.send(message).await.map(|_| ()).map_err(|e| format!("sending to {to_addr}: {e}")) |
| 38 |
} |