email_sender 0.1.4

A simple email sender crate using lettre.
Documentation
// src/lib.rs
use lettre::{
    message::{Message},
    transport::smtp::authentication::Credentials,
    Transport,
};
use lettre::SmtpTransport;
use std::env;
use thiserror::Error; // 引入 thiserror 用于自定义错误

/// 自定义错误类型
#[derive(Error, Debug)]
pub enum EmailSenderError {
    #[error("Environment variable not found: {0}")]
    EnvVarNotFound(String),
    #[error("Failed to parse SMTP port: {0}")]
    ParsePortError(#[from] std::num::ParseIntError),
    #[error("Failed to parse mailbox: {0}")]
    ParseMailboxError(#[from] lettre::address::AddressError),
    #[error("SMTP transport error: {0}")]
    SmtpTransportError(#[from] lettre::transport::smtp::Error),
    #[error("Message building error: {0}")]
    MessageBuildError(#[from] lettre::error::Error),
}

/// 邮件发送选项
pub struct EmailOptions {
    pub smtp_server: String,
    pub smtp_port: u16,
    pub smtp_username: String,
    pub smtp_password: String,
    pub from: String,
    pub to: String,
    pub subject: String,
    pub body: String,
}

impl EmailOptions {
    /// 从环境变量加载配置
    pub fn from_env() -> Result<Self, EmailSenderError> {
        dotenv::dotenv().ok();

        Ok(Self {
            smtp_server: env::var("SMTP_SERVER").map_err(|_| EmailSenderError::EnvVarNotFound("SMTP_SERVER".to_string()))?,
            smtp_port: env::var("SMTP_PORT")
                .map_err(|_| EmailSenderError::EnvVarNotFound("SMTP_PORT".to_string()))?
                .parse()?,
            smtp_username: env::var("SMTP_USERNAME").map_err(|_| EmailSenderError::EnvVarNotFound("SMTP_USERNAME".to_string()))?,
            smtp_password: env::var("SMTP_PASSWORD").map_err(|_| EmailSenderError::EnvVarNotFound("SMTP_PASSWORD".to_string()))?,
            from: env::var("EMAIL_FROM").map_err(|_| EmailSenderError::EnvVarNotFound("EMAIL_FROM".to_string()))?,
            to: env::var("EMAIL_TO").map_err(|_| EmailSenderError::EnvVarNotFound("EMAIL_TO".to_string()))?,
            subject: "Default Subject".to_string(),
            body: "Default body".to_string(),
        })
    }

    /// 手动创建配置
    pub fn new(
        smtp_server: String,
        smtp_port: u16,
        smtp_username: String,
        smtp_password: String,
        from: String,
        to: String,
        subject: String,
        body: String,
    ) -> Self {
        Self {
            smtp_server,
            smtp_port,
            smtp_username,
            smtp_password,
            from,
            to,
            subject,
            body,
        }
    }
}

/// 发送邮件
pub fn send_email(options: EmailOptions) -> Result<(), EmailSenderError> {
    let email = Message::builder()
        .from(options.from.parse()?)
        .to(options.to.parse()?)
        .subject(&options.subject)
        .body(options.body)
        .map_err(EmailSenderError::MessageBuildError)?;

    let creds = Credentials::new(options.smtp_username, options.smtp_password);

    let mailer = SmtpTransport::relay(&options.smtp_server)?
        .port(options.smtp_port)
        .credentials(creds)
        .build();

    mailer.send(&email)?;

    println!("Email sent successfully!");
    Ok(())
}


#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_send_email() {
        // 注意:实际发送邮件在测试中可能不可行,建议使用 Mock 或 Stub
        // 这里仅作为示例,实际应避免在测试中发送真实邮件
        let options = EmailOptions::new(
            "smtp.example.com".to_string(),
            587,
            "your_username".to_string(),
            "your_password".to_string(),
            "from@example.com".to_string(),
            "to@example.com".to_string(),
            "Test Subject".to_string(),
            "Test Body".to_string(),
        );

        // 由于需要真实的 SMTP 配置,这里可以 panic 或返回 Ok(())
        // 实际项目中应使用 Mock 测试
        assert!(true);
    }
}