Repository: lettre/lettre (language: rust)

Issue:
Is it possible to make SendableEmail (or Message in 0.10) Sync?
**Is your feature request related to a problem? Please describe.**
`SendableEmail` is not `Sync` at the moment. I think this is because the body of the email is represented by `(Box<dyn Read + Send>)`, which doesn't have the `Sync` marker trait. Being `!Sync` prevents sharing `&SendableEmail` between threads, and sending them to executors that require `Sync` for such references. It also prevents me from storing e-mails in a statically alloctated mutex-protected `Vec`, that I use as a central context for collecting e-mails before sending them.

**Describe the solution you'd like**
It would be nice, if the email types would implement both `Send` and `Sync`, like the all primitive types and compound types of them do. I feel that a piece of data that represents an e-mail ought not to have needlessly complicated invariants; it should be entirely possible to represent it as a "plain old value".

**Describe alternatives you've considered**
Nothing at the moment.

**Additional context**
- Adding the trait is likely to be a breaking change in some cases, so this would be good to consider before releasing 0.10.
- My workaround at the moment is that I keep the e-mail message as `lettre_email::Email` which implements `Sync`, and convert it to `SendableEmail` only in the mailer thread, just before sending.
feat(builder): Add helper methods for attachments
A first draft of shortcuts for building parts.

Still unsure if we need them bot both `SinglePart` and `MultiPart`.


Discussion / hints:
This is definitely something we want to fix before the final 0.10.0 release, even more important if we consider the fact that with the merging of `lettre_email` into `lettre` the workaround you mentioned won't be possible anymore. We might also want to add tests to make sure this doesn't regress in the future.

