4 个版本

使用旧的 Rust 2015

0.2.2 2018年11月10日
0.2.1 2018年11月5日
0.2.0 2018年11月4日
0.1.0 2018年4月25日

#157 in 邮件

MIT 许可证

125KB
2.5K SLoC

Rust 的 Email Message 库

License: MIT Crates.io Package Docs.rs API Documentation Travis-CI Build Status Appveyor Build status

该项目旨在提供一种适当的强类型方式来构建和解析电子邮件。

特性

  • 使用 hyperx::Header 的类型化头信息
  • 支持包含 Unicode 值的头信息
  • 支持 MIME 1.0 多部分内容
  • 流式传输消息以节省内存使用
  • Email AddressMailboxMailboxes 类型

用法

格式化电子邮件消息

使用字符串正文

以简单字符串创建电子邮件消息的最简单方法(见 format_string.rs)。

extern crate emailmessage;

use emailmessage::Message;

fn main() {
    let m: Message<&str> = Message::builder()
        .from("NoBody <[email protected]>".parse().unwrap())
        .reply_to("Yuin <[email protected]>".parse().unwrap())
        .to("Hei <[email protected]>".parse().unwrap())
        .subject("Happy new year")
        .body("Be happy!");

    println!("{}", m);
}

运行此示例

$ cargo run --example format_string

From: NoBody <[email protected]>
Reply-To: Yuin <[email protected]>
To: Hei <[email protected]>
Subject: Happy new year

Be happy!

Unicode 头数据将使用 UTF8-Base64 编码。

使用 MIME 正文

单部分

更复杂的方法是使用 MIME 内容(见 format_mime.rs)。

extern crate emailmessage;

use emailmessage::{header, Message, SinglePart};

fn main() {
    let m: Message<SinglePart<&str>> = Message::builder()
        .from("NoBody <[email protected]>".parse().unwrap())
        .reply_to("Yuin <[email protected]>".parse().unwrap())
        .to("Hei <[email protected]>".parse().unwrap())
        .subject("Happy new year")
        .mime_body(
            SinglePart::builder()
                .header(header::ContentType(
                    "text/plain; charset=utf8".parse().unwrap(),
                )).header(header::ContentTransferEncoding::QuotedPrintable)
                .body("Привет, мир!"),
        );

    println!("{}", m);
}

正文将使用选定的 Content-Transfer-Encoding 编码。

$ cargo run --example format_mime

From: NoBody <[email protected]>
Reply-To: Yuin <[email protected]>
To: Hei <[email protected]>
Subject: Happy new year
MIME-Version: 1.0
Content-Type: text/plain; charset=utf8
Content-Transfer-Encoding: quoted-printable

=D0=9F=D1=80=D0=B8=D0=B2=D0=B5=D1=82, =D0=BC=D0=B8=D1=80!

多部分

更高级的方法是使用多部分 MIME 内容(见 format_multipart.rs)。

extern crate emailmessage;
use emailmessage::{header, Message, MultiPart, SinglePart};
fn main() {
    let m: Message<MultiPart<&str>> = Message::builder()
        .from("NoBody <[email protected]>".parse().unwrap())
        .reply_to("Yuin <[email protected]>".parse().unwrap())
        .to("Hei <[email protected]>".parse().unwrap())
        .subject("Happy new year")
        .mime_body(
            MultiPart::mixed()
            .multipart(
                MultiPart::alternative()
                .singlepart(
                    SinglePart::quoted_printable()
                    .header(header::ContentType("text/plain; charset=utf8".parse().unwrap()))
                    .body("Привет, мир!")
                )
                .multipart(
                    MultiPart::related()
                    .singlepart(
                        SinglePart::eight_bit()
                        .header(header::ContentType("text/html; charset=utf8".parse().unwrap()))
                        .body("<p><b>Hello</b>, <i>world</i>! <img src=smile.png></p>")
                    )
                    .singlepart(
                        SinglePart::base64()
                        .header(header::ContentType("image/png".parse().unwrap()))
                        .header(header::ContentDisposition {
                            disposition: header::DispositionType::Inline,
                            parameters: vec![],
                        })
                        .body("<smile-raw-image-data>")
                    )
                )
            )
            .singlepart(
                SinglePart::seven_bit()
                .header(header::ContentType("text/plain; charset=utf8".parse().unwrap()))
                .header(header::ContentDisposition {
                                 disposition: header::DispositionType::Attachment,
                                 parameters: vec![
                                     header::DispositionParam::Filename(
                                         header::Charset::Ext("utf-8".into()),
                                         None, "example.c".as_bytes().into()
                                     )
                                 ]
                             })
                .body("int main() { return 0; }")
            )
        );

    println!("{}", m);
}
$ cargo run --example format_multipart

