diff --git a/CHANGELOG b/CHANGELOG
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,3 +1,37 @@
+0.7 (2025-06-08)
+  - updated to the lastest GHC, tested with 8.10.7 — 9.12.2 (PR #103)
+    Thanks to @thomasjm
+  - various improvements in IMAP protocol (#94, #97, #99)
+    Thanks to @mpscholten
+
+0.6.1.0 (2023-06-27)
+  - fixed bug with wrong IMAP header decoding ([Issue 89](https://github.com/qnikst/HaskellNet/issues/89))
+    Thanks to William Arteroi (@wwmoraes) and Nathan Collins (@ntc2)
+  - added support for OAuth2 authorization
+    Thanks to @paumr.
+
+0.6.0.1 (2022-05-17)
+  - GHC 9.0+ support
+
+0.6 (2020-12-27)
+----------------
+  - sendMail API was simplified (see Updating.md for details)
+  - Functions for graceful close were introduced.
+  - Use Text type instead of String internally.
+  - Use mime-mail.Address type instead of String for representing
+    addresses. (Issues #78, #59)
+  - Introduce SMTP exceptions family and move to using that
+    from fail method.
+  - Fixes possible double handle close in case of exceptions (Issue #76)
+  - SMTP: Fix issue with extra flushes. Now there are twice
+    as few messages during communication and that largely improves
+    communication speed.
+  - Module HaskellNet.SMTP.Internal was introduced. It contains
+    internal functions that may be useful when implementing primitives.
+    There is no API stability guarantees on the module.
+  - SMTP documentation largely improved
+  - Package moved from base64-string to base64 package (Issue #61)
+
 0.5.3 (2020-12-22)
 ------------------
 
diff --git a/HaskellNet.cabal b/HaskellNet.cabal
--- a/HaskellNet.cabal
+++ b/HaskellNet.cabal
@@ -1,10 +1,12 @@
 Name:           HaskellNet
 Synopsis:       Client support for POP3, SMTP, and IMAP
 Description:    This package provides client support for the POP3,
-                SMTP, and IMAP protocols.  NOTE: this package will be
-                split into smaller, protocol-specific packages in the
-                future.
-Version:        0.5.3
+                SMTP, and IMAP protocols.
+                .
+                Full examples can be found in the <https://github.com/qnikst/HaskellNet/tree/master/example repository>.
+                Additional documentation on the major updates can be found in the
+                <https://github.com/qnikst/HaskellNet/blob/master/Updating.md Updating.md> file
+Version:        0.6.2
 Copyright:      (c) 2006 Jun Mukai
 Author:         Jun Mukai
 Maintainer:     Alexander Vershilov <alexander.vershilov@sirius.online>,
@@ -14,21 +16,19 @@
 License-file:   LICENSE
 Category:       Network
 Homepage:       https://github.com/qnikst/HaskellNet
-Cabal-version:  >=1.10
+Cabal-version:  1.22
 Build-type:     Simple
 Tested-with:
-  GHC ==7.8.4
-   || ==7.10.3
-   || ==8.0.2
-   || ==8.2.2
-   || ==8.4.4
-   || ==8.6.5
-   || ==8.8.4
-   || ==8.10.2
+  GHC ==9.4.8
+   || ==9.6.7
+   || ==9.8.4
+   || ==9.10.2
+   || ==9.12.2
 
 Extra-Source-Files:
   CHANGELOG
   README.md
+  Updating.md
   example/*.hs
 
 Source-Repository head
@@ -51,6 +51,7 @@
     Network.HaskellNet.IMAP.Types
     Network.HaskellNet.IMAP.Parsers
     Network.HaskellNet.SMTP
+    Network.HaskellNet.SMTP.Internal
     Network.HaskellNet.POP3
     Network.HaskellNet.POP3.Connection
     Network.HaskellNet.POP3.Types
@@ -62,19 +63,21 @@
     Network.Compat
     Text.Packrat.Pos
     Text.Packrat.Parse
+  Reexported-modules:
+    Network.Mail.Mime
 
   Build-Depends:
-    base >= 4.3 && < 4.15,
-    network >= 2.6.3.1 && < 3.2,
-    mtl,
-    bytestring >=0.10.2,
-    pretty,
-    array,
-    cryptohash-md5,
-    base64-string,
-    old-time,
-    mime-mail >= 0.4.7 && < 0.6,
-    text
+    base >= 4.3 && < 4.22,
+    network >= 2.6.3.1 && < 3.3,
+    mtl >= 2.2.2 && < 2.4,
+    bytestring >=0.10.2 && < 0.13,
+    pretty >= 1.1.3 && < 1.2,
+    array >= 0.5 && < 0.6,
+    cryptohash-md5  >= 0.11 && < 0.12,
+    base64 < 1.1,
+    old-time  >= 1.0 && < 1.2,
+    mime-mail >= 0.4 && < 0.6,
+    text  >= 1.0 && < 3
 
   if !impl(ghc >= 8.0)
     Build-depends: fail >= 4.9.0.0 && <4.10
diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -1,7 +1,7 @@
 HaskellNet
 ==========
 
-![Haskell-CI](https://github.com/qnikst/HaskellNet/workflows/Haskell-CI/badge.svg)
+[![ci](https://github.com/qnikst/HaskellNet/actions/workflows/ci.yml/badge.svg)](https://github.com/qnikst/HaskellNet/actions/workflows/ci.yml)
 
 This package provides client support for the E-mail protocols POP3,
 SMTP, and IMAP.
diff --git a/Updating.md b/Updating.md
new file mode 100644
--- /dev/null
+++ b/Updating.md
@@ -0,0 +1,31 @@
+# Updating
+
+This document explains how to update package between major versions
+of the package.
+
+0.5.x -> 0.6
+============
+
+1. Sender, Recipient types were changed from 'Data.Text.Text' to
+[Network.Mail.Mime.Address](http://hackage.haskell.org/package/mime-mail-0.5.0/docs/Network-Mail-Mime.html#t:Address).
+   This change allows better interoperability with 'mime-mail' package, in addition
+   it solved a problem with specifying sender address in the API.
+   As Address type implements 'IsString' type class then just having '-XOverloadedStrings'
+   extension will be enough. However you should note, that "FirstName LastName <email>"
+   will be parsed as `Address Nothing "FirstName LastName <email>"` and not as
+   `Address (Just "FirstName LastName") "email"`, it may be surprising but that functionality
+   didn't work with older HaskellNet, so it was not changed.
+2. Exception. Previously the package exposed only `IOException` custom exceptions were send
+   using `failure` (`UserException` constructor). Now there is `SMTPException` family, so
+   if you used to capture SMTP exceptions by processing `IOExceptions` you should capture
+   `SMTPException` now. The package may still expose `IOException` in case of a network
+   failure.
+3. `Subject` type is now using Text. Enabling `-XOverladedStrings` extension should help
+   in this case.
+4. All mail sending functions were deprecated instead of them there is the 'sendMail'
+   function. Documentation and deprecation warning for each function explains how to change
+   the code to make it work.
+5. Now all `doSmtp*` functions implements graceful close, and there is a new `gracefullyCloseSMTP`
+   function to run graceful close, you may consider switching to that function from `closeSTMP`.
+   However it should be done with care as `gracefullyCloseSTMP` can be run only on the
+   connection that is in a awaiting command from the user state.
diff --git a/example/smtp.hs b/example/smtp.hs
--- a/example/smtp.hs
+++ b/example/smtp.hs
@@ -22,37 +22,44 @@
 attachments  = [] -- example [("application/octet-stream", "/path/to/file1.tar.gz), ("application/pdf", "/path/to/file2.pdf")]
 
 -- | Send plain text mail
-example1 = doSMTP server $ \conn ->
-    sendPlainTextMail to from subject plainBody conn
+example1 = doSMTP server $ \conn -> do
+    let mail = simpleMail' to from subject plainBody
+    sendMail mail conn
 
 -- | With custom port number
-example2 = doSMTPPort server port $ \conn ->
-    sendPlainTextMail to from subject plainBody conn
+example2 = doSMTPPort server port $ \conn -> do
+    let mail = simpleMail' to from subject plainBody
+    sendMail mail conn
 
 -- | Manually open and close the connection
 example3 = do
     conn <- connectSMTP server
-    sendPlainTextMail to from subject plainBody conn
+    let mail = simpleMail' to from subject plainBody
+    sendMail mail conn
     closeSMTP conn
 
 -- | Send mime mail
-example4 = doSMTP server $ \conn ->
-    sendMimeMail to from subject plainBody htmlBody [] conn
+example4 = doSMTP server $ \conn -> do
+    mail <-  simpleMail to from subject plainBody htmlBody []
+    sendMail mail conn
 
 -- | With file attachments (modify the `attachments` binding)
-example5 = doSMTP server $ \conn ->
-    sendMimeMail to from subject plainBody htmlBody attachments conn
+example5 = doSMTP server $ \conn -> do
+    mail <-  simpleMail to from subject plainBody htmlBody attachments
+    sendMail mail conn
 
 -- | With ByteString attachments
 bsContent = B.pack [43,43,43,43]
-example5_2 = doSMTP server $ \conn ->
-    sendMimeMail' to from subject plainBody htmlBody [("application/zip", "filename.zip", bsContent)] conn
+example5_2 = doSMTP server $ \conn -> do
+    let mail = simpleMailInMemory to from subject plainBody htmlBody [("application/zip", "filename.zip", bsContent)]
+    sendMail mail conn
 
 -- | With authentication
 example6 = doSMTP server $ \conn -> do
     authSuccess <- authenticate authType username password conn
     if authSuccess
-        then sendMimeMail to from subject plainBody htmlBody [] conn
+        then do mail <- simpleMail to from subject plainBody htmlBody []
+                sendMail mail conn
         else putStrLn "Authentication failed."
 
 -- | Custom
@@ -62,11 +69,10 @@
                        [(Address Nothing "to@test.org")]
                        [(Address Nothing "cc1@test.org"), (Address Nothing "cc2@test.org")]
                        []
-                       [("Subject", T.pack subject)]
+                       [("Subject", subject)]
                        [[htmlPart htmlBody, plainPart plainBody]]
     newMail' <- addAttachments attachments newMail
-    renderedMail <- renderMail' newMail'
-    sendMail from [to] (S.concat . B.toChunks $ renderedMail) conn
+    sendMail newMail' conn
     closeSMTP conn
 
 main = example1
diff --git a/example/smtp2.hs b/example/smtp2.hs
new file mode 100644
--- /dev/null
+++ b/example/smtp2.hs
@@ -0,0 +1,34 @@
+{-# OPTIONS -fno-warn-missing-signatures #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+import           Network.HaskellNet.Auth
+import           Network.HaskellNet.SMTP
+import           Network.HaskellNet.SMTP.SSL
+import           Network.Mail.Mime
+import Network.HaskellNet.Debug
+import Control.Monad
+import qualified Data.Text as T
+import qualified Data.ByteString as S
+import qualified Data.ByteString.Lazy as B
+
+-- | Your settings
+server       = "smtp.yandex.ru"
+username     = "no-reply@cheops.olimpiada.ru"
+password     = "Voo3gahm"
+authType     = PLAIN
+from         = Address (Just "A V") "no-reply@cheops.olimpiada.ru"
+to           = Address (Just "V A") "alexander.vershilov@gmail.com"
+subject      = "Network.HaskellNet.SMTP Test :)"
+plainBody    = "Hello world!\r\n.\r\nsomething else"
+htmlBody     = "<html><head></head><body><h1>Hello <i>world!</i></h1></body></html>"
+attachments  = [] -- example [("application/octet-stream", "/path/to/file1.tar.gz), ("application/pdf", "/path/to/file2.pdf")]
+
+main = do
+  conn <- connectSMTPSSLWithSettings server defaultSettingsSMTPSSL{sslLogToConsole=True}
+  authSucceed <- authenticate PLAIN (T.unpack username) (T.unpack password) conn
+  when authSucceed $ do
+    let newMail = Mail from [to] [] [] [("Subject", T.pack subject)] [[htmlPart htmlBody, plainPart plainBody]]
+    newMail' <- addAttachments attachments newMail
+    sendMail newMail' conn
+  Network.HaskellNet.SMTP.SSL.closeSMTP conn
+
diff --git a/src/Network/HaskellNet/Auth.hs b/src/Network/HaskellNet/Auth.hs
--- a/src/Network/HaskellNet/Auth.hs
+++ b/src/Network/HaskellNet/Auth.hs
@@ -1,21 +1,30 @@
+{-# language CPP #-}
+
 module Network.HaskellNet.Auth
 where
 
 import Crypto.Hash.MD5
-import qualified Codec.Binary.Base64.String as B64 (encode, decode)
+import Data.Text.Encoding.Base64 as B64
 
+#if MIN_VERSION_base64(0,5,0)
+import Data.Base64.Types as B64
+#endif
+
 import Data.Word
 import Data.List
 import Data.Bits
 import Data.Array
 import qualified Data.ByteString as B
+import qualified Data.Text as T
 
 type UserName = String
 type Password = String
 
+-- | Authorization types supported by the <https://www.ietf.org/rfc/rfc4954.txt RFC5954>
 data AuthType = PLAIN
               | LOGIN
               | CRAM_MD5
+              | XOAUTH2
                 deriving Eq
 
 instance Show AuthType where
@@ -24,16 +33,22 @@
               showMain PLAIN    = "PLAIN"
               showMain LOGIN    = "LOGIN"
               showMain CRAM_MD5 = "CRAM-MD5"
+              showMain XOAUTH2  = "XOAUTH2"
 
 b64Encode :: String -> String
-b64Encode = map (toEnum.fromEnum)
-          -- Hotfix for https://github.com/jtdaugherty/HaskellNet/issues/61
-          . delete '\n'
-          . B64.encode
-          . map (toEnum.fromEnum)
+b64Encode = T.unpack . encode . T.pack
+    where encode =
+#if MIN_VERSION_base64(0,5,0)
+              B64.extractBase64 . B64.encodeBase64
+#else
+              B64.encodeBase64
+#endif
 
+
+
 b64Decode :: String -> String
-b64Decode = map (toEnum.fromEnum) . B64.decode . map (toEnum.fromEnum)
+b64Decode = T.unpack . decode . T.pack
+    where decode = B64.decodeBase64Lenient
 
 showOctet :: [Word8] -> String
 showOctet = concatMap hexChars
@@ -68,3 +83,4 @@
 auth PLAIN    _ u p = plain u p
 auth LOGIN    _ u p = let (u', p') = login u p in unwords [u', p']
 auth CRAM_MD5 c u p = cramMD5 c u p
+auth XOAUTH2  _ u p = b64Encode $ "user=" ++ u ++ "\001auth=" ++ p ++ "\001\001"
diff --git a/src/Network/HaskellNet/IMAP.hs b/src/Network/HaskellNet/IMAP.hs
--- a/src/Network/HaskellNet/IMAP.hs
+++ b/src/Network/HaskellNet/IMAP.hs
@@ -8,14 +8,15 @@
       -- ** autenticated state commands
     , select, examine, create, delete, rename
     , subscribe, unsubscribe
-    , list, lsub, status, append
+    , list, lsub, status, append, appendFull
       -- ** selected state commands
     , check, close, expunge
-    , search, store, copy
+    , search, store, copy, move
     , idle
       -- * fetch commands
     , fetch, fetchHeader, fetchSize, fetchHeaderFields, fetchHeaderFieldsNot
     , fetchFlags, fetchR, fetchByString, fetchByStringR
+    , fetchPeek, fetchRPeek
       -- * other types
     , Flag(..), Attribute(..), MailboxStatus(..)
     , SearchQuery(..), FlagsQuery(..)
@@ -23,13 +24,13 @@
     )
 where
 
-import Network.Socket (PortNumber)
 import Network.Compat
+import qualified Network.HaskellNet.Auth as A
 import Network.HaskellNet.BSStream
 import Network.HaskellNet.IMAP.Connection
-import Network.HaskellNet.IMAP.Types
 import Network.HaskellNet.IMAP.Parsers
-import qualified Network.HaskellNet.Auth as A
+import Network.HaskellNet.IMAP.Types
+import Network.Socket (PortNumber)
 
 import Data.ByteString (ByteString)
 import qualified Data.ByteString.Char8 as BS
@@ -70,6 +71,7 @@
                  | SUBJECTs String
                  | TEXTs String
                  | TOs String
+                 | XGMRAW String
                  | UIDs [UID]
 
 
@@ -99,6 +101,7 @@
               showQuery (SUBJECTs s)    = "SUBJECT " ++ s
               showQuery (TEXTs s)       = "TEXT " ++ s
               showQuery (TOs addr)      = "TO " ++ addr
+              showQuery (XGMRAW s)      = "X-GM-RAW " ++ s
               showQuery (UIDs uids)     = concat $ intersperse "," $
                                           map show uids
               showFlag Seen        = "SEEN"
@@ -112,6 +115,9 @@
 data FlagsQuery = ReplaceFlags [Flag]
                 | PlusFlags [Flag]
                 | MinusFlags [Flag]
+                | ReplaceGmailLabels [GmailLabel]
+                | PlusGmailLabels [GmailLabel]
+                | MinusGmailLabels [GmailLabel]
 
 ----------------------------------------------------------------------
 -- establish connection
@@ -213,10 +219,10 @@
                     return buf'
         let (resp, mboxUp, value) = eval pNone (show6 num) buf
         case resp of
-         OK _ _        -> do mboxUpdate conn mboxUp
-                             return value
-         NO _ msg      -> fail ("NO: " ++ msg)
-         BAD _ msg     -> fail ("BAD: " ++ msg)
+         OK _ _ -> do mboxUpdate conn mboxUp
+                      return value
+         NO _ msg -> fail ("NO: " ++ msg)
+         BAD _ msg -> fail ("BAD: " ++ msg)
          PREAUTH _ msg -> fail ("preauth: " ++ msg)
 
 noop :: IMAPConnection -> IO ()
@@ -332,13 +338,13 @@
        buf2 <- getResponse $ stream conn
        let (resp, mboxUp, ()) = eval pNone (show6 num) buf2
        case resp of
-         OK _ _ -> mboxUpdate conn mboxUp
-         NO _ msg -> fail ("NO: "++msg)
-         BAD _ msg -> fail ("BAD: "++msg)
+         OK _ _        -> mboxUpdate conn mboxUp
+         NO _ msg      -> fail ("NO: "++msg)
+         BAD _ msg     -> fail ("BAD: "++msg)
          PREAUTH _ msg -> fail ("PREAUTH: "++msg)
     where mailLines = BS.lines mailData
           len       = sum $ map ((2+) . BS.length) mailLines
-          tstr      = maybe "" ((" "++) . show) time
+          tstr      = maybe "" ((" "++) . datetimeToStringIMAP) time
           fstr      = maybe "" ((" ("++) . (++")") . unwords . map show) flags'
 
 check :: IMAPConnection -> IO ()
@@ -369,6 +375,12 @@
     do lst <- fetchByString conn uid "BODY[]"
        return $ maybe BS.empty BS.pack $ lookup' "BODY[]" lst
 
+-- | Like 'fetch' but without marking the email as seen/read
+fetchPeek :: IMAPConnection -> UID -> IO ByteString
+fetchPeek conn uid =
+    do lst <- fetchByString conn uid "BODY.PEEK[]"
+       return $ maybe BS.empty BS.pack $ lookup' "BODY[]" lst
+
 fetchHeader :: IMAPConnection -> UID -> IO ByteString
 fetchHeader conn uid =
     do lst <- fetchByString conn uid "BODY[HEADER]"
@@ -377,26 +389,26 @@
 fetchSize :: IMAPConnection -> UID -> IO Int
 fetchSize conn uid =
     do lst <- fetchByString conn uid "RFC822.SIZE"
-       return $ maybe 0 read $ lookup' "RFC822.SIZE" lst
+       return $ maybe 0 read $ lookup "RFC822.SIZE" lst
 
 fetchHeaderFields :: IMAPConnection
                   -> UID -> [String] -> IO ByteString
 fetchHeaderFields conn uid hs =
-    do lst <- fetchByString conn uid ("BODY[HEADER.FIELDS "++unwords hs++"]")
-       return $ maybe BS.empty BS.pack $
-              lookup' ("BODY[HEADER.FIELDS "++unwords hs++"]") lst
+    do let fetchCmd = "BODY[HEADER.FIELDS ("++unwords hs++")]"
+       lst <- fetchByString conn uid fetchCmd
+       return $ maybe BS.empty BS.pack $ lookup' fetchCmd lst
 
 fetchHeaderFieldsNot :: IMAPConnection
                      -> UID -> [String] -> IO ByteString
 fetchHeaderFieldsNot conn uid hs =
-    do let fetchCmd = "BODY[HEADER.FIELDS.NOT "++unwords hs++"]"
+    do let fetchCmd = "BODY[HEADER.FIELDS.NOT ("++unwords hs++")]"
        lst <- fetchByString conn uid fetchCmd
        return $ maybe BS.empty BS.pack $ lookup' fetchCmd lst
 
 fetchFlags :: IMAPConnection -> UID -> IO [Flag]
 fetchFlags conn uid =
     do lst <- fetchByString conn uid "FLAGS"
-       return $ getFlags $ lookup' "FLAGS" lst
+       return $ getFlags $ lookup "FLAGS" lst
     where getFlags Nothing  = []
           getFlags (Just s) = eval' dvFlags "" s
 
@@ -406,6 +418,13 @@
     do lst <- fetchByStringR conn r "BODY[]"
        return $ map (\(uid, vs) -> (uid, maybe BS.empty BS.pack $
                                        lookup' "BODY[]" vs)) lst
+
+-- | Like 'fetchR' but without marking the email as seen/read
+fetchRPeek :: IMAPConnection -> (UID, UID) -> IO [(UID, ByteString)]
+fetchRPeek conn range =
+    do ls <- fetchByStringR conn range "BODY.PEEK[]"
+       return $ map (\(uid, vs) -> (uid, maybe BS.empty BS.pack $ lookup' "BODY[]" vs)) ls
+
 fetchByString :: IMAPConnection -> UID -> String
               -> IO [(String, String)]
 fetchByString conn uid command =
@@ -431,9 +450,12 @@
     where fstrs fs = "(" ++ (concat $ intersperse " " $ map show fs) ++ ")"
           toFStr s fstrs' =
               s ++ (if isSilent then ".SILENT" else "") ++ " " ++ fstrs'
-          flgs (ReplaceFlags fs) = toFStr "FLAGS" $ fstrs fs
-          flgs (PlusFlags fs)    = toFStr "+FLAGS" $ fstrs fs
-          flgs (MinusFlags fs)   = toFStr "-FLAGS" $ fstrs fs
+          flgs (ReplaceGmailLabels ls) = toFStr "X-GM-LABELS" $ fstrs ls
+          flgs (PlusGmailLabels ls)    = toFStr "+X-GM-LABELS" $ fstrs ls
+          flgs (MinusGmailLabels ls)   = toFStr "-X-GM-LABELS" $ fstrs ls
+          flgs (ReplaceFlags fs)       = toFStr "FLAGS" $ fstrs fs
+          flgs (PlusFlags fs)          = toFStr "+FLAGS" $ fstrs fs
+          flgs (MinusFlags fs)         = toFStr "-FLAGS" $ fstrs fs
           procStore (n, ps) = (maybe (toEnum (fromIntegral n)) read
                                          (lookup' "UID" ps)
                               ,maybe [] (eval' dvFlags "") (lookup' "FLAG" ps))
@@ -449,28 +471,61 @@
 copy :: IMAPConnection -> UID -> MailboxName -> IO ()
 copy conn uid mbox     = copyFull conn (show uid) mbox
 
+move :: IMAPConnection -> UID -> MailboxName -> IO ()
+move conn uid mboxname = sendCommand conn ("UID MOVE " ++ show uid ++ " " ++ mboxname) pNone
+
 ----------------------------------------------------------------------
 -- auxialiary functions
 
+showMonth :: Month -> String
+showMonth January   = "Jan"
+showMonth February  = "Feb"
+showMonth March     = "Mar"
+showMonth April     = "Apr"
+showMonth May       = "May"
+showMonth June      = "Jun"
+showMonth July      = "Jul"
+showMonth August    = "Aug"
+showMonth September = "Sep"
+showMonth October   = "Oct"
+showMonth November  = "Nov"
+showMonth December  = "Dec"
+
+show2 :: Int -> String
+show2 n | n < 10    = '0' : show n
+        | otherwise = show n
+
+
+show4 :: (Ord a, Num a, Show a) => a -> String
+show4 n | n > 1000 = show n
+        | n > 100  = '0' : show n
+        | n > 10   = "00" ++ show n
+        | otherwise  = "000" ++ show n
+
 dateToStringIMAP :: CalendarTime -> String
 dateToStringIMAP date = concat $ intersperse "-" [show2 $ ctDay date
                                                  , showMonth $ ctMonth date
                                                  , show $ ctYear date]
-    where show2 n | n < 10    = '0' : show n
-                  | otherwise = show n
-          showMonth January   = "Jan"
-          showMonth February  = "Feb"
-          showMonth March     = "Mar"
-          showMonth April     = "Apr"
-          showMonth May       = "May"
-          showMonth June      = "Jun"
-          showMonth July      = "Jul"
-          showMonth August    = "Aug"
-          showMonth September = "Sep"
-          showMonth October   = "Oct"
-          showMonth November  = "Nov"
-          showMonth December  = "Dec"
+timeToStringIMAP :: CalendarTime -> String
+timeToStringIMAP c = concat
+                     $ intersperse ":"
+                     $ fmap show2 [ctHour c, ctMin c, ctSec c]
 
+-- Convert CalenarTime to "date-time" string per RFC3501
+datetimeToStringIMAP :: CalendarTime -> String
+datetimeToStringIMAP c =
+  "\""
+  ++ dateToStringIMAP c
+  ++ " "
+  ++ timeToStringIMAP c
+  ++ " "
+  ++ zone (ctTZ c)
+  ++ "\""
+  where
+    zone s =
+      (if s>=0 then "+" else "-") ++
+      show4 (s `div` 3600)
+
 strip :: ByteString -> ByteString
 strip = fst . BS.spanEnd isSpace . BS.dropWhile isSpace
 
@@ -482,10 +537,10 @@
 
 lookup' :: String -> [(String, b)] -> Maybe b
 lookup' _ [] = Nothing
-lookup' q ((k,v):xs) | q == lastWord k  = return v
+lookup' q ((k,v):xs) | q == query k  = return v
                      | otherwise        = lookup' q xs
     where
-        lastWord = last . words
+        query = unwords . drop 2 . words
 
 -- TODO: This is just a first trial solution for this stack overflow question:
 --       http://stackoverflow.com/questions/26183675/error-when-fetching-subject-from-email-using-haskellnets-imap
diff --git a/src/Network/HaskellNet/IMAP/Parsers.hs b/src/Network/HaskellNet/IMAP/Parsers.hs
--- a/src/Network/HaskellNet/IMAP/Parsers.hs
+++ b/src/Network/HaskellNet/IMAP/Parsers.hs
@@ -237,7 +237,7 @@
        return $ Right (attrs, sep, mbox)
     where parseAttr =
               do char '\\'
-                 choice [ string "Noinferior" >> return Noinferiors
+                 choice [ string "Noinferiors" >> return Noinferiors
                         , string "Noselect" >> return Noselect
                         , string "Marked" >> return Marked
                         , string "Unmarked" >> return Unmarked
diff --git a/src/Network/HaskellNet/IMAP/Types.hs b/src/Network/HaskellNet/IMAP/Types.hs
--- a/src/Network/HaskellNet/IMAP/Types.hs
+++ b/src/Network/HaskellNet/IMAP/Types.hs
@@ -1,5 +1,6 @@
 module Network.HaskellNet.IMAP.Types
     ( MailboxName
+    , GmailLabel
     , UID
     , Charset
     , MailboxInfo(..)
@@ -28,6 +29,7 @@
 type MailboxName = String
 type UID = Word64
 type Charset = String
+type GmailLabel = String
 
 data MailboxInfo = MboxInfo { _mailbox :: MailboxName
                             , _exists :: Integer
diff --git a/src/Network/HaskellNet/POP3.hs b/src/Network/HaskellNet/POP3.hs
--- a/src/Network/HaskellNet/POP3.hs
+++ b/src/Network/HaskellNet/POP3.hs
@@ -31,10 +31,10 @@
     )
     where
 
-import Network.HaskellNet.BSStream
-import Network.Socket
 import Network.Compat
 import qualified Network.HaskellNet.Auth as A
+import Network.HaskellNet.BSStream
+import Network.Socket
 
 import Data.ByteString (ByteString)
 import qualified Data.ByteString as B
@@ -159,9 +159,9 @@
                          (UIDL msg)   -> "UIDL " ++ maybe "" show msg
                          (APOP usern passw) -> "APOP " ++ usern ++ " " ++
                                              hexDigest (apopKey conn ++ passw)
-                         (AUTH _ _ _) -> error "BUG: AUTH should not get matched here"
-                         (RETR _) -> error "BUG: RETR should not get matched here"
-                         (TOP _ _) -> error "BUG: TOP should not get matched here"
+                         AUTH _ _ _   -> error "sendCommand: impossible happened AUTH expected to be processed in the preceeding clause"
+                         RETR _       -> error "sendCommand: impossible happened AUTH expected to be processed in the preceeding clause"
+                         TOP _ _      -> error "sendCommand: impossible happened AUTH expected to be processed in the preceeding clause"
 
 user :: POP3Connection -> String -> IO ()
 user conn name = do (resp, _) <- sendCommand conn (USER name)
diff --git a/src/Network/HaskellNet/SMTP.hs b/src/Network/HaskellNet/SMTP.hs
--- a/src/Network/HaskellNet/SMTP.hs
+++ b/src/Network/HaskellNet/SMTP.hs
@@ -1,141 +1,167 @@
 {-# LANGUAGE ScopedTypeVariables #-}
 {- |
 
-This module provides functions for working with the SMTP protocol in the client side,
-including /opening/ and /closing/ connections, /sending commands/ to the server,
-/authenticate/ and /sending mails/.
-
-Here's a basic usage example:
-
->
-> import Network.HaskellNet.SMTP
-> import Network.HaskellNet.Auth
-> import qualified Data.Text.Lazy as T
->
-> main = doSMTP "your.smtp.server.com" $ \conn ->
->    authSucceed <- authenticate PLAIN "username" "password" conn
->    if authSucceed
->        then sendPlainTextMail "receiver@server.com" "sender@server.com" "subject" (T.pack "Hello! This is the mail body!") conn
->        else print "Authentication failed."
+This module provides functions client side of the SMTP protocol.
 
-Notes for the above example:
+A basic usage example:
 
-   * First the 'SMTPConnection' is opened with the 'doSMTP' function.
-     The connection should also be established with functions such as 'connectSMTP',
-     'connectSMTPPort' and 'doSMTPPort'.
-     With the @doSMTP*@ functions the connection is opened, then executed an action
-     with it and then closed automatically.
-     If the connection is opened with the @connectSMTP*@ functions you may want to
-     close it with the 'closeSMTP' function after using it.
-     It is also possible to create a 'SMTPConnection' from an already opened connection
-     stream ('BSStream') using the 'connectStream' or 'doSMTPStream' functions.
+@
+\{\-\# LANGUAGE OverloadedStrings \#\-\}
+import "Network.HaskellNet.SMTP"
+import "Network.HaskellNet.Auth"
+import "Network.Mail.Mime"
+import "System.Exit" (die)
 
-     /NOTE:/ For /SSL\/TLS/ support you may establish the connection using
-             the functions (such as @connectSMTPSSL@) provided in the
-             @Network.HaskellNet.SMTP.SSL@ module of the
-             <http://hackage.haskell.org/package/HaskellNet-SSL HaskellNet-SSL>
-             package.
+main :: IO ()
+main = 'doSMTP' "your.smtp.server.com" $ \\conn -> do -- (1)
+   authSucceed <- 'authenticate' 'PLAIN' "username" "password" conn -- (2)
+   if authSucceed
+   then do
+     let mail = 'Network.Mail.Mime.simpleMail''
+           "receiver\@server.com"
+           "sender\@server.com"
+           "subject"
+           "Hello! This is the mail body!"
+     sendMail mail conn -- (3)
+   else die "Authentication failed."
+@
 
-   * The 'authenticate' function authenticates to the server with the specified 'AuthType'.
-     'PLAIN', 'LOGIN' and 'CRAM_MD5' 'AuthType's are available. It returns a 'Bool'
-     indicating either the authentication succeed or not.
+Notes for the above example:
 
+   * @(1)@ The connection (@conn::@'SMTPConnection') is opened using the 'doSMTP' function.
+     We can use this connection to communicate with @SMTP@ server.
+   * @(2)@ The 'authenticate' function authenticates to the server with the specified 'AuthType'.
+     It returns a 'Bool' indicating either the authentication succeed or not.
+   * @(3)@ The 'sendMail' is used to send a email a plain text email.
 
-   * To send a mail you can use 'sendPlainTextMail' for plain text mail, or 'sendMimeMail'
-     for mime mail.
+__N.B.__ For /SSL\/TLS/ support you may establish the connection using
+  the functions (such as @connectSMTPSSL@) provided by the @Network.HaskellNet.SMTP.SSL@ module
+  of the <http://hackage.haskell.org/package/HaskellNet-SSL HaskellNet-SSL> package.
 -}
 module Network.HaskellNet.SMTP
-    ( -- * Types
-      Command(..)
-    , Response(..)
-    , AuthType(..)
-    , SMTPConnection
-      -- * Establishing Connection
-    , connectSMTPPort
-    , connectSMTP
-    , connectStream
-      -- * Operation to a Connection
-    , sendCommand
-    , closeSMTP
-      -- * Other Useful Operations
-    , authenticate
-    , sendMail
+    ( -- * Workflow
+      -- $workflow
+
+      -- ** Controlling connections
+      -- $controlling-connections
+      SMTPConnection
+      -- $controlling-connections-1
     , doSMTPPort
     , doSMTP
     , doSMTPStream
+      -- $controlling-connections-2
+
+      -- ** Authentication
+    , authenticate
+      -- $authentication
+    , AuthType(..)
+
+      -- ** Sending emails
+      -- $sending-mail
+    , sendMail
+      -- *** Deprecated functions
     , sendPlainTextMail
     , sendMimeMail
     , sendMimeMail'
     , sendMimeMail2
-    )
-    where
+      -- * Low level commands
+      -- ** Establishing Connection
+      -- $low-level-connection
+    , connectSMTPPort
+    , connectSMTP
+    , connectStream
+    , closeSMTP
+    , gracefullyCloseSMTP
+    , SMTPException(..)
+    ) where
 
 import Network.HaskellNet.BSStream
-import Data.ByteString (ByteString)
 import qualified Data.ByteString.Char8 as BS
 import Network.BSD (getHostName)
 import Network.Socket
 import Network.Compat
 
-import Control.Applicative ((<$>))
+import Control.Applicative
 import Control.Exception
 import Control.Monad (unless, when)
 
-import Data.Char (isDigit)
-
 import Network.HaskellNet.Auth
 
 import Network.Mail.Mime
 import qualified Data.ByteString.Lazy as B
-import qualified Data.ByteString as S
 
 import qualified Data.Text.Lazy as LT
 import qualified Data.Text as T
 
--- The response field seems to be unused. It's saved at one place, but never
--- retrieved.
-data SMTPConnection = SMTPC { bsstream :: !BSStream, _response :: ![ByteString] }
+import GHC.Stack
+import Prelude
+import Network.HaskellNet.SMTP.Internal
 
-data Command = HELO String
-             | EHLO String
-             | MAIL String
-             | RCPT String
-             | DATA ByteString
-             | EXPN String
-             | VRFY String
-             | HELP String
-             | AUTH AuthType UserName Password
-             | NOOP
-             | RSET
-             | QUIT
-               deriving (Show, Eq)
 
-type ReplyCode = Int
+-- $workflow
+-- The common workflow while working with the library is:
+--
+--   1. Establish a new connection
+--   2. Authenticate to the server
+--   3. Perform message sending
+--   4. Close connections
+--
+-- Steps 1 and 4 are combined together using @bracket@-like API. Other than that
+-- the documentation sections are structured according to this workflow.
 
-data Response = Ok
-              | SystemStatus
-              | HelpMessage
-              | ServiceReady
-              | ServiceClosing
-              | UserNotLocal
-              | CannotVerify
-              | StartMailInput
-              | ServiceNotAvailable
-              | MailboxUnavailable
-              | ErrorInProcessing
-              | InsufficientSystemStorage
-              | SyntaxError
-              | ParameterError
-              | CommandNotImplemented
-              | BadSequence
-              | ParameterNotImplemented
-              | MailboxUnavailableError
-              | UserNotLocalError
-              | ExceededStorage
-              | MailboxNotAllowed
-              | TransactionFailed
-                deriving (Show, Eq)
+-- $controlling-connections
 
+-- $controlling-connections-1
+-- The library encourages creation of 'SMTPConnection' using the @doSMTP@-family functions.
+-- These functions provide @bracket@-like pattern that manages connection state:
+-- creates a connection, passes it to the user defined @IO@ action and frees connection
+-- when the action exits. This approach is simple and exception safe.
+--
+-- __N.B.__ It should be noted that none of these functions implements keep alive of any kind,
+-- so the server is free to close the connection by timeout even the end of before the users
+-- action exits.
+--
+
+-- $controlling-connections-2
+--
+-- __NOTE:__ For /SSL\/TLS/ support you may establish the connection using
+-- the functions (such as @connectSMTPSSL@) provided by the @Network.HaskellNet.SMTP.SSL@ module
+-- of the <http://hackage.haskell.org/package/HaskellNet-SSL HaskellNet-SSL> package.
+--
+-- @bracket-@ style is not the only possible style for resource management,
+-- it's possible to use <http://hackage.haskell.org/package/resourcet resourcet> or
+-- <http://hackage.haskell.org/package/resource-pool resource-pool> as well. In both of the
+-- approaches you need to use low-level 'connectSTM*' and 'closeSMTP' functions.
+--
+-- Basic example using @resourcet@.
+--
+-- @
+-- \{\-\# LANGUAGE OverloadedStrings \#\-\}
+-- import "Network.HaskellNet.SMTP"
+-- import "Network.HaskellNet.Auth"
+-- import "Control.Monad.Trans.Resource"
+-- import "System.Exit" (die)
+--
+-- main :: IO ()
+-- main = 'Control.Monad.Trans.Resource.runResourceT' $ do
+--    (key, conn)
+--        <- 'Control.Monad.Trans.Resource.allocate'
+--               ('connectSMTP' "your.smtp.server.com")
+--               ('closeSMTP')
+--    ... conn
+-- @
+--
+-- This approach allows resource management even if the code does not form
+-- a stack, so is more general.
+--
+-- __NOTE__. SMTP protocol advices to use 'QUIT' command for graceful connection
+-- close. Before version 0.6 the library never sent it, so does 'closeSMTP' call.
+--
+-- Starting from 0.6 'doSMTP'-family uses graceful exit and sends 'QUIT' before terminating
+-- a connection. This way of termination is exposed as 'gracefullyCloseSTMP' function,
+-- however it's not a default method because it requires a connection to be in
+-- a valid state. So it's not possible to guarantee backwards compatibility.
+
 -- | connecting SMTP server with the specified name and port number.
 connectSMTPPort :: String     -- ^ name of the server
                 -> PortNumber -- ^ port number
@@ -149,229 +175,180 @@
             -> IO SMTPConnection
 connectSMTP = flip connectSMTPPort 25
 
-tryCommand :: SMTPConnection -> Command -> Int -> [ReplyCode]
-           -> IO ByteString
-tryCommand conn cmd tries expectedReplies = do
-    (code, msg) <- sendCommand conn cmd
-    case () of
-        _ | code `elem` expectedReplies -> return msg
-        _ | tries > 1 ->
-            tryCommand conn cmd (tries - 1) expectedReplies
-          | otherwise -> do
-            bsClose (bsstream conn)
-            fail $ "cannot execute command " ++ show cmd ++
-                ", " ++ prettyExpected expectedReplies ++
-                ", " ++ prettyReceived code msg
-
-  where
-    prettyReceived :: Int -> ByteString -> String
-    prettyReceived co ms = "but received" ++ show co ++ " (" ++ BS.unpack ms ++ ")"
-
-    prettyExpected :: [ReplyCode] -> String
-    prettyExpected [x] = "expected reply code of " ++ show x
-    prettyExpected xs = "expected any reply code of " ++ show xs
-
--- | create SMTPConnection from already connected Stream
-connectStream :: BSStream -> IO SMTPConnection
+-- | Create SMTPConnection from already connected Stream
+--
+-- Throws @CantConnect :: SMTPException@ in case if got illegal
+-- greeting.
+connectStream :: HasCallStack => BSStream -> IO SMTPConnection
 connectStream st =
     do (code1, _) <- parseResponse st
-       unless (code1 == 220) $
-              do bsClose st
-                 fail "cannot connect to the server"
-       senderHost <- getHostName
+       unless (code1 == 220) $ do
+         bsClose st
+         throwIO $ UnexpectedGreeting code1
+       senderHost <- T.pack <$> getHostName
        msg <- tryCommand (SMTPC st []) (EHLO senderHost) 3 [250]
        return (SMTPC st (tail $ BS.lines msg))
 
-parseResponse :: BSStream -> IO (ReplyCode, ByteString)
-parseResponse st =
-    do (code, bdy) <- readLines
-       return (read $ BS.unpack code, BS.unlines bdy)
-    where readLines =
-              do l <- bsGetLine st
-                 let (c, bdy) = BS.span isDigit l
-                 if not (BS.null bdy) && BS.head bdy == '-'
-                    then do (c2, ls) <- readLines
-                            return (c2, BS.tail bdy:ls)
-                    else return (c, [BS.tail bdy])
 
-
--- | send a method to a server
-sendCommand :: SMTPConnection -> Command -> IO (ReplyCode, ByteString)
-sendCommand (SMTPC conn _) (DATA dat) =
-    do bsPutCrLf conn $ BS.pack "DATA"
-       (code, msg) <- parseResponse conn
-       unless (code == 354) $ fail $ "this server cannot accept any data. code: " ++ show code ++ ", msg: " ++ BS.unpack msg
-       mapM_ (sendLine . stripCR) $ BS.lines dat ++ [BS.pack "."]
-       parseResponse conn
-    where sendLine = bsPutCrLf conn
-          stripCR bs = case BS.unsnoc bs of
-                         Just (line, '\r') -> line
-                         _                 -> bs
-sendCommand (SMTPC conn _) (AUTH LOGIN username password) =
-    do bsPutCrLf conn command
-       (_, _) <- parseResponse conn
-       bsPutCrLf conn $ BS.pack userB64
-       (_, _) <- parseResponse conn
-       bsPutCrLf conn $ BS.pack passB64
-       parseResponse conn
-    where command = BS.pack "AUTH LOGIN"
-          (userB64, passB64) = login username password
-sendCommand (SMTPC conn _) (AUTH at username password) =
-    do bsPutCrLf conn command
-       (code, msg) <- parseResponse conn
-       unless (code == 334) $ fail $ "authentication failed. code: " ++ show code ++ ", msg: " ++ BS.unpack msg
-       bsPutCrLf conn $ BS.pack $ auth at (BS.unpack msg) username password
-       parseResponse conn
-    where command = BS.pack $ unwords ["AUTH", show at]
-sendCommand (SMTPC conn _) meth =
-    do bsPutCrLf conn $ BS.pack command
-       parseResponse conn
-    where command = case meth of
-                      (HELO param) -> "HELO " ++ param
-                      (EHLO param) -> "EHLO " ++ param
-                      (MAIL param) -> "MAIL FROM:<" ++ param ++ ">"
-                      (RCPT param) -> "RCPT TO:<" ++ param ++ ">"
-                      (EXPN param) -> "EXPN " ++ param
-                      (VRFY param) -> "VRFY " ++ param
-                      (HELP msg)   -> if null msg
-                                        then "HELP\r\n"
-                                        else "HELP " ++ msg
-                      NOOP         -> "NOOP"
-                      RSET         -> "RSET"
-                      QUIT         -> "QUIT"
-                      (DATA _)     ->
-                          error "BUG: DATA pattern should be matched by sendCommand patterns"
-                      (AUTH {})     ->
-                          error "BUG: AUTH pattern should be matched by sendCommand patterns"
-
--- | close the connection.  This function send the QUIT method, so you
--- do not have to QUIT method explicitly.
-closeSMTP :: SMTPConnection -> IO ()
-closeSMTP (SMTPC conn _) = bsClose conn
-
-{-
-I must be being stupid here
-
-I can't seem to be able to catch the exception arising from the
-connection already being closed this would be the correct way to do it
-but instead we're being naughty above by just closes the connection
-without first sending QUIT
-
-closeSMTP c@(SMTPC conn _) =
-    do sendCommand c QUIT
-       bsClose conn `catch` \(_ :: IOException) -> return ()
--}
+-- $authentication
 
 {- |
-This function will return 'True' if the authentication succeeds.
-Here's an example of sending a mail with a server that requires
-authentication:
+Authenticates user on the remote server. Returns 'True' if the authentication succeeds,
+otherwise returns 'False'.
 
->    authSucceed <- authenticate PLAIN "username" "password" conn
->    if authSucceed
->        then sendPlainTextMail "receiver@server.com" "sender@server.com" "subject" (T.pack "Hello!") conn
->        else print "Authentication failed."
+Usage example:
+
+@
+\{\-\# LANGUAGE OverloadedStrings \#\-\}
+authSucceed <- 'authenticate' 'PLAIN' "username" "password" conn
+if authSucceed
+then 'sendPlainTextMail' "receiver\@server.com" "sender\@server.com" "subject" "Hello!" conn
+else 'print' "Authentication failed."
+@
 -}
 authenticate :: AuthType -> UserName -> Password -> SMTPConnection -> IO Bool
 authenticate at username password conn  = do
         (code, _) <- sendCommand conn $ AUTH at username password
         return (code == 235)
 
--- | sending a mail to a server. This is achieved by sendMessage.  If
--- something is wrong, it raises an IOexception.
-sendMail :: String     -- ^ sender mail
-         -> [String]   -- ^ receivers
-         -> ByteString -- ^ data
-         -> SMTPConnection
-         -> IO ()
-sendMail sender receivers dat conn = do
-                 sendAndCheck (MAIL sender)
-                 mapM_ (sendAndCheck . RCPT) receivers
-                 sendAndCheck (DATA dat)
-                 return ()
-  where
-    -- Try the command once and @fail@ if the response isn't 250.
-    sendAndCheck cmd = tryCommand conn cmd 1 [250, 251]
+-- $authentication
+-- __N.B.__ The choice of the authentication method is currently explicit and the library
+-- does not analyze server capabilities reply for choosing the right method.
+--
 
--- | doSMTPPort open a connection, and do an IO action with the
--- connection, and then close it.
+-- | 'doSMTPPort' opens a connection to the given port server and
+-- performs an IO action with the connection, and then close it.
+--
+-- 'SMTPConnection' is freed once 'IO' action scope is finished, it means that
+-- 'SMTPConnection' value should not escape the action scope.
 doSMTPPort :: String -> PortNumber -> (SMTPConnection -> IO a) -> IO a
-doSMTPPort host port =
-    bracket (connectSMTPPort host port) closeSMTP
+doSMTPPort host port f =
+    bracket (connectSMTPPort host port)
+            (\(SMTPC conn _) -> bsClose conn)
+            (\c -> f c >>= \x -> quitSMTP c >> pure x)
 
--- | doSMTP is similar to doSMTPPort, except that it does not require
--- port number but connects to the server with port 25.
+-- | 'doSMTP' is similar to 'doSMTPPort', except that it does not require
+-- port number and connects to the default SMTP port — 25.
 doSMTP :: String -> (SMTPConnection -> IO a) -> IO a
 doSMTP host = doSMTPPort host 25
 
--- | doSMTPStream is similar to doSMTPPort, except that its argument
--- is a Stream data instead of hostname and port number.
+-- | 'doSMTPStream' is similar to 'doSMTPPort', except that its argument
+-- is a Stream data instead of hostname and port number. Using this function
+-- you can embed connections maintained by the other libraries or add debug info
+-- in a common  way.
+--
+-- Using this function you can create an 'SMTPConnection' from an already
+-- opened connection stream. See more info on the 'BStream' abstraction in the
+-- "Network.HaskellNet.BSStream" module.
 doSMTPStream :: BSStream -> (SMTPConnection -> IO a) -> IO a
-doSMTPStream s = bracket (connectStream s) closeSMTP
+doSMTPStream s f =
+  bracket (connectStream s)
+          (\(SMTPC conn _) -> bsClose conn)
+          (\c -> f c >>= \x -> quitSMTP c >> pure x)
 
+-- $sending-mail
+--
+-- Since version 0.6 there is only one function 'sendMail' that sends a email
+-- rendered using mime-mail package. Historically there is a family of @send*Mail@
+-- functions that provide simpler interface but they basically mimic the functions
+-- from the mime-mail package, and it's encouraged to use those functions directly.
+--
+-- +------------------------+------------+----------+-------------+-------------+
+-- | Method                 | Plain text | Html body| Attachments |  Note       |
+-- |                        | body       |          |             |             |
+-- +========================+============+==========+=============+=============+
+-- | 'sendMail'             |   Uses mail-mime 'Mail' type        |             |
+-- +------------------------+------------+----------+-------------+-------------+
+-- | 'sendPlainTextMail'    |     ✓      |    ✗     |     ✗       | deprecated  |
+-- +------------------------+------------+----------+-------------+-------------+
+-- | 'sendMimeMail'         |     ✓      |    ✓     | ✓ (filepath)| deprecated  |
+-- +------------------------+------------+----------+-------------+-------------+
+-- | 'sendMimeMail''        |    ✓       |    ✓     | ✓  (memory) | deprecated  |
+-- +------------------------+------------+----------+-------------+-------------+
+-- | 'sendMimeMail2'        |   Uses mail-mime 'Mail' type        | deprecated  |
+-- +------------------------+-------------------------------------+-------------+
+
 -- | Send a plain text mail.
-sendPlainTextMail :: String  -- ^ receiver
-                  -> String  -- ^ sender
-                  -> String  -- ^ subject
+--
+-- __DEPRECATED__. Instead of @sendPlainTextMail to from subject plainBody@ use:
+--
+-- @
+-- mail = 'Network.Mail.Mime.simpleMail'' to from subject plainBody
+-- sendMail mail conn
+-- @
+{-# DEPRECATED sendPlainTextMail "Use 'sendMail (Network.Mail.Mime.simpleMail' to from subject plainBody)' instead" #-}
+sendPlainTextMail :: Address -- ^ receiver
+                  -> Address -- ^ sender
+                  -> T.Text  -- ^ subject
                   -> LT.Text -- ^ body
                   -> SMTPConnection -- ^ the connection
                   -> IO ()
-sendPlainTextMail to from subject body con = do
-    renderedMail <- renderMail' myMail
-    sendMail from [to] (lazyToStrict renderedMail) con
-    where
-        myMail = simpleMail' (address to) (address from) (T.pack subject) body
-        address = Address Nothing . T.pack
+sendPlainTextMail to from subject body con =
+  let mail = simpleMail' to from subject body
+  in sendMail mail con
 
+
 -- | Send a mime mail. The attachments are included with the file path.
-sendMimeMail :: String               -- ^ receiver
-             -> String               -- ^ sender
-             -> String               -- ^ subject
+--
+-- __DEPRECATED__. Instead of @sendMimeMail to from subject plainBody htmlBody attachments@ use:
+--
+-- @
+-- mail <- 'Network.Mail.Mime.simpleMail' to from subject plainBody htmlBody attachments
+-- sendMail mail conn
+-- @
+--
+{-# DEPRECATED sendMimeMail "Use 'Network.Mail.Mime.simpleMail to from subject plainBody htmlBody attachments >>= \\mail -> sendMail mail conn' instead" #-}
+sendMimeMail :: Address              -- ^ receiver
+             -> Address              -- ^ sender
+             -> T.Text               -- ^ subject
              -> LT.Text              -- ^ plain text body
              -> LT.Text              -- ^ html body
              -> [(T.Text, FilePath)] -- ^ attachments: [(content_type, path)]
              -> SMTPConnection
              -> IO ()
 sendMimeMail to from subject plainBody htmlBody attachments con = do
-  myMail <- simpleMail (address to) (address from) (T.pack subject)
-            plainBody htmlBody attachments
-  renderedMail <- renderMail' myMail
-  sendMail from [to] (lazyToStrict renderedMail) con
-  where
-    address = Address Nothing . T.pack
+  myMail <- simpleMail to from subject plainBody htmlBody attachments
+  sendMail myMail con
 
 -- | Send a mime mail. The attachments are included with in-memory 'ByteString'.
-sendMimeMail' :: String                         -- ^ receiver
-              -> String                         -- ^ sender
-              -> String                         -- ^ subject
+--
+-- __DEPRECATED__. Instead of @sendMimeMail to from subject plainBody htmlBody attachments@ use:
+--
+-- @
+-- let mail = Network.Mail.Mime.simpleMailInMemory to from subject plainBody htmlBody attachments
+-- sendMail mail conn
+-- @
+--
+{-# DEPRECATED sendMimeMail' "Use 'sendMail (Network.Mail.Mime.simpleMailInMemory to from subject plainBody htmlBody attachments) conn'" #-}
+sendMimeMail' :: Address                        -- ^ receiver
+              -> Address                        -- ^ sender
+              -> T.Text                         -- ^ subject
               -> LT.Text                        -- ^ plain text body
               -> LT.Text                        -- ^ html body
               -> [(T.Text, T.Text, B.ByteString)] -- ^ attachments: [(content_type, file_name, content)]
               -> SMTPConnection
               -> IO ()
 sendMimeMail' to from subject plainBody htmlBody attachments con = do
-  let myMail = simpleMailInMemory (address to) (address from) (T.pack subject)
-                                  plainBody htmlBody attachments
-  sendMimeMail2 myMail con
-  where
-    address = Address Nothing . T.pack
-
-sendMimeMail2 :: Mail -> SMTPConnection -> IO ()
-sendMimeMail2 mail con = do
-    let (Address _ from) = mailFrom mail
-        recps = map (T.unpack . addressEmail)
-                     $ (mailTo mail ++ mailCc mail ++ mailBcc mail)
-    when (null recps) $ fail "no receiver specified."
-    renderedMail <- renderMail' $ mail { mailBcc = [] }
-    sendMail (T.unpack from) recps (lazyToStrict renderedMail) con
-
--- haskellNet uses strict bytestrings
--- TODO: look at making haskellnet lazy
-lazyToStrict :: B.ByteString -> S.ByteString
-lazyToStrict = B.toStrict
+  let myMail = simpleMailInMemory to from subject plainBody htmlBody attachments
+  sendMail myMail con
 
-crlf :: BS.ByteString
-crlf = BS.pack "\r\n"
+-- | Sends email in generated using 'mime-mail' package.
+--
+-- Throws 'UserError' @::@ 'IOError' if recipient address not specified.
+{-# DEPRECATED sendMimeMail2 "Use sendMail instead" #-}
+sendMimeMail2 :: HasCallStack => Mail -> SMTPConnection -> IO ()
+sendMimeMail2 = sendMail
 
-bsPutCrLf :: BSStream -> ByteString -> IO ()
-bsPutCrLf h s = bsPut h s >> bsPut h crlf >> bsFlush h
+-- | Sends email using 'Mail' type from the mime-mail package.
+--
+-- Sender is taken from the 'mailFrom' field of the @mail@. Message
+-- is sent to all the recipients in the 'mailTo', 'mailCc', 'mailBcc' fields.
+-- But 'mailBcc' emails are not visible to other recipients as it should be.
+--
+-- @since 0.6
+sendMail :: HasCallStack => Mail -> SMTPConnection -> IO ()
+sendMail mail conn = do
+  let recps = mailTo mail ++ mailCc mail ++ mailBcc mail
+  when (null recps) $ throwIO $ NoRecipients mail
+  renderedMail <- renderMail' $ mail { mailBcc = [] }
+  sendMailData (mailFrom mail) recps (B.toStrict renderedMail) conn
diff --git a/src/Network/HaskellNet/SMTP/Internal.hs b/src/Network/HaskellNet/SMTP/Internal.hs
new file mode 100644
--- /dev/null
+++ b/src/Network/HaskellNet/SMTP/Internal.hs
@@ -0,0 +1,354 @@
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE DerivingStrategies #-}
+-- |
+-- Internal functions that are used in the SMTP protocol,
+-- you may need these module in case if you want to implement additional
+-- functionality that does not exist in the "Network.HaskellNet.SMTP".
+--
+-- __Example__.
+--
+-- One example could be sending multiple emails over the same stream
+-- in order to use that you may want to use 'RSET' command, so you can implement:
+--
+-- @
+-- import "Network.HaskellNet.SMTP.Internal"
+--
+-- resetConnection :: SMTPConnection -> IO ()
+-- resetConnection conn = do
+--    (code, _) <- 'sendCommand' conn 'RSET'
+--    'unless' (code == 250) $ 'throwIO' $ 'UnexpectedReply' 'RSET' [250] code ""
+-- @
+--
+module Network.HaskellNet.SMTP.Internal
+  ( SMTPConnection(..)
+  , Command(..)
+  , SMTPException(..)
+  , ReplyCode
+  , tryCommand
+  , parseResponse
+  , sendCommand
+  , sendMailData
+  , closeSMTP
+  , gracefullyCloseSMTP
+  , quitSMTP
+    -- * Reexports
+  , Address(..)
+  ) where
+
+import Control.Exception
+import Control.Monad (unless)
+import Data.Char (isDigit)
+import Data.ByteString (ByteString)
+import qualified Data.ByteString.Char8 as BS
+import qualified Data.Text as T
+import qualified Data.Text.Encoding as T
+import Data.Typeable
+
+import Network.HaskellNet.Auth
+import Network.HaskellNet.BSStream
+
+import Network.Mail.Mime
+
+import Prelude
+
+-- | All communication with server is done using @SMTPConnection@ value.
+data SMTPConnection = SMTPC {
+  -- | Connection communication channel.
+  bsstream :: !BSStream,
+  -- | Server properties as per reply to the 'EHLO' request.
+  _response :: ![ByteString]
+  }
+
+-- | SMTP commands.
+--
+-- Supports basic and extended SMTP protocol without TLS support.
+--
+-- For each command we provide list of the expected reply codes that happens in success and failure cases
+-- respectively.
+data Command
+  = -- | The @HELO@ command initiates the SMTP session conversation. The client greets the server and introduces itself.
+    -- As a rule, HELO is attributed with an argument that specifies the domain name or IP address of the SMTP client.
+    --
+    -- Success: 250
+    -- Failure: 504, 550
+    HELO T.Text
+  | -- | @EHLO@ is an alternative to HELO for servers that support the SMTP service extensions (ESMTP)
+    --
+    -- Success: 250
+    -- Failure: 502, 504, 550
+    EHLO T.Text
+  | -- | @MAIL FROM@ command initiates a mail transfer. As an argument, MAIL FROM includes a sender mailbox (reverse-path)
+    -- can accept optional parameters.
+    --
+    -- Success: 250
+    --
+    -- Failure: 451, 452, 455, 503, 550, 552, 553, 555
+    MAIL T.Text
+  | -- | The @RCPT TO@ command specifies exactly one recipient.
+    --
+    -- Success: 250 251
+    --
+    -- Failure: 450 451 452 455 503 550 551 552 553 555
+    RCPT T.Text
+  | -- | With the @DATA@ command, the client asks the server for permission to transfer the mail data.
+    --
+    -- Success: 250, 354
+    --
+    -- Failure: 450 451 452 503 550 552 554
+    --
+    -- Client just sends data and after receiving 354 starts streaming email, terminating transfer by
+    -- sending @\r\n.\r\n@.
+    DATA ByteString
+  | -- |
+    -- @EXPN@ is used to verify whether a mailing list in the argument exists on the local host.
+    -- The positive response will specify the membership of the recipients.
+    --
+    -- Success: 250 252
+    --
+    -- Failure: 502 504 550
+    EXPN T.Text
+  | -- |
+    -- @VRFY@ is used to verify whether a mailbox in the argument exists on the local host.
+    -- The server response includes the user’s mailbox and may include the user’s full name.
+    --
+    -- Success: 250 251 252
+    --
+    -- Failure: 502 504 550 551 553
+    VRFY T.Text
+  | -- |
+    -- With the @HELP@ command, the client requests a list of commands the server supports, may request
+    -- help for specific command
+    --
+    -- Success: 211 214
+    --
+    -- Failure: 502 504
+    HELP T.Text
+  | -- | Authorization support
+    AUTH AuthType UserName Password
+  | -- | @NOOP@  can be used to verify if the connection is alive
+    --
+    -- Success: 250
+    NOOP
+  | -- | @RSET@ Resets the state
+    --
+    -- Success: 250
+    RSET
+  | -- | @QUIT@ asks server to close connection. Client should terminate the connection when receives
+    -- status.
+    --
+    -- Success: 221
+    QUIT
+    deriving (Show, Eq)
+
+-- | Code reply from the server. It's always 3 digit integer.
+type ReplyCode = Int
+
+-- | Exceptions that can happen during communication.
+data SMTPException
+  = -- | Reply code was not in the list of expected.
+    --
+    --  * @Command@ - command that was sent.
+    --  * @[ReplyCode]@ -- list of expected codes
+    --  * @ReplyCode@ -- the code that we have received
+    --  * @ByteString@ -- additional data returned by the server.
+    UnexpectedReply Command [ReplyCode] ReplyCode BS.ByteString
+    -- | The server didn't accept the start of the message delivery
+  | NotConfirmed ReplyCode BS.ByteString
+    -- | The server does not support current authentication method
+  | AuthNegotiationFailed ReplyCode BS.ByteString
+    -- | Can't send email because no recipients were specified.
+  | NoRecipients Mail
+    -- | Received an unexpected greeting from the server.
+  | UnexpectedGreeting ReplyCode
+  deriving (Show)
+  deriving (Typeable)
+
+instance Exception SMTPException where
+  displayException (UnexpectedReply cmd expected code msg) =
+    "Cannot execute command " ++ show cmd ++
+       ", " ++ prettyExpected expected ++
+       ", " ++ prettyReceived code msg
+    where
+      prettyReceived :: Int -> ByteString -> String
+      prettyReceived co ms = "but received" ++ show co ++ " (" ++ BS.unpack ms ++ ")"
+      prettyExpected :: [ReplyCode] -> String
+      prettyExpected [x] = "expected reply code of " ++ show x
+      prettyExpected xs = "expected any reply code of " ++ show xs
+  displayException (NotConfirmed code msg) =
+    "This server cannot accept any data. code: " ++ show code ++ ", msg: " ++ BS.unpack msg
+  displayException (AuthNegotiationFailed code msg) =
+    "Authentication failed. code: " ++ show code ++ ", msg: " ++ BS.unpack msg
+  displayException (NoRecipients _mail) =
+    "No recipients were specified"
+  displayException (UnexpectedGreeting code) =
+    "Expected greeting from the server, but got: " <> show code
+
+
+-- | Safe wrapper for running a client command over the SMTP
+-- connection.
+--
+-- /Note on current behavior/
+--
+-- We allow the command to fail several times, retry
+-- happens in case if we have received unexpected status code.
+-- In this case message will be sent again. However in case
+-- of other synchronous or asynchronous exceptions there will
+-- be no retries.
+--
+-- It case if number of retries were exceeded connection will
+-- be closed automatically.
+--
+-- The behaviors in notes will likely be changed in the future
+-- and should not be relied upon, see issues 76, 77.
+tryCommand
+  :: SMTPConnection -- ^ Connection
+  -> Command -- ^ Supported command
+  -> Int -- ^ Number of allowed retries
+  -> [ReplyCode] -- ^ List of accepted codes
+  -> IO ByteString -- ^ Resulting data
+tryCommand conn cmd tries expectedReplies = do
+    (code, msg) <- sendCommand conn cmd
+    case () of
+        _ | code `elem` expectedReplies -> return msg
+        _ | tries > 1 ->
+            tryCommand conn cmd (tries - 1) expectedReplies
+          | otherwise ->
+            throwIO $ UnexpectedReply cmd expectedReplies code msg
+
+-- | Read response from the stream. Response consists of the code
+-- and one or more lines of data.
+--
+-- In case if it's not the last line of reply the code is followed
+-- by the '-' sign. We return the code and all the data with the code
+-- stripped.
+--
+-- Eg.:
+--
+-- @
+-- "250-8BITMIME\\r"
+-- "250-PIPELINING\\r"
+-- "250-SIZE 42991616\\r"
+-- "250-AUTH LOGIN PLAIN XOAUTH2\\r"
+-- "250-DSN\\r"
+-- "250 ENHANCEDSTATUSCODES\\r"
+-- @
+--
+-- Returns:
+--
+-- @
+-- (250, "8BITMIME\\nPIPELINING\nSIZE 42991616\\nAUTH LOGIN PLAIN XOAUTH2\\nDSN\\nENHANCEDSTATUSCODES")
+-- @
+--
+-- Throws 'SMTPException'.
+parseResponse :: BSStream -> IO (ReplyCode, ByteString)
+parseResponse st =
+    do (code, bdy) <- readLines
+       return (read $ BS.unpack code, BS.unlines bdy)
+    where readLines =
+              do l <- bsGetLine st
+                 let (c, bdy) = BS.span isDigit l
+                 if not (BS.null bdy) && BS.head bdy == '-'
+                    then do (c2, ls) <- readLines
+                            return (c2, BS.tail bdy:ls)
+                    else return (c, [BS.tail bdy])
+
+-- | Sends a 'Command' to the server. Function that performs all the logic
+-- for sending messages. Throws an exception if something goes wrong.
+--
+-- Throws 'SMTPException'.
+sendCommand
+  :: SMTPConnection
+  -> Command
+  -> IO (ReplyCode, ByteString)
+sendCommand (SMTPC conn _) (DATA dat) =
+    do bsPutCrLf conn "DATA"
+       (code, msg) <- parseResponse conn
+       unless (code == 354) $ throwIO $ NotConfirmed code msg
+       mapM_ (sendLine . stripCR) $ BS.lines dat ++ [BS.pack "."]
+       parseResponse conn
+    where sendLine = bsPutCrLf conn
+          stripCR bs = case BS.unsnoc bs of
+                         Just (line, '\r') -> line
+                         _                 -> bs
+sendCommand (SMTPC conn _) (AUTH LOGIN username password) =
+    do bsPutCrLf conn command
+       (_, _) <- parseResponse conn
+       bsPutCrLf conn $ BS.pack userB64
+       (_, _) <- parseResponse conn
+       bsPutCrLf conn $ BS.pack passB64
+       parseResponse conn
+    where command = "AUTH LOGIN"
+          (userB64, passB64) = login username password
+sendCommand (SMTPC conn _) (AUTH at username password) =
+    do bsPutCrLf conn $ T.encodeUtf8 command
+       (code, msg) <- parseResponse conn
+       unless (code == 334) $ throwIO $ AuthNegotiationFailed code msg
+       bsPutCrLf conn $ BS.pack $ auth at (BS.unpack msg) username password
+       parseResponse conn
+    where command = T.unwords ["AUTH", T.pack (show at)]
+sendCommand (SMTPC conn _) meth =
+    do bsPutCrLf conn $! T.encodeUtf8 command
+       parseResponse conn
+    where command = case meth of
+                      (HELO param) -> "HELO " <> param
+                      (EHLO param) -> "EHLO " <> param
+                      (MAIL param) -> "MAIL FROM:<" <> param <> ">"
+                      (RCPT param) -> "RCPT TO:<" <> param <> ">"
+                      (EXPN param) -> "EXPN " <> param
+                      (VRFY param) -> "VRFY " <> param
+                      (HELP msg)   -> if T.null msg
+                                      then "HELP\r\n"
+                                      else "HELP " <> msg
+                      NOOP         -> "NOOP"
+                      RSET         -> "RSET"
+                      QUIT         -> "QUIT"
+                      DATA _       -> error "sendCommand: impossible happened DATA command expected to be processed in thepreceeding clause"
+                      AUTH _ _ _   -> error "sendCommand: impossible happened AUTH command expected to be processed in the preceeding clause"
+
+-- | Sends quit to the server. Connection must be terminated afterwards, i.e. it's not
+-- allowed to issue any command on this connection.
+quitSMTP :: SMTPConnection -> IO ()
+quitSMTP c = do
+  _ <- tryCommand c QUIT 1 [221]
+  pure ()
+
+-- | Terminates the connection. 'Quit' command is not send in this case.
+-- It's safe to issue this command at any time if the connection is still
+-- open.
+closeSMTP :: SMTPConnection -> IO ()
+closeSMTP (SMTPC conn _) = bsClose conn
+
+-- | Gracefully closes SMTP connection. Connection should be in available
+-- state. First it sends quit command and then closes connection itself.
+-- Connection should not be used after this command exits (even if it exits with an exception).
+-- This command may throw an exception in case of network failure or
+-- protocol failure when sending 'QUIT' command. If it happens connection
+-- nevertheless is closed.
+--
+-- @since 0.6
+gracefullyCloseSMTP :: SMTPConnection -> IO ()
+gracefullyCloseSMTP c@(SMTPC conn _) = quitSMTP c `finally` bsClose conn
+
+-- | Sends a mail to the server.
+--
+-- Throws 'SMTPException'.
+sendMailData :: Address -- ^ sender mail
+         -> [Address] -- ^ receivers
+         -> ByteString -- ^ data
+         -> SMTPConnection
+         -> IO ()
+sendMailData sender receivers dat conn = do
+   sendAndCheck (MAIL (addressEmail sender))
+   mapM_ (sendAndCheck . RCPT . addressEmail) receivers
+   sendAndCheck (DATA dat)
+   return ()
+  where
+    sendAndCheck cmd = tryCommand conn cmd 1 [250, 251]
+
+-- | Just a crlf constant.
+crlf :: BS.ByteString
+crlf = BS.pack "\r\n"
+
+-- | Write a message ending with ctlf.
+bsPutCrLf :: BSStream -> ByteString -> IO ()
+bsPutCrLf h s = bsPut h (s <> crlf)
diff --git a/src/Text/Packrat/Parse.hs b/src/Text/Packrat/Parse.hs
--- a/src/Text/Packrat/Parse.hs
+++ b/src/Text/Packrat/Parse.hs
@@ -12,7 +12,6 @@
 import Text.Packrat.Pos
 
 import Control.Monad
-import           Control.Applicative (Applicative(..))
 import qualified Control.Applicative as A
 
 import qualified Control.Monad.Fail as Fail
@@ -84,7 +83,7 @@
 (<|>) :: Derivs d => Parser d v -> Parser d v -> Parser d v
 (Parser p1) <|> (Parser p2) = Parser parse
     where parse dvs = first dvs (p1 dvs)
-          first _ (result @ (Parsed {})) = result
+          first _ (result@(Parsed {})) = result
           first dvs (NoParse err) = second err (p2 dvs)
           second err1 (Parsed val rem err) =
               Parsed val rem (joinErrors err1 err)
@@ -94,7 +93,7 @@
 satisfy :: Derivs d => Parser d v -> (v -> Bool) -> Parser d v
 satisfy (Parser p) test = Parser parse
     where parse dvs = check dvs (p dvs)
-          check dvs (result @ (Parsed val _ _)) =
+          check dvs (result@(Parsed val _ _)) =
               if test val
               then result
               else NoParse (nullError dvs)
@@ -204,7 +203,7 @@
               Parsed v rem (fix dvs err)
           munge dvs (NoParse err) =
               NoParse (fix dvs err)
-          fix dvs (err @ (ParseError ep _)) =
+          fix dvs (err@(ParseError ep _)) =
               if ep > dvPos dvs
               then err
               else expError (dvPos dvs) desc
@@ -224,7 +223,7 @@
 -- but only if the position didn't change from the first to the second.
 -- If it did, just return the "new" (second) set of errors.
 joinErrors :: ParseError -> ParseError -> ParseError
-joinErrors (e @ (ParseError p m)) (e' @ (ParseError p' m'))
+joinErrors (e@(ParseError p m)) (e'@(ParseError p' m'))
     | p' > p || null m  = e'
     | p > p' || null m' = e
     | otherwise         = ParseError p (m `union` m')
@@ -371,4 +370,3 @@
     case dvChar d of
       NoParse _ -> []
       Parsed c rem _ -> c : dvString rem
-
