diff --git a/sievelib/commands.py b/sievelib/commands.py
index 6b3c1d6..7f6df2e 100644
--- a/sievelib/commands.py
+++ b/sievelib/commands.py
@@ -182,7 +182,7 @@ class Command(object):
 
                 if "string" in arg["type"]:
                     target.write(value)
-                    if not value.startswith('"'):
+                    if not value.startswith('"') and not value.startswith("["):
                         target.write("\n")
                 else:
                     target.write(value)
@@ -739,6 +739,20 @@ class EnvelopeCommand(TestCommand):
          "required": True}
     ]
 
+    def args_as_tuple(self):
+        """Return arguments as a list."""
+        result = ("envelope", self.arguments["match-type"])
+        if self.arguments["header-list"].startswith("["):
+            result += (tools.to_list(self.arguments["header-list"]),)
+        else:
+            result += ([self.arguments["header-list"].strip('"')],)
+        if self.arguments["key-list"].startswith("["):
+            result = result + (
+                tools.to_list(self.arguments["key-list"]),)
+        else:
+            result = result + ([self.arguments["key-list"].strip('"')],)
+        return result
+
 
 class ExistsCommand(TestCommand):
     args_definition = [
@@ -749,9 +763,8 @@ class ExistsCommand(TestCommand):
 
     def args_as_tuple(self):
         value = self.arguments["header-names"]
-        if ',' not in value:
-            return ('exists', value)
-
+        if not value.startswith("["):
+            return ('exists', value.strip('"'))
         return ("exists", ) + tuple(tools.to_list(value))
 
 
@@ -783,7 +796,6 @@ class HeaderCommand(TestCommand):
             result = (self.arguments["header-names"].strip('"'),)
         result = result + (self.arguments["match-type"],)
         if "," in self.arguments["key-list"]:
-            keylist = self.arguments["key-list"][1:-1]
             result = result + tuple(
                 tools.to_list(self.arguments["key-list"], unquote=False))
         else:
@@ -811,6 +823,18 @@ class BodyCommand(TestCommand):
     ]
     extension = "body"
 
+    def args_as_tuple(self):
+        """Return arguments as a list."""
+        result = ("body", )
+        result = result + (
+            self.arguments["body-transform"], self.arguments["match-type"])
+        if self.arguments["key-list"].startswith("["):
+            result = result + tuple(
+                tools.to_list(self.arguments["key-list"]))
+        else:
+            result = result + (self.arguments["key-list"].strip('"'),)
+        return result
+
 
 class NotCommand(TestCommand):
     accept_children = True
@@ -860,10 +884,11 @@ class HasflagCommand(TestCommand):
         """Deal with optional stringlist before a required one."""
         condition = (
             "variable-list" in self.arguments and
-            not "list-of-flags" in self.arguments
+            "list-of-flags" not in self.arguments
         )
         if condition:
-            self.arguments["list-of-flags"] = self.arguments.pop("variable-list")
+            self.arguments["list-of-flags"] = (
+                self.arguments.pop("variable-list"))
             self.rargs_cnt = 1
 
 
diff --git a/sievelib/factory.py b/sievelib/factory.py
index 4fa1743..caf2661 100644
--- a/sievelib/factory.py
+++ b/sievelib/factory.py
@@ -185,6 +185,26 @@ class FiltersSet(object):
                     "stringlist",
                     "[%s]" % (",".join('"%s"' % val for val in c[1:]))
                 )
