diff --git a/imapclient/imapclient.py b/imapclient/imapclient.py
index ac89622..46e9257 100644
--- a/imapclient/imapclient.py
+++ b/imapclient/imapclient.py
@@ -1288,6 +1288,23 @@ class IMAPClient(object):
                                        to_bytes(msg),
                                        unpack=True)
 
+    @require_capability('MULTIAPPEND')
+    def multiappend(self, folder, msgs):
+        """Append messages to *folder* using the MULTIAPPEND feature from :rfc:`3502`.
+
+        *msgs* should be a list of strings containing the full message including
+        headers.
+
+        Returns the APPEND response from the server.
+        """
+        msgs = [_literal(to_bytes(m)) for m in msgs]
+
+        return self._raw_command(
+            b'APPEND',
+            [self._normalise_folder(folder)] + msgs,
+            uid=False,
+        )
+
     def copy(self, messages, folder):
         """Copy one or more messages from the current folder to
         *folder*. Returns the COPY response string returned by the
@@ -1538,8 +1555,14 @@ class IMAPClient(object):
     def _send_literal(self, tag, item):
         """Send a single literal for the command with *tag*.
         """
+        if b'LITERAL+' in self._cached_capabilities:
+            out = b' {' + str(len(item)).encode('ascii') + b'+}\r\n' + item
+            logger.debug('> %s', debug_trunc(out, 64))
+            self._imap.send(out)
+            return
+
         out = b' {' + str(len(item)).encode('ascii') + b'}\r\n'
-        logger.debug(out)
+        logger.debug('> %s', out)
         self._imap.send(out)
 
         # Wait for continuation response
@@ -1666,6 +1689,9 @@ def _normalise_sort_criteria(criteria, charset=None):
         criteria = [criteria]
     return b'(' + b' '.join(to_bytes(item).upper() for item in criteria) + b')'
 
+class _literal(bytes):
+    """Hold message data that should always be sent as a literal."""
+    pass
 
 class _quoted(binary_type):
     """
@@ -1764,7 +1790,7 @@ def as_triplets(items):
 
 
 def _is8bit(data):
-    return any(b > 127 for b in iterbytes(data))
+    return isinstance(data, _literal) or any(b > 127 for b in iterbytes(data))
 
 
 def _iter_with_last(items):

diff --git a/tests/test_imapclient.py b/tests/test_imapclient.py
index 3d6dd74..13afd05 100644
--- a/tests/test_imapclient.py
+++ b/tests/test_imapclient.py
@@ -18,7 +18,7 @@ from imapclient.exceptions import (
 )
 from imapclient.imapclient import (
     IMAPlibLoggerAdapter, _parse_quota, Quota, MailboxQuotaRoots,
-    require_capability
+    require_capability, _literal
 )
 from imapclient.fixed_offset import FixedOffset
 from imapclient.testable_imapclient import TestableIMAPClient as IMAPClient
@@ -270,6 +270,14 @@ class TestAppend(IMAPClientTest):
         self.client._imap.append.assert_called_with(
             b'"foobar"', '(FLAG WAVE)', '"somedate"', msg)
 
+    def test_multiappend(self):
+        self.client._cached_capabilities = (b'MULTIAPPEND',)
+        self.client._raw_command = Mock()
+        self.client.multiappend('foobar', ['msg1', 'msg2'])
+
+        self.client._raw_command.assert_called_once_with(
+            b'APPEND', [b'"foobar"', b'msg1', b'msg2'], uid=False
+        )
 
 class TestAclMethods(IMAPClientTest):
 
@@ -805,6 +813,7 @@ class TestRawCommand(IMAPClientTest):
         super(TestRawCommand, self).setUp()
         self.client._imap._get_response.return_value = None
         self.client._imap._command_complete.return_value = ('OK', ['done'])
+        self.client._cached_capabilities = ()
 
     def check(self, command, args, expected):
         typ, data = self.client._raw_command(command, args)
@@ -842,6 +851,17 @@ class TestRawCommand(IMAPClientTest):
                    b'\xcc\r\n'
                    )
 
