用Rust发一封图文并茂的邮件
这篇文章的内容其实和之前的文章《用Python发一封图文并茂的邮件》差不多,跟之前不同之处在于之前是使用Python来完成邮件的发送任务,而本文使用Rust发送邮件而已。
虽然我们已经有了各种各样的通讯软件和APP但是邮件还是一直占据一席之地的,这可能主要在于邮件有比较强的表达能力(即可以使用HTML语法), 再者就是它比较易用和通用,而其他通讯软件有各种各样的限制。
Cargo.toml
依赖如下
[dependencies]
lettre = "0.11.4"
值得注意的是: 对于非1.0的包,如果打算使用最新的包一定要参考最新的官方示例代码, 因为作者可能会改接口。
快速入门
以下代码直接改自https://github.com/lettre/lettre
.
use lettre::message::header::ContentType;
use lettre::transport::smtp::authentication::Credentials;
use lettre::{Message, SmtpTransport, Transport};
fn main() {
let from_address = "你的邮箱";
// 大家可以测试的时候可以随便往这个邮箱发....
let to_address = "[email protected]";
let subject = "用Rust发一封简单的邮件";
let content = String::from("做人嘛, 最重要是开心啦...");
// 认证相关
let username = String::from("你的邮箱");
let password = String::from("你的密码");
let smtp_server = "smtp.126.com";
let email = Message::builder()
.from(from_address.parse().expect("解析发送邮箱地址失败:"))
// 这个reply_to我没用过就去掉了.
.to(to_address.parse().expect("解析收件邮箱地址失败: "))
.subject(subject)
.header(ContentType::TEXT_PLAIN)
.body(content)
.expect("构造邮件消息体失败:");
let creds = Credentials::new(username, password);
// 默认使用TLS连接
let mailer = SmtpTransport::relay(smtp_server)
.expect("解析SMTP服务器失败:")
.credentials(creds)
.build();
match mailer.send(&email) {
Ok(_) => println!("邮件发送成功!"),
Err(e) => panic!("发送邮件失败: {e:?}"),
}
}
上面的代码使用126邮箱测试是没有问题的,QQ邮箱没有测试过。
值得注意的是,国内的知名邮箱服务如126, 163, qq等,都会提供一个专门用于SMTP接口发送邮件的密码,如果你直接使用126邮箱的密码,或者qq密码来发送邮件那是不会成功的。
这里提供一个126邮箱获取授权码(SMTP接口登录的密码)的链接: https://help.lunkr.cn/126-IMAP-Login-Tutorial/IMAP-Login-Tutorial-126.html, 如果不行的话可以自行再搜索一下。
邮件的格式
邮件的格式主要就两种: plain
和html
plain
就像一个普通的文本, 没有格式,也就是上面代码的例子,这个并没有太多可以说的。
html
正如其名, 是html
的格式,相当于一个邮件就是一个静态的网页,这样的话可玩性就很高了,你可以通过css
控制表现形式.
注意: 这里的css虽然语法一样,但,是否与浏览器渲染结果完全一致, 是不一定的。
那么可能有人要问了,我要发一个动态的网页怎么办? 发个链接呀
发送HTML格式的邮件
与纯文本的主要不同在于设置的ContentType
不同, 以及请求头body
的内容不同(这是当然的啦, HTML
肯定得是一个合法的HTML
格式内容。)
use lettre::{
message::{header, MultiPart, SinglePart},
SmtpTransport, Message, Transport,
};
use lettre::transport::smtp::authentication::Credentials;
fn main() {
// 邮件内容
let html = r#"<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>来自Rust代码发送的邮件</title>
</head>
<body>
<div style="display: flex; flex-direction: column; align-items: center;">
<h2 style="font-family: Arial, Helvetica, sans-serif;">做人嘛, 最重要是开心啦...</h2>
</div>
</body>
</html>"#;
// 邮件的参数
let from_address = "你的邮箱";
// 大家可以测试的时候可以随便往这个邮箱发....
let to_address = "[email protected]";
let subject = "用Rust发一封HTML格式的邮件";
// 认证相关
let username = String::from("你的邮箱");
let password = String::from("你的密码");
let smtp_server = "smtp.126.com";
// 构造邮件体内容
let email = Message::builder()
.from(from_address.parse().expect("解析发送邮箱地址失败:"))
.to(to_address.parse().expect("解析收件邮箱地址失败: "))
.subject(subject)
.multipart(
MultiPart::alternative() // alternative构造一个可回退的邮件体
.singlepart(
SinglePart::builder()
.header(header::ContentType::TEXT_PLAIN)
// 应该是为了防止邮件服务器不支持HTML格式邮件
.body(String::from("发送HTML格式失败就只能回退了.")),
)
.singlepart(
SinglePart::builder()
.header(header::ContentType::TEXT_HTML)
.body(String::from(html)),
),
)
.expect("构造邮件消息体失败:");
let creds = Credentials::new(username, password);
// 默认使用TLS连接
let mailer = SmtpTransport::relay(smtp_server)
.expect("解析SMTP服务器失败:")
.credentials(creds)
.build();
match mailer.send(&email) {
Ok(_) => println!("邮件发送成功!"),
Err(e) => panic!("发送邮件失败: {e:?}"),
}
}
这和之前的案例应该区别不大, 不需要额外的讲解。
效果如下:
在邮件中嵌入图片
在邮件中嵌入一个图片就稍稍的复杂一点,这跟邮件的协议有关。
use std::fs;
use lettre::{
message::{header::{self, Header}, MultiPart, SinglePart},
SmtpTransport, Message, Transport,
};
use lettre::transport::smtp::authentication::Credentials;
fn main() {
// 邮件内容
let html = r#"<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>来自Rust代码发送的邮件</title>
</head>
<body>
<div style="display: flex; flex-direction: column; align-items: center;">
<img src="cid:demo.jpg" height="85" width="128" >
<h2 style="font-family: Arial, Helvetica, sans-serif;">做人嘛, 最重要是开心啦...</h2>
</div>
</body>
</html>"#;
// 注意上面的demo.jpg没有用<>包裹起来
// 邮件的参数
let from_address = "你的邮箱";
// 大家可以测试的时候可以随便往这个邮箱发....
let to_address = "[email protected]";
let subject = "用Rust发一封在内容中嵌入图片的邮件";
// 认证相关
let username = String::from("你的邮箱");
let password = String::from("你的密码");
let smtp_server = "smtp.126.com";
// 构造邮件体内容
let email = Message::builder()
.from(from_address.parse().expect("解析发送邮箱地址失败:"))
.to(to_address.parse().expect("解析收件邮箱地址失败: "))
.subject(subject)
.multipart(
MultiPart::alternative() // alternative构造一个可回退的邮件体
.singlepart(
SinglePart::builder()
.header(header::ContentType::TEXT_PLAIN)
// 应该是为了防止邮件服务器不支持HTML格式邮件
.body(String::from("发送HTML格式失败就只能回退了.")),
)
.multipart(
MultiPart::related()
.singlepart(
SinglePart::builder()
.header(header::ContentType::TEXT_HTML)
.body(String::from(html))
)
.singlepart(
SinglePart::builder()
.header(header::ContentType::parse("image/jpeg").expect("解析图片格式失败:"))
.header(header::ContentDisposition::inline_with_name("demo.jpg"))
// 注意demo.jpg用尖括号包裹起来了
.header(header::ContentId::parse("<demo.jpg>").expect("解析成content-id失败:"))
.body(fs::read("demo.jpg").expect("读取文件失败:"))
)
,
)
)
.expect("构造邮件消息体失败:");
let creds = Credentials::new(username, password);
// 默认使用TLS连接
let mailer = SmtpTransport::relay(smtp_server)
.expect("解析SMTP服务器失败:")
.credentials(creds)
.build();
match mailer.send(&email) {
Ok(_) => println!("邮件发送成功!"),
Err(e) => panic!("发送邮件失败: {e:?}"),
}
}
在HTML
中嵌入图片要注意的点主要是content-id
, 只有设置了这个header
才能在html
中引用,而引用需要使用cid:demo.jpg
这种格式,其中demo.jpg
是content-id
, 但是看代码你会发现设置的是<demo.jpg>
, 为啥要加个尖括号包裹起来勒? 我认为可能是为了包裹特殊字符吧。
上面发送的代码效果如下:
发送带附件的邮件
发送附件就要比嵌入图片容易很多了,因为提供了对于的类型。
use lettre::{
message::{header::{self, ContentType}, Attachment, MultiPart, SinglePart},
SmtpTransport, Message, Transport,
};
use std::fs;
use lettre::transport::smtp::authentication::Credentials;
fn main() {
// 邮件内容
let html = r#"<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>来自Rust代码发送的邮件</title>
</head>
<body>
<div style="display: flex; flex-direction: column; align-items: center;">
<h2 style="font-family: Arial, Helvetica, sans-serif;">做人嘛, 最重要是开心啦...</h2>
</div>
</body>
</html>"#;
// 邮件的附件构造
let attachment = Attachment::new("demo.jpg".into())
.body(
fs::read("demo.jpg").expect("读取照片失败"),
ContentType::parse("image/jpeg").expect("解析图片格式失败:")
);
// 邮件的参数
let from_address = "你的邮箱";
// 大家可以测试的时候可以随便往这个邮箱发....
let to_address = "[email protected]";
let subject = "用Rust发一封带附件的邮件";
// 认证相关
let username = String::from("你的邮箱");
let password = String::from("你的密码");
let smtp_server = "smtp.126.com";
// 构造邮件体内容
let email = Message::builder()
.from(from_address.parse().expect("解析发送邮箱地址失败:"))
.to(to_address.parse().expect("解析收件邮箱地址失败: "))
.subject(subject)
.multipart(
MultiPart::alternative() // alternative构造一个可回退的邮件体
.singlepart(
SinglePart::builder()
.header(header::ContentType::TEXT_PLAIN)
// 应该是为了防止邮件服务器不支持HTML格式邮件
.body(String::from("发送HTML格式失败就只能回退了.")),
)
.singlepart(
SinglePart::builder()
.header(header::ContentType::TEXT_HTML)
.body(String::from(html)),
)
.singlepart(attachment),
)
.expect("构造邮件消息体失败:");
let creds = Credentials::new(username, password);
// 默认使用TLS连接
let mailer = SmtpTransport::relay(smtp_server)
.expect("解析SMTP服务器失败:")
.credentials(creds)
.build();
match mailer.send(&email) {
Ok(_) => println!("邮件发送成功!"),
Err(e) => panic!("发送邮件失败: {e:?}"),
}
}
总结
不同的程序写邮件其实大同小异,这就跟不同的编程使用http客户端一样,因为协议是一样的,所以很多概念都是差不多的,不同点主要是不同的程序库会提供不同的接口来供用户使用,所以需要查阅相关文档来熟悉接口,但是接口很容易忘记的啦,所以写成文章以备后续自己复制粘贴。