+            elif cname == "envelope":
+                cmd = commands.get_command_instance("envelope", ifcontrol)
+                cmd.check_next_arg("tag", c[1])
+                cmd.check_next_arg(
+                    "stringlist",
+                    "[{}]".format(",".join('"{}"'.format(val) for val in c[2]))
+                )
+                cmd.check_next_arg(
+                    "stringlist",
+                    "[{}]".format(",".join('"{}"'.format(val) for val in c[3]))
+                )
+            elif cname == "body":
+                cmd = commands.get_command_instance("body", ifcontrol, False)
+                self.require(cmd.extension)
+                cmd.check_next_arg("tag", c[1])
+                cmd.check_next_arg("tag", c[2])
+                cmd.check_next_arg(
+                    "stringlist",
+                    "[%s]" % (",".join('"%s"' % val for val in c[3:]))
+                )
             else:
                 if c[1].startswith(':not'):
                     cmd = self.__build_condition(
@@ -308,7 +328,8 @@ class FiltersSet(object):
         if not flt:
             return None
         for node in flt.walk():
-            if isinstance(node, (commands.AllofCommand, commands.AnyofCommand)):
+            if isinstance(node, (commands.AllofCommand,
+                                 commands.AnyofCommand)):
                 return node.__class__.__name__.lower().replace("command", "")
         return None
 
@@ -324,7 +345,9 @@ class FiltersSet(object):
                 negate = True
             elif isinstance(node, (commands.HeaderCommand,
                                    commands.SizeCommand,
-                                   commands.ExistsCommand)):
+                                   commands.ExistsCommand,
+                                   commands.BodyCommand,
+                                   commands.EnvelopeCommand)):
                 args = node.args_as_tuple()
                 if negate:
                     if node.name == "header":
diff --git a/sievelib/managesieve.py b/sievelib/managesieve.py
index 365f098..60307b2 100644
--- a/sievelib/managesieve.py
+++ b/sievelib/managesieve.py
@@ -702,8 +702,8 @@ class Client(object):
         if "VERSION" not in self.__capabilities:
             raise NotImplementedError(
                 "server does not support CHECKSCRIPT command")
-        content = tools.to_bytes(content)
-        content = tools.to_bytes("{%d+}" % len(content)) + CRLF + content
+        content = tools.to_bytes(
+            u"{%d+}%s%s" % (len(content), str(CRLF), content))
         code, data = self.__send_command("CHECKSCRIPT", [content])
         if code == "OK":
             return True

Factory: add support for body and envelope tests
Those tests are useful and should be handled by the factory module.
diff --git a/sievelib/tests/test_factory.py b/sievelib/tests/test_factory.py
index 4ac273c..a8fb390 100644
--- a/sievelib/tests/test_factory.py
+++ b/sievelib/tests/test_factory.py
@@ -4,9 +4,7 @@ from __future__ import unicode_literals
 import unittest
 import six
 
-from sievelib import commands
 from sievelib.factory import FiltersSet
-from sievelib.parser import Parser
 
 
 class FactoryTestCase(unittest.TestCase):
@@ -52,6 +50,24 @@ class FactoryTestCase(unittest.TestCase):
         conditions = self.fs.get_filter_conditions("ruleA")
         self.assertEqual(orig_conditions, conditions)
 