From: NoBody <[email protected]>
Reply-To: Yuin <[email protected]>
To: Hei <[email protected]>
Subject: Happy new year
MIME-Version: 1.0
Content-Type: multipart/mixed; boundary="RTxPCn9p31oAAAAAeQxtr1FbXr/i5vW1hFlH9oJqZRMWxRMK1QLjQ4OPqFk9R+0xUb/m"

--RTxPCn9p31oAAAAAeQxtr1FbXr/i5vW1hFlH9oJqZRMWxRMK1QLjQ4OPqFk9R+0xUb/m
Content-Type: multipart/alternative; boundary="qW9QCn9p31oAAAAAodFBg1L1Qrraa5hEl0bDJ6kfJMUcRT2LLSWEoeyhSEbUBIqbjWqy"

--qW9QCn9p31oAAAAAodFBg1L1Qrraa5hEl0bDJ6kfJMUcRT2LLSWEoeyhSEbUBIqbjWqy
Content-Transfer-Encoding: quoted-printable
Content-Type: text/plain; charset=utf8

=D0=9F=D1=80=D0=B8=D0=B2=D0=B5=D1=82, =D0=BC=D0=B8=D1=80!
--qW9QCn9p31oAAAAAodFBg1L1Qrraa5hEl0bDJ6kfJMUcRT2LLSWEoeyhSEbUBIqbjWqy
Content-Type: multipart/related; boundary="BV5RCn9p31oAAAAAUt42E9bYMDEAGCOWlxEz89Bv0qFA5Xsy6rOC3zRahMQ39IFZNnp8"

--BV5RCn9p31oAAAAAUt42E9bYMDEAGCOWlxEz89Bv0qFA5Xsy6rOC3zRahMQ39IFZNnp8
Content-Transfer-Encoding: 8bit
Content-Type: text/html; charset=utf8

<p><b>Hello</b>, <i>world</i>! <img src=smile.png></p>
--BV5RCn9p31oAAAAAUt42E9bYMDEAGCOWlxEz89Bv0qFA5Xsy6rOC3zRahMQ39IFZNnp8
Content-Transfer-Encoding: base64
Content-Type: image/png
Content-Disposition: inline

PHNtaWxlLXJhdy1pbWFnZS1kYXRhPg==
--BV5RCn9p31oAAAAAUt42E9bYMDEAGCOWlxEz89Bv0qFA5Xsy6rOC3zRahMQ39IFZNnp8--
--qW9QCn9p31oAAAAAodFBg1L1Qrraa5hEl0bDJ6kfJMUcRT2LLSWEoeyhSEbUBIqbjWqy--
--RTxPCn9p31oAAAAAeQxtr1FbXr/i5vW1hFlH9oJqZRMWxRMK1QLjQ4OPqFk9R+0xUb/m
Content-Transfer-Encoding: 7bit
Content-Type: text/plain; charset=utf8
Content-Disposition: attachment; filename="example.c"

int main() { return 0; }
--RTxPCn9p31oAAAAAeQxtr1FbXr/i5vW1hFlH9oJqZRMWxRMK1QLjQ4OPqFk9R+0xUb/m--

使用流式传输

格式化和发送相对较大的电子邮件且内存效率高的方式是使用流式传输。例如,您可能希望将服务器访问日志作为附件发送,或者发送包含相关媒体资源的 HTML 文档或 PDF。

在上面的示例中,我们实际上在内存中分配了格式化的电子邮件,但通常我们不希望对大小以 MB 为单位的较大电子邮件执行相同的操作。

