diff --git a/imap.cabal b/imap.cabal
--- a/imap.cabal
+++ b/imap.cabal
@@ -2,7 +2,7 @@
 -- see http://haskell.org/cabal/users-guide/
 
 name:                imap
-version:             0.2.0.0
+version:             0.2.0.1
 synopsis:            An efficient IMAP client library, with SSL and streaming
 license:             BSD3
 license-file:        LICENSE
@@ -97,6 +97,9 @@
   exposed-modules:
     Network.IMAP
     Network.IMAP.Parsers
+    Network.IMAP.Parsers.Fetch
+    Network.IMAP.Parsers.Untagged
+    Network.IMAP.Parsers.Utils
     Network.IMAP.RequestWatcher
     Network.IMAP.Types
     Network.IMAP.Utils
diff --git a/src/Network/IMAP/Parsers/Fetch.hs b/src/Network/IMAP/Parsers/Fetch.hs
new file mode 100644
--- /dev/null
+++ b/src/Network/IMAP/Parsers/Fetch.hs
@@ -0,0 +1,115 @@
+module Network.IMAP.Parsers.Fetch where
+
+import Network.IMAP.Types
+import Network.IMAP.Parsers.Utils
+import Network.IMAP.Parsers.Untagged (parseFlags)
+
+import Data.Attoparsec.ByteString
+import qualified Data.Attoparsec.ByteString as AP
+import Data.Word8
+import qualified Data.ByteString.Char8 as BSC
+import Data.Maybe (fromJust, isNothing)
+import Data.Either.Combinators (isRight, fromRight', fromLeft')
+
+import Control.Applicative
+import Control.Monad (liftM)
+
+
+parseFetch :: Parser (Either ErrorMessage CommandResult)
+parseFetch = do
+  string "* "
+  msgId <- liftM toInt $ AP.takeWhile1 isDigit
+  let msgId' = msgId >>= Right . MessageId
+  string " FETCH ("
+
+  parsedFetch <- parseSpecifiers
+
+  let allInOneEither = sequence $ msgId':parsedFetch
+  return $ liftM (Untagged . Fetch) allInOneEither
+
+parseSpecifiers :: Parser [Either ErrorMessage UntaggedResult]
+parseSpecifiers = do
+  nextChar <- AP.peekWord8
+  if isNothing nextChar || (fromJust nextChar == _cr)
+    then return []
+    else do
+      nextRes <- (Right <$> parseEnvelope) <|>
+                  parseFlags <|>
+                  (Right <$> parseInternalDate) <|>
+                  parseNumber Size "RFC822.SIZE" "" <|>
+                  ((string "BODY[" <|> string "RFC822.HEADER"
+                   <|> string "RFC822.TEXT" <|> string "RFC822") *> parseBody) <|>
+                  parseNumber UID "UID" "" <|>
+                  Right <$> parseBodyStructure
+
+      (nextRes:) <$> (AP.anyWord8 *> parseSpecifiers)
+
+parseInternalDate :: Parser UntaggedResult
+parseInternalDate = liftM InternalDate $ string "INTERNALDATE " *> parseQuotedText
+
+parseBody :: Parser (Either ErrorMessage UntaggedResult)
+parseBody = do
+  AP.takeWhile (/= _braceleft)
+  word8 _braceleft
+  size <- AP.takeWhile1 isDigit
+  string "}\r\n"
+
+  let parsedSize = toInt size
+  if isRight parsedSize
+    then do
+      msg <- AP.take (fromRight' parsedSize)
+      return . Right . Body $ msg
+    else return . Left $ fromLeft' parsedSize
+
+parseBodyStructure :: Parser UntaggedResult
+parseBodyStructure = do
+  string "BODYSTRUCTURE " <|> string "BODY "
+  structure <- eatUntilClosingParen
+  return . BodyStructure $ BSC.snoc structure ')'
+
+
+parseEnvelope :: Parser UntaggedResult
+parseEnvelope = do
+  string "ENVELOPE ("
+  date <- nilOrValue parseQuotedText
+  word8 _space
+
+  subject <- nilOrValue parseQuotedText
+  word8 _space
+
+  from <- nilOrValue parseEmailList
+  word8 _space
+
+  sender <- nilOrValue parseEmailList
+  word8 _space
+
+  replyTo <- nilOrValue parseEmailList
+  word8 _space
+
+  to <- nilOrValue parseEmailList
+  word8 _space
+
+  cc <- nilOrValue parseEmailList
+  word8 _space
+
+  bcc <- nilOrValue parseEmailList
+  word8 _space
+
+  inReplyTo <- nilOrValue parseQuotedText
+  word8 _space
+
+  messageId <- nilOrValue parseQuotedText
+  string ")"
+
+  return Envelope {
+    eDate = date,
+    eSubject = subject,
+    eFrom = from,
+    eSender = sender,
+    eReplyTo = replyTo,
+    eTo = to,
+    eCC = cc,
+    eBCC = bcc,
+    eInReplyTo = inReplyTo,
+    eMessageId = messageId
+  }
diff --git a/src/Network/IMAP/Parsers/Untagged.hs b/src/Network/IMAP/Parsers/Untagged.hs
new file mode 100644
--- /dev/null
+++ b/src/Network/IMAP/Parsers/Untagged.hs
@@ -0,0 +1,168 @@
+module Network.IMAP.Parsers.Untagged where
+
+import Network.IMAP.Types
+import Network.IMAP.Parsers.Utils
+
+import Data.Attoparsec.ByteString
+import qualified Data.Attoparsec.ByteString as AP
+import Data.Word8
+import qualified Data.Text as T
+import qualified Data.Text.Read as TR
+import Data.Text.Encoding (decodeUtf8)
+import Data.Either.Combinators (mapBoth, mapRight)
+
+import Control.Applicative
+import Control.Monad (mzero, liftM, (>=>))
+
+parseOk :: Parser UntaggedResult
+parseOk = do
+  string "OK "
+  contents <- AP.takeWhile (/= _cr)
+  return . OKResult . decodeUtf8 $ contents
+
+parseFlag :: Parser Flag
+parseFlag = do
+  word8 _backslash
+  flagName <- takeWhile1 (\c -> isLetter c || c == _asterisk)
+  case flagName of
+            "Seen" -> return FSeen
+            "Answered" -> return FAnswered
+            "Flagged" -> return FFlagged
+            "Deleted" -> return FDeleted
+            "Draft" -> return FDraft
+            "Recent" -> return FRecent
+            "*" -> return FAny
+            _ -> mzero
+
+parseWeirdFlag :: Parser Flag
+parseWeirdFlag = do
+  flagText <- AP.takeWhile1 (\c -> isLetter c || c == _dollar)
+  return . FOther . decodeUtf8 $ flagText
+
+parseFlagList :: Parser [Flag]
+parseFlagList = word8 _parenleft *>
+                (parseFlag <|> parseWeirdFlag) `sepBy` word8 _space
+                <* word8 _parenright
+
+parseFlags :: Parser (Either ErrorMessage UntaggedResult)
+parseFlags = Right . Flags <$> (string "FLAGS " *> parseFlagList)
+
+parseExists :: Parser (Either ErrorMessage UntaggedResult)
+parseExists = parseNumber Exists "" "EXISTS"
+
+parseBye :: Parser UntaggedResult
+parseBye = string "BYE" *> AP.takeWhile (/= _cr) *> return Bye
+
+parseRecent :: Parser (Either ErrorMessage UntaggedResult)
+parseRecent = parseNumber Recent "" "RECENT"
+
+parseOkResp :: Parser a -> Parser a
+parseOkResp innerParser = string "OK [" *> innerParser <* string "]"
+
+parseUnseen :: Parser (Either ErrorMessage UntaggedResult)
+parseUnseen = parseOkResp $
+  (toInt >=> (Right . Unseen)) <$>
+  (string "UNSEEN " *> takeWhile1 isDigit)
+
+parsePermanentFlags :: Parser UntaggedResult
+parsePermanentFlags = parseOkResp $
+  PermanentFlags <$> (string "PERMANENTFLAGS " *> parseFlagList)
+
+parseUidNext :: Parser (Either ErrorMessage UntaggedResult)
+parseUidNext = parseOkResp $ parseNumber UIDNext "UIDNEXT" ""
+
+parseUidValidity :: Parser (Either ErrorMessage UntaggedResult)
+parseUidValidity = parseOkResp $ parseNumber UIDValidity "UIDVALIDITY" ""
+
+parseHighestModSeq :: Parser (Either ErrorMessage UntaggedResult)
+parseHighestModSeq = parseOkResp $ parseNumber HighestModSeq "HIGHESTMODSEQ" ""
+
+parseStatusItem :: Parser (Either ErrorMessage UntaggedResult)
+parseStatusItem = do
+  anyWord8
+  itemName <- liftM decodeUtf8 $ AP.takeWhile1 isAtomChar
+  string " "
+  value <- liftM decodeUtf8 $ AP.takeWhile1 isDigit
+
+  let decodingError = T.concat ["Error decoding '", value, "' as integer"]
+  let valAsNumber = mapBoth (const decodingError) fst $ TR.decimal value
+
+  return $ valAsNumber >>= \n -> case itemName of
+    "MESSAGES" -> Right $ Messages n
+    "RECENT" -> Right $ Recent n
+    "UIDNEXT" -> Right $ UIDNext n
+    "UIDVALIDITY" -> Right $ UIDValidity n
+    "UNSEEN" -> Right $ Unseen n
+    _ -> Left $ T.concat ["Unknown status item '", itemName, "'"]
+
+parseStatus :: Parser (Either ErrorMessage UntaggedResult)
+parseStatus = do
+  string "STATUS "
+  mailboxName <- liftM decodeUtf8 $ AP.takeWhile1 isAtomChar
+
+  string " "
+  statuses <- parseStatusItem `manyTill` string ")"
+  let formattedStatuses = sequence statuses
+
+  return $ liftM (StatusR mailboxName) formattedStatuses
+
+parseCapabilityList :: Parser (Either ErrorMessage UntaggedResult)
+parseCapabilityList = do
+  string "CAPABILITY "
+  caps <- (parseCapabilityWithValue <|> parseNamedCapability) `sepBy` word8 _space
+  return . mapRight Capabilities $ sequence caps
+
+parseCapabilityWithValue :: Parser (Either ErrorMessage Capability)
+parseCapabilityWithValue = do
+  name <- liftM decodeUtf8 (AP.takeWhile1 isAtomChar)
+  word8 _equal
+  value <- AP.takeWhile1 isAtomChar
+
+  let decodedValue = decodeUtf8 value
+  let decodingError = T.concat ["Error decoding '", decodedValue, "' as integer"]
+  let valAsNumber = mapBoth (const decodingError) fst $ TR.decimal decodedValue
+  case T.toLower name of
+    "compress" -> return . Right . CCompress $ decodedValue
+    "utf8" -> return . Right . CUtf8 $ decodedValue
+    "auth" -> return . Right . CAuth $ decodedValue
+    "appendlimit" -> return $ liftM CAppendLimit valAsNumber
+    _ -> return . Right $ COther name (Just decodedValue)
+
+parseNamedCapability :: Parser (Either ErrorMessage Capability)
+parseNamedCapability = do
+  name <- AP.takeWhile isAtomChar
+  let decodedName = decodeUtf8 name
+
+  return . Right $ case T.toLower decodedName of
+    "imap4" -> CIMAP4
+    "imap4rev1" -> CIMAP4
+    "unselect" -> CUnselect
+    "idle" -> CIdle
+    "namespace" -> CNamespace
+    "quota" -> CQuota
+    "id" -> CId
+    "children" -> CChildren
+    "uidplus" -> CUIDPlus
+    "enable" -> CEnable
+    "move" -> CMove
+    "condstore" -> CCondstore
+    "esearch" -> CEsearch
+    "list-extended" -> CListExtended
+    "list-status" -> CListStatus
+    _ -> if T.head decodedName == 'X'
+          then CExperimental decodedName
+          else COther decodedName Nothing
+
+parseExpunge :: Parser (Either ErrorMessage UntaggedResult)
+parseExpunge = do
+  msgId <- AP.takeWhile1 isDigit
+  string " EXPUNGE"
+
+  return $ liftM Expunge (toInt msgId)
+
+parseSearchResult :: Parser (Either ErrorMessage UntaggedResult)
+parseSearchResult = do
+  string "SEARCH "
+  msgIds <- AP.takeWhile1 isDigit `sepBy` word8 _space
+  let parsedIds = mapM toInt msgIds
+  return $ liftM Search parsedIds
diff --git a/src/Network/IMAP/Parsers/Utils.hs b/src/Network/IMAP/Parsers/Utils.hs
new file mode 100644
--- /dev/null
+++ b/src/Network/IMAP/Parsers/Utils.hs
@@ -0,0 +1,103 @@
+module Network.IMAP.Parsers.Utils where
+
+import Network.IMAP.Types
+
+import Data.Attoparsec.ByteString
+import qualified Data.Attoparsec.ByteString as AP
+import Data.Word8
+import qualified Data.Text as T
+import Data.Text.Encoding (decodeUtf8)
+import qualified Data.ByteString.Char8 as BSC
+import qualified Data.ByteString as BS
+import Data.Either.Combinators (rightToMaybe)
+import Control.Monad (liftM)
+
+eatUntilClosingParen :: Parser BSC.ByteString
+eatUntilClosingParen = scan 0 hadClosedAllParens <* word8 _parenright
+
+hadClosedAllParens :: Int -> Word8 -> Maybe Int
+hadClosedAllParens openingParenCount char
+  | char == _parenright =
+    if openingParenCount == 1
+      then Nothing
+      else Just $ openingParenCount - 1
+  | char == _parenleft = Just $ openingParenCount + 1
+  | otherwise =  Just openingParenCount
+
+
+parseEmailList :: Parser [EmailAddress]
+parseEmailList = string "(" *> parseEmail `sepBy` word8 _space <* string ")"
+
+parseEmail :: Parser EmailAddress
+parseEmail = do
+  string "(\""
+  label <- nilOrValue $ AP.takeWhile1 (/= _quotedbl)
+  string "\" NIL \""
+
+  emailUsername <- AP.takeWhile1 (/= _quotedbl)
+  string "\" \""
+  emailDomain <- AP.takeWhile1 (/= _quotedbl)
+  string "\")"
+  let fullAddr = decodeUtf8 $ BSC.concat [emailUsername, "@", emailDomain]
+
+  return $ EmailAddress (liftM decodeUtf8 label) fullAddr
+
+nilOrValue :: Parser a -> Parser (Maybe a)
+nilOrValue parser = rightToMaybe <$> AP.eitherP (string "NIL") parser
+
+parseQuotedText :: Parser T.Text
+parseQuotedText = do
+  word8 _quotedbl
+  date <- AP.takeWhile1 (/= _quotedbl)
+  word8 _quotedbl
+
+  return . decodeUtf8 $ date
+
+parseNameAttribute :: Parser NameAttribute
+parseNameAttribute = do
+  string "\\"
+  name <- AP.takeWhile1 isAtomChar
+  return $ case name of
+          "Noinferiors" -> Noinferiors
+          "Noselect" -> Noselect
+          "Marked" -> Marked
+          "Unmarked" -> Unmarked
+          "HasNoChildren" -> HasNoChildren
+          _ -> OtherNameAttr $ decodeUtf8 name
+
+parseListLikeResp :: BSC.ByteString -> Parser UntaggedResult
+parseListLikeResp prefix = do
+  string prefix
+  string " ("
+  nameAttributes <- parseNameAttribute `sepBy` word8 _space
+
+  string ") \""
+  delimiter <- liftM (decodeUtf8 . BS.singleton) AP.anyWord8
+  string "\" "
+  name <- liftM decodeUtf8 $ AP.takeWhile1 (/= _cr)
+
+  let actualName = T.dropAround (== '"') name
+  return $ ListR nameAttributes delimiter actualName
+
+isAtomChar :: Word8 -> Bool
+isAtomChar c = isLetter c || isNumber c || c == _hyphen || c == _quotedbl || c == _period
+
+toInt :: BSC.ByteString -> Either ErrorMessage Int
+toInt bs = if null parsed
+    then Left errorMsg
+    else Right . fst . head $ parsed
+  where parsed = reads $ BSC.unpack bs
+        errorMsg = T.concat ["Count not parse '", decodeUtf8 bs, "' as an integer"]
+
+parseNumber :: (Int -> a) -> BSC.ByteString ->
+  BSC.ByteString -> Parser (Either ErrorMessage a)
+parseNumber constructor prefix postfix = do
+  if not . BSC.null $ prefix
+    then string prefix <* word8 _space
+    else return BSC.empty
+  number <- takeWhile1 isDigit
+  if not . BSC.null $ postfix
+    then word8 _space *> string postfix
+    else return BSC.empty
+
+  return $ liftM constructor (toInt number)
