如何使用Rust的google_gmail1库结合服务账户发送带附件邮件
解决Rust使用
google_gmail1发送带附件邮件的问题 核心结论
发送带附件的邮件不需要使用upload_resumable()方法,该方法适用于上传大附件到草稿等场景。正确的方式是构建完整的MIME格式邮件,编码为base64url后赋值给Message的raw字段,再调用messages_send接口发送。
具体实现步骤
1. 添加必要依赖
在Cargo.toml中添加构建MIME消息和编码所需的依赖:
[dependencies] google-gmail1 = "7.0.1" mime = "0.3.17" base64 = "0.21.5" tokio = { version = "1.0", features = ["full"] }
2. 构建MIME格式邮件
编写工具函数生成包含正文、附件的完整MIME邮件:
use mime::Mime; use base64::engine::general_purpose::{STANDARD, URL_SAFE_NO_PAD}; use base64::Engine as _; use std::io::Write; fn build_mime_email( to: &str, subject: &str, content: &str, file_name: &str, file_data: &[u8], file_mime: &Mime ) -> Vec<u8> { let boundary = "----RustGmailAttachmentBoundary"; let mut mime = Vec::new(); // 邮件头部 writeln!(&mut mime, "To: {}", to).unwrap(); writeln!(&mut mime, "Subject: {}", subject).unwrap(); writeln!(&mut mime, "Content-Type: multipart/mixed; boundary=\"{}\"", boundary).unwrap(); writeln!(&mut mime).unwrap(); // 头部与内容之间必须有空行 // 文本正文部分 writeln!(&mut mime, "--{}", boundary).unwrap(); writeln!(&mut mime, "Content-Type: text/plain; charset=utf-8").unwrap(); writeln!(&mut mime).unwrap(); writeln!(&mut mime, "{}", content).unwrap(); // 附件部分 writeln!(&mut mime, "\n--{}", boundary).unwrap(); writeln!(&mut mime, "Content-Type: {}", file_mime).unwrap(); writeln!(&mut mime, "Content-Disposition: attachment; filename=\"{}\"", file_name).unwrap(); writeln!(&mut mime, "Content-Transfer-Encoding: base64").unwrap(); writeln!(&mut mime).unwrap(); // 附件内容按MIME规范分割为每行76字符的base64编码 let encoded_file = STANDARD.encode(file_data); for chunk in encoded_file.as_bytes().chunks(76) { writeln!(&mut mime, "{}", String::from_utf8_lossy(chunk)).unwrap(); } // 结束MIME边界 writeln!(&mut mime, "\n--{}--", boundary).unwrap(); mime }
3. 构建请求并发送邮件
结合你已有的代码,完成邮件发送逻辑:
use google_gmail1::{api::Message, hyper, hyper_rustls, oauth2, Gmail}; use std::fs; #[tokio::main] async fn main() -> Result<(), Box<dyn std::error::Error>> { // 读取服务账户密钥 let secret = oauth2::read_service_account_key("service_account.json")?; // 创建Gmail客户端(需配置域范围委派,模拟目标用户) let auth = oauth2::ServiceAccountAuthenticator::builder(secret) .with_scopes(&["https://www.googleapis.com/auth/gmail.send"]) .with_subject("target-user@your-domain.com") // 替换为要模拟的用户邮箱 .build() .await?; let hub = Gmail::new(hyper::Client::builder().build(hyper_rustls::HttpsConnectorBuilder::new().with_native_roots().https_or_http().enable_http1().build()), auth); // 你的现有参数 let to = "recipient@example.com".to_string(); let subject = "带附件的测试邮件".to_string(); let content = "这是邮件正文内容".to_string(); let file_name = "test.csv".to_string(); let file_data = fs::read(&file_name)?; let file_mime: Mime = "text/csv".parse()?; // 根据实际文件类型调整MIME类型 // 构建MIME邮件并编码为base64url let mime_email = build_mime_email(&to, &subject, &content, &file_name, &file_data, &file_mime); let raw = URL_SAFE_NO_PAD.encode(&mime_email); // 创建Message请求 let req = Message { raw: Some(raw), ..Message::default() }; // 发送邮件("me"代表当前认证的用户,即模拟的target-user) let (_, message) = hub.users().messages_send(req, "me").doit().await?; println!("邮件发送成功,ID: {:?}", message.id); Ok(()) }
关键说明
- 附件存放位置:附件内容需和邮件正文、头部一起封装成完整MIME邮件,无需通过
upload_resumable()传递。upload_resumable()仅用于上传大附件到草稿等特殊场景,不适合直接发送完整邮件。 - 服务账户配置:确保服务账户已开启域范围委派,并授予
Gmail Send权限,同时在代码中指定要模拟的用户邮箱(with_subject)——服务账户本身没有Gmail邮箱,必须模拟域内用户发送。 - MIME类型:附件的MIME类型需与文件实际类型匹配(如
text/csv对应CSV文件,application/pdf对应PDF文件),避免解析错误。
内容的提问来源于stack exchange,提问作者啊鹿Dizzyi
相关产品推荐
相关产品推荐

