1use std::process::Stdio;
6
7use async_trait::async_trait;
8use tokio::{io::AsyncWriteExt, process::Command};
9use wae_types::{WaeError, WaeResult};
10
11use crate::EmailProvider;
12
13#[derive(Debug, Clone)]
15pub struct SendmailConfig {
16 pub command: String,
18}
19
20impl Default for SendmailConfig {
21 fn default() -> Self {
22 Self { command: "sendmail".to_string() }
23 }
24}
25
26pub struct SendmailTransport {
31 config: SendmailConfig,
33}
34
35impl SendmailTransport {
36 pub fn new() -> Self {
40 Self { config: SendmailConfig::default() }
41 }
42
43 pub fn with_config(config: SendmailConfig) -> Self {
45 Self { config }
46 }
47
48 pub async fn send_raw(&self, raw_email: &str) -> WaeResult<()> {
55 let mut child = Command::new(&self.config.command)
56 .arg("-t")
57 .stdin(Stdio::piped())
58 .stdout(Stdio::null())
59 .stderr(Stdio::piped())
60 .spawn()
61 .map_err(|e| WaeError::internal(format!("Failed to spawn sendmail process: {}", e)))?;
62
63 if let Some(mut stdin) = child.stdin.take() {
64 stdin
65 .write_all(raw_email.as_bytes())
66 .await
67 .map_err(|e| WaeError::internal(format!("Failed to write to sendmail stdin: {}", e)))?;
68 stdin.flush().await.map_err(|e| WaeError::internal(format!("Failed to flush sendmail stdin: {}", e)))?;
69 }
70 else {
71 return Err(WaeError::internal("Failed to open stdin for sendmail process"));
72 }
73
74 let status =
75 child.wait().await.map_err(|e| WaeError::internal(format!("Failed to wait for sendmail process: {}", e)))?;
76
77 if status.success() {
78 Ok(())
79 }
80 else {
81 let code = status.code().unwrap_or(-1);
82 Err(WaeError::internal(format!("Sendmail exited with non-zero status: {}", code)))
83 }
84 }
85}
86
87impl Default for SendmailTransport {
88 fn default() -> Self {
89 Self::new()
90 }
91}
92
93pub struct SendmailEmailProvider {
97 transport: SendmailTransport,
99 from_email: String,
101}
102
103impl SendmailEmailProvider {
104 pub fn new(from_email: String) -> Self {
109 Self { transport: SendmailTransport::new(), from_email }
110 }
111
112 pub fn with_config(from_email: String, config: SendmailConfig) -> Self {
114 Self { transport: SendmailTransport::with_config(config), from_email }
115 }
116
117 pub fn build_raw_email(&self, to: &str, subject: &str, body: &str) -> String {
119 format!("From: {}\r\nTo: {}\r\nSubject: {}\r\n\r\n{}", self.from_email, to, subject, body)
120 }
121}
122
123#[async_trait]
124impl EmailProvider for SendmailEmailProvider {
125 async fn send_email(&self, to: &str, subject: &str, body: &str) -> WaeResult<()> {
126 let raw_email = self.build_raw_email(to, subject, body);
127 self.transport.send_raw(&raw_email).await
128 }
129}