Skip to main content

wae_email/
sendmail.rs

1//! Sendmail 传输模块
2//!
3//! 通过调用本地 sendmail 命令发送邮件,使用异步子进程实现。
4
5use 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/// Sendmail 传输配置
14#[derive(Debug, Clone)]
15pub struct SendmailConfig {
16    /// sendmail 命令路径
17    pub command: String,
18}
19
20impl Default for SendmailConfig {
21    fn default() -> Self {
22        Self { command: "sendmail".to_string() }
23    }
24}
25
26/// Sendmail 传输提供者
27///
28/// 通过调用本地 sendmail 命令发送邮件。
29/// 使用 `-t` 参数从邮件头读取收件人信息。
30pub struct SendmailTransport {
31    /// 配置
32    config: SendmailConfig,
33}
34
35impl SendmailTransport {
36    /// 创建新的 Sendmail 传输
37    ///
38    /// 使用默认配置(sendmail 命令)
39    pub fn new() -> Self {
40        Self { config: SendmailConfig::default() }
41    }
42
43    /// 使用指定配置创建 Sendmail 传输
44    pub fn with_config(config: SendmailConfig) -> Self {
45        Self { config }
46    }
47
48    /// 发送原始邮件内容
49    ///
50    /// 通过标准输入将邮件内容传递给 sendmail 命令。
51    ///
52    /// # 参数
53    /// - `raw_email`: 完整的原始邮件内容(包含邮件头和正文)
54    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
93/// Sendmail 邮件提供者
94///
95/// 实现 `EmailProvider` trait,通过 sendmail 命令发送邮件。
96pub struct SendmailEmailProvider {
97    /// 传输实例
98    transport: SendmailTransport,
99    /// 发件人邮箱
100    from_email: String,
101}
102
103impl SendmailEmailProvider {
104    /// 创建新的 Sendmail 邮件提供者
105    ///
106    /// # 参数
107    /// - `from_email`: 发件人邮箱地址
108    pub fn new(from_email: String) -> Self {
109        Self { transport: SendmailTransport::new(), from_email }
110    }
111
112    /// 使用指定配置创建 Sendmail 邮件提供者
113    pub fn with_config(from_email: String, config: SendmailConfig) -> Self {
114        Self { transport: SendmailTransport::with_config(config), from_email }
115    }
116
117    /// 构建原始邮件内容
118    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}