As Hacktoberfest is starting in a few days I'll leave this open to see if somebody is interested in having a go at it, but I'll definitely come back to this if nobody else does.
I'm interested in contributing this, but being a bit busy at the moment it might take a week or two before I get into it.
I looked into this and [the problem seems to be with `hyperx`](https://docs.rs/hyperx/1.1.0/hyperx/header/struct.Headers.html#impl-Sync)

#443 tracks our public dependencies and we are deciding whether `hyperx` is something we want to keep in our public API. For the moment we only need header encoding, so maybe we could re-implement it ourselves? There are multiple options here, so we probably want to evaluate them before sending a PR
Indeed, there is also a similar crate, `headers` that is maintained by the authors of `hyper`, but from what I gather after using it for a bit, it seems to be a bit barebones/unpolished.

If it's just a single header, I'd go opt for re-implementing it, pulling a whole library for a single case sounds a bit heavy-handed. But that needs evaluation, indeed.
Looking more into this, I think re-implementing it looks like the right choice here. ~~It would also solve problems like: #414, by correctly encoding non ASCII headers https://tools.ietf.org/html/rfc2822 (example: https://stackoverflow.com/a/27041436)~~ edit: this was already fixed


I made #495 to track improvements to the message builder
In the meantime, if you want to try out the 0.10 alphas you can still use a similar workaround as the one you are using with 0.9.

In 0.10 [Transport::send](https://github.com/lettre/lettre/blob/b55bcfb6fb6f4d37fa82eb3c4977b7d01ef2052d/src/lib.rs#L88-L91) just extracts the `Envelope` and the formatted message (`Vec<u8>`) out of `Message` and passes it to `send_raw`. `Envelope` and `Vec<u8>` are Send + Sync, so you could store those and pass them to `send_raw` whenever you are ready to send the email. @golddranks
Sorry, I'm really swamped at the moment! (Trying to get my Master's thesis done.) I think I test stuff and/or contribute around halfway of November at earliest. Thanks for the pointers!
We decided to postpone this fix to the next minor/major version (depending on if 0.11 or 1.0 will be the next one). We've been dragging this 0.10.0 release for too long and there's an easy workaround for this issue. Many users are using the alphas because they're better than 0.9, and this issue is already present in the 0.9 release, so we're not adding a regression by deciding to skip it for the moment.

0.10.0 release PR: #538
Turns out there are still a few PRs we have to merge before we can release the final 0.10.0, and in the meantime I was able to think how to reimplement headers specifically for email, so this is going to be fixed before the final release.

See #593
> I'm not too convinced by this API. It seems to me that there's a lot of duplication and unclear method naming. What do you think about having an `Attachment` and `AttachmentBuilder` style of API?

I was not convinced either actually. The Builder interface could be a better option, I'll give it a try.

Fix patch:
diff --git a/Cargo.toml b/Cargo.toml
index ef4553ab7..b8b7f2aca 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -23,7 +23,6 @@ tracing = { version = "0.1.16", default-features = false, features = ["std"], op
 
 # builder
 httpdate = { version = "1", optional = true }
-hyperx = { version = "1", optional = true, features = ["headers"] }
 mime = { version = "0.3.4", optional = true }
 fastrand = { version = "1.4", optional = true }
 quoted_printable = { version = "0.4", optional = true }
@@ -82,7 +81,7 @@ name = "transport_smtp"
 
 [features]
 default = ["smtp-transport", "native-tls", "hostname", "r2d2", "builder"]
-builder = ["httpdate", "mime", "base64", "hyperx", "fastrand", "quoted_printable"]
+builder = ["httpdate", "mime", "base64", "fastrand", "quoted_printable"]
 
 # transports
 file-transport = ["uuid"]
diff --git a/src/address/envelope.rs b/src/address/envelope.rs
index 6d5c2994b..b5f18c1dc 100644
--- a/src/address/envelope.rs
+++ b/src/address/envelope.rs
@@ -112,11 +112,11 @@ impl TryFrom<&Headers> for Envelope {
     fn try_from(headers: &Headers) -> Result<Self, Self::Error> {
         let from = match headers.get::<header::Sender>() {
             // If there is a Sender, use it
-            Some(sender) => Some(Mailbox::from(sender.clone()).email),
+            Some(sender) => Some(Mailbox::from(sender).email),
             // ... else try From
             None => match headers.get::<header::From>() {
                 Some(header::From(a)) => {
-                    let from: Vec<Mailbox> = a.clone().into();
+                    let from: Vec<Mailbox> = a.into();
                     if from.len() > 1 {
                         return Err(Error::TooManyFrom);
                     }
@@ -128,7 +128,7 @@ impl TryFrom<&Headers> for Envelope {
 
         fn add_addresses_from_mailboxes(
             addresses: &mut Vec<Address>,
-            mailboxes: Option<&Mailboxes>,
+            mailboxes: Option<Mailboxes>,
         ) {
             if let Some(mailboxes) = mailboxes {
                 for mailbox in mailboxes.iter() {
@@ -137,9 +137,9 @@ impl TryFrom<&Headers> for Envelope {
             }
         }
         let mut to = vec![];
-        add_addresses_from_mailboxes(&mut to, headers.get::<header::To>().map(|h| &h.0));
-        add_addresses_from_mailboxes(&mut to, headers.get::<header::Cc>().map(|h| &h.0));
-        add_addresses_from_mailboxes(&mut to, headers.get::<header::Bcc>().map(|h| &h.0));
+        add_addresses_from_mailboxes(&mut to, headers.get::<header::To>().map(|h| h.0));
+        add_addresses_from_mailboxes(&mut to, headers.get::<header::Cc>().map(|h| h.0));
+        add_addresses_from_mailboxes(&mut to, headers.get::<header::Bcc>().map(|h| h.0));
 
         Self::new(from, to)
     }
diff --git a/src/lib.rs b/src/lib.rs
index 5704ca3e5..dd147dd39 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -173,10 +173,11 @@ pub(crate) type BoxError = Box<dyn StdError + Send + Sync>;
 #[cfg(test)]
 #[cfg(feature = "builder")]
 mod test {
+    use std::convert::TryFrom;
+
     use super::*;
+    use crate::message::header::Headers;
     use crate::message::{header, Mailbox, Mailboxes};
-    use hyperx::header::Headers;
-    use std::convert::TryFrom;
 
     #[test]
     fn envelope_from_headers() {
diff --git a/src/message/header/content.rs b/src/message/header/content.rs
index 0f23b027f..4e1d3ea66 100644
--- a/src/message/header/content.rs
+++ b/src/message/header/content.rs
@@ -1,12 +1,11 @@
-use hyperx::{
-    header::{Formatter as HeaderFormatter, Header, RawLike},
-    Error as HeaderError, Result as HyperResult,
-};
 use std::{
     fmt::{Display, Formatter as FmtFormatter, Result as FmtResult},
-    str::{from_utf8, FromStr},
+    str::FromStr,
 };
 
+use super::{Header, HeaderName};
+use crate::BoxError;
+
 /// `Content-Transfer-Encoding` of the body
 ///
 /// The `Message` builder takes care of choosing the most
@@ -22,9 +21,17 @@ pub enum ContentTransferEncoding {
     Binary,
 }
 
-impl Default for ContentTransferEncoding {
-    fn default() -> Self {
-        ContentTransferEncoding::Base64
+impl Header for ContentTransferEncoding {
+    fn name() -> HeaderName {
+        HeaderName::new_from_ascii_str("Content-Transfer-Encoding")
+    }
+
+    fn parse(s: &str) -> Result<Self, BoxError> {
+        Ok(s.parse()?)
+    }
+
+    fn display(&self) -> String {
+        self.to_string()
     }
 }
 
@@ -54,35 +61,16 @@ impl FromStr for ContentTransferEncoding {
     }
 }
 
-impl Header for ContentTransferEncoding {
-    fn header_name() -> &'static str {
-        "Content-Transfer-Encoding"
-    }
-
-    // FIXME HeaderError->HeaderError, same for result
-    fn parse_header<'a, T>(raw: &'a T) -> HyperResult<Self>
-    where
-        T: RawLike<'a>,
-        Self: Sized,
-    {
-        raw.one()
-            .ok_or(HeaderError::Header)
-            .and_then(|r| from_utf8(r).map_err(|_| HeaderError::Header))
-            .and_then(|s| {
-                s.parse::<ContentTransferEncoding>()
-                    .map_err(|_| HeaderError::Header)
-            })
-    }
-
-    fn fmt_header(&self, f: &mut HeaderFormatter<'_, '_>) -> FmtResult {
-        f.fmt_line(&format!("{}", self))
+impl Default for ContentTransferEncoding {
+    fn default() -> Self {
+        ContentTransferEncoding::Base64
     }
 }
 
 #[cfg(test)]
 mod test {
     use super::ContentTransferEncoding;
-    use hyperx::header::Headers;
+    use crate::message::header::{HeaderName, Headers};
 
     #[test]
     fn format_content_transfer_encoding() {
@@ -90,35 +78,35 @@ mod test {
 
         headers.set(ContentTransferEncoding::SevenBit);
 
-        assert_eq!(
-            format!("{}", headers),
-            "Content-Transfer-Encoding: 7bit\r\n"
-        );
+        assert_eq!(headers.to_string(), "Content-Transfer-Encoding: 7bit\r\n");
 
         headers.set(ContentTransferEncoding::Base64);
 
-        assert_eq!(
-            format!("{}", headers),
-            "Content-Transfer-Encoding: base64\r\n"
-        );
+        assert_eq!(headers.to_string(), "Content-Transfer-Encoding: base64\r\n");
     }
 
     #[test]
     fn parse_content_transfer_encoding() {
         let mut headers = Headers::new();
 
-        headers.set_raw("Content-Transfer-Encoding", "7bit");
+        headers.set_raw(
+            HeaderName::new_from_ascii_str("Content-Transfer-Encoding"),
+            "7bit".to_string(),
+        );
 
         assert_eq!(
             headers.get::<ContentTransferEncoding>(),
-            Some(&ContentTransferEncoding::SevenBit)
+            Some(ContentTransferEncoding::SevenBit)
         );
 
-        headers.set_raw("Content-Transfer-Encoding", "base64");
+        headers.set_raw(
+            HeaderName::new_from_ascii_str("Content-Transfer-Encoding"),
+            "base64".to_string(),
+        );
 
         assert_eq!(
             headers.get::<ContentTransferEncoding>(),
-            Some(&ContentTransferEncoding::Base64)
+            Some(ContentTransferEncoding::Base64)
         );
     }
 }
diff --git a/src/message/header/content_disposition.rs b/src/message/header/content_disposition.rs
index 4a8fb7bcf..d1dd77b78 100644
--- a/src/message/header/content_disposition.rs
+++ b/src/message/header/content_disposition.rs
@@ -1,9 +1,5 @@
-use std::{fmt::Result as FmtResult, str::from_utf8};
-
-use hyperx::{
-    header::{Formatter as HeaderFormatter, Header, RawLike},
-    Error as HeaderError, Result as HyperResult,
-};
+use super::{Header, HeaderName};
+use crate::BoxError;
 
 /// `Content-Disposition` of an attachment
 ///
@@ -32,31 +28,23 @@ impl ContentDisposition {
 }
 
 impl Header for ContentDisposition {
-    fn header_name() -> &'static str {
-        "Content-Disposition"
+    fn name() -> HeaderName {
+        HeaderName::new_from_ascii_str("Content-Disposition")
     }
 
-    // FIXME HeaderError->HeaderError, same for result
-    fn parse_header<'a, T>(raw: &'a T) -> HyperResult<Self>
-    where
-        T: RawLike<'a>,
-        Self: Sized,
-    {
-        raw.one()
-            .ok_or(HeaderError::Header)
-            .and_then(|r| from_utf8(r).map_err(|_| HeaderError::Header))
-            .map(|s| Self(s.into()))
+    fn parse(s: &str) -> Result<Self, BoxError> {
+        Ok(Self(s.into()))
     }
 
-    fn fmt_header(&self, f: &mut HeaderFormatter<'_, '_>) -> FmtResult {
-        f.fmt_line(&self.0)
+    fn display(&self) -> String {
+        self.0.clone()
     }
 }
 
 #[cfg(test)]
 mod test {
     use super::ContentDisposition;
-    use hyperx::header::Headers;
+    use crate::message::header::{HeaderName, Headers};
 
     #[test]
     fn format_content_disposition() {
@@ -78,21 +66,24 @@ mod test {
     fn parse_content_disposition() {
         let mut headers = Headers::new();
 
-        headers.set_raw("Content-Disposition", "inline");
+        headers.set_raw(
+            HeaderName::new_from_ascii_str("Content-Disposition"),
+            "inline".to_string(),
+        );
 
         assert_eq!(
             headers.get::<ContentDisposition>(),
-            Some(&ContentDisposition::inline())
+            Some(ContentDisposition::inline())
         );
 
         headers.set_raw(
-            "Content-Disposition",
-            "attachment; filename=\"something.txt\"",
+            HeaderName::new_from_ascii_str("Content-Disposition"),
+            "attachment; filename=\"something.txt\"".to_string(),
         );
 
         assert_eq!(
             headers.get::<ContentDisposition>(),
-            Some(&ContentDisposition::attachment("something.txt"))
+            Some(ContentDisposition::attachment("something.txt"))
         );
     }
 }
diff --git a/src/message/header/content_type.rs b/src/message/header/content_type.rs
index 0d17673b7..f563bce28 100644
--- a/src/message/header/content_type.rs
+++ b/src/message/header/content_type.rs
@@ -1,15 +1,14 @@
 use std::{
     error::Error as StdError,
-    fmt::{self, Display, Result as FmtResult},
-    str::{from_utf8, FromStr},
+    fmt::{self, Display},
+    str::FromStr,
 };
 
-use hyperx::{
-    header::{Formatter as HeaderFormatter, Header, RawLike},
-    Error as HeaderError, Result as HyperResult,
-};
 use mime::Mime;
 
+use super::{Header, HeaderName};
+use crate::BoxError;
+
 /// `Content-Type` of the body
 ///
 /// Defined in [RFC2045](https://tools.ietf.org/html/rfc2045#section-5)
@@ -42,24 +41,16 @@ impl ContentType {
 }
 
 impl Header for ContentType {
-    fn header_name() -> &'static str {
-        "Content-Type"
+    fn name() -> HeaderName {
+        HeaderName::new_from_ascii_str("Content-Type")
     }
 
-    // FIXME HeaderError->HeaderError, same for result
-    fn parse_header<'a, T>(raw: &'a T) -> HyperResult<Self>
-    where
-        T: RawLike<'a>,
-        Self: Sized,
-    {
-        raw.one()
-            .ok_or(HeaderError::Header)
-            .and_then(|r| from_utf8(r).map_err(|_| HeaderError::Header))
-            .and_then(|s| s.parse::<Mime>().map(Self).map_err(|_| HeaderError::Header))
+    fn parse(s: &str) -> Result<Self, BoxError> {
+        Ok(Self(s.parse()?))
     }
 
-    fn fmt_header(&self, f: &mut HeaderFormatter<'_, '_>) -> FmtResult {
-        f.fmt_line(&self.0)
+    fn display(&self) -> String {
+        self.0.to_string()
     }
 }
 
@@ -89,9 +80,8 @@ impl Display for ContentTypeErr {
 
 #[cfg(test)]
 mod test {
-    use hyperx::header::Headers;
-
     use super::ContentType;
+    use crate::message::header::{HeaderName, Headers};
 
     #[test]
     fn format_content_type() {
@@ -100,14 +90,14 @@ mod test {
         headers.set(ContentType::TEXT_PLAIN);
 
         assert_eq!(
-            format!("{}", headers),
+            headers.to_string(),
             "Content-Type: text/plain; charset=utf-8\r\n"
         );
 
         headers.set(ContentType::TEXT_HTML);
 
         assert_eq!(
-            format!("{}", headers),
+            headers.to_string(),
             "Content-Type: text/html; charset=utf-8\r\n"
         );
     }
@@ -116,12 +106,18 @@ mod test {
     fn parse_content_type() {
         let mut headers = Headers::new();
 
-        headers.set_raw("Content-Type", "text/plain; charset=utf-8");
+        headers.set_raw(
+            HeaderName::new_from_ascii_str("Content-Type"),
+            "text/plain; charset=utf-8".to_string(),
+        );
 
-        assert_eq!(headers.get::<ContentType>(), Some(&ContentType::TEXT_PLAIN));
+        assert_eq!(headers.get::<ContentType>(), Some(ContentType::TEXT_PLAIN));
 
-        headers.set_raw("Content-Type", "text/html; charset=utf-8");
+        headers.set_raw(
+            HeaderName::new_from_ascii_str("Content-Type"),
+            "text/html; charset=utf-8".to_string(),
+        );
 
-        assert_eq!(headers.get::<ContentType>(), Some(&ContentType::TEXT_HTML));
+        assert_eq!(headers.get::<ContentType>(), Some(ContentType::TEXT_HTML));
     }
 }
diff --git a/src/message/header/date.rs b/src/message/header/date.rs
index 23480d210..fc5f8e6a2 100644
--- a/src/message/header/date.rs
+++ b/src/message/header/date.rs
@@ -1,10 +1,9 @@
-use std::{fmt::Result as FmtResult, str::from_utf8, time::SystemTime};
+use std::time::SystemTime;
 
 use httpdate::HttpDate;
-use hyperx::{
-    header::{Formatter as HeaderFormatter, Header, RawLike},
-    Error as HeaderError, Result as HyperResult,
-};
+
+use super::{Header, HeaderName};
+use crate::BoxError;
 
 /// Message `Date` header
 ///
@@ -27,36 +26,24 @@ impl Date {
 }
 
 impl Header for Date {
-    fn header_name() -> &'static str {
-        "Date"
+    fn name() -> HeaderName {
+        HeaderName::new_from_ascii_str("Date")
     }
 
-    // FIXME HeaderError->HeaderError, same for result
-    fn parse_header<'a, T>(raw: &'a T) -> HyperResult<Self>
-    where
-        T: RawLike<'a>,
-        Self: Sized,
-    {
-        raw.one()
-            .ok_or(HeaderError::Header)
-            .and_then(|r| from_utf8(r).map_err(|_| HeaderError::Header))
-            .and_then(|s| {
-                let mut s = String::from(s);
-                if s.ends_with(" -0000") {
-                    // The httpdate crate expects the `Date` to end in ` GMT`, but email
-                    // uses `-0000`, so we crudely fix this issue here.
-
-                    s.truncate(s.len() - "-0000".len());
-                    s.push_str("GMT");
-                }
-
-                s.parse::<HttpDate>()
-                    .map(Self)
-                    .map_err(|_| HeaderError::Header)
-            })
+    fn parse(s: &str) -> Result<Self, BoxError> {
+        let mut s = String::from(s);
+        if s.ends_with(" -0000") {
+            // The httpdate crate expects the `Date` to end in ` GMT`, but email
+            // uses `-0000`, so we crudely fix this issue here.
+
+            s.truncate(s.len() - "-0000".len());
+            s.push_str("GMT");
+        }
+
+        Ok(Self(s.parse::<HttpDate>()?))
     }
 
-    fn fmt_header(&self, f: &mut HeaderFormatter<'_, '_>) -> FmtResult {
+    fn display(&self) -> String {
         let mut s = self.0.to_string();
         if s.ends_with(" GMT") {
             // The httpdate crate always appends ` GMT` to the end of the string,
@@ -67,7 +54,7 @@ impl Header for Date {
             s.push_str("-0000");
         }
 
-        f.fmt_line(&s)
+        s
     }
 }
 
@@ -85,10 +72,10 @@ impl From<Date> for SystemTime {
 
 #[cfg(test)]
 mod test {
-    use hyperx::header::Headers;
     use std::time::{Duration, SystemTime};
 
     use super::Date;
+    use crate::message::header::{HeaderName, Headers};
 
     #[test]
     fn format_date() {
@@ -100,8 +87,8 @@ mod test {
         ));
 
         assert_eq!(
-            format!("{}", headers),
-            "Date: Tue, 15 Nov 1994 08:12:31 -0000\r\n"
+            headers.to_string(),
+            "Date: Tue, 15 Nov 1994 08:12:31 -0000\r\n".to_string()
         );
 
         // Tue, 15 Nov 1994 08:12:32 GMT
@@ -110,7 +97,7 @@ mod test {
         ));
 
         assert_eq!(
-            format!("{}", headers),
+            headers.to_string(),
             "Date: Tue, 15 Nov 1994 08:12:32 -0000\r\n"
         );
     }
@@ -119,20 +106,26 @@ mod test {
     fn parse_date() {
         let mut headers = Headers::new();
 
-        headers.set_raw("Date", "Tue, 15 Nov 1994 08:12:31 -0000");
+        headers.set_raw(
+            HeaderName::new_from_ascii_str("Date"),
+            "Tue, 15 Nov 1994 08:12:31 -0000".to_string(),
+        );
 
         assert_eq!(
             headers.get::<Date>(),
-            Some(&Date::from(
+            Some(Date::from(
                 SystemTime::UNIX_EPOCH + Duration::from_secs(784887151),
             ))
         );
 
-        headers.set_raw("Date", "Tue, 15 Nov 1994 08:12:32 -0000");
+        headers.set_raw(
+            HeaderName::new_from_ascii_str("Date"),
+            "Tue, 15 Nov 1994 08:12:32 -0000".to_string(),
+        );
 
         assert_eq!(
             headers.get::<Date>(),
-            Some(&Date::from(
+            Some(Date::from(
                 SystemTime::UNIX_EPOCH + Duration::from_secs(784887152),
             ))
         );
diff --git a/src/message/header/mailbox.rs b/src/message/header/mailbox.rs
index 19a336f9d..bed4170b9 100644
--- a/src/message/header/mailbox.rs
+++ b/src/message/header/mailbox.rs
@@ -1,12 +1,6 @@
-use crate::message::{
-    mailbox::{Mailbox, Mailboxes},
-    utf8_b,
-};
-use hyperx::{
-    header::{Formatter as HeaderFormatter, Header, RawLike},
-    Error as HeaderError, Result as HyperResult,
-};
-use std::{fmt::Result as FmtResult, slice::Iter, str::from_utf8};
+use super::{Header, HeaderName};
+use crate::message::mailbox::{Mailbox, Mailboxes};
+use crate::BoxError;
 
 /// Header which can contains multiple mailboxes
 pub trait MailboxesHeader {
@@ -20,23 +14,17 @@ macro_rules! mailbox_header {
         pub struct $type_name(Mailbox);
 
         impl Header for $type_name {
-            fn header_name() -> &'static str {
-                $header_name
+            fn name() -> HeaderName {
+                HeaderName::new_from_ascii_str($header_name)
             }
 
-            fn parse_header<'a, T>(raw: &'a T) -> HyperResult<Self> where
-    T: RawLike<'a>,
-    Self: Sized {
-                raw.one()
-                    .ok_or(HeaderError::Header)
-                    .and_then(parse_mailboxes)
-                    .and_then(|mbs| {
-                        mbs.into_single().ok_or(HeaderError::Header)
-                    }).map($type_name)
+            fn parse(s: &str) -> Result<Self, BoxError> {
+                let mailbox: Mailbox = s.parse()?;
+                Ok(Self(mailbox))
             }
 
-            fn fmt_header(&self, f: &mut HeaderFormatter<'_, '_>) -> FmtResult {
-                f.fmt_line(&self.0.recode_name(utf8_b::encode))
+            fn display(&self) -> String {
+                self.0.to_string()
             }
         }
 
@@ -69,23 +57,17 @@ macro_rules! mailboxes_header {
         }
 
         impl Header for $type_name {
-            fn header_name() -> &'static str {
-                $header_name
+            fn name() -> HeaderName {
+                HeaderName::new_from_ascii_str($header_name)
             }
 
-            fn parse_header<'a, T>(raw: &'a T) -> HyperResult<$type_name>
-            where
-                T: RawLike<'a>,
-                Self: Sized,
-            {
-                raw.one()
-                    .ok_or(HeaderError::Header)
-                    .and_then(parse_mailboxes)
-                    .map($type_name)
+            fn parse(s: &str) -> Result<Self, BoxError> {
+                let mailbox: Mailboxes = s.parse()?;
+                Ok(Self(mailbox))
             }
 
-            fn fmt_header(&self, f: &mut HeaderFormatter<'_, '_>) -> FmtResult {
-                format_mailboxes(self.0.iter(), f)
+            fn display(&self) -> String {
+                self.0.to_string()
             }
         }
 
@@ -174,26 +156,10 @@ mailboxes_header! {
     (Bcc, "Bcc")
 }
 
-fn parse_mailboxes(raw: &[u8]) -> HyperResult<Mailboxes> {
-    if let Ok(src) = from_utf8(raw) {
-        if let Ok(mbs) = src.parse() {
-            return Ok(mbs);
-        }
-    }
-    Err(HeaderError::Header)
-}
-
-fn format_mailboxes<'a>(mbs: Iter<'a, Mailbox>, f: &mut HeaderFormatter<'_, '_>) -> FmtResult {
-    f.fmt_line(&Mailboxes::from(
-        mbs.map(|mb| mb.recode_name(utf8_b::encode))
-            .collect::<Vec<_>>(),
-    ))
-}
-
 #[cfg(test)]
 mod test {
     use super::{From, Mailbox, Mailboxes};
-    use hyperx::header::Headers;
+    use crate::message::header::{HeaderName, Headers};
 
     #[test]
     fn format_single_without_name() {
@@ -202,7 +168,7 @@ mod test {
         let mut headers = Headers::new();
         headers.set(From(from));
 
-        assert_eq!(format!("{}", headers), "From: kayo@example.com\r\n");
+        assert_eq!(headers.to_string(), "From: kayo@example.com\r\n");
     }
 
     #[test]
@@ -212,7 +178,7 @@ mod test {
         let mut headers = Headers::new();
         headers.set(From(from));
 
-        assert_eq!(format!("{}", headers), "From: K. <kayo@example.com>\r\n");
+        assert_eq!(headers.to_string(), "From: K. <kayo@example.com>\r\n");
     }
 
     #[test]
@@ -225,7 +191,7 @@ mod test {
         headers.set(From(from));
 
         assert_eq!(
-            format!("{}", headers),
+            headers.to_string(),
             "From: kayo@example.com, pony@domain.tld\r\n"
         );
     }
@@ -241,7 +207,7 @@ mod test {
         headers.set(From(from.into()));
 
         assert_eq!(
-            format!("{}", headers),
+            headers.to_string(),
             "From: K. <kayo@example.com>, Pony P. <pony@domain.tld>\r\n"
         );
     }
@@ -254,7 +220,7 @@ mod test {
         headers.set(From(from.into()));
 
         assert_eq!(
-            format!("{}", headers),
+            headers.to_string(),
             "From: =?utf-8?b?0JrQsNC50L4=?= <kayo@example.com>\r\n"
         );
     }
@@ -264,9 +230,12 @@ mod test {
         let from = vec!["kayo@example.com".parse().unwrap()].into();
 
         let mut headers = Headers::new();
-        headers.set_raw("From", "kayo@example.com");
+        headers.set_raw(
+            HeaderName::new_from_ascii_str("From"),
+            "kayo@example.com".to_string(),
+        );
 
-        assert_eq!(headers.get::<From>(), Some(&From(from)));
+        assert_eq!(headers.get::<From>(), Some(From(from)));
     }
 
     #[test]
@@ -274,9 +243,12 @@ mod test {
         let from = vec!["K. <kayo@example.com>".parse().unwrap()].into();
 
         let mut headers = Headers::new();
-        headers.set_raw("From", "K. <kayo@example.com>");
+        headers.set_raw(
+            HeaderName::new_from_ascii_str("From"),
+            "K. <kayo@example.com>".to_string(),
+        );
 
-        assert_eq!(headers.get::<From>(), Some(&From(from)));
+        assert_eq!(headers.get::<From>(), Some(From(from)));
     }
 
     #[test]
@@ -287,9 +259,12 @@ mod test {
         ];
 
         let mut headers = Headers::new();
-        headers.set_raw("From", "kayo@example.com, pony@domain.tld");
+        headers.set_raw(
+            HeaderName::new_from_ascii_str("From"),
+            "kayo@example.com, pony@domain.tld".to_string(),
+        );
 
-        assert_eq!(headers.get::<From>(), Some(&From(from.into())));
+        assert_eq!(headers.get::<From>(), Some(From(from.into())));
     }
 
     #[test]
@@ -300,18 +275,11 @@ mod test {
         ];
 
         let mut headers = Headers::new();
-        headers.set_raw("From", "K. <kayo@example.com>, Pony P. <pony@domain.tld>");
-
-        assert_eq!(headers.get::<From>(), Some(&From(from.into())));
-    }
-
-    #[test]
-    fn parse_single_with_utf8_name() {
-        let from: Vec<Mailbox> = vec!["Кайо <kayo@example.com>".parse().unwrap()];
-
-        let mut headers = Headers::new();
-        headers.set_raw("From", "=?utf-8?b?0JrQsNC50L4=?= <kayo@example.com>");
+        headers.set_raw(
+            HeaderName::new_from_ascii_str("From"),
+            "K. <kayo@example.com>, Pony P. <pony@domain.tld>".to_string(),
+        );
 
-        assert_eq!(headers.get::<From>(), Some(&From(from.into())));
+        assert_eq!(headers.get::<From>(), Some(From(from.into())));
     }
 }
diff --git a/src/message/header/mod.rs b/src/message/header/mod.rs
index e0fe90744..79fa584ca 100644
--- a/src/message/header/mod.rs
+++ b/src/message/header/mod.rs
@@ -1,11 +1,16 @@
 //! Headers widely used in email messages
 
-pub use hyperx::header::{Charset, Header, Headers};
+use std::{
+    borrow::Cow,
+    fmt::{self, Display, Formatter},
+    ops::Deref,
+};
 
 pub use self::content_disposition::ContentDisposition;
 pub use self::content_type::{ContentType, ContentTypeErr};
 pub use self::date::Date;
 pub use self::{content::*, mailbox::*, special::*, textual::*};
+use crate::BoxError;
 
 mod content;
 mod content_disposition;
@@ -14,3 +19,718 @@ mod date;
 mod mailbox;
 mod special;
 mod textual;
+
+pub trait Header: Clone {
+    fn name() -> HeaderName;
+
+    fn parse(s: &str) -> Result<Self, BoxError>;
+
+    fn display(&self) -> String;
+}
+
+#[derive(Debug, Clone, Default)]
+pub struct Headers {
+    headers: Vec<(HeaderName, String)>,
+}
+
+impl Headers {
+    #[inline]
+    pub const fn new() -> Self {
+        Self {
+            headers: Vec::new(),
+        }
+    }
+
+    #[inline]
+    pub fn with_capacity(capacity: usize) -> Self {
+        Self {
+            headers: Vec::with_capacity(capacity),
+        }
+    }
+
+    pub fn get<H: Header>(&self) -> Option<H> {
+        self.get_raw(&H::name()).and_then(|raw| H::parse(raw).ok())
+    }
+
+    pub fn set<H: Header>(&mut self, header: H) {
+        self.set_raw(H::name(), header.display());
+    }
+
+    pub fn remove<H: Header>(&mut self) -> Option<H> {
+        self.remove_raw(&H::name())
+            .and_then(|(_name, raw)| H::parse(&raw).ok())
+    }
+
+    #[inline]
+    pub fn clear(&mut self) {
+        self.headers.clear();
+    }
+
+    pub fn get_raw(&self, name: &str) -> Option<&str> {
+        self.find_header(name).map(|(_name, value)| value)
+    }
+
+    pub fn insert_raw(&mut self, name: HeaderName, value: String) {
+        match self.find_header_mut(&name) {
+            Some((_name, prev_value)) => {
+                prev_value.push_str(", ");
+                prev_value.push_str(&value);
+            }
+            None => self.headers.push((name, value)),
+        }
+    }
+
+    pub fn set_raw(&mut self, name: HeaderName, value: String) {
+        match self.find_header_mut(&name) {
+            Some((_, current_value)) => {
+                *current_value = value;
+            }
+            None => {
+                self.headers.push((name, value));
+            }
+        }
+    }
+
+    pub fn remove_raw(&mut self, name: &str) -> Option<(HeaderName, String)> {
+        self.find_header_index(name).map(|i| self.headers.remove(i))
+    }
+
+    fn find_header(&self, name: &str) -> Option<(&HeaderName, &str)> {
+        self.headers
+            .iter()
+            .find(|&(name_, _value)| name.eq_ignore_ascii_case(name_))
+            .map(|t| (&t.0, t.1.as_str()))
+    }
+
+    fn find_header_mut(&mut self, name: &str) -> Option<(&HeaderName, &mut String)> {
+        self.headers
+            .iter_mut()
+            .find(|(name_, _value)| name.eq_ignore_ascii_case(name_))
+            .map(|t| (&t.0, &mut t.1))
+    }
+
+    fn find_header_index(&self, name: &str) -> Option<usize> {
+        self.headers
+            .iter()
+            .enumerate()
+            .find(|&(_i, (name_, _value))| name.eq_ignore_ascii_case(name_))
+            .map(|(i, _)| i)
+    }
+}
+
+impl Display for Headers {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        for (name, value) in &self.headers {
+            Display::fmt(name, f)?;
+            f.write_str(": ")?;
+            HeaderValueEncoder::encode(&name, &value, f)?;
+            f.write_str("\r\n")?;
+        }
+
+        Ok(())
+    }
+}
+
+#[derive(Debug, Clone)]
+pub struct HeaderName(Cow<'static, str>);
+
+impl HeaderName {
+    pub fn new_from_ascii(ascii: String) -> Option<Self> {
+        if !ascii.is_empty()
+            && ascii.len() <= 76
+            && ascii.is_ascii()
+            && !ascii.contains(|c| c == ':' || c == ' ')
+        {
+            Some(Self(Cow::Owned(ascii)))
+        } else {
+            None
+        }
+    }
+
+    pub const fn new_from_ascii_str(ascii: &'static str) -> Self {
+        macro_rules! static_assert {
+            ($condition:expr) => {
+                let _ = [()][(!($condition)) as usize];
+            };
+        }
+
+        static_assert!(!ascii.is_empty());
+        static_assert!(ascii.len() <= 76);
+
+        let bytes = ascii.as_bytes();
+        let mut i = 0;
+        while i < bytes.len() {
+            static_assert!(bytes[i].is_ascii());
+            static_assert!(bytes[i] != b' ');
+            static_assert!(bytes[i] != b':');
+
+            i += 1;
+        }
+
+        Self(Cow::Borrowed(ascii))
+    }
+}
+
+impl Display for HeaderName {
+    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
+        f.write_str(&self)
+    }
+}
+
+impl Deref for HeaderName {
+    type Target = str;
+
+    #[inline]
+    fn deref(&self) -> &Self::Target {
+        &self.0
+    }
+}
+
+impl AsRef<[u8]> for HeaderName {
+    #[inline]
+    fn as_ref(&self) -> &[u8] {
+        let s: &str = self.as_ref();
+        s.as_bytes()
+    }
+}
+
+impl AsRef<str> for HeaderName {
+    #[inline]
+    fn as_ref(&self) -> &str {
+        &self.0
+    }
+}
+
+impl PartialEq<HeaderName> for HeaderName {
+    fn eq(&self, other: &HeaderName) -> bool {
+        let s1: &str = self.as_ref();
+        let s2: &str = other.as_ref();
+        s1 == s2
+    }
+}
+
+impl PartialEq<&str> for HeaderName {
+    fn eq(&self, other: &&str) -> bool {
+        let s: &str = self.as_ref();
+        s == *other
+    }
+}
+
+impl PartialEq<HeaderName> for &str {
+    fn eq(&self, other: &HeaderName) -> bool {
+        let s: &str = other.as_ref();
+        *self == s
+    }
+}
+
+const ENCODING_START_PREFIX: &str = "=?utf-8?b?";
+const ENCODING_END_SUFFIX: &str = "?=";
+const MAX_LINE_LEN: usize = 76;
+
+/// [RFC 1522](https://tools.ietf.org/html/rfc1522) header value encoder
+struct HeaderValueEncoder {
+    line_len: usize,
+    encode_buf: String,
+}
+
+impl HeaderValueEncoder {
+    fn encode(name: &str, value: &str, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        let (words_iter, encoder) = Self::new(name, value);
+        encoder.format(words_iter, f)
+    }
+
+    fn new<'a>(name: &str, value: &'a str) -> (WordsPlusFillIterator<'a>, Self) {
+        (
+            WordsPlusFillIterator { s: value },
+            Self {
+                line_len: name.len() + ": ".len(),
+                encode_buf: String::new(),
+            },
+        )
+    }
+
+    fn format(
+        mut self,
+        words_iter: WordsPlusFillIterator<'_>,
+        f: &mut fmt::Formatter<'_>,
+    ) -> fmt::Result {
+        /// Estimate if an encoded string of `len` would fix in an empty line
+        fn would_fit_new_line(len: usize) -> bool {
+            len < (MAX_LINE_LEN - " ".len())
+        }
+
+        /// Estimate how long a string of `len` would be after base64 encoding plus
+        /// adding the encoding prefix and suffix to it
+        fn base64_len(len: usize) -> usize {
+            ENCODING_START_PREFIX.len() + (len * 4 / 3 + 4) + ENCODING_END_SUFFIX.len()
+        }
+
+        /// Estimate how many more bytes we can fit in the current line
+        fn available_len_to_max_encode_len(len: usize) -> usize {
+            len.saturating_sub(
+                ENCODING_START_PREFIX.len() + (len * 3 / 4 + 4) + ENCODING_END_SUFFIX.len(),
+            )
+        }
+
+        for next_word in words_iter {
+            let allowed = allowed_str(next_word);
+
+            if allowed {
+                // This word only contains allowed characters
+
+                // the next word is allowed, but we may have accumulated some words to encode
+                self.flush_encode_buf(f, true)?;
+
+                if next_word.len() > self.remaining_line_len() {
+                    // not enough space left on this line to encode word
+
+                    if self.something_written_to_this_line() && would_fit_new_line(next_word.len())
+                    {
+                        // word doesn't fit this line, but something had already been written to it,
+                        // and word would fit the next line, so go to a new line
+                        // so go to new line
+                        self.new_line(f)?;
+                    } else {
+                        // word neither fits this line and the next one, cut it
+                        // in the middle and make it fit
+
+                        let mut next_word = next_word;
+
+                        while !next_word.is_empty() {
+                            if self.remaining_line_len() == 0 {
+                                self.new_line(f)?;
+                            }
+
+                            let len = self.remaining_line_len().min(next_word.len());
+                            let first_part = &next_word[..len];
+                            next_word = &next_word[len..];
+
+                            f.write_str(first_part)?;
+                            self.line_len += first_part.len();
+                        }
+
+                        continue;
+                    }
+                }
+
+                // word fits, write it!
+                f.write_str(next_word)?;
+                self.line_len += next_word.len();
+            } else {
+                // This word contains unallowed characters
+
+                if self.remaining_line_len() >= base64_len(self.encode_buf.len() + next_word.len())
+                {
+                    // next_word fits
+                    self.encode_buf.push_str(next_word);
+                    continue;
+                }
+
+                // next_word doesn't fit this line
+
+                if would_fit_new_line(base64_len(next_word.len())) {
+                    // ...but it would fit the next one
+
+                    self.flush_encode_buf(f, false)?;
+                    self.new_line(f)?;
+
+                    self.encode_buf.push_str(next_word);
+                    continue;
+                }
+
+                // ...and also wouldn't fit the next one.
+                // chop it up into pieces
+
+                let mut next_word = next_word;
+
+                while !next_word.is_empty() {
+                    if self.remaining_line_len() <= base64_len(1) {
+                        self.flush_encode_buf(f, false)?;
+                        self.new_line(f)?;
+                    }
+
+                    let mut len = available_len_to_max_encode_len(self.remaining_line_len())
+                        .min(next_word.len());
+                    // avoid slicing on a char boundary
+                    while !next_word.is_char_boundary(len) {
+                        len += 1;
+                    }
+                    let first_part = &next_word[..len];
+                    next_word = &next_word[len..];
+
+                    self.encode_buf.push_str(first_part);
+                }
+            }
+        }
+
+        self.flush_encode_buf(f, false)?;
+
+        Ok(())
+    }
+
+    /// Returns the number of bytes left for the current line
+    fn remaining_line_len(&self) -> usize {
+        MAX_LINE_LEN - self.line_len
+    }
+
+    /// Returns true if something has been written to the current line
+    fn something_written_to_this_line(&self) -> bool {
+        self.line_len > 1
+    }
+
+    fn flush_encode_buf(
+        &mut self,
+        f: &mut fmt::Formatter<'_>,
+        switching_to_allowed: bool,
+    ) -> fmt::Result {
+        use std::fmt::Write;
+
+        if self.encode_buf.is_empty() {
+            // nothing to encode
+            return Ok(());
+        }
+
+        let mut write_after = None;
+
+        if switching_to_allowed {
+            // If the next word only contains allowed characters, and the string to encode
+            // ends with a space, take the space out of the part to encode
+
+            let last_char = self.encode_buf.pop().expect("self.encode_buf isn't empty");
+            if is_space_like(last_char) {
+                write_after = Some(last_char);
+            } else {
+                self.encode_buf.push(last_char);
+            }
+        }
+
+        f.write_str(ENCODING_START_PREFIX)?;
+        let encoded = base64::display::Base64Display::with_config(
+            self.encode_buf.as_bytes(),
+            base64::STANDARD,
+        );
+        Display::fmt(&encoded, f)?;
+        f.write_str(ENCODING_END_SUFFIX)?;
+
+        self.line_len += ENCODING_START_PREFIX.len();
+        self.line_len += self.encode_buf.len() * 4 / 3 + 4;
+        self.line_len += ENCODING_END_SUFFIX.len();
+
+        if let Some(write_after) = write_after {
+            f.write_char(write_after)?;
+            self.line_len += 1;
+        }
+
+        self.encode_buf.clear();
+        Ok(())
+    }
+
+    fn new_line(&mut self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        f.write_str("\r\n ")?;
+        self.line_len = 1;
+
+        Ok(())
+    }
+}
+
+/// Iterator yielding a string split space by space, but including all space
+/// characters between it and the next word
+struct WordsPlusFillIterator<'a> {
+    s: &'a str,
+}
+
+impl<'a> Iterator for WordsPlusFillIterator<'a> {
+    type Item = &'a str;
+
+    fn next(&mut self) -> Option<Self::Item> {
+        if self.s.is_empty() {
+            return None;
+        }
+
+        let next_word = self
+            .s
+            .char_indices()
+            .skip(1)
+            .skip_while(|&(_i, c)| !is_space_like(c))
+            .find(|&(_i, c)| !is_space_like(c))
+            .map(|(i, _)| i);
+
+        let word = &self.s[..next_word.unwrap_or_else(|| self.s.len())];
+        self.s = &self.s[word.len()..];
+        Some(word)
+    }
+}
+
+const fn is_space_like(c: char) -> bool {
+    c == ',' || c == ' '
+}
+
+fn allowed_str(s: &str) -> bool {
+    s.chars().all(allowed_char)
+}
+
+const fn allowed_char(c: char) -> bool {
+    c >= 1 as char && c <= 9 as char
+        || c == 11 as char
+        || c == 12 as char
+        || c >= 14 as char && c <= 127 as char
+}
+
+#[cfg(test)]
+mod tests {
+    use super::{HeaderName, Headers};
+
+    #[test]
+    fn valid_headername() {
+        assert!(HeaderName::new_from_ascii(String::from("From")).is_some());
+    }
+
+    #[test]
+    fn non_ascii_headername() {
+        assert!(HeaderName::new_from_ascii(String::from("🌎")).is_none());
+    }
+
+    #[test]
+    fn spaces_in_headername() {
+        assert!(HeaderName::new_from_ascii(String::from("From ")).is_none());
+    }
+
+    #[test]
+    fn colons_in_headername() {
+        assert!(HeaderName::new_from_ascii(String::from("From:")).is_none());
+    }
+
+    #[test]
+    fn empty_headername() {
+        assert!(HeaderName::new_from_ascii(String::from("")).is_none());
+    }
+
+    #[test]
+    fn const_valid_headername() {
+        let _ = HeaderName::new_from_ascii_str("From");
+    }
+
+    #[test]
+    #[should_panic]
+    fn const_non_ascii_headername() {
+        let _ = HeaderName::new_from_ascii_str("🌎");
+    }
+
+    #[test]
+    #[should_panic]
+    fn const_spaces_in_headername() {
+        let _ = HeaderName::new_from_ascii_str("From ");
+    }
+
+    #[test]
+    #[should_panic]
+    fn const_colons_in_headername() {
+        let _ = HeaderName::new_from_ascii_str("From:");
+    }
+
+    #[test]
+    #[should_panic]
+    fn const_empty_headername() {
+        let _ = HeaderName::new_from_ascii_str("");
+    }
+
+    // names taken randomly from https://it.wikipedia.org/wiki/Pinco_Pallino
+
+    #[test]
+    fn format_ascii() {
+        let mut headers = Headers::new();
+        headers.insert_raw(
+            HeaderName::new_from_ascii_str("To"),
+            "John Doe <example@example.com>, Jean Dupont <jean@example.com>".to_string(),
+        );
+
+        assert_eq!(
+            headers.to_string(),
+            "To: John Doe <example@example.com>, Jean Dupont <jean@example.com>\r\n"
+        );
+    }
+
+    #[test]
+    fn format_ascii_with_folding() {
+        let mut headers = Headers::new();
+        headers.insert_raw(
+            HeaderName::new_from_ascii_str("To"),
+            "Ascii <example@example.com>, John Doe <johndoe@example.com, John Smith <johnsmith@example.com>, Pinco Pallino <pincopallino@example.com>, Jemand <jemand@example.com>, Jean Dupont <jean@example.com>".to_string(),
+        );
+
+        assert_eq!(
+            headers.to_string(),
+            concat!(
+                "To: Ascii <example@example.com>, John Doe <johndoe@example.com, John Smith \r\n",
+                " <johnsmith@example.com>, Pinco Pallino <pincopallino@example.com>, Jemand \r\n",
+                " <jemand@example.com>, Jean Dupont <jean@example.com>\r\n"
+            )
+        );
+    }
+
+    #[test]
+    fn format_ascii_with_folding_long_line() {
+        let mut headers = Headers::new();
+        headers.insert_raw(
+            HeaderName::new_from_ascii_str("Subject"),
+            "Hello! This is lettre, and this IsAVeryLongLineDoYouKnowWhatsGoingToHappenIGuessWeAreGoingToFindOut. Ok I guess that's it!".to_string()
+        );
+
+        assert_eq!(
+            headers.to_string(),
+            concat!(
+                "Subject: Hello! This is lettre, and this \r\n ",
+                "IsAVeryLongLineDoYouKnowWhatsGoingToHappenIGuessWeAreGoingToFindOut. Ok I \r\n",
+                " guess that's it!\r\n"
+            )
+        );
+    }
+
+    #[test]
+    fn format_ascii_with_folding_very_long_line() {
+        let mut headers = Headers::new();
+        headers.insert_raw(
+            HeaderName::new_from_ascii_str("Subject"),
+            "Hello! IGuessTheLastLineWasntLongEnoughSoLetsTryAgainShallWeWhatDoYouThinkItsGoingToHappenIGuessWereAboutToFindOut! I don't know".to_string()
+        );
+
+        assert_eq!(
+            headers.to_string(),
+            concat!(
+                "Subject: Hello! IGuessTheLastLineWasntLongEnoughSoLetsTryAgainShallWeWhatDoY\r\n",
+                " ouThinkItsGoingToHappenIGuessWereAboutToFindOut! I don't know\r\n",
+            )
+        );
+    }
+
+    #[test]
+    fn format_ascii_with_folding_giant_word() {
+        let mut headers = Headers::new();
+        headers.insert_raw(
+            HeaderName::new_from_ascii_str("Subject"),
+            "1abcdefghijklmnopqrstuvwxyz2abcdefghijklmnopqrstuvwxyz3abcdefghijklmnopqrstuvwxyz4abcdefghijklmnopqrstuvwxyz5abcdefghijklmnopqrstuvwxyz6abcdefghijklmnopqrstuvwxyz".to_string()
+        );
+
+        assert_eq!(
+            headers.to_string(),
+            concat!(
+                "Subject: 1abcdefghijklmnopqrstuvwxyz2abcdefghijklmnopqrstuvwxyz3abcdefghijkl\r\n",
+                " mnopqrstuvwxyz4abcdefghijklmnopqrstuvwxyz5abcdefghijklmnopqrstuvwxyz6abcdef\r\n",
+                " ghijklmnopqrstuvwxyz\r\n",
+            )
+        );
+    }
+
+    #[test]
+    fn format_special() {
+        let mut headers = Headers::new();
+        headers.insert_raw(
+            HeaderName::new_from_ascii_str("To"),
+            "Seán <sean@example.com>".to_string(),
+        );
+
+        assert_eq!(
+            headers.to_string(),
+            "To: =?utf-8?b?U2XDoW4=?= <sean@example.com>\r\n"
+        );
+    }
+
+    #[test]
+    fn format_special_emoji() {
+        let mut headers = Headers::new();
+        headers.insert_raw(
+            HeaderName::new_from_ascii_str("To"),
+            "🌎 <world@example.com>".to_string(),
+        );
+
+        assert_eq!(
+            headers.to_string(),
+            "To: =?utf-8?b?8J+Mjg==?= <world@example.com>\r\n"
+        );
+    }
+
+    #[test]
+    fn format_special_with_folding() {
+        let mut headers = Headers::new();
+        headers.insert_raw(
+            HeaderName::new_from_ascii_str("To"),
+            "🌍 <world@example.com>, 🦆 Everywhere <ducks@example.com>, Иванов Иван Иванович <ivanov@example.com>, Jānis Bērziņš <janis@example.com>, Seán Ó Rudaí <sean@example.com>".to_string(),
+        );
+
+        assert_eq!(
+            headers.to_string(),
+            concat!(
+                "To: =?utf-8?b?8J+MjQ==?= <world@example.com>, =?utf-8?b?8J+mhg==?= \r\n",
+                " Everywhere <ducks@example.com>, =?utf-8?b?0JjQstCw0L3QvtCyIA==?=\r\n",
+                " =?utf-8?b?0JjQstCw0L0g0JjQstCw0L3QvtCy0LjRhw==?= <ivanov@example.com>, \r\n",
+                " =?utf-8?b?SsSBbmlzIELEk3J6acWGxaE=?= <janis@example.com>, \r\n",
+                " =?utf-8?b?U2XDoW4gw5MgUnVkYcOt?= <sean@example.com>\r\n"
+            )
+        );
+    }
+
+    #[test]
+    fn format_slice_on_char_boundary_bug() {
+        let mut headers = Headers::new();
+        headers.insert_raw(
+            HeaderName::new_from_ascii_str("Subject"),
+            "🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳".to_string(),
+        );
+
+        assert_eq!(
+            headers.to_string(),
+            "Subject: =?utf-8?b?8J+ls/CfpbPwn6Wz8J+ls/CfpbPwn6Wz8J+ls/CfpbPwn6Wz8J+ls/CfpbPwn6Wz8J+ls/CfpbPwn6Wz8J+ls/CfpbPwn6Wz8J+ls/CfpbPwn6Wz8J+ls/CfpbPwn6Wz8J+ls/CfpbPwn6Wz8J+ls/CfpbPwn6Wz8J+ls/CfpbPwn6Wz8J+ls/CfpbPwn6Wz8J+ls/CfpbPwn6Wz8J+ls/CfpbPwn6Wz8J+ls/CfpbPwn6Wz8J+ls/CfpbPwn6Wz8J+ls/CfpbPwn6Wz8J+ls/CfpbPwn6Wz8J+ls/CfpbPwn6Wz8J+ls/CfpbPwn6Wz?=\r\n"
+        );
+    }
+
+    #[test]
+    fn format_bad_stuff() {
+        let mut headers = Headers::new();
+        headers.insert_raw(
+            HeaderName::new_from_ascii_str("Subject"),
+            "Hello! \r\n This is \" bad \0. 👋".to_string(),
+        );
+
+        assert_eq!(
+            headers.to_string(),
+            "Subject: Hello! =?utf-8?b?DQo=?= This is \" bad =?utf-8?b?AC4g8J+Riw==?=\r\n"
+        );
+    }
+
+    #[test]
+    fn format_everything() {
+        let mut headers = Headers::new();
+        headers.insert_raw(
+            HeaderName::new_from_ascii_str("Subject"),
+            "Hello! This is lettre, and this IsAVeryLongLineDoYouKnowWhatsGoingToHappenIGuessWeAreGoingToFindOut. Ok I guess that's it!".to_string()
+        );
+        headers.insert_raw(
+            HeaderName::new_from_ascii_str("To"),
+            "🌍 <world@example.com>, 🦆 Everywhere <ducks@example.com>, Иванов Иван Иванович <ivanov@example.com>, Jānis Bērziņš <janis@example.com>, Seán Ó Rudaí <sean@example.com>".to_string(),
+        );
+        headers.insert_raw(
+            HeaderName::new_from_ascii_str("From"),
+            "Someone <somewhere@example.com>".to_string(),
+        );
+        headers.insert_raw(
+            HeaderName::new_from_ascii_str("Content-Transfer-Encoding"),
+            "quoted-printable".to_string(),
+        );
+
+        assert_eq!(
+            headers.to_string(),
+            concat!(
+                "Subject: Hello! This is lettre, and this \r\n",
+                " IsAVeryLongLineDoYouKnowWhatsGoingToHappenIGuessWeAreGoingToFindOut. Ok I \r\n",
+                " guess that's it!\r\n",
+                "To: =?utf-8?b?8J+MjQ==?= <world@example.com>, =?utf-8?b?8J+mhg==?= \r\n",
+                " Everywhere <ducks@example.com>, =?utf-8?b?0JjQstCw0L3QvtCyIA==?=\r\n",
+                " =?utf-8?b?0JjQstCw0L0g0JjQstCw0L3QvtCy0LjRhw==?= <ivanov@example.com>, \r\n",
+                " =?utf-8?b?SsSBbmlzIELEk3J6acWGxaE=?= <janis@example.com>, \r\n",
+                " =?utf-8?b?U2XDoW4gw5MgUnVkYcOt?= <sean@example.com>\r\n",
+                "From: Someone <somewhere@example.com>\r\n",
+                "Content-Transfer-Encoding: quoted-printable\r\n",
+            )
+        );
+    }
+}
diff --git a/src/message/header/special.rs b/src/message/header/special.rs
index 4cbba7444..c5c879d58 100644
--- a/src/message/header/special.rs
+++ b/src/message/header/special.rs
@@ -1,11 +1,8 @@
-use hyperx::{
-    header::{Formatter as HeaderFormatter, Header, RawLike},
-    Error as HeaderError, Result as HyperResult,
-};
-use std::{fmt::Result as FmtResult, str::from_utf8};
+use crate::message::header::{Header, HeaderName};
+use crate::BoxError;
 
-#[derive(Debug, Copy, Clone, PartialEq)]
 /// Message format version, defined in [RFC2045](https://tools.ietf.org/html/rfc2045#section-4)
+#[derive(Debug, Copy, Clone, PartialEq)]
 pub struct MimeVersion {
     major: u8,
     minor: u8,
@@ -29,42 +26,40 @@ impl MimeVersion {
     }
 }
 
-impl Default for MimeVersion {
-    fn default() -> Self {
-        MIME_VERSION_1_0
+impl Header for MimeVersion {
+    fn name() -> HeaderName {
+        HeaderName::new_from_ascii_str("MIME-Version")
     }
-}
 
-impl Header for MimeVersion {
-    fn header_name() -> &'static str {
-        "MIME-Version"
+    fn parse(s: &str) -> Result<Self, BoxError> {
+        let mut s = s.split('.');
+
+        let major = s
+            .next()
+            .expect("The first call to next for a Split<char> always succeeds");
+        let minor = s
+            .next()
+            .ok_or_else(|| String::from("MIME-Version header doesn't contain '.'"))?;
+        let major = major.parse()?;
+        let minor = minor.parse()?;
+        Ok(MimeVersion::new(major, minor))
     }
 
-    fn parse_header<'a, T>(raw: &'a T) -> HyperResult<Self>
-    where
-        T: RawLike<'a>,
-        Self: Sized,
-    {
-        raw.one().ok_or(HeaderError::Header).and_then(|r| {
-            let mut s = from_utf8(r).map_err(|_| HeaderError::Header)?.split('.');
-
-            let major = s.next().ok_or(HeaderError::Header)?;
-            let minor = s.next().ok_or(HeaderError::Header)?;
-            let major = major.parse().map_err(|_| HeaderError::Header)?;
-            let minor = minor.parse().map_err(|_| HeaderError::Header)?;
-            Ok(MimeVersion::new(major, minor))
-        })
+    fn display(&self) -> String {
+        format!("{}.{}", self.major, self.minor)
     }
+}
 
-    fn fmt_header(&self, f: &mut HeaderFormatter<'_, '_>) -> FmtResult {
-        f.fmt_line(&format!("{}.{}", self.major, self.minor))
+impl Default for MimeVersion {
+    fn default() -> Self {
+        MIME_VERSION_1_0
     }
 }
 
 #[cfg(test)]
 mod test {
     use super::{MimeVersion, MIME_VERSION_1_0};
-    use hyperx::header::Headers;
+    use crate::message::header::{HeaderName, Headers};
 
     #[test]
     fn format_mime_version() {
@@ -72,23 +67,29 @@ mod test {
 
         headers.set(MIME_VERSION_1_0);
 
-        assert_eq!(format!("{}", headers), "MIME-Version: 1.0\r\n");
+        assert_eq!(headers.to_string(), "MIME-Version: 1.0\r\n");
 
         headers.set(MimeVersion::new(0, 1));
 
-        assert_eq!(format!("{}", headers), "MIME-Version: 0.1\r\n");
+        assert_eq!(headers.to_string(), "MIME-Version: 0.1\r\n");
     }
 
     #[test]
     fn parse_mime_version() {
         let mut headers = Headers::new();
 
-        headers.set_raw("MIME-Version", "1.0");
+        headers.set_raw(
+            HeaderName::new_from_ascii_str("MIME-Version"),
+            "1.0".to_string(),
+        );
 
-        assert_eq!(headers.get::<MimeVersion>(), Some(&MIME_VERSION_1_0));
+        assert_eq!(headers.get::<MimeVersion>(), Some(MIME_VERSION_1_0));
 
-        headers.set_raw("MIME-Version", "0.1");
+        headers.set_raw(
+            HeaderName::new_from_ascii_str("MIME-Version"),
+            "0.1".to_string(),
+        );
 
-        assert_eq!(headers.get::<MimeVersion>(), Some(&MimeVersion::new(0, 1)));
+        assert_eq!(headers.get::<MimeVersion>(), Some(MimeVersion::new(0, 1)));
     }
 }
diff --git a/src/message/header/textual.rs b/src/message/header/textual.rs
index 1c4a4c261..be7b660d2 100644
--- a/src/message/header/textual.rs
+++ b/src/message/header/textual.rs
@@ -1,34 +1,23 @@
-use crate::message::utf8_b;
-use hyperx::{
-    header::{Formatter as HeaderFormatter, Header, RawLike},
-    Error as HeaderError, Result as HyperResult,
-};
-use std::{fmt::Result as FmtResult, str::from_utf8};
+use super::{Header, HeaderName};
+use crate::BoxError;
 
 macro_rules! text_header {
     ($(#[$attr:meta])* Header($type_name: ident, $header_name: expr )) => {
-        #[derive(Debug, Clone, PartialEq)]
         $(#[$attr])*
+        #[derive(Debug, Clone, PartialEq)]
         pub struct $type_name(String);
 
         impl Header for $type_name {
-            fn header_name() -> &'static str {
-                $header_name
+            fn name() -> HeaderName {
+                HeaderName::new_from_ascii_str($header_name)
             }
 
-            fn parse_header<'a, T>(raw: &'a T) -> HyperResult<$type_name>
-            where
-                T: RawLike<'a>,
-                Self: Sized,
-            {
-                raw.one()
-                    .ok_or(HeaderError::Header)
-                    .and_then(parse_text)
-                    .map($type_name)
+            fn parse(s: &str) -> Result<Self, BoxError> {
+                Ok(Self(s.into()))
             }
 
-            fn fmt_header(&self, f: &mut HeaderFormatter<'_, '_>) -> FmtResult {
-                fmt_text(&self.0, f)
+            fn display(&self) -> String {
+                self.0.clone()
             }
         }
 
@@ -94,30 +83,17 @@ text_header! {
     Header(ContentLocation, "Content-Location")
 }
 
-fn parse_text(raw: &[u8]) -> HyperResult<String> {
-    if let Ok(src) = from_utf8(raw) {
-        if let Some(txt) = utf8_b::decode(src) {
-            return Ok(txt);
-        }
-    }
-    Err(HeaderError::Header)
-}
-
-fn fmt_text(s: &str, f: &mut HeaderFormatter<'_, '_>) -> FmtResult {
-    f.fmt_line(&utf8_b::encode(s))
-}
-
 #[cfg(test)]
 mod test {
     use super::Subject;
-    use hyperx::header::Headers;
+    use crate::message::header::{HeaderName, Headers};
 
     #[test]
     fn format_ascii() {
         let mut headers = Headers::new();
         headers.set(Subject("Sample subject".into()));
 
-        assert_eq!(format!("{}", headers), "Subject: Sample subject\r\n");
+        assert_eq!(headers.to_string(), "Subject: Sample subject\r\n");
     }
 
     #[test]
@@ -126,33 +102,22 @@ mod test {
         headers.set(Subject("Тема сообщения".into()));
 
         assert_eq!(
-            format!("{}", headers),
+            headers.to_string(),
             "Subject: =?utf-8?b?0KLQtdC80LAg0YHQvtC+0LHRidC10L3QuNGP?=\r\n"
         );
     }
 
     #[test]
     fn parse_ascii() {
-        let mut headers = Headers::new();
-        headers.set_raw("Subject", "Sample subject");
-
-        assert_eq!(
-            headers.get::<Subject>(),
-            Some(&Subject("Sample subject".into()))
-        );
-    }
-
-    #[test]
-    fn parse_utf8() {
         let mut headers = Headers::new();
         headers.set_raw(
-            "Subject",
-            "=?utf-8?b?0KLQtdC80LAg0YHQvtC+0LHRidC10L3QuNGP?=",
+            HeaderName::new_from_ascii_str("Subject"),
+            "Sample subject".to_string(),
         );
 
         assert_eq!(
             headers.get::<Subject>(),
-            Some(&Subject("Тема сообщения".into()))
+            Some(Subject("Sample subject".into()))
         );
     }
 }
diff --git a/src/message/mailbox/types.rs b/src/message/mailbox/types.rs
index e1a95eec2..a08d61092 100644
--- a/src/message/mailbox/types.rs
+++ b/src/message/mailbox/types.rs
@@ -1,7 +1,4 @@
-use crate::{
-    address::{Address, AddressError},
-    message::utf8_b,
-};
+use crate::address::{Address, AddressError};
 use std::{
     convert::TryFrom,
     fmt::{Display, Formatter, Result as FmtResult, Write},
@@ -66,14 +63,6 @@ impl Mailbox {
     pub fn new(name: Option<String>, email: Address) -> Self {
         Mailbox { name, email }
     }
-
-    /// Encode addressee name using function
-    pub(crate) fn recode_name<F>(&self, f: F) -> Self
-    where
-        F: FnOnce(&str) -> String,
-    {
-        Mailbox::new(self.name.clone().map(|s| f(&s)), self.email.clone())
-    }
 }
 
 impl Display for Mailbox {
@@ -332,19 +321,7 @@ impl FromStr for Mailboxes {
 
     fn from_str(src: &str) -> Result<Self, Self::Err> {
         src.split(',')
-            .map(|m| {
-                m.trim().parse().and_then(|Mailbox { name, email }| {
-                    if let Some(name) = name {
-                        if let Some(name) = utf8_b::decode(&name) {
-                            Ok(Mailbox::new(Some(name), email))
-                        } else {
-                            Err(AddressError::InvalidUtf8b)
-                        }
-                    } else {
-                        Ok(Mailbox::new(None, email))
-                    }
-                })
-            })
+            .map(|m| m.trim().parse())
             .collect::<Result<Vec<_>, _>>()
             .map(Mailboxes)
     }
diff --git a/src/message/mimebody.rs b/src/message/mimebody.rs
index 545f9c42d..e039c7c0b 100644
--- a/src/message/mimebody.rs
+++ b/src/message/mimebody.rs
@@ -66,7 +66,7 @@ impl SinglePartBuilder {
 
     /// Build singlepart using body
     pub fn body<T: IntoBody>(mut self, body: T) -> SinglePart {
-        let maybe_encoding = self.headers.get::<ContentTransferEncoding>().copied();
+        let maybe_encoding = self.headers.get::<ContentTransferEncoding>();
         let body = body.into_body(maybe_encoding);
 
         self.headers.set(body.encoding());
@@ -471,7 +471,7 @@ mod test {
     #[test]
     fn multi_part_mixed() {
         let part = MultiPart::mixed()
-            .boundary("F2mTKN843loAAAAA8porEdAjCKhArPxGeahYoZYSftse1GT/84tup+O0bs8eueVuAlMK")
+            .boundary("0oVZ2r6AoLAhLlb0gPNSKy6BEqdS2IfwxrcbUuo1")
             .part(Part::Single(
                 SinglePart::builder()
                     .header(header::ContentType::TEXT_PLAIN)
@@ -486,27 +486,31 @@ mod test {
                     .body(String::from("int main() { return 0; }")),
             );
 
-        assert_eq!(String::from_utf8(part.formatted()).unwrap(),
-                   concat!("Content-Type: multipart/mixed;",
-                           " boundary=\"F2mTKN843loAAAAA8porEdAjCKhArPxGeahYoZYSftse1GT/84tup+O0bs8eueVuAlMK\"\r\n",
-                           "\r\n",
-                           "--F2mTKN843loAAAAA8porEdAjCKhArPxGeahYoZYSftse1GT/84tup+O0bs8eueVuAlMK\r\n",
-                           "Content-Type: text/plain; charset=utf-8\r\n",
-                           "Content-Transfer-Encoding: binary\r\n",
-                           "\r\n",
-                           "Текст письма в уникоде\r\n",
-                           "--F2mTKN843loAAAAA8porEdAjCKhArPxGeahYoZYSftse1GT/84tup+O0bs8eueVuAlMK\r\n",
-                           "Content-Type: text/plain; charset=utf-8\r\n",
-                           "Content-Disposition: attachment; filename=\"example.c\"\r\n",
-                           "Content-Transfer-Encoding: binary\r\n",
-                           "\r\n",
-                           "int main() { return 0; }\r\n",
-                           "--F2mTKN843loAAAAA8porEdAjCKhArPxGeahYoZYSftse1GT/84tup+O0bs8eueVuAlMK--\r\n"));
+        assert_eq!(
+            String::from_utf8(part.formatted()).unwrap(),
+            concat!(
+                "Content-Type: multipart/mixed; \r\n",
+                " boundary=\"0oVZ2r6AoLAhLlb0gPNSKy6BEqdS2IfwxrcbUuo1\"\r\n",
+                "\r\n",
+                "--0oVZ2r6AoLAhLlb0gPNSKy6BEqdS2IfwxrcbUuo1\r\n",
+                "Content-Type: text/plain; charset=utf-8\r\n",
+                "Content-Transfer-Encoding: binary\r\n",
+                "\r\n",
+                "Текст письма в уникоде\r\n",
+                "--0oVZ2r6AoLAhLlb0gPNSKy6BEqdS2IfwxrcbUuo1\r\n",
+                "Content-Type: text/plain; charset=utf-8\r\n",
+                "Content-Disposition: attachment; filename=\"example.c\"\r\n",
+                "Content-Transfer-Encoding: binary\r\n",
+                "\r\n",
+                "int main() { return 0; }\r\n",
+                "--0oVZ2r6AoLAhLlb0gPNSKy6BEqdS2IfwxrcbUuo1--\r\n"
+            )
+        );
     }
     #[test]
     fn multi_part_encrypted() {
         let part = MultiPart::encrypted("application/pgp-encrypted".to_owned())
-            .boundary("F2mTKN843loAAAAA8porEdAjCKhArPxGeahYoZYSftse1GT/84tup+O0bs8eueVuAlMK")
+            .boundary("0oVZ2r6AoLAhLlb0gPNSKy6BEqdS2IfwxrcbUuo1")
             .part(Part::Single(
                 SinglePart::builder()
                     .header(header::ContentType::parse("application/pgp-encrypted").unwrap())
@@ -529,27 +533,31 @@ mod test {
                     ))),
             );
 
-        assert_eq!(String::from_utf8(part.formatted()).unwrap(),
-                   concat!("Content-Type: multipart/encrypted;",
-                           " boundary=\"F2mTKN843loAAAAA8porEdAjCKhArPxGeahYoZYSftse1GT/84tup+O0bs8eueVuAlMK\";",
-                           " protocol=\"application/pgp-encrypted\"\r\n",
-                           "\r\n",
-                           "--F2mTKN843loAAAAA8porEdAjCKhArPxGeahYoZYSftse1GT/84tup+O0bs8eueVuAlMK\r\n",
-                           "Content-Type: application/pgp-encrypted\r\n",
-                           "Content-Transfer-Encoding: 7bit\r\n",
-                           "\r\n",
-                           "Version: 1\r\n",
-                           "--F2mTKN843loAAAAA8porEdAjCKhArPxGeahYoZYSftse1GT/84tup+O0bs8eueVuAlMK\r\n",
-                           "Content-Type: application/octet-stream; name=\"encrypted.asc\"\r\n",
-                           "Content-Disposition: inline; filename=\"encrypted.asc\"\r\n",
-                           "Content-Transfer-Encoding: 7bit\r\n",
-                           "\r\n",
-                           "-----BEGIN PGP MESSAGE-----\r\n",
-                           "wV4D0dz5vDXklO8SAQdA5lGX1UU/eVQqDxNYdHa7tukoingHzqUB6wQssbMfHl8w\r\n",
-                           "...\r\n",
-                           "-----END PGP MESSAGE-----\r\n",
-                           "\r\n",
-                           "--F2mTKN843loAAAAA8porEdAjCKhArPxGeahYoZYSftse1GT/84tup+O0bs8eueVuAlMK--\r\n"));
+        assert_eq!(
+            String::from_utf8(part.formatted()).unwrap(),
+            concat!(
+                "Content-Type: multipart/encrypted; \r\n",
+                " boundary=\"0oVZ2r6AoLAhLlb0gPNSKy6BEqdS2IfwxrcbUuo1\"; \r\n",
+                " protocol=\"application/pgp-encrypted\"\r\n",
+                "\r\n",
+                "--0oVZ2r6AoLAhLlb0gPNSKy6BEqdS2IfwxrcbUuo1\r\n",
+                "Content-Type: application/pgp-encrypted\r\n",
+                "Content-Transfer-Encoding: 7bit\r\n",
+                "\r\n",
+                "Version: 1\r\n",
+                "--0oVZ2r6AoLAhLlb0gPNSKy6BEqdS2IfwxrcbUuo1\r\n",
+                "Content-Type: application/octet-stream; name=\"encrypted.asc\"\r\n",
+                "Content-Disposition: inline; filename=\"encrypted.asc\"\r\n",
+                "Content-Transfer-Encoding: 7bit\r\n",
+                "\r\n",
+                "-----BEGIN PGP MESSAGE-----\r\n",
+                "wV4D0dz5vDXklO8SAQdA5lGX1UU/eVQqDxNYdHa7tukoingHzqUB6wQssbMfHl8w\r\n",
+                "...\r\n",
+                "-----END PGP MESSAGE-----\r\n",
+                "\r\n",
+                "--0oVZ2r6AoLAhLlb0gPNSKy6BEqdS2IfwxrcbUuo1--\r\n"
+            )
+        );
     }
     #[test]
     fn multi_part_signed() {
@@ -557,7 +565,7 @@ mod test {
             "application/pgp-signature".to_owned(),
             "pgp-sha256".to_owned(),
         )
-        .boundary("F2mTKN843loAAAAA8porEdAjCKhArPxGeahYoZYSftse1GT/84tup+O0bs8eueVuAlMK")
+        .boundary("0oVZ2r6AoLAhLlb0gPNSKy6BEqdS2IfwxrcbUuo1")
         .part(Part::Single(
             SinglePart::builder()
                 .header(header::ContentType::TEXT_PLAIN)
@@ -581,37 +589,41 @@ mod test {
                 ))),
         );
 
-        assert_eq!(String::from_utf8(part.formatted()).unwrap(),
-                   concat!("Content-Type: multipart/signed;",
-                           " boundary=\"F2mTKN843loAAAAA8porEdAjCKhArPxGeahYoZYSftse1GT/84tup+O0bs8eueVuAlMK\";",
-                           " protocol=\"application/pgp-signature\";",
-                           " micalg=\"pgp-sha256\"\r\n",
-                           "\r\n",
-                           "--F2mTKN843loAAAAA8porEdAjCKhArPxGeahYoZYSftse1GT/84tup+O0bs8eueVuAlMK\r\n",
-                           "Content-Type: text/plain; charset=utf-8\r\n",
-                           "Content-Transfer-Encoding: 7bit\r\n",
-                           "\r\n",
-                           "Test email for signature\r\n",
-                           "--F2mTKN843loAAAAA8porEdAjCKhArPxGeahYoZYSftse1GT/84tup+O0bs8eueVuAlMK\r\n",
-                           "Content-Type: application/pgp-signature; name=\"signature.asc\"\r\n",
-                           "Content-Disposition: attachment; filename=\"signature.asc\"\r\n",
-                           "Content-Transfer-Encoding: 7bit\r\n",
-                           "\r\n",
-                           "-----BEGIN PGP SIGNATURE-----\r\n",
-                            "\r\n",
-                            "iHUEARYIAB0WIQTNsp3S/GbdE0KoiQ+IGQOscREZuQUCXyOzDAAKCRCIGQOscREZ\r\n",
-                            "udgDAQCv3FJ3QWW5bRaGZAa0Ug6vASFdkvDMKoRwcoFnHPthjQEAiQ8skkIyE2GE\r\n",
-                            "PoLpAXiKpT+NU8S8+8dfvwutnb4dSwM=\r\n",
-                            "=3FYZ\r\n",
-                            "-----END PGP SIGNATURE-----\r\n",
-                           "\r\n",
-                           "--F2mTKN843loAAAAA8porEdAjCKhArPxGeahYoZYSftse1GT/84tup+O0bs8eueVuAlMK--\r\n"));
+        assert_eq!(
+            String::from_utf8(part.formatted()).unwrap(),
+            concat!(
+                "Content-Type: multipart/signed; \r\n",
+                " boundary=\"0oVZ2r6AoLAhLlb0gPNSKy6BEqdS2IfwxrcbUuo1\"; \r\n",
+                " protocol=\"application/pgp-signature\";",
+                " micalg=\"pgp-sha256\"\r\n",
+                "\r\n",
+                "--0oVZ2r6AoLAhLlb0gPNSKy6BEqdS2IfwxrcbUuo1\r\n",
+                "Content-Type: text/plain; charset=utf-8\r\n",
+                "Content-Transfer-Encoding: 7bit\r\n",
+                "\r\n",
+                "Test email for signature\r\n",
+                "--0oVZ2r6AoLAhLlb0gPNSKy6BEqdS2IfwxrcbUuo1\r\n",
+                "Content-Type: application/pgp-signature; name=\"signature.asc\"\r\n",
+                "Content-Disposition: attachment; filename=\"signature.asc\"\r\n",
+                "Content-Transfer-Encoding: 7bit\r\n",
+                "\r\n",
+                "-----BEGIN PGP SIGNATURE-----\r\n",
+                "\r\n",
+                "iHUEARYIAB0WIQTNsp3S/GbdE0KoiQ+IGQOscREZuQUCXyOzDAAKCRCIGQOscREZ\r\n",
+                "udgDAQCv3FJ3QWW5bRaGZAa0Ug6vASFdkvDMKoRwcoFnHPthjQEAiQ8skkIyE2GE\r\n",
+                "PoLpAXiKpT+NU8S8+8dfvwutnb4dSwM=\r\n",
+                "=3FYZ\r\n",
+                "-----END PGP SIGNATURE-----\r\n",
+                "\r\n",
+                "--0oVZ2r6AoLAhLlb0gPNSKy6BEqdS2IfwxrcbUuo1--\r\n"
+            )
+        );
     }
 
     #[test]
     fn multi_part_alternative() {
         let part = MultiPart::alternative()
-            .boundary("F2mTKN843loAAAAA8porEdAjCKhArPxGeahYoZYSftse1GT/84tup+O0bs8eueVuAlMK")
+            .boundary("0oVZ2r6AoLAhLlb0gPNSKy6BEqdS2IfwxrcbUuo1")
             .part(Part::Single(SinglePart::builder()
                              .header(header::ContentType::TEXT_PLAIN)
                              .header(header::ContentTransferEncoding::Binary)
@@ -622,28 +634,28 @@ mod test {
                              .body(String::from("<p>Текст <em>письма</em> в <a href=\"https://ru.wikipedia.org/wiki/Юникод\">уникоде</a><p>")));
 
         assert_eq!(String::from_utf8(part.formatted()).unwrap(),
-                   concat!("Content-Type: multipart/alternative;",
-                           " boundary=\"F2mTKN843loAAAAA8porEdAjCKhArPxGeahYoZYSftse1GT/84tup+O0bs8eueVuAlMK\"\r\n",
+                   concat!("Content-Type: multipart/alternative; \r\n",
+                           " boundary=\"0oVZ2r6AoLAhLlb0gPNSKy6BEqdS2IfwxrcbUuo1\"\r\n",
                            "\r\n",
-                           "--F2mTKN843loAAAAA8porEdAjCKhArPxGeahYoZYSftse1GT/84tup+O0bs8eueVuAlMK\r\n",
+                           "--0oVZ2r6AoLAhLlb0gPNSKy6BEqdS2IfwxrcbUuo1\r\n",
                            "Content-Type: text/plain; charset=utf-8\r\n",
                            "Content-Transfer-Encoding: binary\r\n",
                            "\r\n",
                            "Текст письма в уникоде\r\n",
-                           "--F2mTKN843loAAAAA8porEdAjCKhArPxGeahYoZYSftse1GT/84tup+O0bs8eueVuAlMK\r\n",
+                           "--0oVZ2r6AoLAhLlb0gPNSKy6BEqdS2IfwxrcbUuo1\r\n",
                            "Content-Type: text/html; charset=utf-8\r\n",
                            "Content-Transfer-Encoding: binary\r\n",
                            "\r\n",
                            "<p>Текст <em>письма</em> в <a href=\"https://ru.wikipedia.org/wiki/Юникод\">уникоде</a><p>\r\n",
-                           "--F2mTKN843loAAAAA8porEdAjCKhArPxGeahYoZYSftse1GT/84tup+O0bs8eueVuAlMK--\r\n"));
+                           "--0oVZ2r6AoLAhLlb0gPNSKy6BEqdS2IfwxrcbUuo1--\r\n"));
     }
 
     #[test]
     fn multi_part_mixed_related() {
         let part = MultiPart::mixed()
-            .boundary("F2mTKN843loAAAAA8porEdAjCKhArPxGeahYoZYSftse1GT/84tup+O0bs8eueVuAlMK")
+            .boundary("0oVZ2r6AoLAhLlb0gPNSKy6BEqdS2IfwxrcbUuo1")
             .multipart(MultiPart::related()
-                            .boundary("E912L4JH3loAAAAAFu/33Gx7PEoTMmhGaxG3FlbVMQHctj96q4nHvBM+7DTtXo/im8gh")
+                            .boundary("0oVZ2r6AoLAhLlb0gPNSKy6BEqdS2IfwxrcbUuo1")
                             .singlepart(SinglePart::builder()
                                              .header(header::ContentType::TEXT_HTML)
                                              .header(header::ContentTransferEncoding::Binary)
@@ -660,19 +672,19 @@ mod test {
                              .body(String::from("int main() { return 0; }")));
 
         assert_eq!(String::from_utf8(part.formatted()).unwrap(),
-                   concat!("Content-Type: multipart/mixed;",
-                           " boundary=\"F2mTKN843loAAAAA8porEdAjCKhArPxGeahYoZYSftse1GT/84tup+O0bs8eueVuAlMK\"\r\n",
+                   concat!("Content-Type: multipart/mixed; \r\n",
+                           " boundary=\"0oVZ2r6AoLAhLlb0gPNSKy6BEqdS2IfwxrcbUuo1\"\r\n",
                            "\r\n",
-                           "--F2mTKN843loAAAAA8porEdAjCKhArPxGeahYoZYSftse1GT/84tup+O0bs8eueVuAlMK\r\n",
-                           "Content-Type: multipart/related;",
-                           " boundary=\"E912L4JH3loAAAAAFu/33Gx7PEoTMmhGaxG3FlbVMQHctj96q4nHvBM+7DTtXo/im8gh\"\r\n",
+                           "--0oVZ2r6AoLAhLlb0gPNSKy6BEqdS2IfwxrcbUuo1\r\n",
+                           "Content-Type: multipart/related; \r\n",
+                           " boundary=\"0oVZ2r6AoLAhLlb0gPNSKy6BEqdS2IfwxrcbUuo1\"\r\n",
                            "\r\n",
-                           "--E912L4JH3loAAAAAFu/33Gx7PEoTMmhGaxG3FlbVMQHctj96q4nHvBM+7DTtXo/im8gh\r\n",
+                           "--0oVZ2r6AoLAhLlb0gPNSKy6BEqdS2IfwxrcbUuo1\r\n",
                            "Content-Type: text/html; charset=utf-8\r\n",
                            "Content-Transfer-Encoding: binary\r\n",
                            "\r\n",
                            "<p>Текст <em>письма</em> в <a href=\"https://ru.wikipedia.org/wiki/Юникод\">уникоде</a><p>\r\n",
-                           "--E912L4JH3loAAAAAFu/33Gx7PEoTMmhGaxG3FlbVMQHctj96q4nHvBM+7DTtXo/im8gh\r\n",
+                           "--0oVZ2r6AoLAhLlb0gPNSKy6BEqdS2IfwxrcbUuo1\r\n",
                            "Content-Type: image/png\r\n",
                            "Content-Location: /image.png\r\n",
                            "Content-Transfer-Encoding: base64\r\n",
@@ -680,14 +692,14 @@ mod test {
                            "MTIzNDU2Nzg5MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTIzNDU2Nzg5MDEyMzQ1Njc4OTAxMjM0NTY3\r\n",
                            "ODkwMTIzNDU2Nzg5MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTIzNDU2Nzg5MDEyMzQ1Njc4OTAxMjM0\r\n",
                            "NTY3ODkwMTIzNDU2Nzg5MA==\r\n",
-                           "--E912L4JH3loAAAAAFu/33Gx7PEoTMmhGaxG3FlbVMQHctj96q4nHvBM+7DTtXo/im8gh--\r\n",
-                           "--F2mTKN843loAAAAA8porEdAjCKhArPxGeahYoZYSftse1GT/84tup+O0bs8eueVuAlMK\r\n",
+                           "--0oVZ2r6AoLAhLlb0gPNSKy6BEqdS2IfwxrcbUuo1--\r\n",
+                           "--0oVZ2r6AoLAhLlb0gPNSKy6BEqdS2IfwxrcbUuo1\r\n",
                            "Content-Type: text/plain; charset=utf-8\r\n",
                            "Content-Disposition: attachment; filename=\"example.c\"\r\n",
                            "Content-Transfer-Encoding: binary\r\n",
                            "\r\n",
                            "int main() { return 0; }\r\n",
-                           "--F2mTKN843loAAAAA8porEdAjCKhArPxGeahYoZYSftse1GT/84tup+O0bs8eueVuAlMK--\r\n"));
+                           "--0oVZ2r6AoLAhLlb0gPNSKy6BEqdS2IfwxrcbUuo1--\r\n"));
     }
 
     #[test]
diff --git a/src/message/mod.rs b/src/message/mod.rs
index 7117fbec3..b09e6142b 100644
--- a/src/message/mod.rs
+++ b/src/message/mod.rs
@@ -226,6 +226,8 @@
 //! ```
 //! </details>
 
+use std::{convert::TryFrom, io::Write, iter, time::SystemTime};
+
 pub use body::{Body, IntoBody, MaybeString};
 pub use mailbox::*;
 pub use mimebody::*;
@@ -234,9 +236,6 @@ mod body;
 pub mod header;
 mod mailbox;
 mod mimebody;
-mod utf8_b;
-
-use std::{convert::TryFrom, io::Write, iter, time::SystemTime};
 
 use crate::{
     address::Envelope,
@@ -275,12 +274,13 @@ impl MessageBuilder {
     }
 
     /// Add mailbox to header
-    pub fn mailbox<H: Header + MailboxesHeader>(mut self, header: H) -> Self {
-        if self.headers.has::<H>() {
-            self.headers.get_mut::<H>().unwrap().join_mailboxes(header);
-            self
-        } else {
-            self.header(header)
+    pub fn mailbox<H: Header + MailboxesHeader>(self, header: H) -> Self {
+        match self.headers.get::<H>() {
+            Some(mut header_) => {
+                header_.join_mailboxes(header);
+                self.header(header_)
+            }
+            None => self.header(header),
         }
     }
 
@@ -431,7 +431,7 @@ impl MessageBuilder {
         // Fail is missing correct originator (Sender or From)
         match res.headers.get::<header::From>() {
             Some(header::From(f)) => {
-                let from: Vec<Mailbox> = f.clone().into();
+                let from: Vec<Mailbox> = f.into();
                 if from.len() > 1 && res.headers.get::<header::Sender>().is_none() {
                     return Err(EmailError::TooManyFrom);
                 }
@@ -458,7 +458,7 @@ impl MessageBuilder {
     /// `Content-Transfer-Encoding`, based on the most efficient and valid encoding
     /// for `body`.
     pub fn body<T: IntoBody>(mut self, body: T) -> Result<Message, EmailError> {
-        let maybe_encoding = self.headers.get::<ContentTransferEncoding>().copied();
+        let maybe_encoding = self.headers.get::<ContentTransferEncoding>();
         let body = body.into_body(maybe_encoding);
 
         self.headers.set(body.encoding());
@@ -643,7 +643,7 @@ mod test {
         let expected = String::from_utf8(file_expected).unwrap();
 
         for (i, line) in output.lines().zip(expected.lines()).enumerate() {
-            if i == 6 || i == 8 || i == 13 || i == 232 {
+            if i == 7 || i == 9 || i == 14 || i == 233 {
                 continue;
             }
 
diff --git a/src/message/utf8_b.rs b/src/message/utf8_b.rs
deleted file mode 100644
index ae6d78ecd..000000000
--- a/src/message/utf8_b.rs
+++ /dev/null
@@ -1,60 +0,0 @@
-// https://tools.ietf.org/html/rfc1522
-
-fn allowed_char(c: char) -> bool {
-    c >= 1 as char && c <= 9 as char
-        || c == 11 as char
-        || c == 12 as char
-        || c >= 14 as char && c <= 127 as char
-}
-
-pub fn encode(s: &str) -> String {
-    if s.chars().all(allowed_char) {
-        s.into()
-    } else {
-        format!("=?utf-8?b?{}?=", base64::encode(s))
-    }
-}
-
-pub fn decode(s: &str) -> Option<String> {
-    s.strip_prefix("=?utf-8?b?")
-        .and_then(|stripped| stripped.strip_suffix("?="))
-        .map_or_else(
-            || Some(s.into()),
-            |stripped| {
-                let decoded = base64::decode(stripped).ok()?;
-                let decoded = String::from_utf8(decoded).ok()?;
-                Some(decoded)
-            },
-        )
-}
-
-#[cfg(test)]
-mod test {
-    use super::{decode, encode};
-
-    #[test]
-    fn encode_ascii() {
-        assert_eq!(&encode("Kayo. ?"), "Kayo. ?");
-    }
-
-    #[test]
-    fn decode_ascii() {
-        assert_eq!(decode("Kayo. ?"), Some("Kayo. ?".into()));
-    }
-
-    #[test]
-    fn encode_utf8() {
-        assert_eq!(
-            &encode("Привет, мир!"),
-            "=?utf-8?b?0J/RgNC40LLQtdGCLCDQvNC40YAh?="
-        );
-    }
-
-    #[test]
-    fn decode_utf8() {
-        assert_eq!(
-            decode("=?utf-8?b?0J/RgNC40LLQtdGCLCDQvNC40YAh?="),
-            Some("Привет, мир!".into())
-        );
-    }
-}


Test patch:
diff --git a/testdata/email_with_png.eml b/testdata/email_with_png.eml
index 298e48ac1..a00c8b4a8 100644
--- a/testdata/email_with_png.eml
+++ b/testdata/email_with_png.eml
@@ -4,7 +4,8 @@ Reply-To: Yuin <yuin@domain.tld>
 To: Hei <hei@domain.tld>
 Subject: Happy new year
 MIME-Version: 1.0
-Content-Type: multipart/related; boundary="0oVZ2r6AoLAhLlb0gPNSKy6BEqdS2IfwxrcbUuo1"
+Content-Type: multipart/related; 
+ boundary="GUEEoEeTXtLcK2sMhmH1RfC1co13g4rtnRUFjQFA"
 
 --0oVZ2r6AoLAhLlb0gPNSKy6BEqdS2IfwxrcbUuo1
 Content-Type: text/html; charset=utf-8