使用Rust的Lettre发送邮件,第3封后无法继续发送
问题分析与解决思路
核心问题推测
你的代码出现第4封邮件卡住的情况,大概率是以下两个原因之一:
- 异步调度阻塞:在Tokio runtime中混用
futures::executor::block_on阻塞主线程,导致Tokio线程池资源耗尽,后续异步任务(SMTP发送)无法被调度执行。 - 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
相关产品推荐
相关产品推荐

