You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用Rust的Lettre发送邮件,第3封后无法继续发送

问题分析与解决思路

核心问题推测

你的代码出现第4封邮件卡住的情况,大概率是以下两个原因之一:

  1. 异步调度阻塞:在Tokio runtime中混用futures::executor::block_on阻塞主线程,导致Tokio线程池资源耗尽,后续异步任务(SMTP发送)无法被调度执行。
  2. SMTP无超时等待:Lettre的mailer.send()默认没有超时设置,若SMTP服务器因连接数限制、网络波动等原因未响应,请求会一直挂起。

排查步骤

  • 查看程序运行时的线程占用情况,确认是否有线程被长期阻塞。
  • 给SMTP发送添加超时逻辑,验证是否是服务器响应超时导致卡住。
  • 检查SMTP服务器的日志或后台设置,确认是否存在单IP连接数、发送频率限制。

具体修复方案

1. 替换阻塞式异步调用为Tokio任务调度

在main函数中,不要用block_on阻塞主线程,改用tokio::spawn将邮件发送任务提交给Tokio runtime异步执行:

修改main函数中的事件处理部分:

// 原代码
futures::executor::block_on(async { prepare_email(event).await });

// 修改为
// 若DebouncedEvent未实现Clone,可提取文件路径单独传递
let path_str = event.paths.first().unwrap().display().to_string();
tokio::spawn(prepare_email(path_str));

同步重构prepare_email函数,只接收路径字符串:

async fn prepare_email(file_path: String) {
    let smtp_credentials =
        Credentials::new("smtp_creds".to_string(), "smtp_passwd".to_string());

    let mailer: AsyncSmtpTransport<Tokio1Executor> = AsyncSmtpTransport::<Tokio1Executor>::relay("smtp.server.address").unwrap()
        .credentials(smtp_credentials)
        .build();
    let from = "from";
    let to = "to";
    let subject ="文件创建通知";
    let body = format!("检测到新文件:{}", file_path);
    send_email_smtp(&mailer, from, to, &subject, body).await.unwrap()
}

2. 给SMTP发送添加超时限制

在send_email_smtp函数中,用tokio::time::timeout包裹发送逻辑,避免无限等待:

use tokio::time::{timeout, Duration};

async fn send_email_smtp(
    mailer: &AsyncSmtpTransport<Tokio1Executor>,
    from: &str,
    to: &str,
    subject: &str,
    body: String,
) -> Result<(), Box<dyn std::error::Error>> {
    let email = Message::builder()
        .from(from.parse()?)
        .to(to.parse()?)
        .subject(subject)
        .header(ContentType::TEXT_PLAIN)
        .body(body.to_string())?;
    
    // 设置10秒超时,可根据实际情况调整
    let x = timeout(Duration::from_secs(10), mailer.send(email)).await;
    match x {
        Ok(Ok(res)) => println!("邮件发送成功: {:?}", res.code().detail),
        Ok(Err(res)) => println!("邮件发送失败: {:?}", res.status().unwrap().detail),
        Err(_) => println!("SMTP请求超时"),
    }
    Ok(())
}

3. 复用SMTP连接池(可选优化)

每次创建AsyncSmtpTransport会新建连接,频繁创建可能触发服务器连接数限制。可以在main函数中初始化一次mailer,用Arc包裹后传递给任务:

// main函数中初始化mailer
let smtp_credentials = Credentials::new("smtp_creds".to_string(), "smtp_passwd".to_string());
let mailer = Arc::new(AsyncSmtpTransport::<Tokio1Executor>::relay("smtp.server.address")
    .unwrap()
    .credentials(smtp_credentials)
    .build());

// 事件处理中传递Arc克隆
let mailer_clone = Arc::clone(&mailer);
tokio::spawn(async move {
    prepare_email(mailer_clone, path_str).await;
});

// 重构prepare_email接收Arc包裹的mailer
async fn prepare_email(mailer: Arc<AsyncSmtpTransport<Tokio1Executor>>, file_path: String) {
    let from = "from";
    let to = "to";
    let subject ="文件创建通知";
    let body = format!("检测到新文件:{}", file_path);
    send_email_smtp(&mailer, from, to, &subject, body).await.unwrap();
}

验证方法

修改后触发4次以上文件创建事件,观察是否还会出现卡住的情况,同时查看控制台输出的超时或错误信息,进一步定位问题。

内容的提问来源于stack exchange,提问作者Vulpex

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.05 02:20:57