简单字符串

以下简单示例显示了实际发送的流式消息块(见 format_stream.rs)。

extern crate emailmessage;
extern crate futures;
extern crate tokio;

use emailmessage::Message;
use futures::{Future, Stream};
use std::str::from_utf8;
use tokio::run;

fn main() {
    let m: Message = Message::builder()
        .from("NoBody <[email protected]>".parse().unwrap())
        .reply_to("Yuin <[email protected]>".parse().unwrap())
        .to("Hei <[email protected]>".parse().unwrap())
        .subject("Happy new year")
        .body("Be happy!".into());

    let f = m
        .into_stream()
        .map(|chunk| {
            println!("CHUNK[[\n{}]]", from_utf8(&chunk).unwrap());
            chunk
        }).concat2()
        .map(|message| {
            println!("MESSAGE[[\n{}]]", from_utf8(&message).unwrap());
        }).map_err(|error| {
            eprintln!("ERROR: {}", error);
        });

    run(f);
}
$ cargo run --example format_stream

CHUNK[[
From: NoBody <[email protected]>
Reply-To: Yuin <[email protected]>
To: Hei <[email protected]>
Subject: Happy new year
]]
CHUNK[[

Be happy!]]

在实际应用程序中,我们可能需要对流进行一些缓冲,以防止发送过短或过长。

多部分数据

(见 format_stream_multipart.rs)

extern crate emailmessage;
extern crate futures;
extern crate tokio;
use emailmessage::{header, Message, MultiPart, SinglePart};
use futures::{Future, Stream};
use std::str::from_utf8;
use tokio::run;
fn main() {
    let b: MultiPart = MultiPart::mixed()
        .multipart(
            MultiPart::alternative()
                .singlepart(
                    SinglePart::quoted_printable()
                        .header(header::ContentType(
                            "text/plain; charset=utf8".parse().unwrap(),
                        )).body("Привет, мир!".into()),
                ).multipart(
                    MultiPart::related()
                        .singlepart(
                            SinglePart::eight_bit()
                                .header(header::ContentType(
                                    "text/html; charset=utf8".parse().unwrap(),
                                )).body(
                                    "<p><b>Hello</b>, <i>world</i>! <img src=smile.png></p>".into(),
                                ),
                        ).singlepart(
                            SinglePart::base64()
                                .header(header::ContentType("image/png".parse().unwrap()))
                                .header(header::ContentDisposition {
                                    disposition: header::DispositionType::Inline,
                                    parameters: vec![],
                                }).body("<smile-raw-image-data>".into()),
                        ),
                ),
        ).singlepart(
            SinglePart::seven_bit()
                .header(header::ContentType(
                    "text/plain; charset=utf8".parse().unwrap(),
                )).header(header::ContentDisposition {
                    disposition: header::DispositionType::Attachment,
                    parameters: vec![header::DispositionParam::Filename(
                        header::Charset::Ext("utf-8".into()),
                        None,
                        "example.c".as_bytes().into(),
                    )],
                }).body("int main() { return 0; }".into()),
        );

    let m = Message::builder()
        .from("NoBody <[email protected]>".parse().unwrap())
        .reply_to("Yuin <[email protected]>".parse().unwrap())
        .to("Hei <[email protected]>".parse().unwrap())
        .subject("Happy new year")
        .mime_body(b.into_stream());

    let f = m
        .into_stream()
        .map(|chunk| {
            println!("CHUNK[[\n{}]]", from_utf8(&chunk).unwrap());
            chunk
        }).concat2()
        .map(|message| {
            println!("MESSSAGE[[\n{}]]", from_utf8(&message).unwrap());
        }).map_err(|error| {
            eprintln!("ERROR: {:?}", error);
        });

    run(f);
}
$ cargo run --example format_stream_multipart

