daaki-message 0.2.0

RFC 5322 email message parser and builder
Documentation
# daaki-message

An RFC 5322 email message parser and builder.

## Highlights

- **Parse and build** — turn raw bytes into structured data, or structured data into raw bytes.
- **Full MIME support** — multipart messages, attachments, Content-Transfer-Encoding, boundary handling.
- **Encoded words** — RFC 2047 decoding and encoding for non-ASCII headers.
- **Internationalized headers** — RFC 6532 UTF-8 support.
- **Validated types**`HeaderName`, `MessageId`, `Address` enforce RFC syntax at construction time.
- **7-bit safe output** — always produces `7bit` or `quoted-printable` encoding, compatible with any SMTP server.
- **Zero unsafe code** — enforced by `#![deny(unsafe_code)]` crate-wide.
- **No runtime dependency** — works with any async runtime or without one.
- **Optional serde** — enable the `serde` feature for `Serialize`/`Deserialize` on all public types.

## Quick Start

```toml
[dependencies]
daaki-message = "0.2"
```

### Parsing

Parse a raw email into its structured parts:

```rust
use daaki_message::parse_email;

let raw = b"From: alice@example.com\r\n\
            To: bob@example.com\r\n\
            Subject: Hi\r\n\
            \r\n\
            Hello!";

let email = parse_email(raw).unwrap();
assert_eq!(email.subject.as_deref(), Some("Hi"));
assert_eq!(email.body_text.as_deref(), Some("Hello!"));
```

### Building

Construct a well-formed RFC 5322 message from typed inputs:

```rust
use daaki_message::{build_message, OutgoingEmail, Address};

let mut email = OutgoingEmail::default();
email.from = vec![Address::with_name("Alice", "alice@example.com").unwrap()];
email.to = vec![Address::new("bob@example.com").unwrap()];
email.subject = "Hello from daaki".into();
email.body_text = Some("Plain text body".into());

let built = build_message(&email).unwrap();
assert!(String::from_utf8_lossy(&built.raw).contains("Subject: Hello from daaki"));
assert_eq!(built.envelope_recipients, vec!["bob@example.com".to_string()]);
```

### Headers-only parsing

Parse just headers for fast metadata extraction (skips body and MIME processing):

```rust
use daaki_message::parse_headers_only;

let raw = b"From: alice@example.com\r\nSubject: Hi\r\n\r\nBody here";
let email = parse_headers_only(raw).unwrap();
assert_eq!(email.subject.as_deref(), Some("Hi"));
// body_text, body_html, attachments are not populated
```

### Parsing address lists

Parse a comma-separated address list — for example, a user-typed recipient
field in a compose form or a decoded `To`/`Cc` header value — using the liberal
RFC 5322 Section 3.4 parser that `daaki-message` uses internally for inbound
headers. It handles quoted display names, parenthesized comments, group
syntax, domain literals, and RFC 2047 encoded-word display names:

```rust
use daaki_message::{parse_address_list, Address};

let raw = r#""Doe, Jane" <jane@example.com>, alice@example.com"#;
let addrs = parse_address_list(raw);

assert_eq!(addrs.len(), 2);
assert_eq!(addrs[0].name.as_deref(), Some("Doe, Jane"));
assert_eq!(addrs[0].email, "jane@example.com");
assert_eq!(addrs[1].email, "alice@example.com");

// `parse_address_list` is liberal (Postel's law) and never errors —
// the returned `Address` records are not validated for outgoing mail.
// For outgoing mail, re-validate each result through `Address::new` /
// `Address::with_name`, which enforce the same strict rules the
// message builder uses:
let validated: Result<Vec<Address>, _> = addrs
    .into_iter()
    .map(|a| match a.name {
        Some(name) => Address::with_name(name, a.email),
        None => Address::new(a.email),
    })
    .collect();
assert!(validated.is_ok());
```

Note: this function expects already-decoded text. It does **not** perform
header unfolding, charset detection, or transfer-encoding decoding — use
`parse_email` for raw message bytes.

### Encoding

`build_message` always produces 7-bit safe output. Pure ASCII with conforming
lines uses `Content-Transfer-Encoding: 7bit`; anything else uses
`quoted-printable`. This guarantees the built bytes can be sent to any SMTP
server — with or without 8BITMIME — and stored via IMAP `APPEND` without
re-encoding.

```rust
use daaki_message::{build_message, OutgoingEmail, Address};

let mut email = OutgoingEmail::default();
email.from = vec![Address::new("a@example.com").unwrap()];
email.to = vec![Address::new("b@example.com").unwrap()];
email.subject = "Héllo".into();
email.body_text = Some("Café".into());
let built = build_message(&email).unwrap();
// Non-ASCII body is automatically quoted-printable encoded
assert!(String::from_utf8_lossy(&built.raw).contains("quoted-printable"));
```

## Standards

| Standard | Coverage |
|----------|----------|
| **RFC 5322** | Internet Message Format — headers, date-time, addresses, message identification |
| **RFC 2045** | MIME Part One — Content-Transfer-Encoding (base64, quoted-printable, 7bit, 8bit) |
| **RFC 2046** | MIME Part Two — multipart structure, boundary handling, media types |
| **RFC 2047** | MIME Part Three — encoded words for non-ASCII in headers (Q and B encoding) |
| **RFC 2183** | Content-Disposition — inline and attachment handling |
| **RFC 2387** | multipart/related — HTML with inline images via Content-ID |
| **RFC 2231** | MIME parameter encoding — charset, language, and continuations |
| **RFC 2392** | Content-ID — `cid:` URL references for inline attachments |
| **RFC 6531** | Internationalized email — non-ASCII local-parts and domains |
| **RFC 6532** | Internationalized headers — UTF-8 throughout header fields |

## License

The contents of this package are licensed under the terms of the MIT license.