+        orig_conditions = [("envelope", ":is", ["From"], ["hello"])]
+        self.fs.addfilter(
+            "ruleB",
+            orig_conditions,
+            [("fileinto", "INBOX")]
+        )
+        conditions = self.fs.get_filter_conditions("ruleB")
+        self.assertEqual(orig_conditions, conditions)
+
+        orig_conditions = [("body", ":raw", ":contains", "matteo")]
+        self.fs.addfilter(
+            "ruleC",
+            orig_conditions,
+            [("fileinto", "INBOX")]
+        )
+        conditions = self.fs.get_filter_conditions("ruleC")
+        self.assertEqual(orig_conditions, conditions)
+
     def test_get_filter_matchtype(self):
         """Test get_filter_matchtype method."""
         self.fs.addfilter(
@@ -222,6 +238,36 @@ if anyof (header :is "Sender" "toto@toto.com") {
 }
 """)
 
+    def test_add_body_filter(self):
+        """Add a body filter."""
+        self.fs.addfilter(
+            "test",
+            [("body", ":raw", ":contains", "matteo")],
+            [("fileinto", "Toto")]
+        )
+        self.assertEqual("{}".format(self.fs), """require ["body", "fileinto"];
+
+# Filter: test
+if anyof (body :contains :raw ["matteo"]) {
+    fileinto "Toto";
+}
+""")
+
+    def test_add_envelope_filter(self):
+        """Add a envelope filter."""
+        self.fs.addfilter(
+            "test",
+            [("envelope", ":is", ["From"], ["hello"])],
+            [("fileinto", "INBOX")]
+        )
+        self.assertEqual("{}".format(self.fs), """require ["fileinto"];
+
+# Filter: test
+if anyof (envelope :is ["From"] ["hello"]) {
+    fileinto "INBOX";
+}
+""")
+
 
 if __name__ == "__main__":
     unittest.main()

pip install -e .
parse_log_pytest
requirements.txt
nose pytest
3.6
requirements.txt
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
attrs==22.2.0
certifi==2021.5.30
future==1.0.0
importlib-metadata==4.8.3
iniconfig==1.1.1
nose==1.3.7
packaging==21.3
pluggy==1.0.0
py==1.11.0
pyparsing==3.1.4
pytest==7.0.1
-e git+https://github.com/tonioo/sievelib.git@5e96cc168543f19faaadd673740b83a56aa86350#egg=sievelib
six==1.17.0
swebench-matterhorn @ file:///swebench_matterhorn
tomli==1.2.3
typing_extensions==4.1.1
zipp==3.6.0

name: sievelib
channels:
  - defaults
  - https://repo.anaconda.com/pkgs/main
  - https://repo.anaconda.com/pkgs/r
  - conda-forge
dependencies:
  - _libgcc_mutex=0.1=main
  - _openmp_mutex=5.1=1_gnu
  - ca-certificates=2025.2.25=h06a4308_0
  - certifi=2021.5.30=py36h06a4308_0
  - ld_impl_linux-64=2.40=h12ee557_0
  - libffi=3.3=he6710b0_2
  - libgcc-ng=11.2.0=h1234567_1
  - libgomp=11.2.0=h1234567_1
  - libstdcxx-ng=11.2.0=h1234567_1
  - ncurses=6.4=h6a678d5_0
  - openssl=1.1.1w=h7f8727e_0
  - pip=21.2.2=py36h06a4308_0
  - python=3.6.13=h12debd9_1
  - readline=8.2=h5eee18b_0
  - setuptools=58.0.4=py36h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - wheel=0.37.1=pyhd3eb1b0_0
  - xz=5.6.4=h5eee18b_1
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - attrs==22.2.0
      - future==1.0.0
      - importlib-metadata==4.8.3
      - iniconfig==1.1.1
      - nose==1.3.7
      - packaging==21.3
      - pluggy==1.0.0
      - py==1.11.0
      - pyparsing==3.1.4
      - pytest==7.0.1
      - six==1.17.0
      - swebench-matterhorn==0.0.0
      - tomli==1.2.3
      - typing-extensions==4.1.1
      - zipp==3.6.0
prefix: /opt/conda/envs/sievelib

sievelib/tests/test_factory.py::FactoryTestCase::test_add_body_filter sievelib/tests/test_factory.py::FactoryTestCase::test_add_envelope_filter sievelib/tests/test_factory.py::FactoryTestCase::test_get_filter_conditions
sievelib/tests/test_factory.py::FactoryTestCase::test_add_exists_filter sievelib/tests/test_factory.py::FactoryTestCase::test_add_exists_filter_with_not sievelib/tests/test_factory.py::FactoryTestCase::test_add_filter_unicode sievelib/tests/test_factory.py::FactoryTestCase::test_add_header_filter sievelib/tests/test_factory.py::FactoryTestCase::test_add_header_filter_with_not sievelib/tests/test_factory.py::FactoryTestCase::test_add_size_filter sievelib/tests/test_factory.py::FactoryTestCase::test_disablefilter sievelib/tests/test_factory.py::FactoryTestCase::test_get_filter_actions sievelib/tests/test_factory.py::FactoryTestCase::test_get_filter_matchtype sievelib/tests/test_factory.py::FactoryTestCase::test_remove_filter sievelib/tests/test_factory.py::FactoryTestCase::test_use_action_with_tag
sievelib/commands.py sievelib/factory.py sievelib/managesieve.py
sievelib/commands.py sievelib/factory.py sievelib/managesieve.py