# email-message A Rust library for parsing, modifying and serializing email messages (RFC 5322 / MIME). Copy-on-write throughout: a parsed message borrows from the buffer it was parsed from, and only the fields you actually change become owned. Serializing hands back a `Vec>` of borrowed and owned chunks rather than one concatenated buffer, so untouched parts are never copied. `parse → serialize` is **byte-identical**. The separators a message was written with — line endings, delimiter-line padding, preamble and epilogue — are kept verbatim, so a message can be read, modified in one place, and written back without disturbing anything else. This matters for signed messages and for anything that has to pass a message through unchanged. ## Quickstart ```rust use email_message::{CowMail, HeaderMap, Mail}; let raw = b"Subject: Hello\r\n\ Content-Type: text/plain\r\n\ \r\n\ Hi there\r\n"; let mut mail = CowMail::try_from(&raw[..]).unwrap(); // Read headers and the decoded body. assert_eq!(mail.find_value("Subject").unwrap(), "Hello"); assert_eq!(mail.get_content().unwrap(), "Hi there"); // An untouched message serializes back to exactly the bytes it was parsed from. assert_eq!(mail.serialize().concat(), raw.to_vec()); // Modify it. mail.add_header("X-Mailer", "email-message"); mail.set_text_body("Hi again").unwrap(); let out: Vec = mail.serialize().concat(); assert!(out.starts_with(b"Subject: Hello")); ``` Building a multipart message from scratch: ```rust use email_message::{CowMail, HeaderMap, Mail, MediaType, names}; let mut mail = CowMail::default(); mail.add_header("Subject", "Report"); mail.set_body("plain text version", "

html version

").unwrap(); let mut attachment = CowMail::new(); attachment.add_header("Content-Disposition", "attachment; filename=\"report.csv\""); attachment.set_content("a,b\n1,2\n", Some(MediaType::new(names::TEXT, names::CSV))); mail.add_attachment(attachment); assert!(mail.is_multipart()); ``` ## Design - **`CowMail`** is the message, and every part of a multipart message is a `CowMail` as well. Its fields are public: `headers`, `body_bytes` and `subparts` are meant to be edited directly when the provided methods do not fit. - The `*_orig` fields (`bytes_orig`, `header_bytes_orig`, `body_bytes_orig`) keep the originally parsed slices even after `body_bytes` has become owned, so the original and the modified message can be compared. - **`HeaderMap`** finds, iterates and edits headers by key; keys match case-insensitively and an `index` argument counts occurrences of that key, not positions in the list. It is implemented for `CowMail` and for `Vec`. - **`Mail`** adds the MIME questions on top: content type, disposition, multipart-ness, and decoding the body. - **`Header`** abstracts a single header line; `CowHeader` is the default implementation and stores the raw line plus the offsets of its key and value. - MIME structure is decided by [`mailparse`](https://crates.io/crates/mailparse) — boundary matching, `multipart/digest` propagation, recursion limits. This crate keeps the separators mailparse discards, which is what makes the round-trip exact. ## Limitations - Anything written by this crate uses CRLF. A message whose headers were rewritten therefore mixes line endings with its untouched parts — that is deliberate: fidelity for what was not touched. - A message truncated before its closing delimiter gains a `--boundary--` on the way out, because its declared structure says there should be one. - A message that ends with its header block and no blank line gains that blank line, because the header/body separator is always written. ## License GPL-3.0. See [LICENSE](LICENSE).