使用Rust Lettre库发邮件遇STARTTLS命令未识别及证书验证问题
场景
用Rust的lettre库后端发送邮件到Postfix服务器,Python的smtplib脚本或邮件客户端都能正常发送,但lettre脚本报两类错误。
错误1:使用域名连接时
lettre报错内容:
lettre::transport::smtp::Error { kind: Permanent(Code { severity: PermanentNegativeCompletion, category: Syntax, detail: Zero }), source: "5.5.2 Error: command not recognized" }
Postfix服务器日志:
improper command pipelining after EHLO from x.x.x.x: QUIT\r\n
错误2:使用Postfix容器内网IP连接时
Some(lettre::transport::smtp::Error { kind: Connection, source: Failure(Ssl(Error { code: ErrorCode(1), cause: Some(Ssl(ErrorStack([Error { code: 337047686, library: "SSL routines", function: "tls_process_server_certificate", reason: "certificate verify failed", file: "ssl/statem/statem_clnt.c", line: 1919 }]))) }, X509VerifyResult { code: 64, error: "IP address mismatch" })) })
实现代码
use lettre::{ message::header::ContentType, transport::smtp::authentication::Credentials, Message, SmtpTransport, Transport, }; fn main() { let email = Message::builder() .from("nobody@domain.tld".parse().unwrap()) .to("mailer@domain.tld".parse().unwrap()) .subject("Test mailer") .header(ContentType::TEXT_PLAIN) .body(String::from("Hello world !")) .unwrap(); let creds = Credentials::new("smtp_username".to_owned(), "smtp_password".to_owned()); let mailer = SmtpTransport::starttls_relay("mydomain.tld") .unwrap() .credentials(creds) .build(); // Send the email match mailer.send(&email) { Ok(_) => println!("Email sent successfully!"), Err(e) => panic!("Could not send email: {e:?}"), } }
解决域名连接的命令管道错误
问题根源是lettre默认启用SMTP命令管道,但你的Postfix服务器不兼容或配置了严格的管道检查。只需禁用管道功能:
修改SmtpTransport构建代码,添加.disable_pipelining():
let mailer = SmtpTransport::starttls_relay("mydomain.tld") .unwrap() .credentials(creds) .disable_pipelining() // 新增该行禁用管道 .build();
禁用后lettre会逐个发送SMTP命令,避免触发Postfix的管道规则错误。
解决内网IP连接的SSL证书验证错误
错误原因是SSL证书绑定的是域名,用IP访问时证书主题与IP不匹配,导致验证失败。有三种解决方式:
- 优先方案:用域名连接:内网环境配置DNS解析,让域名指向Postfix容器IP,保持访问地址与证书一致,无需修改代码。
- 安全长期方案:更新证书:给Postfix的SSL证书添加内网IP作为SAN(备用名称),这样IP访问时证书验证也能通过。
- 临时应急方案:跳过证书验证(不推荐生产):必须用IP连接时,添加
.danger_accept_invalid_certs(true)跳过验证,但会引入安全风险:
let mailer = SmtpTransport::starttls_relay("192.168.x.x") // 替换为你的内网IP .unwrap() .credentials(creds) .disable_pipelining() .danger_accept_invalid_certs(true) // 新增该行跳过验证 .build();
内容的提问来源于stack exchange,提问作者Yok0