+    def test_literal_plus(self):
+        self.client._cached_capabilities = (b'LITERAL+',)
+
+        typ, data = self.client._raw_command(b'APPEND', [b'\xff', _literal(b'hello')], uid=False)
+        self.assertEqual(typ, 'OK')
+        self.assertEqual(data, ['done'])
+        self.assertEqual(self.client._imap.sent,
+                         b'tag APPEND {1+}\r\n'
+                         b'\xff  {5+}\r\n'
+                         b'hello\r\n')
+
     def test_complex(self):
         self.check(b'search', [b'FLAGGED', b'TEXT', b'\xfe\xff', b'TEXT', b'\xcc', b'TEXT', b'foo'],
                    b'tag UID SEARCH FLAGGED TEXT {2}\r\n'

MULTIAPPEND support
Originally reported by: **Erik Quaeghebeur (Bitbucket: [equaeghe](https://bitbucket.org/equaeghe))**

---

See https://tools.ietf.org/html/rfc3502 (and perhaps updates).

It would seem impossible to do this by extending the append method without breaking backwards compatability, so perhaps an option is to add a multiappend method taking an iterator over {'msg': ...[, 'flags': ...][, 'date': ...]}-dicts that can fall-back to multiple appends.

This kind of functionality may be useful for clients also working in disconnected mode.

---
- Bitbucket: https://bitbucket.org/mjs0/imapclient/issue/198

tests/test_imapclient.py::TestListFolders::test_blanks tests/test_imapclient.py::TestListFolders::test_empty_response tests/test_imapclient.py::TestListFolders::test_folder_encode_off tests/test_imapclient.py::TestListFolders::test_funky_characters tests/test_imapclient.py::TestListFolders::test_list_folders tests/test_imapclient.py::TestListFolders::test_list_folders_NO tests/test_imapclient.py::TestListFolders::test_list_sub_folders tests/test_imapclient.py::TestListFolders::test_list_sub_folders_NO tests/test_imapclient.py::TestListFolders::test_mixed tests/test_imapclient.py::TestListFolders::test_quoted_specials tests/test_imapclient.py::TestListFolders::test_simple tests/test_imapclient.py::TestListFolders::test_unquoted_numeric_folder_name tests/test_imapclient.py::TestListFolders::test_unquoted_numeric_folder_name_parsed_as_long tests/test_imapclient.py::TestListFolders::test_utf7_decoding tests/test_imapclient.py::TestListFolders::test_without_quotes tests/test_imapclient.py::TestFindSpecialFolder::test_find_special_folder_with_special_use tests/test_imapclient.py::TestFindSpecialFolder::test_find_special_folder_with_special_use_single_flag tests/test_imapclient.py::TestFindSpecialFolder::test_find_special_folder_without_special_use_nor_namespace tests/test_imapclient.py::TestSelectFolder::test_normal tests/test_imapclient.py::TestSelectFolder::test_unselect tests/test_imapclient.py::TestAppend::test_multiappend tests/test_imapclient.py::TestAppend::test_with_msg_time tests/test_imapclient.py::TestAppend::test_without_msg_time tests/test_imapclient.py::TestAclMethods::test_getacl tests/test_imapclient.py::TestAclMethods::test_setacl tests/test_imapclient.py::TestQuota::test__get_quota tests/test_imapclient.py::TestQuota::test_get_quota_root tests/test_imapclient.py::TestQuota::test_parse_quota tests/test_imapclient.py::TestQuota::test_set_quota tests/test_imapclient.py::TestIdleAndNoop::test_consume_until_tagged_response tests/test_imapclient.py::TestIdleAndNoop::test_idle tests/test_imapclient.py::TestIdleAndNoop::test_idle_check_blocking tests/test_imapclient.py::TestIdleAndNoop::test_idle_check_timeout tests/test_imapclient.py::TestIdleAndNoop::test_idle_check_with_data tests/test_imapclient.py::TestIdleAndNoop::test_idle_done tests/test_imapclient.py::TestIdleAndNoop::test_noop tests/test_imapclient.py::TestDebugLogging::test_IMAP_is_patched tests/test_imapclient.py::TestDebugLogging::test_redacted_password tests/test_imapclient.py::TestTimeNormalisation::test_default tests/test_imapclient.py::TestTimeNormalisation::test_pass_through tests/test_imapclient.py::TestNamespace::test_complex tests/test_imapclient.py::TestNamespace::test_folder_decoding tests/test_imapclient.py::TestNamespace::test_other_only tests/test_imapclient.py::TestNamespace::test_simple tests/test_imapclient.py::TestNamespace::test_without_folder_decoding tests/test_imapclient.py::TestCapabilities::test_caching tests/test_imapclient.py::TestCapabilities::test_decorator tests/test_imapclient.py::TestCapabilities::test_has_capability tests/test_imapclient.py::TestCapabilities::test_post_auth_request tests/test_imapclient.py::TestCapabilities::test_preauth tests/test_imapclient.py::TestCapabilities::test_server_returned_capability_after_auth tests/test_imapclient.py::TestCapabilities::test_with_starttls tests/test_imapclient.py::TestId::test_id tests/test_imapclient.py::TestId::test_invalid_parameters tests/test_imapclient.py::TestId::test_no_support tests/test_imapclient.py::TestRawCommand::test_complex tests/test_imapclient.py::TestRawCommand::test_embedded_literal tests/test_imapclient.py::TestRawCommand::test_failed_continuation_wait tests/test_imapclient.py::TestRawCommand::test_invalid_input_type tests/test_imapclient.py::TestRawCommand::test_literal_at_end tests/test_imapclient.py::TestRawCommand::test_literal_plus tests/test_imapclient.py::TestRawCommand::test_multiple_literals tests/test_imapclient.py::TestRawCommand::test_not_uid tests/test_imapclient.py::TestRawCommand::test_plain tests/test_imapclient.py::TestExpunge::test_expunge tests/test_imapclient.py::TestExpunge::test_id_expunge tests/test_imapclient.py::TestShutdown::test_shutdown tests/test_imapclient.py::TestContextManager::test_context_manager tests/test_imapclient.py::TestContextManager::test_context_manager_fail_closing tests/test_imapclient.py::TestContextManager::test_exception_inside_context_manager tests/test_imapclient.py::TestProtocolError::test_tagged_response_with_parse_error