【Rust发邮件】Rust如何通过smtp协议发送邮件
✨✨ 欢迎大家来到景天科技苑✨✨
🎈🎈 养成好习惯,先赞后看哦~🎈🎈
🏆 作者简介:景天科技苑
🏆《头衔》:大厂架构师,华为云开发者社区专家博主,阿里云开发者社区专家博主,CSDN全栈领域优质创作者,掘金优秀博主,51CTO博客专家等。
🏆《博客》:Rust开发,Python全栈,Golang开发,云原生开发,PyQt5和Tkinter桌面开发,小程序开发,人工智能,js逆向,App逆向,网络系统安全,数据分析,Django,fastapi,flask等框架,云原生K8S,linux,shell脚本等实操经验,网站搭建,数据库等分享。所属的专栏:Rust语言通关之路
景天的主页:景天科技苑
文章目录
- Rust发邮件
- 1. 前言
- 2. SMTP协议简介
- 2.1 SMTP基本概念
- 2.2 SMTP基本命令集
- 2.3 邮件封装
- 3. Rust 中的邮件库 lettre
- 3.1 简介
- 3.2 安装依赖
- 4. 发送邮件的基本步骤
- 4.1 使用126邮箱发送邮件
- 4.2 发送html格式的邮件
- 4.3 发送带附件的邮件
- 4.4 安全建议
Rust发邮件
1. 前言
在现代软件开发中,邮件发送功能是许多应用程序不可或缺的一部分。无论是用户注册验证、密码重置、通知提醒还是营销推广,邮件服务都扮演着重要角色。Rust作为一门系统级编程语言,以其安全性、并发性和高性能著称,同样可以用于实现邮件发送功能。
本文将详细介绍如何使用 Rust 通过 SMTP 协议发送邮件,使用主流库 lettre,并涵盖:
SMTP 协议基础
安装与配置 lettre 库
构建邮件内容(text/html)
TLS 与身份验证
完整代码示例(包括 Gmail、QQ 邮箱等)
错误处理与调试技巧
2. SMTP协议简介
2.1 SMTP基本概念
SMTP(Simple Mail Transfer Protocol,简单邮件传输协议)是用来传输电子邮件的协议,主要用于系统之间的邮件信息传递,并提供有关来信的通知。
使用SMTP,可实现相同网络处理进程之间的邮件传输,也可通过中继器或网关实现某处理进程与其它网络之间的邮件传输。
SMTP工作在两种情况下,一种是电子邮件从客户机传输到服务器,另外一种是从某个服务器传输到另外一个服务器(即中继)。
SMTP是请求/响应协议,命令和响应都是基于ASCII文本,并且以CR和LF符结尾,响应包括一个表示返回状态的三位数字代码。
SMTP(Simple Mail Transfer Protocol)是邮件传输的标准协议。它使用 TCP 通信,默认端口包括:
25:非加密(已逐渐废弃)
465:使用 SSL/TLS 加密
587:使用 STARTTLS 加密(推荐)
2.2 SMTP基本命令集
SMTP应答码
2.3 邮件封装
SMTP将传输的内容封装在对象中,邮件对象主要包括信封和内容两部分。内容又分为头部和邮件内容本身
SMTP协议在传输报文时,只能够传输7位的ASCI格式的报文,不支持不使用7位ASCI格式的语种,也不支持语音和视频数据的传输,因此需要扩展协议MIME来解决此问题。
MIME协议定义了5种头部,加在原始STMP头部,如下:
发送邮件流程如下:
建立 TCP 连接
TLS 握手(STARTTLS 或 SSL)
身份验证(如 LOGIN、PLAIN)
发送邮件头和正文
结束会话
3. Rust 中的邮件库 lettre
3.1 简介
lettre 是 Rust 社区维护的一个成熟邮件发送库,支持多种 SMTP 模式、身份验证、HTML 邮件等。
3.2 安装依赖
编辑你的 Cargo.toml 添加如下依赖:
[dependencies]
anyhow = "1.0.98"
lettre = { version = "0.11", features = ["builder","smtp-transport","rustls-tls","tokio1-rustls","tokio1-native-tls",
] }
tokio = { version = "1", features = ["full"] }
使用 tokio 异步运行时
rustls-tls 为安全加密库的实现(比 OpenSSL 更轻量)
4. 发送邮件的基本步骤
4.1 使用126邮箱发送邮件
✅ 126 邮箱 SMTP 信息
use lettre::message::{ header, Mailbox, Message, SinglePart };
use lettre::transport::smtp::authentication::Credentials;
use lettre::{ SmtpTransport, Transport };//使用anyhow处理错误
#[tokio::main]
async fn main() -> anyhow::Result<()> {// 你的126邮箱账号和授权码let email_address = "ldddddd1@126.com";let password = "NVTtB33ggsgsgsSvB"; // 注意是授权码而不是登录密码// 构建邮件内容let email = Message::builder().from(Mailbox::new(Some("Rust 邮件测试".to_string()), email_address.parse()?)).to("34224572@qq.com".parse()?).subject("来自 Rust 的邮件测试 - 126邮箱").singlepart(SinglePart::builder().header(header::ContentType::TEXT_PLAIN) // 指定邮件内容类型为纯文本.body(String::from("你好!这是一封由 Rust 通过 126 邮箱发出的测试邮件。")))?;// 凭证配置let creds = Credentials::new(email_address.to_string(), password.to_string());// 创建支持 SMTPS (SSL on port 465) 的邮件客户端let mailer = SmtpTransport::relay("smtp.126.com")? // 自动使用 SMTPS.port(465).credentials(creds).build();// 发送邮件match mailer.send(&email) {Ok(_) => println!("✅ 邮件发送成功!"),Err(e) => println!("❌ 邮件发送失败: {:?}", e),}Ok(())
}
4.2 发送html格式的邮件
header::ContentType有两种格式
一种是纯文本,一种是html格式
use lettre::message::{ header, Mailbox, Message, SinglePart };
use lettre::transport::smtp::authentication::Credentials;
use lettre::{ SmtpTransport, Transport };//使用anyhow处理错误
#[tokio::main]
async fn main() -> anyhow::Result<()> {// 你的126邮箱账号和授权码let email_address = "lifsfsfs324242@126.com";let password = "NVTt424rggsdBSvB"; // 注意是授权码而不是登录密码// 构建邮件内容let email = Message::builder().from(Mailbox::new(Some("Rust 邮件测试".to_string()), email_address.parse()?)).to("243342572@qq.com".parse()?).subject("来自 Rust 的邮件测试 - 126邮箱").singlepart(SinglePart::builder().header(header::ContentType::TEXT_HTML) // 指定邮件内容类型为 HTML.body(String::from("<h2>你好!这是一封由 Rust 通过 126 邮箱发出的测试邮件。</h2>")))?;// 凭证配置let creds = Credentials::new(email_address.to_string(), password.to_string());// 创建支持 SMTPS (SSL on port 465) 的邮件客户端let mailer = SmtpTransport::relay("smtp.126.com")? // 自动使用 SMTPS.port(465).credentials(creds).build();// 发送邮件match mailer.send(&email) {Ok(_) => println!("✅ 邮件发送成功!"),Err(e) => println!("❌ 邮件发送失败: {:?}", e),}Ok(())
}
4.3 发送带附件的邮件
lettre_email 是一个旧版的 lettre 附加库,可以用于发送带附件的邮件。它基于早期版本的 lettre,提供更方便的 API 来构建包含文本、HTML 以及附件的电子邮件。
⚠️ 注意事项
lettre_email 不再积极维护,只支持旧版本 lettre(<= 0.9)
与 lettre 0.10+(目前主流版本)不兼容
对于附件发送,可以考虑使用lettre::Message来添加附件
✅ 替代方案(现代 lettre::Message)
要使用 lettre::Message(推荐方式)发送带附件的邮件,需要:
构造标准的 multipart/mixed MIME 邮件;
附件使用 Base64 编码并嵌入;
设置适当的 Content-Type、Content-Disposition、Content-Transfer-Encoding。
在使用 lettre::Message 构建附件时,不需要你手动进行 Base64 编码!lettre 会 自动编码附件内容,只要你提供原始字节,并正确设置 ContentTransferEncoding::Base64 即可。
🧪MIME 类型参考
可以使用mime_guess::from_path 自动识别mime类型
附件支持的格式(自动识别)
你可以传入任意类型的文件,mime_guess 会自动识别 MIME 类型,如:
.pdf → application/pdf
.png → image/png
.zip → application/zip
.txt → text/plain
✅ 示例:使用 lettre::Message 发送带附件邮件
📦 Cargo.toml
[dependencies]
anyhow = "1.0.98"
base64 = "0.22.1"
lettre = { version = "0.11", features = ["builder","smtp-transport","rustls-tls","tokio1-rustls","tokio1-native-tls",
] }
mime = "0.3.17"
mime_guess = "2.0.5"
tokio = { version = "1", features = ["full"] }
🦀 完整代码
use lettre::{message::{ header::ContentType, Mailbox, Message, MultiPart, SinglePart, header },transport::smtp::authentication::Credentials,SmtpTransport,Transport,
};
use anyhow::Result;
use std::{ fs, path::Path };
use mime_guess::from_path;fn main() -> Result<()> {let from = "lifsfs424@126.com";let to = "412313170@qq.com";let subject = "看看Kitty漂不漂亮";let body = "Hello, Kitty!";let file_path = "kitty.jpg";// 构建邮件正文let text_part = SinglePart::builder().header(ContentType::TEXT_PLAIN).body(String::from(body));// 构建附件部分let attachment = build_attachment_part(file_path)?;// 构建整个 multipartlet multipart = MultiPart::mixed().singlepart(text_part).singlepart(attachment);// 构建邮件let email = Message::builder().from(from.parse::<Mailbox>()?).to(to.parse::<Mailbox>()?).subject(subject).multipart(multipart)?;// 配置 SMTPlet creds = Credentials::new(from.into(), "NVvsvvSvB".into());let mailer = SmtpTransport::relay("smtp.126.com")?.credentials(creds).port(465).build();mailer.send(&email)?;println!("✅ 邮件发送成功");Ok(())
}//构建附件部分
fn build_attachment_part(path: &str) -> Result<SinglePart> {let data = fs::read(path)?;let filename = Path::new(path).file_name().unwrap().to_string_lossy().to_string();//根据文件后缀获取mime类型let mime = from_path(path).first_or_octet_stream();//构建附件部分Ok(SinglePart::builder().header(header::ContentType::parse(&format!("{}; name=\"{}\"", mime, filename))?).header(header::ContentDisposition::attachment(&filename.clone())).header(header::ContentTransferEncoding::Base64).body(data))
}
📌 关键点说明
📁 支持多个附件?
只需 .singlepart() 多次调用:
let multipart = MultiPart::mixed().singlepart(text_part).singlepart(build_attachment_part("a.pdf")?).singlepart(build_attachment_part("b.png")?);
多个附件完整代码:
//多个附件
use lettre::{message::{ header::ContentType, Mailbox, Message, MultiPart, SinglePart, header },transport::smtp::authentication::Credentials,SmtpTransport,Transport,
};
use anyhow::Result;
use std::{ fs, path::Path };
use mime_guess::from_path;fn main() -> Result<()> {let from = "3141fsf1@126.com";let to = "ffsfsfsf70@qq.com";let subject = "看看Kitty漂不漂亮";let body = "Hello, Kitty!";let file_path = "kitty.jpg";let file_path2 = "kitty2.jpg";// 构建邮件正文let text_part = SinglePart::builder().header(ContentType::TEXT_PLAIN).body(String::from(body));// 构建整个 multipart//发送多个附件let multipart = MultiPart::mixed().singlepart(text_part).singlepart(build_attachment_part(file_path)?).singlepart(build_attachment_part(file_path2)?);// 构建邮件let email = Message::builder().from(from.parse::<Mailbox>()?).to(to.parse::<Mailbox>()?).subject(subject).multipart(multipart)?;// 配置 SMTPlet creds = Credentials::new(from.into(), "fsfsfKfsf".into());let mailer = SmtpTransport::relay("smtp.126.com")?.credentials(creds).port(465).build();mailer.send(&email)?;println!("✅ 邮件发送成功");Ok(())
}//构建附件部分
fn build_attachment_part(path: &str) -> Result<SinglePart> {let data = fs::read(path)?;let filename = Path::new(path).file_name().unwrap().to_string_lossy().to_string();//根据文件后缀获取mime类型let mime = from_path(path).first_or_octet_stream();//构建附件部分Ok(SinglePart::builder().header(header::ContentType::parse(&format!("{}; name=\"{}\"", mime, filename))?).header(header::ContentDisposition::attachment(&filename.clone())).header(header::ContentTransferEncoding::Base64).body(data))
}
多个收件人:
.to(“收件人1 a@example.com”.parse()?)
.to(“收件人2 b@example.com”.parse()?)
📌 总结:构建附件的 3 要素
4.4 安全建议
切勿将密码硬编码在源码中,应使用 .env 或配置文件
dotenvy::dotenv() 自动加载 .env 文件
env::var(“KEY”) 从环境中读取变量
创建.env
放在项目根目录:
EMAIL_ADDRESS = “3131fsfs@126.com”
PASSWORD = “NVdasfsds”
注意:.env 文件不要提交到 Git,建议添加 .gitignore:
.env
使用 dotenvy 读取环境变量:
[dependencies]
dotenvy = “0.15.7”
应用代码
use lettre::message::{ header, Mailbox, Message, SinglePart };
use lettre::transport::smtp::authentication::Credentials;
use lettre::{ SmtpTransport, Transport };use dotenvy::dotenv;
use std::env;//使用anyhow处理错误
#[tokio::main]
async fn main() -> anyhow::Result<()> {// 从 .env 文件中加载环境变量dotenv()?;// 从.env读取126邮箱账号和授权码let email_address = env::var("EMAIL_ADDRESS")?;let password = env::var("PASSWORD")?;// 构建邮件内容let email = Message::builder().from(Mailbox::new(Some("Rust 邮件测试".to_string()), email_address.parse()?)).to("64745772@qq.com".parse()?).to("6464hao@t64464c.cn".parse()?) //多个收件人.subject("来自 Rust 的邮件测试 - 126邮箱").singlepart(SinglePart::builder().header(header::ContentType::TEXT_HTML) // 指定邮件内容类型为 HTML.body(String::from("<h2>你好!这是一封由 Rust 通过 126 邮箱发出的测试邮件。</h2>")))?;// 凭证配置//参数是邮箱账号和授权码let creds = Credentials::new(email_address.to_string(), password.to_string());// 创建支持 SMTPS (SSL on port 465) 的邮件客户端//relay自动使用SMTPSlet mailer = SmtpTransport::relay("smtp.126.com")? // 自动使用 SMTPS.port(465).credentials(creds).build();// 发送邮件match mailer.send(&email) {Ok(_) => println!("✅ 邮件发送成功!"),Err(e) => println!("❌ 邮件发送失败: {:?}", e),}Ok(())
}