Repository: duesee/imap-codec (language: python)

Issue:
feat: Revisit `Encode` trait.
We really want the `Encode` trait to tell us how to proceed with encoding, i.e., during literal handling or authentication. We already implemented something for `tokio_util` but should make this more generically accessible to ease the integration of imap-codec into existing applications.


Discussion / hints:

Fix patch:
diff --git a/README.md b/README.md
index a71d2058..b5fd25a7 100644
--- a/README.md
+++ b/README.md
@@ -34,7 +34,7 @@ fn main() {
     let (remainder, parsed) = Command::decode(input).unwrap();
     println!("# Parsed\n\n{:#?}\n\n", parsed);
 
-    let buffer = parsed.encode_detached().unwrap();
+    let buffer = parsed.encode().dump();
 
     // Note: IMAP4rev1 may produce messages that are not valid UTF-8.
     println!("# Serialized\n\n{:?}", std::str::from_utf8(&buffer));
diff --git a/benches/serialize_command.rs b/benches/serialize_command.rs
index 9cd9c23e..2cb62fd2 100644
--- a/benches/serialize_command.rs
+++ b/benches/serialize_command.rs
@@ -10,10 +10,6 @@ use imap_codec::{
     message::Section,
 };
 
-fn serialize_command(cmd: &Command, out: &mut Vec<u8>) {
-    cmd.encode(out).unwrap();
-}
-
 fn criterion_benchmark(c: &mut Criterion) {
     // # Setup
     //
@@ -45,7 +41,8 @@ fn criterion_benchmark(c: &mut Criterion) {
 
     c.bench_function("serialize_command", |b| {
         b.iter(|| {
-            serialize_command(black_box(&cmd), black_box(&mut out));
+            let tmp = cmd.encode().dump();
+            out.extend_from_slice(black_box(&tmp));
 
             // TODO: This should be a single instruction... should...
             out.clear();
diff --git a/benches/serialize_response.rs b/benches/serialize_response.rs
index fa79a959..70100d7b 100644
--- a/benches/serialize_response.rs
+++ b/benches/serialize_response.rs
@@ -6,10 +6,6 @@ use imap_codec::{
     response::{Code, Response, Status},
 };
 
-fn serialize_response(rsp: &Response, out: &mut Vec<u8>) {
-    rsp.encode(out).unwrap();
-}
-
 fn criterion_benchmark(c: &mut Criterion) {
     // # Setup
     //
@@ -29,7 +25,8 @@ fn criterion_benchmark(c: &mut Criterion) {
 
     c.bench_function("serialize_response", |b| {
         b.iter(|| {
-            serialize_response(black_box(&rsp), black_box(&mut out));
+            let tmp = rsp.encode().dump();
+            out.extend_from_slice(black_box(&tmp));
 
             // TODO: This should be a single instruction... should...
             out.clear();
diff --git a/fuzz/fuzz_targets/command.rs b/fuzz/fuzz_targets/command.rs
index 47a80e93..589bfc5f 100644
--- a/fuzz/fuzz_targets/command.rs
+++ b/fuzz/fuzz_targets/command.rs
@@ -20,7 +20,7 @@ fuzz_target!(|data: &[u8]| {
             println!("[!] Parsed1: {parsed1:?}");
         }
 
-        let output = parsed1.encode_detached().unwrap();
+        let output = parsed1.encode().dump();
         #[cfg(feature = "debug")]
         println!("[!] Serialized: {}", escape_byte_string(&output));
 
diff --git a/fuzz/fuzz_targets/command_to_bytes_and_back.rs b/fuzz/fuzz_targets/command_to_bytes_and_back.rs
index a3daef90..d9e1aaa5 100644
--- a/fuzz/fuzz_targets/command_to_bytes_and_back.rs
+++ b/fuzz/fuzz_targets/command_to_bytes_and_back.rs
@@ -12,7 +12,7 @@ fuzz_target!(|test: Command| {
     #[cfg(feature = "debug")]
     println!("[!] Input: {test:?}");
 
-    let buffer = test.encode_detached().unwrap();
+    let buffer = test.encode().dump();
 
     #[cfg(feature = "debug")]
     println!("[!] Serialized: {}", escape_byte_string(&buffer));
diff --git a/fuzz/fuzz_targets/greeting.rs b/fuzz/fuzz_targets/greeting.rs
index 4a433e25..6c29f5b0 100644
--- a/fuzz/fuzz_targets/greeting.rs
+++ b/fuzz/fuzz_targets/greeting.rs
@@ -20,7 +20,7 @@ fuzz_target!(|data: &[u8]| {
             println!("[!] Parsed1: {parsed1:?}");
         }
 
-        let output = parsed1.encode_detached().unwrap();
+        let output = parsed1.encode().dump();
         #[cfg(feature = "debug")]
         println!("[!] Serialized: {}", escape_byte_string(&output));
 
diff --git a/fuzz/fuzz_targets/greeting_to_bytes_and_back.rs b/fuzz/fuzz_targets/greeting_to_bytes_and_back.rs
index 6bd93d1c..35baf9d3 100644
--- a/fuzz/fuzz_targets/greeting_to_bytes_and_back.rs
+++ b/fuzz/fuzz_targets/greeting_to_bytes_and_back.rs
@@ -12,7 +12,7 @@ fuzz_target!(|test: Greeting| {
     #[cfg(feature = "debug")]
     println!("[!] Input: {test:?}");
 
-    let buffer = test.encode_detached().unwrap();
+    let buffer = test.encode().dump();
 
     #[cfg(feature = "debug")]
     println!("[!] Serialized: {}", escape_byte_string(&buffer));
diff --git a/fuzz/fuzz_targets/response.rs b/fuzz/fuzz_targets/response.rs
index 3971434b..4bcbffc5 100644
--- a/fuzz/fuzz_targets/response.rs
+++ b/fuzz/fuzz_targets/response.rs
@@ -20,7 +20,7 @@ fuzz_target!(|data: &[u8]| {
             println!("[!] Parsed1: {parsed1:?}");
         }
 
-        let output = parsed1.encode_detached().unwrap();
+        let output = parsed1.encode().dump();
         #[cfg(feature = "debug")]
         println!("[!] Serialized: {}", escape_byte_string(&output));
 
diff --git a/fuzz/fuzz_targets/response_to_bytes_and_back.rs b/fuzz/fuzz_targets/response_to_bytes_and_back.rs
index fa16ad38..dac37b80 100644
--- a/fuzz/fuzz_targets/response_to_bytes_and_back.rs
+++ b/fuzz/fuzz_targets/response_to_bytes_and_back.rs
@@ -12,7 +12,7 @@ fuzz_target!(|test: Response| {
     #[cfg(feature = "debug")]
     println!("[!] Input: {test:#?}");
 
-    let buffer = test.encode_detached().unwrap();
+    let buffer = test.encode().dump();
 
     #[cfg(feature = "debug")]
     println!("[!] Serialized: {}", escape_byte_string(&buffer));
diff --git a/imap-types/src/lib.rs b/imap-types/src/lib.rs
index b6bc0d51..4b89f7c4 100644
--- a/imap-types/src/lib.rs
+++ b/imap-types/src/lib.rs
@@ -149,7 +149,8 @@ pub mod core {
     //! ```
 
     pub use crate::imap4rev1::core::{
-        AString, Atom, AtomExt, IString, Literal, NString, NonEmptyVec, NonEmptyVecError, Quoted,
+        AString, Atom, AtomError, AtomExt, AtomExtError, IString, Literal, LiteralError, NString,
+        NonEmptyVec, NonEmptyVecError, Quoted, QuotedCharError, QuotedError, TagError, TextError,
     };
 }
 
diff --git a/imap-types/src/secret.rs b/imap-types/src/secret.rs
index e5ed6b7b..98b244fd 100644
--- a/imap-types/src/secret.rs
+++ b/imap-types/src/secret.rs
@@ -157,7 +157,9 @@ mod tests {
     #[cfg(not(debug_assertions))]
     #[allow(clippy::redundant_clone)]
     fn test_that_secret_is_redacted() {
-        use crate::{command::AuthenticateData, message::AuthMechanism};
+        use crate::command::AuthenticateData;
+        #[cfg(feature = "ext_sasl_ir")]
+        use crate::message::AuthMechanism;
 
         let secret = Secret("xyz123");
         let got = format!("{:?}", secret);
diff --git a/imap-types/src/utils.rs b/imap-types/src/utils.rs
index 43a7352a..3cb0c3bb 100644
--- a/imap-types/src/utils.rs
+++ b/imap-types/src/utils.rs
@@ -1,6 +1,11 @@
 use std::borrow::Cow;
 
-pub fn escape_byte_string(bytes: &[u8]) -> String {
+pub fn escape_byte_string<B>(bytes: B) -> String
+where
+    B: AsRef<[u8]>,
+{
+    let bytes = bytes.as_ref();
+
     bytes
         .iter()
         .map(|byte| match byte {
diff --git a/src/codec.rs b/src/codec.rs
index e7257069..60617083 100644
--- a/src/codec.rs
+++ b/src/codec.rs
@@ -1,7 +1,7 @@
 //! # Serialization of messages
 //!
 //! All messages implement the `Encode` trait.
-//! You can `use imap_codec::Encode` and call the `.encode(...)` (or `.encode_detached(...)`) method to serialize a message (into a writer).
+//! You can `use imap_codec::Encode` and call the `.encode()` (or `.encode().dump()`) method to serialize a message.
 //! Note that IMAP traces are not guaranteed to be UTF-8. Thus, be careful when using things like `std::str::from_utf8(...).unwrap()`.
 //! It should generally be better not to think about IMAP as being UTF-8.
 //! This is also why `Display` is not implemented.
@@ -19,7 +19,7 @@
 //! let cmd = Command::new("A123", CommandBody::login("alice", "password").unwrap()).unwrap();
 //!
 //! // Encode the `cmd` into `out`.
-//! let out = cmd.encode_detached().unwrap();
+//! let out = cmd.encode().dump();
 //!
 //! // Print the command.
 //! // (Note that IMAP traces are not guaranteed to be valid UTF-8.)
@@ -27,7 +27,15 @@
 //! ```
 
 pub use decode::{Decode, DecodeError};
-pub use encode::Encode;
+#[cfg(any(
+    feature = "ext_compress",
+    feature = "ext_enable",
+    feature = "ext_idle",
+    feature = "ext_literal",
+    feature = "ext_quota",
+))]
+pub(crate) use encode::CoreEncode;
+pub use encode::{Action, Encode, EncodeContext};
 
 mod decode;
 mod encode;
diff --git a/src/codec/encode.rs b/src/codec/encode.rs
index 051b99d3..c99999a6 100644
--- a/src/codec/encode.rs
+++ b/src/codec/encode.rs
@@ -32,48 +32,261 @@ use crate::{
 };
 
 pub trait Encode {
-    fn encode(&self, writer: &mut impl Write) -> std::io::Result<()>;
+    /// Create an [`Encoder`] for this message.
+    fn encode(&self) -> Encoder;
+}
+
+#[allow(missing_debug_implementations)]
+/// Message encoder.
+///
+/// This encoder facilitates the implementation of IMAP client- and server implementations by
+/// yielding the encoding of a message through [`Action`]s. This is required, because the usage of
+/// literals (and some other types) may change the IMAP message flow. Thus, in many cases, it is an
+/// error to just "dump" a message and send it over the network.
+///
+/// # Example
+///
+/// ```rust
+/// use imap_codec::{
+///     codec::{Action, Encode},
+///     command::{Command, CommandBody},
+/// };
+///
+/// let cmd = Command::new("A", CommandBody::login("alice", "pass").unwrap()).unwrap();
+///
+/// for action in cmd.encode() {
+///     match action {
+///         Action::Send { data } => {}
+///         Action::RecvContinuationRequest => {}
+///         Action::Unknown => {}
+///     }
+/// }
+/// ```
+pub struct Encoder {
+    items: Vec<Action>,
+}
+
+impl Encoder {
+    /// Dump the (remaining) encoded data without being guided by [`Action`]s.
+    ///
+    /// Note: This method should (likely) not be used in a real implementation. It is often not
+    /// possible to send a full message at once in IMAP. Notably, a client needs to wait for
+    /// continuation requests before sending literals.
+    ///
+    /// Prefer to iterate over the encoder and follow the yielded actions:
+    ///
+    /// ```rust
+    /// use imap_codec::{
+    ///     codec::{Action, Encode},
+    ///     command::{Command, CommandBody},
+    /// };
+    ///
+    /// let cmd = Command::new("A", CommandBody::login("alice", "pass").unwrap()).unwrap();
+    ///
+    /// for action in cmd.encode() {
+    ///     match action {
+    ///         Action::Send { data } => {}
+    ///         Action::RecvContinuationRequest => {}
+    ///         Action::Unknown => {}
+    ///     }
+    /// }
+    /// ```
+    pub fn dump(self) -> Vec<u8> {
+        let mut out = Vec::new();
+
+        for action in self.items {
+            match action {
+                Action::Send { mut data } => out.append(&mut data),
+                Action::RecvContinuationRequest | Action::Unknown => {
+                    // Nothing to do.
+                }
+            }
+        }
+
+        out
+    }
+}
+
+impl Iterator for Encoder {
+    type Item = Action;
+
+    fn next(&mut self) -> Option<Self::Item> {
+        if !self.items.is_empty() {
+            Some(self.items.remove(0))
+        } else {
+            None
+        }
+    }
+}
+
+/// The intended action of a client or server.
+#[derive(Clone, Debug, Eq, PartialEq)]
+pub enum Action {
+    /// Send this data over the network.
+    Send { data: Vec<u8> },
+    /// (Maybe) wait for a continuation request.
+    ///
+    /// Note: This action MUST be ignored by a server.
+    RecvContinuationRequest,
+    /// Encoding of a non-supported type may (or may not) require further action.
+    Unknown,
+}
+
+//--------------------------------------------------------------------------------------------------
+
+#[derive(Clone, Debug, Default, Eq, PartialEq)]
+pub struct EncodeContext {
+    accumulator: Vec<u8>,
+    items: Vec<Action>,
+}
+
+impl EncodeContext {
+    pub fn new() -> Self {
+        Self::default()
+    }
+
+    pub fn into_items(mut self) -> Vec<Action> {
+        if !self.accumulator.is_empty() {
+            self.items.push(Action::Send {
+                data: self.accumulator,
+            })
+        }
+        // Not needed, as it is dropped anyway.
+        // self.accumulator.clear();
+
+        self.items
+    }
+
+    pub fn dump(mut self) -> Vec<u8> {
+        if !self.accumulator.is_empty() {
+            self.items.push(Action::Send {
+                data: self.accumulator.clone(),
+            })
+        }
+        self.accumulator.clear();
+
+        self.items
+            .into_iter()
+            .fold(vec![], |mut acc, action| match action {
+                Action::Send { mut data } => {
+                    acc.append(&mut data);
+                    acc
+                }
+                Action::RecvContinuationRequest | Action::Unknown => acc,
+            })
+    }
+
+    pub fn recv_continuation_request(&mut self) {
+        self.items.push(Action::Send {
+            data: self.accumulator.clone(),
+        });
+        self.items.push(Action::RecvContinuationRequest);
+        self.accumulator.clear();
+    }
+
+    pub fn unknown(&mut self) {
+        self.items.push(Action::Send {
+            data: self.accumulator.clone(),
+        });
+        self.items.push(Action::Unknown);
+        self.accumulator.clear();
+    }
+}
+
+impl Write for EncodeContext {
+    fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
+        self.accumulator.extend_from_slice(buf);
+        Ok(buf.len())
+    }
+
+    fn flush(&mut self) -> std::io::Result<()> {
+        Ok(())
+    }
+}
+
+impl<T> Encode for T
+where
+    T: CoreEncode,
+{
+    fn encode(&self) -> Encoder {
+        let mut encode_context = EncodeContext::new();
+        T::core_encode(self, &mut encode_context).unwrap();
 
-    fn encode_detached(&self) -> std::io::Result<Vec<u8>> {
-        let mut serialized = Vec::new();
-        self.encode(&mut serialized)?;
-        Ok(serialized)
+        Encoder {
+            items: encode_context.into_items(),
+        }
     }
 }
 
-// ----- Primitive -----
+// -------------------------------------------------------------------------------------------------
+
+pub trait CoreEncode {
+    fn core_encode(&self, writer: &mut EncodeContext) -> std::io::Result<()>;
+}
 
-impl Encode for u32 {
-    fn encode(&self, writer: &mut impl Write) -> std::io::Result<()> {
+// ----- Primitive ---------------------------------------------------------------------------------
+
+impl CoreEncode for u32 {
+    fn core_encode(&self, writer: &mut EncodeContext) -> std::io::Result<()> {
         writer.write_all(self.to_string().as_bytes())
     }
 }
 
-impl Encode for u64 {
-    fn encode(&self, writer: &mut impl Write) -> std::io::Result<()> {
+impl CoreEncode for u64 {
+    fn core_encode(&self, writer: &mut EncodeContext) -> std::io::Result<()> {
         writer.write_all(self.to_string().as_bytes())
     }
 }
 
-// ----- Command -----
+// ----- Command -----------------------------------------------------------------------------------
 
-impl<'a> Encode for Command<'a> {
-    fn encode(&self, writer: &mut impl Write) -> std::io::Result<()> {
-        self.tag.encode(writer)?;
+impl<'a> CoreEncode for Command<'a> {
+    fn core_encode(&self, writer: &mut EncodeContext) -> std::io::Result<()> {
+        self.tag.core_encode(writer)?;
         writer.write_all(b" ")?;
-        self.body.encode(writer)?;
-        writer.write_all(b"\r\n")
+        self.body.core_encode(writer)?;
+        writer.write_all(b"\r\n")?;
+
+        // Note: We need to do this here because we must do it after the final \r\n was sent.
+        match &self.body {
+            #[cfg(not(feature = "ext_sasl_ir"))]
+            CommandBody::Authenticate { .. } => {
+                writer.recv_continuation_request();
+            }
+            #[cfg(feature = "ext_sasl_ir")]
+            CommandBody::Authenticate {
+                mechanism,
+                initial_response,
+            } => {
+                if initial_response.is_some() {
+                    match mechanism {
+                        AuthMechanism::Plain => {
+                            // Nothing to wait for here.
+                        }
+                        AuthMechanism::Login => {
+                            writer.recv_continuation_request();
+                        }
+                        _ => writer.unknown(),
+                    }
+                } else {
+                    writer.recv_continuation_request();
+                }
+            }
+            _ => {}
+        }
+
+        Ok(())
     }
 }
 
-impl<'a> Encode for Tag<'a> {
-    fn encode(&self, writer: &mut impl Write) -> std::io::Result<()> {
+impl<'a> CoreEncode for Tag<'a> {
+    fn core_encode(&self, writer: &mut EncodeContext) -> std::io::Result<()> {
         writer.write_all(self.inner().as_bytes())
     }
 }
 
-impl<'a> Encode for CommandBody<'a> {
-    fn encode(&self, writer: &mut impl Write) -> std::io::Result<()> {
+impl<'a> CoreEncode for CommandBody<'a> {
+    fn core_encode(&self, writer: &mut EncodeContext) -> std::io::Result<()> {
         match self {
             CommandBody::Capability => writer.write_all(b"CAPABILITY"),
             CommandBody::Noop => writer.write_all(b"NOOP"),
@@ -87,7 +300,7 @@ impl<'a> Encode for CommandBody<'a> {
             } => {
                 writer.write_all(b"AUTHENTICATE")?;
                 writer.write_all(b" ")?;
-                mechanism.encode(writer)?;
+                mechanism.core_encode(writer)?;
 
                 #[cfg(feature = "ext_sasl_ir")]
                 if let Some(ir) = initial_response {
@@ -108,31 +321,31 @@ impl<'a> Encode for CommandBody<'a> {
             CommandBody::Login { username, password } => {
                 writer.write_all(b"LOGIN")?;
                 writer.write_all(b" ")?;
-                username.encode(writer)?;
+                username.core_encode(writer)?;
                 writer.write_all(b" ")?;
-                password.declassify().encode(writer)
+                password.declassify().core_encode(writer)
             }
             CommandBody::Select { mailbox } => {
                 writer.write_all(b"SELECT")?;
                 writer.write_all(b" ")?;
-                mailbox.encode(writer)
+                mailbox.core_encode(writer)
             }
             #[cfg(feature = "ext_unselect")]
             CommandBody::Unselect => writer.write_all(b"UNSELECT"),
             CommandBody::Examine { mailbox } => {
                 writer.write_all(b"EXAMINE")?;
                 writer.write_all(b" ")?;
-                mailbox.encode(writer)
+                mailbox.core_encode(writer)
             }
             CommandBody::Create { mailbox } => {
                 writer.write_all(b"CREATE")?;
                 writer.write_all(b" ")?;
-                mailbox.encode(writer)
+                mailbox.core_encode(writer)
             }
             CommandBody::Delete { mailbox } => {
                 writer.write_all(b"DELETE")?;
                 writer.write_all(b" ")?;
-                mailbox.encode(writer)
+                mailbox.core_encode(writer)
             }
             CommandBody::Rename {
                 from: mailbox,
@@ -140,19 +353,19 @@ impl<'a> Encode for CommandBody<'a> {
             } => {
                 writer.write_all(b"RENAME")?;
                 writer.write_all(b" ")?;
-                mailbox.encode(writer)?;
+                mailbox.core_encode(writer)?;
                 writer.write_all(b" ")?;
-                new_mailbox.encode(writer)
+                new_mailbox.core_encode(writer)
             }
             CommandBody::Subscribe { mailbox } => {
                 writer.write_all(b"SUBSCRIBE")?;
                 writer.write_all(b" ")?;
-                mailbox.encode(writer)
+                mailbox.core_encode(writer)
             }
             CommandBody::Unsubscribe { mailbox } => {
                 writer.write_all(b"UNSUBSCRIBE")?;
                 writer.write_all(b" ")?;
-                mailbox.encode(writer)
+                mailbox.core_encode(writer)
             }
             CommandBody::List {
                 reference,
@@ -160,9 +373,9 @@ impl<'a> Encode for CommandBody<'a> {
             } => {
                 writer.write_all(b"LIST")?;
                 writer.write_all(b" ")?;
-                reference.encode(writer)?;
+                reference.core_encode(writer)?;
                 writer.write_all(b" ")?;
-                mailbox_wildcard.encode(writer)
+                mailbox_wildcard.core_encode(writer)
             }
             CommandBody::Lsub {
                 reference,
@@ -170,9 +383,9 @@ impl<'a> Encode for CommandBody<'a> {
             } => {
                 writer.write_all(b"LSUB")?;
                 writer.write_all(b" ")?;
-                reference.encode(writer)?;
+                reference.core_encode(writer)?;
                 writer.write_all(b" ")?;
-                mailbox_wildcard.encode(writer)
+                mailbox_wildcard.core_encode(writer)
             }
             CommandBody::Status {
                 mailbox,
@@ -180,7 +393,7 @@ impl<'a> Encode for CommandBody<'a> {
             } => {
                 writer.write_all(b"STATUS")?;
                 writer.write_all(b" ")?;
-                mailbox.encode(writer)?;
+                mailbox.core_encode(writer)?;
                 writer.write_all(b" ")?;
                 writer.write_all(b"(")?;
                 join_serializable(attributes, b" ", writer)?;
@@ -194,7 +407,7 @@ impl<'a> Encode for CommandBody<'a> {
             } => {
                 writer.write_all(b"APPEND")?;
                 writer.write_all(b" ")?;
-                mailbox.encode(writer)?;
+                mailbox.core_encode(writer)?;
 
                 if !flags.is_empty() {
                     writer.write_all(b" ")?;
@@ -205,11 +418,11 @@ impl<'a> Encode for CommandBody<'a> {
 
                 if let Some(date) = date {
                     writer.write_all(b" ")?;
-                    date.encode(writer)?;
+                    date.core_encode(writer)?;
                 }
 
                 writer.write_all(b" ")?;
-                message.encode(writer)
+                message.core_encode(writer)
             }
             CommandBody::Check => writer.write_all(b"CHECK"),
             CommandBody::Close => writer.write_all(b"CLOSE"),
@@ -226,10 +439,10 @@ impl<'a> Encode for CommandBody<'a> {
                 }
                 if let Some(charset) = charset {
                     writer.write_all(b" CHARSET ")?;
-                    charset.encode(writer)?;
+                    charset.core_encode(writer)?;
                 }
                 writer.write_all(b" ")?;
-                criteria.encode(writer)
+                criteria.core_encode(writer)
             }
             CommandBody::Fetch {
                 sequence_set,
@@ -242,9 +455,9 @@ impl<'a> Encode for CommandBody<'a> {
                     writer.write_all(b"FETCH ")?;
                 }
 
-                sequence_set.encode(writer)?;
+                sequence_set.core_encode(writer)?;
                 writer.write_all(b" ")?;
-                attributes.encode(writer)
+                attributes.core_encode(writer)
             }
             CommandBody::Store {
                 sequence_set,
@@ -259,7 +472,7 @@ impl<'a> Encode for CommandBody<'a> {
                     writer.write_all(b"STORE ")?;
                 }
 
-                sequence_set.encode(writer)?;
+                sequence_set.core_encode(writer)?;
                 writer.write_all(b" ")?;
 
                 match kind {
@@ -289,9 +502,9 @@ impl<'a> Encode for CommandBody<'a> {
                 } else {
                     writer.write_all(b"COPY ")?;
                 }
-                sequence_set.encode(writer)?;
+                sequence_set.core_encode(writer)?;
                 writer.write_all(b" ")?;
-                mailbox.encode(writer)
+                mailbox.core_encode(writer)
             }
             #[cfg(feature = "ext_idle")]
             CommandBody::Idle => writer.write_all(b"IDLE"),
@@ -303,22 +516,22 @@ impl<'a> Encode for CommandBody<'a> {
             #[cfg(feature = "ext_compress")]
             CommandBody::Compress { algorithm } => {
                 writer.write_all(b"COMPRESS ")?;
-                algorithm.encode(writer)
+                algorithm.core_encode(writer)
             }
             #[cfg(feature = "ext_quota")]
             CommandBody::GetQuota { root } => {
                 writer.write_all(b"GETQUOTA ")?;
-                root.encode(writer)
+                root.core_encode(writer)
             }
             #[cfg(feature = "ext_quota")]
             CommandBody::GetQuotaRoot { mailbox } => {
                 writer.write_all(b"GETQUOTAROOT ")?;
-                mailbox.encode(writer)
+                mailbox.core_encode(writer)
             }
             #[cfg(feature = "ext_quota")]
             CommandBody::SetQuota { root, quotas } => {
                 writer.write_all(b"SETQUOTA ")?;
-                root.encode(writer)?;
+                root.core_encode(writer)?;
                 writer.write_all(b" (")?;
                 join_serializable(quotas.as_ref(), b" ", writer)?;
                 writer.write_all(b")")
@@ -334,75 +547,79 @@ impl<'a> Encode for CommandBody<'a> {
                 } else {
                     writer.write_all(b"MOVE ")?;
                 }
-                sequence_set.encode(writer)?;
+                sequence_set.core_encode(writer)?;
                 writer.write_all(b" ")?;
-                mailbox.encode(writer)
+                mailbox.core_encode(writer)
             }
         }
     }
 }
 
-impl<'a> Encode for AuthMechanism<'a> {
-    fn encode(&self, writer: &mut impl Write) -> std::io::Result<()> {
+impl<'a> CoreEncode for AuthMechanism<'a> {
+    fn core_encode(&self, writer: &mut EncodeContext) -> std::io::Result<()> {
         match &self {
             AuthMechanism::Plain => writer.write_all(b"PLAIN"),
             AuthMechanism::Login => writer.write_all(b"LOGIN"),
-            AuthMechanism::Other(other) => other.encode(writer),
+            AuthMechanism::Other(other) => other.core_encode(writer),
         }
     }
 }
 
-impl<'a> Encode for AuthMechanismOther<'a> {
-    fn encode(&self, writer: &mut impl Write) -> std::io::Result<()> {
-        self.inner().encode(writer)
+impl<'a> CoreEncode for AuthMechanismOther<'a> {
+    fn core_encode(&self, writer: &mut EncodeContext) -> std::io::Result<()> {
+        self.inner().core_encode(writer)
     }
 }
 
-impl Encode for AuthenticateData {
-    fn encode(&self, writer: &mut impl Write) -> std::io::Result<()> {
+impl CoreEncode for AuthenticateData {
+    fn core_encode(&self, writer: &mut EncodeContext) -> std::io::Result<()> {
         let encoded = base64.encode(self.0.declassify());
         writer.write_all(encoded.as_bytes())
     }
 }
 
-impl<'a> Encode for AString<'a> {
-    fn encode(&self, writer: &mut impl Write) -> std::io::Result<()> {
+impl<'a> CoreEncode for AString<'a> {
+    fn core_encode(&self, writer: &mut EncodeContext) -> std::io::Result<()> {
         match self {
-            AString::Atom(atom) => atom.encode(writer),
-            AString::String(imap_str) => imap_str.encode(writer),
+            AString::Atom(atom) => atom.core_encode(writer),
+            AString::String(imap_str) => imap_str.core_encode(writer),
         }
     }
 }
 
-impl<'a> Encode for Atom<'a> {
-    fn encode(&self, writer: &mut impl Write) -> std::io::Result<()> {
+impl<'a> CoreEncode for Atom<'a> {
+    fn core_encode(&self, writer: &mut EncodeContext) -> std::io::Result<()> {
         writer.write_all(self.inner().as_bytes())
     }
 }
 
-impl<'a> Encode for AtomExt<'a> {
-    fn encode(&self, writer: &mut impl Write) -> std::io::Result<()> {
+impl<'a> CoreEncode for AtomExt<'a> {
+    fn core_encode(&self, writer: &mut EncodeContext) -> std::io::Result<()> {
         writer.write_all(self.inner().as_bytes())
     }
 }
 
-impl<'a> Encode for IString<'a> {
-    fn encode(&self, writer: &mut impl Write) -> std::io::Result<()> {
+impl<'a> CoreEncode for IString<'a> {
+    fn core_encode(&self, writer: &mut EncodeContext) -> std::io::Result<()> {
         match self {
-            Self::Literal(val) => val.encode(writer),
-            Self::Quoted(val) => val.encode(writer),
+            Self::Literal(val) => val.core_encode(writer),
+            Self::Quoted(val) => val.core_encode(writer),
         }
     }
 }
 
-impl<'a> Encode for Literal<'a> {
-    fn encode(&self, writer: &mut impl Write) -> std::io::Result<()> {
+impl<'a> CoreEncode for Literal<'a> {
+    fn core_encode(&self, writer: &mut EncodeContext) -> std::io::Result<()> {
         #[cfg(not(feature = "ext_literal"))]
-        write!(writer, "{{{}}}\r\n", self.as_ref().len())?;
+        {
+            write!(writer, "{{{}}}\r\n", self.as_ref().len())?;
+            writer.recv_continuation_request();
+        }
 
         #[cfg(feature = "ext_literal")]
         if self.sync {
             write!(writer, "{{{}}}\r\n", self.as_ref().len())?;
+            writer.recv_continuation_request();
         } else {
             write!(writer, "{{{}+}}\r\n", self.as_ref().len())?;
         }
@@ -411,44 +628,44 @@ impl<'a> Encode for Literal<'a> {
     }
 }
 
-impl<'a> Encode for Quoted<'a> {
-    fn encode(&self, writer: &mut impl Write) -> std::io::Result<()> {
+impl<'a> CoreEncode for Quoted<'a> {
+    fn core_encode(&self, writer: &mut EncodeContext) -> std::io::Result<()> {
         write!(writer, "\"{}\"", escape_quoted(self.inner()))
     }
 }
 
-impl<'a> Encode for Mailbox<'a> {
-    fn encode(&self, writer: &mut impl Write) -> std::io::Result<()> {
+impl<'a> CoreEncode for Mailbox<'a> {
+    fn core_encode(&self, writer: &mut EncodeContext) -> std::io::Result<()> {
         match self {
             Mailbox::Inbox => writer.write_all(b"INBOX"),
-            Mailbox::Other(other) => other.encode(writer),
+            Mailbox::Other(other) => other.core_encode(writer),
         }
     }
 }
 
-impl<'a> Encode for MailboxOther<'a> {
-    fn encode(&self, writer: &mut impl Write) -> std::io::Result<()> {
-        self.inner().encode(writer)
+impl<'a> CoreEncode for MailboxOther<'a> {
+    fn core_encode(&self, writer: &mut EncodeContext) -> std::io::Result<()> {
+        self.inner().core_encode(writer)
     }
 }
 
-impl<'a> Encode for ListMailbox<'a> {
-    fn encode(&self, writer: &mut impl Write) -> std::io::Result<()> {
+impl<'a> CoreEncode for ListMailbox<'a> {
+    fn core_encode(&self, writer: &mut EncodeContext) -> std::io::Result<()> {
         match self {
-            ListMailbox::Token(lcs) => lcs.encode(writer),
-            ListMailbox::String(istr) => istr.encode(writer),
+            ListMailbox::Token(lcs) => lcs.core_encode(writer),
+            ListMailbox::String(istr) => istr.core_encode(writer),
         }
     }
 }
 
-impl<'a> Encode for ListCharString<'a> {
-    fn encode(&self, writer: &mut impl Write) -> std::io::Result<()> {
+impl<'a> CoreEncode for ListCharString<'a> {
+    fn core_encode(&self, writer: &mut EncodeContext) -> std::io::Result<()> {
         writer.write_all(self.as_ref())
     }
 }
 
-impl Encode for StatusAttribute {
-    fn encode(&self, writer: &mut impl Write) -> std::io::Result<()> {
+impl CoreEncode for StatusAttribute {
+    fn core_encode(&self, writer: &mut EncodeContext) -> std::io::Result<()> {
         match self {
             StatusAttribute::Messages => writer.write_all(b"MESSAGES"),
             StatusAttribute::Recent => writer.write_all(b"RECENT"),
@@ -465,160 +682,160 @@ impl Encode for StatusAttribute {
     }
 }
 
-impl<'a> Encode for Flag<'a> {
-    fn encode(&self, writer: &mut impl Write) -> std::io::Result<()> {
+impl<'a> CoreEncode for Flag<'a> {
+    fn core_encode(&self, writer: &mut EncodeContext) -> std::io::Result<()> {
         match self {
             Flag::Seen => writer.write_all(b"\\Seen"),
             Flag::Answered => writer.write_all(b"\\Answered"),
             Flag::Flagged => writer.write_all(b"\\Flagged"),
             Flag::Deleted => writer.write_all(b"\\Deleted"),
             Flag::Draft => writer.write_all(b"\\Draft"),
-            Flag::Extension(other) => other.encode(writer),
-            Flag::Keyword(atom) => atom.encode(writer),
+            Flag::Extension(other) => other.core_encode(writer),
+            Flag::Keyword(atom) => atom.core_encode(writer),
         }
     }
 }
 
-impl<'a> Encode for FlagFetch<'a> {
-    fn encode(&self, writer: &mut impl Write) -> std::io::Result<()> {
+impl<'a> CoreEncode for FlagFetch<'a> {
+    fn core_encode(&self, writer: &mut EncodeContext) -> std::io::Result<()> {
         match self {
-            Self::Flag(flag) => flag.encode(writer),
+            Self::Flag(flag) => flag.core_encode(writer),
             Self::Recent => writer.write_all(b"\\Recent"),
         }
     }
 }
 
-impl<'a> Encode for FlagPerm<'a> {
-    fn encode(&self, writer: &mut impl Write) -> std::io::Result<()> {
+impl<'a> CoreEncode for FlagPerm<'a> {
+    fn core_encode(&self, writer: &mut EncodeContext) -> std::io::Result<()> {
         match self {
-            Self::Flag(flag) => flag.encode(writer),
+            Self::Flag(flag) => flag.core_encode(writer),
             Self::AllowNewKeywords => writer.write_all(b"\\*"),
         }
     }
 }
 
-impl<'a> Encode for FlagExtension<'a> {
-    fn encode(&self, writer: &mut impl Write) -> std::io::Result<()> {
+impl<'a> CoreEncode for FlagExtension<'a> {
+    fn core_encode(&self, writer: &mut EncodeContext) -> std::io::Result<()> {
         writer.write_all(b"\\")?;
         writer.write_all(self.as_ref().as_bytes())
     }
 }
 
-impl Encode for DateTime {
-    fn encode(&self, writer: &mut impl Write) -> std::io::Result<()> {
-        self.as_ref().encode(writer)
+impl CoreEncode for DateTime {
+    fn core_encode(&self, writer: &mut EncodeContext) -> std::io::Result<()> {
+        self.as_ref().core_encode(writer)
     }
 }
 
-impl<'a> Encode for Charset<'a> {
-    fn encode(&self, writer: &mut impl Write) -> std::io::Result<()> {
+impl<'a> CoreEncode for Charset<'a> {
+    fn core_encode(&self, writer: &mut EncodeContext) -> std::io::Result<()> {
         match self {
-            Charset::Atom(atom) => atom.encode(writer),
-            Charset::Quoted(quoted) => quoted.encode(writer),
+            Charset::Atom(atom) => atom.core_encode(writer),
+            Charset::Quoted(quoted) => quoted.core_encode(writer),
         }
     }
 }
 
-impl<'a> Encode for SearchKey<'a> {
-    fn encode(&self, writer: &mut impl Write) -> std::io::Result<()> {
+impl<'a> CoreEncode for SearchKey<'a> {
+    fn core_encode(&self, writer: &mut EncodeContext) -> std::io::Result<()> {
         match self {
             SearchKey::All => writer.write_all(b"ALL"),
             SearchKey::Answered => writer.write_all(b"ANSWERED"),
             SearchKey::Bcc(astring) => {
                 writer.write_all(b"BCC ")?;
-                astring.encode(writer)
+                astring.core_encode(writer)
             }
             SearchKey::Before(date) => {
                 writer.write_all(b"BEFORE ")?;
-                date.encode(writer)
+                date.core_encode(writer)
             }
             SearchKey::Body(astring) => {
                 writer.write_all(b"BODY ")?;
-                astring.encode(writer)
+                astring.core_encode(writer)
             }
             SearchKey::Cc(astring) => {
                 writer.write_all(b"CC ")?;
-                astring.encode(writer)
+                astring.core_encode(writer)
             }
             SearchKey::Deleted => writer.write_all(b"DELETED"),
             SearchKey::Flagged => writer.write_all(b"FLAGGED"),
             SearchKey::From(astring) => {
                 writer.write_all(b"FROM ")?;
-                astring.encode(writer)
+                astring.core_encode(writer)
             }
             SearchKey::Keyword(flag_keyword) => {
                 writer.write_all(b"KEYWORD ")?;
-                flag_keyword.encode(writer)
+                flag_keyword.core_encode(writer)
             }
             SearchKey::New => writer.write_all(b"NEW"),
             SearchKey::Old => writer.write_all(b"OLD"),
             SearchKey::On(date) => {
                 writer.write_all(b"ON ")?;
-                date.encode(writer)
+                date.core_encode(writer)
             }
             SearchKey::Recent => writer.write_all(b"RECENT"),
             SearchKey::Seen => writer.write_all(b"SEEN"),
             SearchKey::Since(date) => {
                 writer.write_all(b"SINCE ")?;
-                date.encode(writer)
+                date.core_encode(writer)
             }
             SearchKey::Subject(astring) => {
                 writer.write_all(b"SUBJECT ")?;
-                astring.encode(writer)
+                astring.core_encode(writer)
             }
             SearchKey::Text(astring) => {
                 writer.write_all(b"TEXT ")?;
-                astring.encode(writer)
+                astring.core_encode(writer)
             }
             SearchKey::To(astring) => {
                 writer.write_all(b"TO ")?;
-                astring.encode(writer)
+                astring.core_encode(writer)
             }
             SearchKey::Unanswered => writer.write_all(b"UNANSWERED"),
             SearchKey::Undeleted => writer.write_all(b"UNDELETED"),
             SearchKey::Unflagged => writer.write_all(b"UNFLAGGED"),
             SearchKey::Unkeyword(flag_keyword) => {
                 writer.write_all(b"UNKEYWORD ")?;
-                flag_keyword.encode(writer)
+                flag_keyword.core_encode(writer)
             }
             SearchKey::Unseen => writer.write_all(b"UNSEEN"),
             SearchKey::Draft => writer.write_all(b"DRAFT"),
             SearchKey::Header(header_fld_name, astring) => {
                 writer.write_all(b"HEADER ")?;
-                header_fld_name.encode(writer)?;
+                header_fld_name.core_encode(writer)?;
                 writer.write_all(b" ")?;
-                astring.encode(writer)
+                astring.core_encode(writer)
             }
             SearchKey::Larger(number) => write!(writer, "LARGER {number}"),
             SearchKey::Not(search_key) => {
                 writer.write_all(b"NOT ")?;
-                search_key.encode(writer)
+                search_key.core_encode(writer)
             }
             SearchKey::Or(search_key_a, search_key_b) => {
                 writer.write_all(b"OR ")?;
-                search_key_a.encode(writer)?;
+                search_key_a.core_encode(writer)?;
                 writer.write_all(b" ")?;
-                search_key_b.encode(writer)
+                search_key_b.core_encode(writer)
             }
             SearchKey::SentBefore(date) => {
                 writer.write_all(b"SENTBEFORE ")?;
-                date.encode(writer)
+                date.core_encode(writer)
             }
             SearchKey::SentOn(date) => {
                 writer.write_all(b"SENTON ")?;
-                date.encode(writer)
+                date.core_encode(writer)
             }
             SearchKey::SentSince(date) => {
                 writer.write_all(b"SENTSINCE ")?;
-                date.encode(writer)
+                date.core_encode(writer)
             }
             SearchKey::Smaller(number) => write!(writer, "SMALLER {number}"),
             SearchKey::Uid(sequence_set) => {
                 writer.write_all(b"UID ")?;
-                sequence_set.encode(writer)
+                sequence_set.core_encode(writer)
             }
             SearchKey::Undraft => writer.write_all(b"UNDRAFT"),
-            SearchKey::SequenceSet(sequence_set) => sequence_set.encode(writer),
+            SearchKey::SequenceSet(sequence_set) => sequence_set.core_encode(writer),
             SearchKey::And(search_keys) => {
                 writer.write_all(b"(")?;
                 join_serializable(search_keys.as_ref(), b" ", writer)?;
@@ -628,27 +845,27 @@ impl<'a> Encode for SearchKey<'a> {
     }
 }
 
-impl Encode for SequenceSet {
-    fn encode(&self, writer: &mut impl Write) -> std::io::Result<()> {
+impl CoreEncode for SequenceSet {
+    fn core_encode(&self, writer: &mut EncodeContext) -> std::io::Result<()> {
         join_serializable(self.0.as_ref(), b",", writer)
     }
 }
 
-impl Encode for Sequence {
-    fn encode(&self, writer: &mut impl Write) -> std::io::Result<()> {
+impl CoreEncode for Sequence {
+    fn core_encode(&self, writer: &mut EncodeContext) -> std::io::Result<()> {
         match self {
-            Sequence::Single(seq_no) => seq_no.encode(writer),
+            Sequence::Single(seq_no) => seq_no.core_encode(writer),
             Sequence::Range(from, to) => {
-                from.encode(writer)?;
+                from.core_encode(writer)?;
                 writer.write_all(b":")?;
-                to.encode(writer)
+                to.core_encode(writer)
             }
         }
     }
 }
 
-impl Encode for SeqOrUid {
-    fn encode(&self, writer: &mut impl Write) -> std::io::Result<()> {
+impl CoreEncode for SeqOrUid {
+    fn core_encode(&self, writer: &mut EncodeContext) -> std::io::Result<()> {
         match self {
             SeqOrUid::Value(number) => write!(writer, "{number}"),
             SeqOrUid::Asterisk => writer.write_all(b"*"),
@@ -656,19 +873,19 @@ impl Encode for SeqOrUid {
     }
 }
 
-impl Encode for NaiveDate {
-    fn encode(&self, writer: &mut impl Write) -> std::io::Result<()> {
+impl CoreEncode for NaiveDate {
+    fn core_encode(&self, writer: &mut EncodeContext) -> std::io::Result<()> {
         write!(writer, "\"{}\"", self.as_ref().format("%d-%b-%Y"))
     }
 }
 
-impl<'a> Encode for MacroOrFetchAttributes<'a> {
-    fn encode(&self, writer: &mut impl Write) -> std::io::Result<()> {
+impl<'a> CoreEncode for MacroOrFetchAttributes<'a> {
+    fn core_encode(&self, writer: &mut EncodeContext) -> std::io::Result<()> {
         match self {
-            MacroOrFetchAttributes::Macro(m) => m.encode(writer),
+            MacroOrFetchAttributes::Macro(m) => m.core_encode(writer),
             MacroOrFetchAttributes::FetchAttributes(attributes) => {
                 if attributes.len() == 1 {
-                    attributes[0].encode(writer)
+                    attributes[0].core_encode(writer)
                 } else {
                     writer.write_all(b"(")?;
                     join_serializable(attributes.as_slice(), b" ", writer)?;
@@ -679,8 +896,8 @@ impl<'a> Encode for MacroOrFetchAttributes<'a> {
     }
 }
 
-impl Encode for Macro {
-    fn encode(&self, writer: &mut impl Write) -> std::io::Result<()> {
+impl CoreEncode for Macro {
+    fn core_encode(&self, writer: &mut EncodeContext) -> std::io::Result<()> {
         match self {
             Macro::All => writer.write_all(b"ALL"),
             Macro::Fast => writer.write_all(b"FAST"),
@@ -689,8 +906,8 @@ impl Encode for Macro {
     }
 }
 
-impl<'a> Encode for FetchAttribute<'a> {
-    fn encode(&self, writer: &mut impl Write) -> std::io::Result<()> {
+impl<'a> CoreEncode for FetchAttribute<'a> {
+    fn core_encode(&self, writer: &mut EncodeContext) -> std::io::Result<()> {
         match self {
             FetchAttribute::Body => writer.write_all(b"BODY"),
             FetchAttribute::BodyExt {
@@ -704,7 +921,7 @@ impl<'a> Encode for FetchAttribute<'a> {
                     writer.write_all(b"BODY[")?;
                 }
                 if let Some(section) = section {
-                    section.encode(writer)?;
+                    section.core_encode(writer)?;
                 }
                 writer.write_all(b"]")?;
                 if let Some((a, b)) = partial {
@@ -726,13 +943,13 @@ impl<'a> Encode for FetchAttribute<'a> {
     }
 }
 
-impl<'a> Encode for Section<'a> {
-    fn encode(&self, writer: &mut impl Write) -> std::io::Result<()> {
+impl<'a> CoreEncode for Section<'a> {
+    fn core_encode(&self, writer: &mut EncodeContext) -> std::io::Result<()> {
         match self {
-            Section::Part(part) => part.encode(writer),
+            Section::Part(part) => part.core_encode(writer),
             Section::Header(maybe_part) => match maybe_part {
                 Some(part) => {
-                    part.encode(writer)?;
+                    part.core_encode(writer)?;
                     writer.write_all(b".HEADER")
                 }
                 None => writer.write_all(b"HEADER"),
@@ -740,7 +957,7 @@ impl<'a> Encode for Section<'a> {
             Section::HeaderFields(maybe_part, header_list) => {
                 match maybe_part {
                     Some(part) => {
-                        part.encode(writer)?;
+                        part.core_encode(writer)?;
                         writer.write_all(b".HEADER.FIELDS (")?;
                     }
                     None => writer.write_all(b"HEADER.FIELDS (")?,
@@ -751,7 +968,7 @@ impl<'a> Encode for Section<'a> {
             Section::HeaderFieldsNot(maybe_part, header_list) => {
                 match maybe_part {
                     Some(part) => {
-                        part.encode(writer)?;
+                        part.core_encode(writer)?;
                         writer.write_all(b".HEADER.FIELDS.NOT (")?;
                     }
                     None => writer.write_all(b"HEADER.FIELDS.NOT (")?,
@@ -761,33 +978,33 @@ impl<'a> Encode for Section<'a> {
             }
             Section::Text(maybe_part) => match maybe_part {
                 Some(part) => {
-                    part.encode(writer)?;
+                    part.core_encode(writer)?;
                     writer.write_all(b".TEXT")
                 }
                 None => writer.write_all(b"TEXT"),
             },
             Section::Mime(part) => {
-                part.encode(writer)?;
+                part.core_encode(writer)?;
                 writer.write_all(b".MIME")
             }
         }
     }
 }
 
-impl Encode for Part {
-    fn encode(&self, writer: &mut impl Write) -> std::io::Result<()> {
+impl CoreEncode for Part {
+    fn core_encode(&self, writer: &mut EncodeContext) -> std::io::Result<()> {
         join_serializable(self.0.as_ref(), b".", writer)
     }
 }
 
-impl Encode for NonZeroU32 {
-    fn encode(&self, writer: &mut impl Write) -> std::io::Result<()> {
+impl CoreEncode for NonZeroU32 {
+    fn core_encode(&self, writer: &mut EncodeContext) -> std::io::Result<()> {
         write!(writer, "{self}")
     }
 }
 
-impl<'a> Encode for Capability<'a> {
-    fn encode(&self, writer: &mut impl Write) -> std::io::Result<()> {
+impl<'a> CoreEncode for Capability<'a> {
+    fn core_encode(&self, writer: &mut EncodeContext) -> std::io::Result<()> {
         match self {
             Self::Imap4Rev1 => writer.write_all(b"IMAP4REV1"),
             Self::Auth(mechanism) => match mechanism {
@@ -795,7 +1012,7 @@ impl<'a> Encode for Capability<'a> {
                 AuthMechanism::Login => writer.write_all(b"AUTH=LOGIN"),
                 AuthMechanism::Other(other) => {
                     writer.write_all(b"AUTH=")?;
-                    other.encode(writer)
+                    other.core_encode(writer)
                 }
             },
             #[cfg(feature = "starttls")]
@@ -821,50 +1038,50 @@ impl<'a> Encode for Capability<'a> {
             #[cfg(feature = "ext_quota")]
             Self::QuotaRes(resource) => {
                 writer.write_all(b"QUOTA=RES-")?;
-                resource.encode(writer)
+                resource.core_encode(writer)
             }
             #[cfg(feature = "ext_quota")]
             Self::QuotaSet => writer.write_all(b"QUOTASET"),
             #[cfg(feature = "ext_literal")]
-            Self::Literal(literal_capability) => literal_capability.encode(writer),
+            Self::Literal(literal_capability) => literal_capability.core_encode(writer),
             #[cfg(feature = "ext_move")]
             Self::Move => writer.write_all(b"MOVE"),
-            Self::Other(other) => other.inner().encode(writer),
+            Self::Other(other) => other.inner().core_encode(writer),
         }
     }
 }
 
-// ----- Responses -----
+// ----- Responses ---------------------------------------------------------------------------------
 
-impl<'a> Encode for Response<'a> {
-    fn encode(&self, writer: &mut impl Write) -> std::io::Result<()> {
+impl<'a> CoreEncode for Response<'a> {
+    fn core_encode(&self, writer: &mut EncodeContext) -> std::io::Result<()> {
         match self {
-            Response::Status(status) => status.encode(writer),
-            Response::Data(data) => data.encode(writer),
-            Response::Continue(continue_request) => continue_request.encode(writer),
+            Response::Status(status) => status.core_encode(writer),
+            Response::Data(data) => data.core_encode(writer),
+            Response::Continue(continue_request) => continue_request.core_encode(writer),
         }
     }
 }
 
-impl<'a> Encode for Greeting<'a> {
-    fn encode(&self, writer: &mut impl Write) -> std::io::Result<()> {
+impl<'a> CoreEncode for Greeting<'a> {
+    fn core_encode(&self, writer: &mut EncodeContext) -> std::io::Result<()> {
         writer.write_all(b"* ")?;
-        self.kind.encode(writer)?;
+        self.kind.core_encode(writer)?;
         writer.write_all(b" ")?;
 
         if let Some(ref code) = self.code {
             writer.write_all(b"[")?;
-            code.encode(writer)?;
+            code.core_encode(writer)?;
             writer.write_all(b"] ")?;
         }
 
-        self.text.encode(writer)?;
+        self.text.core_encode(writer)?;
         writer.write_all(b"\r\n")
     }
 }
 
-impl Encode for GreetingKind {
-    fn encode(&self, writer: &mut impl Write) -> std::io::Result<()> {
+impl CoreEncode for GreetingKind {
+    fn core_encode(&self, writer: &mut EncodeContext) -> std::io::Result<()> {
         match self {
             GreetingKind::Ok => writer.write_all(b"OK"),
             GreetingKind::PreAuth => writer.write_all(b"PREAUTH"),
@@ -873,17 +1090,17 @@ impl Encode for GreetingKind {
     }
 }
 
-impl<'a> Encode for Status<'a> {
-    fn encode(&self, writer: &mut impl Write) -> std::io::Result<()> {
+impl<'a> CoreEncode for Status<'a> {
+    fn core_encode(&self, writer: &mut EncodeContext) -> std::io::Result<()> {
         fn format_status(
             tag: &Option<Tag>,
             status: &str,
             code: &Option<Code>,
             comment: &Text,
-            writer: &mut impl Write,
+            writer: &mut EncodeContext,
         ) -> std::io::Result<()> {
             match tag {
-                Some(tag) => tag.encode(writer)?,
+                Some(tag) => tag.core_encode(writer)?,
                 None => writer.write_all(b"*")?,
             }
             writer.write_all(b" ")?;
@@ -891,10 +1108,10 @@ impl<'a> Encode for Status<'a> {
             writer.write_all(b" ")?;
             if let Some(code) = code {
                 writer.write_all(b"[")?;
-                code.encode(writer)?;
+                code.core_encode(writer)?;
                 writer.write_all(b"] ")?;
             }
-            comment.encode(writer)?;
+            comment.core_encode(writer)?;
             writer.write_all(b"\r\n")
         }
 
@@ -907,8 +1124,8 @@ impl<'a> Encode for Status<'a> {
     }
 }
 
-impl<'a> Encode for Code<'a> {
-    fn encode(&self, writer: &mut impl Write) -> std::io::Result<()> {
+impl<'a> CoreEncode for Code<'a> {
+    fn core_encode(&self, writer: &mut EncodeContext) -> std::io::Result<()> {
         match self {
             Code::Alert => writer.write_all(b"ALERT"),
             Code::BadCharset { allowed } => {
@@ -935,15 +1152,15 @@ impl<'a> Encode for Code<'a> {
             Code::TryCreate => writer.write_all(b"TRYCREATE"),
             Code::UidNext(next) => {
                 writer.write_all(b"UIDNEXT ")?;
-                next.encode(writer)
+                next.core_encode(writer)
             }
             Code::UidValidity(validity) => {
                 writer.write_all(b"UIDVALIDITY ")?;
-                validity.encode(writer)
+                validity.core_encode(writer)
             }
             Code::Unseen(seq) => {
                 writer.write_all(b"UNSEEN ")?;
-                seq.encode(writer)
+                seq.core_encode(writer)
             }
             // RFC 2221
             #[cfg(any(feature = "ext_login_referrals", feature = "ext_mailbox_referrals"))]
@@ -957,25 +1174,25 @@ impl<'a> Encode for Code<'a> {
             Code::OverQuota => writer.write_all(b"OVERQUOTA"),
             #[cfg(feature = "ext_literal")]
             Code::TooBig => writer.write_all(b"TOOBIG"),
-            Code::Other(unknown) => unknown.encode(writer),
+            Code::Other(unknown) => unknown.core_encode(writer),
         }
     }
 }
 
-impl<'a> Encode for CodeOther<'a> {
-    fn encode(&self, writer: &mut impl Write) -> std::io::Result<()> {
+impl<'a> CoreEncode for CodeOther<'a> {
+    fn core_encode(&self, writer: &mut EncodeContext) -> std::io::Result<()> {
         writer.write_all(self.inner())
     }
 }
 
-impl<'a> Encode for Text<'a> {
-    fn encode(&self, writer: &mut impl Write) -> std::io::Result<()> {
+impl<'a> CoreEncode for Text<'a> {
+    fn core_encode(&self, writer: &mut EncodeContext) -> std::io::Result<()> {
         writer.write_all(self.inner().as_bytes())
     }
 }
 
-impl<'a> Encode for Data<'a> {
-    fn encode(&self, writer: &mut impl Write) -> std::io::Result<()> {
+impl<'a> CoreEncode for Data<'a> {
+    fn core_encode(&self, writer: &mut EncodeContext) -> std::io::Result<()> {
         match self {
             Data::Capability(caps) => {
                 writer.write_all(b"* CAPABILITY ")?;
@@ -992,13 +1209,13 @@ impl<'a> Encode for Data<'a> {
 
                 if let Some(delimiter) = delimiter {
                     writer.write_all(b"\"")?;
-                    delimiter.encode(writer)?;
+                    delimiter.core_encode(writer)?;
                     writer.write_all(b"\"")?;
                 } else {
                     writer.write_all(b"NIL")?;
                 }
                 writer.write_all(b" ")?;
-                mailbox.encode(writer)?;
+                mailbox.core_encode(writer)?;
             }
             Data::Lsub {
                 items,
@@ -1011,20 +1228,20 @@ impl<'a> Encode for Data<'a> {
 
                 if let Some(delimiter) = delimiter {
                     writer.write_all(b"\"")?;
-                    delimiter.encode(writer)?;
+                    delimiter.core_encode(writer)?;
                     writer.write_all(b"\"")?;
                 } else {
                     writer.write_all(b"NIL")?;
                 }
                 writer.write_all(b" ")?;
-                mailbox.encode(writer)?;
+                mailbox.core_encode(writer)?;
             }
             Data::Status {
                 mailbox,
                 attributes,
             } => {
                 writer.write_all(b"* STATUS ")?;
-                mailbox.encode(writer)?;
+                mailbox.core_encode(writer)?;
                 writer.write_all(b" (")?;
                 join_serializable(attributes, b" ", writer)?;
                 writer.write_all(b")")?;
@@ -1059,13 +1276,13 @@ impl<'a> Encode for Data<'a> {
 
                 for cap in capabilities {
                     writer.write_all(b" ")?;
-                    cap.encode(writer)?;
+                    cap.core_encode(writer)?;
                 }
             }
             #[cfg(feature = "ext_quota")]
             Data::Quota { root, quotas } => {
                 writer.write_all(b"* QUOTA ")?;
-                root.encode(writer)?;
+                root.core_encode(writer)?;
                 writer.write_all(b" (")?;
                 join_serializable(quotas.as_ref(), b" ", writer)?;
                 writer.write_all(b")")?;
@@ -1073,10 +1290,10 @@ impl<'a> Encode for Data<'a> {
             #[cfg(feature = "ext_quota")]
             Data::QuotaRoot { mailbox, roots } => {
                 writer.write_all(b"* QUOTAROOT ")?;
-                mailbox.encode(writer)?;
+                mailbox.core_encode(writer)?;
                 for root in roots {
                     writer.write_all(b" ")?;
-                    root.encode(writer)?;
+                    root.core_encode(writer)?;
                 }
             }
         }
@@ -1085,8 +1302,8 @@ impl<'a> Encode for Data<'a> {
     }
 }
 
-impl<'a> Encode for FlagNameAttribute<'a> {
-    fn encode(&self, writer: &mut impl Write) -> std::io::Result<()> {
+impl<'a> CoreEncode for FlagNameAttribute<'a> {
+    fn core_encode(&self, writer: &mut EncodeContext) -> std::io::Result<()> {
         match self {
             Self::Noinferiors => writer.write_all(b"\\Noinferiors"),
             Self::Noselect => writer.write_all(b"\\Noselect"),
@@ -1094,14 +1311,14 @@ impl<'a> Encode for FlagNameAttribute<'a> {
             Self::Unmarked => writer.write_all(b"\\Unmarked"),
             Self::Extension(atom) => {
                 writer.write_all(b"\\")?;
-                atom.encode(writer)
+                atom.core_encode(writer)
             }
         }
     }
 }
 
-impl Encode for QuotedChar {
-    fn encode(&self, writer: &mut impl Write) -> std::io::Result<()> {
+impl CoreEncode for QuotedChar {
+    fn core_encode(&self, writer: &mut EncodeContext) -> std::io::Result<()> {
         match self.inner() {
             '\\' => writer.write_all(b"\\\\"),
             '"' => writer.write_all(b"\\\""),
@@ -1110,45 +1327,45 @@ impl Encode for QuotedChar {
     }
 }
 
-impl Encode for StatusAttributeValue {
-    fn encode(&self, writer: &mut impl Write) -> std::io::Result<()> {
+impl CoreEncode for StatusAttributeValue {
+    fn core_encode(&self, writer: &mut EncodeContext) -> std::io::Result<()> {
         match self {
             Self::Messages(count) => {
                 writer.write_all(b"MESSAGES ")?;
-                count.encode(writer)
+                count.core_encode(writer)
             }
             Self::Recent(count) => {
                 writer.write_all(b"RECENT ")?;
-                count.encode(writer)
+                count.core_encode(writer)
             }
             Self::UidNext(next) => {
                 writer.write_all(b"UIDNEXT ")?;
-                next.encode(writer)
+                next.core_encode(writer)
             }
             Self::UidValidity(identifier) => {
                 writer.write_all(b"UIDVALIDITY ")?;
-                identifier.encode(writer)
+                identifier.core_encode(writer)
             }
             Self::Unseen(count) => {
                 writer.write_all(b"UNSEEN ")?;
-                count.encode(writer)
+                count.core_encode(writer)
             }
             #[cfg(feature = "ext_quota")]
             Self::Deleted(count) => {
                 writer.write_all(b"DELETED ")?;
-                count.encode(writer)
+                count.core_encode(writer)
             }
             #[cfg(feature = "ext_quota")]
             Self::DeletedStorage(count) => {
                 writer.write_all(b"DELETED-STORAGE ")?;
-                count.encode(writer)
+                count.core_encode(writer)
             }
         }
     }
 }
 
-impl<'a> Encode for FetchAttributeValue<'a> {
-    fn encode(&self, writer: &mut impl Write) -> std::io::Result<()> {
+impl<'a> CoreEncode for FetchAttributeValue<'a> {
+    fn core_encode(&self, writer: &mut EncodeContext) -> std::io::Result<()> {
         match self {
             Self::BodyExt {
                 section,
@@ -1157,27 +1374,27 @@ impl<'a> Encode for FetchAttributeValue<'a> {
             } => {
                 writer.write_all(b"BODY[")?;
                 if let Some(section) = section {
-                    section.encode(writer)?;
+                    section.core_encode(writer)?;
                 }
                 writer.write_all(b"]")?;
                 if let Some(origin) = origin {
                     write!(writer, "<{origin}>")?;
                 }
                 writer.write_all(b" ")?;
-                data.encode(writer)
+                data.core_encode(writer)
             }
             // FIXME: do not return body-ext-1part and body-ext-mpart here
             Self::Body(body) => {
                 writer.write_all(b"BODY ")?;
-                body.encode(writer)
+                body.core_encode(writer)
             }
             Self::BodyStructure(body) => {
                 writer.write_all(b"BODYSTRUCTURE ")?;
-                body.encode(writer)
+                body.core_encode(writer)
             }
             Self::Envelope(envelope) => {
                 writer.write_all(b"ENVELOPE ")?;
-                envelope.encode(writer)
+                envelope.core_encode(writer)
             }
             Self::Flags(flags) => {
                 writer.write_all(b"FLAGS (")?;
@@ -1186,47 +1403,47 @@ impl<'a> Encode for FetchAttributeValue<'a> {
             }
             Self::InternalDate(datetime) => {
                 writer.write_all(b"INTERNALDATE ")?;
-                datetime.encode(writer)
+                datetime.core_encode(writer)
             }
             Self::Rfc822(nstring) => {
                 writer.write_all(b"RFC822 ")?;
-                nstring.encode(writer)
+                nstring.core_encode(writer)
             }
             Self::Rfc822Header(nstring) => {
                 writer.write_all(b"RFC822.HEADER ")?;
-                nstring.encode(writer)
+                nstring.core_encode(writer)
             }
             Self::Rfc822Size(size) => write!(writer, "RFC822.SIZE {size}"),
             Self::Rfc822Text(nstring) => {
                 writer.write_all(b"RFC822.TEXT ")?;
-                nstring.encode(writer)
+                nstring.core_encode(writer)
             }
             Self::Uid(uid) => write!(writer, "UID {uid}"),
         }
     }
 }
 
-impl<'a> Encode for NString<'a> {
-    fn encode(&self, writer: &mut impl Write) -> std::io::Result<()> {
+impl<'a> CoreEncode for NString<'a> {
+    fn core_encode(&self, writer: &mut EncodeContext) -> std::io::Result<()> {
         match &self.0 {
-            Some(imap_str) => imap_str.encode(writer),
+            Some(imap_str) => imap_str.core_encode(writer),
             None => writer.write_all(b"NIL"),
         }
     }
 }
 
-impl<'a> Encode for BodyStructure<'a> {
-    fn encode(&self, writer: &mut impl Write) -> std::io::Result<()> {
+impl<'a> CoreEncode for BodyStructure<'a> {
+    fn core_encode(&self, writer: &mut EncodeContext) -> std::io::Result<()> {
         writer.write_all(b"(")?;
         match self {
             BodyStructure::Single {
                 body,
                 extension_data: extension,
             } => {
-                body.encode(writer)?;
+                body.core_encode(writer)?;
                 if let Some(extension) = extension {
                     writer.write_all(b" ")?;
-                    extension.encode(writer)?;
+                    extension.core_encode(writer)?;
                 }
             }
             BodyStructure::Multi {
@@ -1235,14 +1452,14 @@ impl<'a> Encode for BodyStructure<'a> {
                 extension_data,
             } => {
                 for body in bodies.as_ref() {
-                    body.encode(writer)?;
+                    body.core_encode(writer)?;
                 }
                 writer.write_all(b" ")?;
-                subtype.encode(writer)?;
+                subtype.core_encode(writer)?;
 
                 if let Some(extension) = extension_data {
                     writer.write_all(b" ")?;
-                    extension.encode(writer)?;
+                    extension.core_encode(writer)?;
                 }
             }
         }
@@ -1250,18 +1467,18 @@ impl<'a> Encode for BodyStructure<'a> {
     }
 }
 
-impl<'a> Encode for Body<'a> {
-    fn encode(&self, writer: &mut impl Write) -> std::io::Result<()> {
+impl<'a> CoreEncode for Body<'a> {
+    fn core_encode(&self, writer: &mut EncodeContext) -> std::io::Result<()> {
         match self.specific {
             SpecificFields::Basic {
                 ref type_,
                 ref subtype,
             } => {
-                type_.encode(writer)?;
+                type_.core_encode(writer)?;
                 writer.write_all(b" ")?;
-                subtype.encode(writer)?;
+                subtype.core_encode(writer)?;
                 writer.write_all(b" ")?;
-                self.basic.encode(writer)
+                self.basic.core_encode(writer)
             }
             SpecificFields::Message {
                 ref envelope,
@@ -1269,11 +1486,11 @@ impl<'a> Encode for Body<'a> {
                 number_of_lines,
             } => {
                 writer.write_all(b"\"MESSAGE\" \"RFC822\" ")?;
-                self.basic.encode(writer)?;
+                self.basic.core_encode(writer)?;
                 writer.write_all(b" ")?;
-                envelope.encode(writer)?;
+                envelope.core_encode(writer)?;
                 writer.write_all(b" ")?;
-                body_structure.encode(writer)?;
+                body_structure.core_encode(writer)?;
                 writer.write_all(b" ")?;
                 write!(writer, "{number_of_lines}")
             }
@@ -1282,9 +1499,9 @@ impl<'a> Encode for Body<'a> {
                 number_of_lines,
             } => {
                 writer.write_all(b"\"TEXT\" ")?;
-                subtype.encode(writer)?;
+                subtype.core_encode(writer)?;
                 writer.write_all(b" ")?;
-                self.basic.encode(writer)?;
+                self.basic.core_encode(writer)?;
                 writer.write_all(b" ")?;
                 write!(writer, "{number_of_lines}")
             }
@@ -1292,96 +1509,96 @@ impl<'a> Encode for Body<'a> {
     }
 }
 
-impl<'a> Encode for BasicFields<'a> {
-    fn encode(&self, writer: &mut impl Write) -> std::io::Result<()> {
-        List1AttributeValueOrNil(&self.parameter_list).encode(writer)?;
+impl<'a> CoreEncode for BasicFields<'a> {
+    fn core_encode(&self, writer: &mut EncodeContext) -> std::io::Result<()> {
+        List1AttributeValueOrNil(&self.parameter_list).core_encode(writer)?;
         writer.write_all(b" ")?;
-        self.id.encode(writer)?;
+        self.id.core_encode(writer)?;
         writer.write_all(b" ")?;
-        self.description.encode(writer)?;
+        self.description.core_encode(writer)?;
         writer.write_all(b" ")?;
-        self.content_transfer_encoding.encode(writer)?;
+        self.content_transfer_encoding.core_encode(writer)?;
         writer.write_all(b" ")?;
         write!(writer, "{}", self.size)
     }
 }
 
-impl<'a> Encode for Envelope<'a> {
-    fn encode(&self, writer: &mut impl Write) -> std::io::Result<()> {
+impl<'a> CoreEncode for Envelope<'a> {
+    fn core_encode(&self, writer: &mut EncodeContext) -> std::io::Result<()> {
         writer.write_all(b"(")?;
-        self.date.encode(writer)?;
+        self.date.core_encode(writer)?;
         writer.write_all(b" ")?;
-        self.subject.encode(writer)?;
+        self.subject.core_encode(writer)?;
         writer.write_all(b" ")?;
-        List1OrNil(&self.from, b"").encode(writer)?;
+        List1OrNil(&self.from, b"").core_encode(writer)?;
         writer.write_all(b" ")?;
-        List1OrNil(&self.sender, b"").encode(writer)?;
+        List1OrNil(&self.sender, b"").core_encode(writer)?;
         writer.write_all(b" ")?;
-        List1OrNil(&self.reply_to, b"").encode(writer)?;
+        List1OrNil(&self.reply_to, b"").core_encode(writer)?;
         writer.write_all(b" ")?;
-        List1OrNil(&self.to, b"").encode(writer)?;
+        List1OrNil(&self.to, b"").core_encode(writer)?;
         writer.write_all(b" ")?;
-        List1OrNil(&self.cc, b"").encode(writer)?;
+        List1OrNil(&self.cc, b"").core_encode(writer)?;
         writer.write_all(b" ")?;
-        List1OrNil(&self.bcc, b"").encode(writer)?;
+        List1OrNil(&self.bcc, b"").core_encode(writer)?;
         writer.write_all(b" ")?;
-        self.in_reply_to.encode(writer)?;
+        self.in_reply_to.core_encode(writer)?;
         writer.write_all(b" ")?;
-        self.message_id.encode(writer)?;
+        self.message_id.core_encode(writer)?;
         writer.write_all(b")")
     }
 }
 
-impl<'a> Encode for Address<'a> {
-    fn encode(&self, writer: &mut impl Write) -> std::io::Result<()> {
+impl<'a> CoreEncode for Address<'a> {
+    fn core_encode(&self, writer: &mut EncodeContext) -> std::io::Result<()> {
         writer.write_all(b"(")?;
-        self.name.encode(writer)?;
+        self.name.core_encode(writer)?;
         writer.write_all(b" ")?;
-        self.adl.encode(writer)?;
+        self.adl.core_encode(writer)?;
         writer.write_all(b" ")?;
-        self.mailbox.encode(writer)?;
+        self.mailbox.core_encode(writer)?;
         writer.write_all(b" ")?;
-        self.host.encode(writer)?;
+        self.host.core_encode(writer)?;
         writer.write_all(b")")?;
 
         Ok(())
     }
 }
 
-impl<'a> Encode for SinglePartExtensionData<'a> {
-    fn encode(&self, writer: &mut impl Write) -> std::io::Result<()> {
-        self.md5.encode(writer)?;
+impl<'a> CoreEncode for SinglePartExtensionData<'a> {
+    fn core_encode(&self, writer: &mut EncodeContext) -> std::io::Result<()> {
+        self.md5.core_encode(writer)?;
 
         if let Some(disposition) = &self.tail {
             writer.write_all(b" ")?;
-            disposition.encode(writer)?;
+            disposition.core_encode(writer)?;
         }
 
         Ok(())
     }
 }
 
-impl<'a> Encode for MultiPartExtensionData<'a> {
-    fn encode(&self, writer: &mut impl Write) -> std::io::Result<()> {
-        List1AttributeValueOrNil(&self.parameter_list).encode(writer)?;
+impl<'a> CoreEncode for MultiPartExtensionData<'a> {
+    fn core_encode(&self, writer: &mut EncodeContext) -> std::io::Result<()> {
+        List1AttributeValueOrNil(&self.parameter_list).core_encode(writer)?;
 
         if let Some(disposition) = &self.tail {
             writer.write_all(b" ")?;
-            disposition.encode(writer)?;
+            disposition.core_encode(writer)?;
         }
 
         Ok(())
     }
 }
 
-impl<'a> Encode for Disposition<'a> {
-    fn encode(&self, writer: &mut impl Write) -> std::io::Result<()> {
+impl<'a> CoreEncode for Disposition<'a> {
+    fn core_encode(&self, writer: &mut EncodeContext) -> std::io::Result<()> {
         match &self.disposition {
             Some((s, param)) => {
                 writer.write_all(b"(")?;
-                s.encode(writer)?;
+                s.core_encode(writer)?;
                 writer.write_all(b" ")?;
-                List1AttributeValueOrNil(param).encode(writer)?;
+                List1AttributeValueOrNil(param).core_encode(writer)?;
                 writer.write_all(b")")?;
             }
             None => writer.write_all(b"NIL")?,
@@ -1389,44 +1606,44 @@ impl<'a> Encode for Disposition<'a> {
 
         if let Some(language) = &self.tail {
             writer.write_all(b" ")?;
-            language.encode(writer)?;
+            language.core_encode(writer)?;
         }
 
         Ok(())
     }
 }
 
-impl<'a> Encode for Language<'a> {
-    fn encode(&self, writer: &mut impl Write) -> std::io::Result<()> {
-        List1OrNil(&self.language, b" ").encode(writer)?;
+impl<'a> CoreEncode for Language<'a> {
+    fn core_encode(&self, writer: &mut EncodeContext) -> std::io::Result<()> {
+        List1OrNil(&self.language, b" ").core_encode(writer)?;
 
         if let Some(location) = &self.tail {
             writer.write_all(b" ")?;
-            location.encode(writer)?;
+            location.core_encode(writer)?;
         }
 
         Ok(())
     }
 }
 
-impl<'a> Encode for Location<'a> {
-    fn encode(&self, writer: &mut impl Write) -> std::io::Result<()> {
-        self.location.encode(writer)?;
+impl<'a> CoreEncode for Location<'a> {
+    fn core_encode(&self, writer: &mut EncodeContext) -> std::io::Result<()> {
+        self.location.core_encode(writer)?;
 
         for body_extension in &self.extensions {
             writer.write_all(b" ")?;
-            body_extension.encode(writer)?;
+            body_extension.core_encode(writer)?;
         }
 
         Ok(())
     }
 }
 
-impl<'a> Encode for BodyExtension<'a> {
-    fn encode(&self, writer: &mut impl Write) -> std::io::Result<()> {
+impl<'a> CoreEncode for BodyExtension<'a> {
+    fn core_encode(&self, writer: &mut EncodeContext) -> std::io::Result<()> {
         match self {
-            BodyExtension::NString(nstring) => nstring.encode(writer),
-            BodyExtension::Number(number) => number.encode(writer),
+            BodyExtension::NString(nstring) => nstring.core_encode(writer),
+            BodyExtension::Number(number) => number.core_encode(writer),
             BodyExtension::List(list) => {
                 writer.write_all(b"(")?;
                 join_serializable(list.as_ref(), b" ", writer)?;
@@ -1436,26 +1653,26 @@ impl<'a> Encode for BodyExtension<'a> {
     }
 }
 
-impl Encode for ChronoDateTime<FixedOffset> {
-    fn encode(&self, writer: &mut impl Write) -> std::io::Result<()> {
+impl CoreEncode for ChronoDateTime<FixedOffset> {
+    fn core_encode(&self, writer: &mut EncodeContext) -> std::io::Result<()> {
         write!(writer, "\"{}\"", self.format("%d-%b-%Y %H:%M:%S %z"))
     }
 }
 
-impl<'a> Encode for Continue<'a> {
-    fn encode(&self, writer: &mut impl Write) -> std::io::Result<()> {
+impl<'a> CoreEncode for Continue<'a> {
+    fn core_encode(&self, writer: &mut EncodeContext) -> std::io::Result<()> {
         match self {
             Continue::Basic(continue_basic) => match continue_basic.code() {
                 Some(code) => {
                     writer.write_all(b"+ [")?;
-                    code.encode(writer)?;
+                    code.core_encode(writer)?;
                     writer.write_all(b"] ")?;
-                    continue_basic.text().encode(writer)?;
+                    continue_basic.text().core_encode(writer)?;
                     writer.write_all(b"\r\n")
                 }
                 None => {
                     writer.write_all(b"+ ")?;
-                    continue_basic.text().encode(writer)?;
+                    continue_basic.text().core_encode(writer)?;
                     writer.write_all(b"\r\n")
                 }
             },
@@ -1472,43 +1689,44 @@ impl<'a> Encode for Continue<'a> {
 mod utils {
     use std::io::Write;
 
-    use super::Encode;
+    use super::CoreEncode;
+    use crate::codec::encode::EncodeContext;
 
     pub struct List1OrNil<'a, T>(pub &'a Vec<T>, pub &'a [u8]);
 
     pub struct List1AttributeValueOrNil<'a, T>(pub &'a Vec<(T, T)>);
 
-    pub fn join_serializable<I: Encode>(
+    pub fn join_serializable<I: CoreEncode>(
         elements: &[I],
         sep: &[u8],
-        writer: &mut impl Write,
+        writer: &mut EncodeContext,
     ) -> std::io::Result<()> {
         if let Some((last, head)) = elements.split_last() {
             for item in head {
-                item.encode(writer)?;
+                item.core_encode(writer)?;
                 writer.write_all(sep)?;
             }
 
-            last.encode(writer)
+            last.core_encode(writer)
         } else {
             Ok(())
         }
     }
 
-    impl<'a, T> Encode for List1OrNil<'a, T>
+    impl<'a, T> CoreEncode for List1OrNil<'a, T>
     where
-        T: Encode,
+        T: CoreEncode,
     {
-        fn encode(&self, writer: &mut impl Write) -> std::io::Result<()> {
+        fn core_encode(&self, writer: &mut EncodeContext) -> std::io::Result<()> {
             if let Some((last, head)) = self.0.split_last() {
                 writer.write_all(b"(")?;
 
                 for item in head {
-                    item.encode(writer)?;
+                    item.core_encode(writer)?;
                     writer.write_all(self.1)?;
                 }
 
-                last.encode(writer)?;
+                last.core_encode(writer)?;
 
                 writer.write_all(b")")
             } else {
@@ -1517,25 +1735,25 @@ mod utils {
         }
     }
 
-    impl<'a, T> Encode for List1AttributeValueOrNil<'a, T>
+    impl<'a, T> CoreEncode for List1AttributeValueOrNil<'a, T>
     where
-        T: Encode,
+        T: CoreEncode,
     {
-        fn encode(&self, writer: &mut impl Write) -> std::io::Result<()> {
+        fn core_encode(&self, writer: &mut EncodeContext) -> std::io::Result<()> {
             if let Some((last, head)) = self.0.split_last() {
                 writer.write_all(b"(")?;
 
                 for (attribute, value) in head {
-                    attribute.encode(writer)?;
+                    attribute.core_encode(writer)?;
                     writer.write_all(b" ")?;
-                    value.encode(writer)?;
+                    value.core_encode(writer)?;
                     writer.write_all(b" ")?;
                 }
 
                 let (attribute, value) = last;
-                attribute.encode(writer)?;
+                attribute.core_encode(writer)?;
                 writer.write_all(b" ")?;
-                value.encode(writer)?;
+                value.core_encode(writer)?;
 
                 writer.write_all(b")")
             } else {
@@ -1544,3 +1762,206 @@ mod utils {
         }
     }
 }
+
+#[cfg(test)]
+mod tests {
+    use std::num::NonZeroU32;
+
+    use imap_types::{
+        core::{AString, Literal, NString, NonEmptyVec},
+        message::AuthMechanism,
+        response::{data::FetchAttributeValue, Data, Response},
+        utils::escape_byte_string,
+    };
+
+    use super::{Action, Encode};
+    use crate::command::{Command, CommandBody};
+
+    #[test]
+    fn test_api_encoder_usage() {
+        let cmd = Command::new(
+            "A",
+            CommandBody::login(
+                AString::from(Literal::unchecked(
+                    b"alice".as_ref(),
+                    #[cfg(feature = "ext_literal")]
+                    false,
+                )),
+                "password",
+            )
+            .unwrap(),
+        )
+        .unwrap();
+
+        // Dump.
+        let got_encoded = cmd.encode().dump();
+
+        // Encoding.
+        let encoder = cmd.encode();
+
+        let mut out = Vec::new();
+
+        for x in encoder {
+            match x {
+                Action::Send { data } => {
+                    println!("C: {}", escape_byte_string(&data));
+                    out.extend_from_slice(&data);
+                }
+                Action::RecvContinuationRequest => {
+                    println!("C: <Waiting for continuation request>");
+                }
+                Action::Unknown => {
+                    println!("C: <Custom message flow required>");
+                }
+            }
+        }
+
+        assert_eq!(got_encoded, out);
+    }
+
+    #[test]
+    fn test_encode_command() {
+        kat_encoder(&[
+            (
+                Command::new("A", CommandBody::login("alice", "pass").unwrap()).unwrap(),
+                [Action::Send {
+                    data: b"A LOGIN alice pass\r\n".to_vec(),
+                }]
+                .as_ref(),
+            ),
+            (
+                Command::new(
+                    "A",
+                    CommandBody::login("alice", b"\xCA\xFE".as_ref()).unwrap(),
+                )
+                .unwrap(),
+                [
+                    Action::Send {
+                        data: b"A LOGIN alice {2}\r\n".to_vec(),
+                    },
+                    Action::RecvContinuationRequest,
+                    Action::Send {
+                        data: b"\xCA\xFE\r\n".to_vec(),
+                    },
+                ]
+                .as_ref(),
+            ),
+            (
+                Command::new(
+                    "A",
+                    CommandBody::authenticate(
+                        AuthMechanism::Login,
+                        #[cfg(feature = "ext_sasl_ir")]
+                        None,
+                    ),
+                )
+                .unwrap(),
+                [
+                    Action::Send {
+                        data: b"A AUTHENTICATE LOGIN\r\n".to_vec(),
+                    },
+                    Action::RecvContinuationRequest,
+                ]
+                .as_ref(),
+            ),
+            #[cfg(feature = "ext_sasl_ir")]
+            (
+                Command::new(
+                    "A",
+                    CommandBody::authenticate(AuthMechanism::Login, Some(b"alice")),
+                )
+                .unwrap(),
+                [
+                    Action::Send {
+                        data: b"A AUTHENTICATE LOGIN YWxpY2U=\r\n".to_vec(),
+                    },
+                    Action::RecvContinuationRequest,
+                ]
+                .as_ref(),
+            ),
+            #[cfg(feature = "ext_sasl_ir")]
+            (
+                Command::new("A", CommandBody::authenticate(AuthMechanism::Plain, None)).unwrap(),
+                [
+                    Action::Send {
+                        data: b"A AUTHENTICATE PLAIN\r\n".to_vec(),
+                    },
+                    Action::RecvContinuationRequest,
+                ]
+                .as_ref(),
+            ),
+            #[cfg(feature = "ext_sasl_ir")]
+            (
+                Command::new(
+                    "A",
+                    CommandBody::authenticate(AuthMechanism::Plain, Some(b"\x00alice\x00pass")),
+                )
+                .unwrap(),
+                [Action::Send {
+                    data: b"A AUTHENTICATE PLAIN AGFsaWNlAHBhc3M=\r\n".to_vec(),
+                }]
+                .as_ref(),
+            ),
+        ]);
+    }
+
+    #[test]
+    fn test_encode_response() {
+        kat_encoder(&[
+            (
+                Response::Data(Data::Fetch {
+                    seq_or_uid: NonZeroU32::new(12345).unwrap(),
+                    attributes: NonEmptyVec::from(FetchAttributeValue::BodyExt {
+                        section: None,
+                        origin: None,
+                        data: NString::from(Literal::unchecked(
+                            b"ABCDE".as_ref(),
+                            #[cfg(feature = "ext_literal")]
+                            true,
+                        )),
+                    }),
+                }),
+                [
+                    Action::Send {
+                        data: b"* 12345 FETCH (BODY[] {5}\r\n".to_vec(),
+                    },
+                    Action::RecvContinuationRequest,
+                    Action::Send {
+                        data: b"ABCDE)\r\n".to_vec(),
+                    },
+                ]
+                .as_ref(),
+            ),
+            #[cfg(feature = "ext_literal")]
+            (
+                Response::Data(Data::Fetch {
+                    seq_or_uid: NonZeroU32::new(12345).unwrap(),
+                    attributes: NonEmptyVec::from(FetchAttributeValue::BodyExt {
+                        section: None,
+                        origin: None,
+                        data: NString::from(Literal::unchecked(b"ABCDE".as_ref(), false)),
+                    }),
+                }),
+                [Action::Send {
+                    data: b"* 12345 FETCH (BODY[] {5+}\r\nABCDE)\r\n".to_vec(),
+                }]
+                .as_ref(),
+            ),
+        ])
+    }
+
+    fn kat_encoder<Object, Actions>(tests: &[(Object, Actions)])
+    where
+        Object: Encode,
+        Actions: AsRef<[Action]>,
+    {
+        for (i, (obj, actions)) in tests.iter().enumerate() {
+            println!("# Testing {i}");
+
+            let encoder = obj.encode();
+            let actions = actions.as_ref();
+
+            assert_eq!(encoder.collect::<Vec<_>>(), actions);
+        }
+    }
+}
diff --git a/src/extensions/compress.rs b/src/extensions/compress.rs
index 02afa58d..afc5710c 100644
--- a/src/extensions/compress.rs
+++ b/src/extensions/compress.rs
@@ -16,7 +16,10 @@ use nom::{
     IResult,
 };
 
-use crate::{codec::Encode, command::CommandBody};
+use crate::{
+    codec::{CoreEncode, EncodeContext},
+    command::CommandBody,
+};
 
 /// `algorithm = "DEFLATE"`
 pub fn algorithm(input: &[u8]) -> IResult<&[u8], CompressionAlgorithm> {
@@ -30,8 +33,8 @@ pub fn compress(input: &[u8]) -> IResult<&[u8], CommandBody> {
     })(input)
 }
 
-impl Encode for CompressionAlgorithm {
-    fn encode(&self, writer: &mut impl Write) -> std::io::Result<()> {
+impl CoreEncode for CompressionAlgorithm {
+    fn core_encode(&self, writer: &mut EncodeContext) -> std::io::Result<()> {
         match self {
             CompressionAlgorithm::Deflate => writer.write_all(b"DEFLATE"),
         }
diff --git a/src/extensions/enable.rs b/src/extensions/enable.rs
index 421aa9fc..8e1e2068 100644
--- a/src/extensions/enable.rs
+++ b/src/extensions/enable.rs
@@ -18,7 +18,12 @@ use nom::{
     IResult,
 };
 
-use crate::{codec::Encode, command::CommandBody, imap4rev1::core::atom, response::Data};
+use crate::{
+    codec::{CoreEncode, EncodeContext},
+    command::CommandBody,
+    imap4rev1::core::atom,
+    response::Data,
+};
 
 /// `command-any =/ "ENABLE" 1*(SP capability)`
 ///
@@ -63,21 +68,21 @@ pub fn enable_data(input: &[u8]) -> IResult<&[u8], Data> {
     Ok((remaining, { Data::Enabled { capabilities } }))
 }
 
-impl<'a> Encode for CapabilityEnable<'a> {
-    fn encode(&self, writer: &mut impl Write) -> std::io::Result<()> {
+impl<'a> CoreEncode for CapabilityEnable<'a> {
+    fn core_encode(&self, writer: &mut EncodeContext) -> std::io::Result<()> {
         match self {
             Self::Utf8(Utf8Kind::Accept) => writer.write_all(b"UTF8=ACCEPT"),
             Self::Utf8(Utf8Kind::Only) => writer.write_all(b"UTF8=ONLY"),
             #[cfg(feature = "ext_condstore_qresync")]
             Self::CondStore => writer.write_all(b"CONDSTORE"),
-            Self::Other(other) => other.encode(writer),
+            Self::Other(other) => other.core_encode(writer),
         }
     }
 }
 
-impl<'a> Encode for CapabilityEnableOther<'a> {
-    fn encode(&self, writer: &mut impl Write) -> std::io::Result<()> {
-        self.inner().encode(writer)
+impl<'a> CoreEncode for CapabilityEnableOther<'a> {
+    fn core_encode(&self, writer: &mut EncodeContext) -> std::io::Result<()> {
+        self.inner().core_encode(writer)
     }
 }
 
diff --git a/src/extensions/idle.rs b/src/extensions/idle.rs
index 6c16f9fe..528813af 100644
--- a/src/extensions/idle.rs
+++ b/src/extensions/idle.rs
@@ -12,7 +12,10 @@ use std::io::Write;
 pub use imap_types::extensions::idle::*;
 use nom::{bytes::streaming::tag_no_case, combinator::value, IResult};
 
-use crate::{codec::Encode, command::CommandBody};
+use crate::{
+    codec::{CoreEncode, EncodeContext},
+    command::CommandBody,
+};
 
 /// `idle = "IDLE" CRLF "DONE"` (edited)
 ///
@@ -47,8 +50,8 @@ pub fn idle_done(input: &[u8]) -> IResult<&[u8], IdleDone> {
     value(IdleDone, tag_no_case("DONE\r\n"))(input)
 }
 
-impl Encode for IdleDone {
-    fn encode(&self, writer: &mut impl Write) -> std::io::Result<()> {
+impl CoreEncode for IdleDone {
+    fn core_encode(&self, writer: &mut EncodeContext) -> std::io::Result<()> {
         writer.write_all(b"DONE\r\n")
     }
 }
diff --git a/src/extensions/literal.rs b/src/extensions/literal.rs
index d6f8933d..5151c211 100644
--- a/src/extensions/literal.rs
+++ b/src/extensions/literal.rs
@@ -2,10 +2,10 @@ use std::io::Write;
 
 pub use imap_types::extensions::literal::*;
 
-use crate::codec::Encode;
+use crate::codec::{CoreEncode, EncodeContext};
 
-impl Encode for LiteralCapability {
-    fn encode(&self, writer: &mut impl Write) -> std::io::Result<()> {
+impl CoreEncode for LiteralCapability {
+    fn core_encode(&self, writer: &mut EncodeContext) -> std::io::Result<()> {
         match self {
             Self::Plus => writer.write_all(b"LITERAL+"),
             Self::Minus => writer.write_all(b"LITERAL-"),
diff --git a/src/extensions/quota.rs b/src/extensions/quota.rs
index ccd84db6..d9023ca5 100644
--- a/src/extensions/quota.rs
+++ b/src/extensions/quota.rs
@@ -13,7 +13,7 @@ use nom::{
 };
 
 use crate::{
-    codec::Encode,
+    codec::{CoreEncode, EncodeContext},
     command::CommandBody,
     core::{AString, NonEmptyVec},
     imap4rev1::{
@@ -191,34 +191,34 @@ pub fn setquota_resource(input: &[u8]) -> IResult<&[u8], QuotaSet> {
 //     Ok((remaining, Capability::QuotaRes(resource)))
 // }
 
-impl<'a> Encode for Resource<'a> {
-    fn encode(&self, writer: &mut impl Write) -> std::io::Result<()> {
+impl<'a> CoreEncode for Resource<'a> {
+    fn core_encode(&self, writer: &mut EncodeContext) -> std::io::Result<()> {
         match self {
             Resource::Storage => writer.write_all(b"STORAGE"),
             Resource::Message => writer.write_all(b"MESSAGE"),
             Resource::Mailbox => writer.write_all(b"MAILBOX"),
             Resource::AnnotationStorage => writer.write_all(b"ANNOTATION-STORAGE"),
-            Resource::Other(atom) => atom.encode(writer),
+            Resource::Other(atom) => atom.core_encode(writer),
         }
     }
 }
 
-impl<'a> Encode for ResourceOther<'a> {
-    fn encode(&self, writer: &mut impl Write) -> std::io::Result<()> {
-        self.inner().encode(writer)
+impl<'a> CoreEncode for ResourceOther<'a> {
+    fn core_encode(&self, writer: &mut EncodeContext) -> std::io::Result<()> {
+        self.inner().core_encode(writer)
     }
 }
 
-impl<'a> Encode for QuotaGet<'a> {
-    fn encode(&self, writer: &mut impl Write) -> std::io::Result<()> {
-        self.resource.encode(writer)?;
+impl<'a> CoreEncode for QuotaGet<'a> {
+    fn core_encode(&self, writer: &mut EncodeContext) -> std::io::Result<()> {
+        self.resource.core_encode(writer)?;
         write!(writer, " {} {}", self.usage, self.limit)
     }
 }
 
-impl<'a> Encode for QuotaSet<'a> {
-    fn encode(&self, writer: &mut impl Write) -> std::io::Result<()> {
-        self.resource.encode(writer)?;
+impl<'a> CoreEncode for QuotaSet<'a> {
+    fn core_encode(&self, writer: &mut EncodeContext) -> std::io::Result<()> {
+        self.resource.core_encode(writer)?;
         write!(writer, " {}", self.limit)
     }
 }
diff --git a/src/imap4rev1/command.rs b/src/imap4rev1/command.rs
index 0b6e48bf..c5ff0c71 100644
--- a/src/imap4rev1/command.rs
+++ b/src/imap4rev1/command.rs
@@ -656,7 +656,7 @@ mod tests {
         )
         .unwrap();
 
-        let buffer = command.encode_detached().unwrap();
+        let buffer = command.encode().dump();
 
         assert_eq!(buffer, b"A AUTHENTICATE PLAIN =\r\n")
     }
diff --git a/src/imap4rev1/core.rs b/src/imap4rev1/core.rs
index ebd60df0..ead4bc0e 100644
--- a/src/imap4rev1/core.rs
+++ b/src/imap4rev1/core.rs
@@ -467,7 +467,7 @@ mod tests {
             let cs = Charset::try_from(*from).unwrap();
             println!("{:?}", cs);
 
-            let out = cs.encode_detached().unwrap();
+            let out = cs.encode().dump();
             assert_eq!(from_utf8(&out).unwrap(), *expected);
         }
 
diff --git a/src/imap4rev1/sequence.rs b/src/imap4rev1/sequence.rs
index 6a6d9627..1f1ffeea 100644
--- a/src/imap4rev1/sequence.rs
+++ b/src/imap4rev1/sequence.rs
@@ -102,7 +102,7 @@ mod tests {
         ];
 
         for (test, expected) in tests {
-            let out = test.encode_detached().unwrap();
+            let out = test.encode().dump();
             assert_eq!(*expected, out);
         }
     }
diff --git a/src/lib.rs b/src/lib.rs
index bca0b935..6037d530 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -15,7 +15,7 @@
 //!
 //! ## Serialization
 //!
-//! Serialization is implemented via the [Encode](crate::codec::Encode) trait.
+//! Serialization is implemented via the [Encode](crate::codec::CoreEncode) trait.
 //! See the [imap-types](imap_types) documentation for the module layout and how to construct messages.
 //!
 //! ## Example
@@ -32,7 +32,7 @@
 //! println!("// Parsed:");
 //! println!("{:#?}", parsed);
 //!
-//! let serialized = parsed.encode_detached().unwrap();
+//! let serialized = parsed.encode().dump();
 //!
 //! let serialized = String::from_utf8(serialized).unwrap(); // Not every IMAP message is valid UTF-8.
 //! println!("// Serialized:"); // We just ignore that, so that we can print the message.
diff --git a/src/tokio/client.rs b/src/tokio/client.rs
index 64d481ba..dea92918 100644
--- a/src/tokio/client.rs
+++ b/src/tokio/client.rs
@@ -1,4 +1,4 @@
-use std::io::Error;
+use std::io::{Error, Write};
 
 use bytes::{Buf, BufMut, BytesMut};
 use imap_types::bounded_static::IntoBoundedStatic;
@@ -182,7 +182,9 @@ impl<'a> Encoder<&Command<'a>> for ImapClientCodec {
     fn encode(&mut self, item: &Command, dst: &mut BytesMut) -> Result<(), Error> {
         //dst.reserve(item.len());
         let mut writer = dst.writer();
-        item.encode(&mut writer).unwrap();
+        // TODO(225): Don't use `dump` here.
+        let data = item.encode().dump();
+        writer.write_all(&data)?;
         Ok(())
     }
 }
diff --git a/src/tokio/server.rs b/src/tokio/server.rs
index 3175052e..699128fa 100644
--- a/src/tokio/server.rs
+++ b/src/tokio/server.rs
@@ -1,4 +1,4 @@
-use std::io::Error;
+use std::io::{Error, Write};
 
 use bytes::{Buf, BufMut, BytesMut};
 use imap_types::{bounded_static::IntoBoundedStatic, response::Greeting};
@@ -169,7 +169,9 @@ impl<'a> Encoder<&Greeting<'a>> for ImapServerCodec {
     fn encode(&mut self, item: &Greeting, dst: &mut BytesMut) -> Result<(), Error> {
         //dst.reserve(item.len());
         let mut writer = dst.writer();
-        item.encode(&mut writer).unwrap();
+        // TODO(225): Don't use `dump` here.
+        let data = item.encode().dump();
+        writer.write_all(&data)?;
         Ok(())
     }
 }
@@ -180,7 +182,9 @@ impl<'a> Encoder<&Response<'a>> for ImapServerCodec {
     fn encode(&mut self, item: &Response, dst: &mut BytesMut) -> Result<(), Error> {
         //dst.reserve(item.len());
         let mut writer = dst.writer();
-        item.encode(&mut writer).unwrap();
+        // TODO(225): Don't use `dump` here.
+        let data = item.encode().dump();
+        writer.write_all(&data)?;
         Ok(())
     }
 }


Test patch:
diff --git a/src/testing.rs b/src/testing.rs
index 7834edf9..6ef9b8c2 100644
--- a/src/testing.rs
+++ b/src/testing.rs
@@ -11,7 +11,7 @@ use crate::{
 
 pub fn known_answer_test_encode((test_object, expected_bytes): (impl Encode, impl AsRef<[u8]>)) {
     let expected_bytes = expected_bytes.as_ref();
-    let got_bytes = test_object.encode_detached().unwrap();
+    let got_bytes = test_object.encode().dump();
     let got_bytes = got_bytes.as_slice();
 
     if expected_bytes != got_bytes {
@@ -51,7 +51,7 @@ macro_rules! impl_kat_inverse {
                 assert_eq!(*expected_object, got_object);
                 assert_eq!(*expected_remainder, got_remainder);
 
-                let got_output = got_object.encode_detached().unwrap();
+                let got_output = got_object.encode().dump();
 
                 // This second `decode` makes using generic bonuds more complicated due to the
                 // different lifetime.
diff --git a/tests/readme.rs b/tests/readme.rs
index a8e56481..a15e1aef 100644
--- a/tests/readme.rs
+++ b/tests/readme.rs
@@ -10,7 +10,7 @@ fn test_from_readme() {
     let (_remainder, parsed) = Command::decode(input).unwrap();
     println!("# Parsed\n\n{:#?}\n\n", parsed);
 
-    let buffer = parsed.encode_detached().unwrap();
+    let buffer = parsed.encode().dump();
 
     // Note: IMAP4rev1 may produce messages that are not valid UTF-8.
     println!("# Serialized\n\n{:?}", std::str::from_utf8(&buffer));
diff --git a/tests/trace.rs b/tests/trace.rs
index 3116eacd..7b82dcd1 100644
--- a/tests/trace.rs
+++ b/tests/trace.rs
@@ -73,7 +73,7 @@ fn test_lines_of_trace(trace: &[u8]) {
                 let (rem, parsed) = Command::decode(&line).unwrap();
                 assert!(rem.is_empty());
                 println!("Parsed      {:?}", parsed);
-                let serialized = parsed.encode_detached().unwrap();
+                let serialized = parsed.encode().dump();
                 println!(
                     "Serialized: {}",
                     String::from_utf8_lossy(&serialized).trim()
@@ -88,7 +88,7 @@ fn test_lines_of_trace(trace: &[u8]) {
                 let (rem, parsed) = Response::decode(&line).unwrap();
                 println!("Parsed:     {:?}", parsed);
                 assert!(rem.is_empty());
-                let serialized = parsed.encode_detached().unwrap();
+                let serialized = parsed.encode().dump();
                 println!(
                     "Serialized: {}",
                     String::from_utf8_lossy(&serialized).trim()
@@ -111,7 +111,7 @@ fn test_trace_known_positive(tests: Vec<(&[u8], Message)>) {
                 assert!(rem.is_empty());
                 assert_eq!(expected, got);
                 println!("{:?}", got);
-                let encoded = got.encode_detached().unwrap();
+                let encoded = got.encode().dump();
                 println!("// {}", String::from_utf8(encoded.clone()).unwrap().trim());
                 let (rem2, got2) = Command::decode(&encoded).unwrap();
                 assert!(rem2.is_empty());
@@ -122,7 +122,7 @@ fn test_trace_known_positive(tests: Vec<(&[u8], Message)>) {
                 assert!(rem.is_empty());
                 assert_eq!(expected, got);
                 println!("{:?}", got);
-                let encoded = got.encode_detached().unwrap();
+                let encoded = got.encode().dump();
                 println!("// {}", String::from_utf8(encoded.clone()).unwrap().trim());
                 let (rem2, got2) = Response::decode(&encoded).unwrap();
                 assert!(rem2.is_empty());
@@ -1274,7 +1274,7 @@ fn test_response_status_preauth() {
     let (rem, parsed) = Greeting::decode(line).unwrap();
     println!("Parsed:     {:?}", parsed);
     assert!(rem.is_empty());
-    let serialized = parsed.encode_detached().unwrap();
+    let serialized = parsed.encode().dump();
     println!(
         "Serialized: {}",
         String::from_utf8_lossy(&serialized).trim()