CHUNK[[
From: NoBody <[email protected]>
Reply-To: Yuin <[email protected]>
To: Hei <[email protected]>
Subject: Happy new year
MIME-Version: 1.0
]]
CHUNK[[
Content-Type: multipart/mixed; boundary="1S8dCMR/31oAAAAApHRNMETjK2uRsQs4mVVFKVNujcqnm8FHOXWvqARiaYy9ZmnpQ7uQ"
]]
CHUNK[[
--1S8dCMR/31oAAAAApHRNMETjK2uRsQs4mVVFKVNujcqnm8FHOXWvqARiaYy9ZmnpQ7uQ
]]
CHUNK[[
--1S8dCMR/31oAAAAApHRNMETjK2uRsQs4mVVFKVNujcqnm8FHOXWvqARiaYy9ZmnpQ7uQ
]]
CHUNK[[
Content-Type: multipart/alternative; boundary="TCMeCMR/31oAAAAAmf7KBuXt4qRk2RnBJCj8YJNdwm2dsadXxjOlC74hlb1tO6U/SqXY"
]]
CHUNK[[
--TCMeCMR/31oAAAAAmf7KBuXt4qRk2RnBJCj8YJNdwm2dsadXxjOlC74hlb1tO6U/SqXY
]]
CHUNK[[
--TCMeCMR/31oAAAAAmf7KBuXt4qRk2RnBJCj8YJNdwm2dsadXxjOlC74hlb1tO6U/SqXY
]]
CHUNK[[
Content-Transfer-Encoding: quoted-printable
Content-Type: text/plain; charset=utf8
]]
CHUNK[[
=D0=9F=D1=80=D0=B8=D0=B2=D0=B5=D1=82, =D0=BC=D0=B8=D1=80!]]
CHUNK[[
]]
CHUNK[[
--TCMeCMR/31oAAAAAmf7KBuXt4qRk2RnBJCj8YJNdwm2dsadXxjOlC74hlb1tO6U/SqXY
]]
CHUNK[[
Content-Type: multipart/related; boundary="YsgeCMR/31oAAAAAanzeyu/dFJGjfzDxpsAOLhRB0RfSw+DXefQybZxGq6HIBEzotZ5Y"
]]
CHUNK[[
--YsgeCMR/31oAAAAAanzeyu/dFJGjfzDxpsAOLhRB0RfSw+DXefQybZxGq6HIBEzotZ5Y
]]
CHUNK[[
--YsgeCMR/31oAAAAAanzeyu/dFJGjfzDxpsAOLhRB0RfSw+DXefQybZxGq6HIBEzotZ5Y
]]
CHUNK[[
Content-Transfer-Encoding: 8bit
Content-Type: text/html; charset=utf8
]]
CHUNK[[
<p><b>Hello</b>, <i>world</i>! <img src=smile.png></p>]]
CHUNK[[
]]
CHUNK[[
--YsgeCMR/31oAAAAAanzeyu/dFJGjfzDxpsAOLhRB0RfSw+DXefQybZxGq6HIBEzotZ5Y
]]
CHUNK[[
Content-Transfer-Encoding: base64
Content-Type: image/png
Content-Disposition: inline

]]
CHUNK[[
PHNtaWxlLXJhdy1pbWFnZS1kYXRhPg==]]
CHUNK[[

]]
CHUNK[[
--YsgeCMR/31oAAAAAanzeyu/dFJGjfzDxpsAOLhRB0RfSw+DXefQybZxGq6HIBEzotZ5Y--
]]
CHUNK[[
--TCMeCMR/31oAAAAAmf7KBuXt4qRk2RnBJCj8YJNdwm2dsadXxjOlC74hlb1tO6U/SqXY--
]]
CHUNK[[
--1S8dCMR/31oAAAAApHRNMETjK2uRsQs4mVVFKVNujcqnm8FHOXWvqARiaYy9ZmnpQ7uQ
]]
CHUNK[[
Content-Transfer-Encoding: 7bit
Content-Type: text/plain; charset=utf8
Content-Disposition: attachment; filename="example.c"

]]
CHUNK[[
int main() { return 0; }]]
CHUNK[[

]]
CHUNK[[
--1S8dCMR/31oAAAAApHRNMETjK2uRsQs4mVVFKVNujcqnm8FHOXWvqARiaYy9ZmnpQ7uQ--
]]
...

依赖关系

~13MB
~261K SLoC