diff --git a/antiope-sqs.cabal b/antiope-sqs.cabal
--- a/antiope-sqs.cabal
+++ b/antiope-sqs.cabal
@@ -4,10 +4,10 @@
 --
 -- see: https://github.com/sol/hpack
 --
--- hash: 610f11f14be9dc0bcd0ae1fa042fa11947b032a11fc8d912735aeb73fb2093c8
+-- hash: 443e48287c77512b5c25fec64ac9d6cf85fec8c2c04eaf787c0ac373596918bd
 
 name:           antiope-sqs
-version:        6.3.0
+version:        6.4.0
 description:    Please see the README on Github at <https://github.com/arbor/antiope#readme>
 category:       Services
 homepage:       https://github.com/arbor/antiope#readme
@@ -27,6 +27,11 @@
   location: https://github.com/arbor/antiope
 
 library
+  exposed-modules:
+      Antiope.SQS
+      Antiope.SQS.Messages
+  other-modules:
+      Paths_antiope_sqs
   hs-source-dirs:
       src
   default-extensions: BangPatterns GeneralizedNewtypeDeriving OverloadedStrings TupleSections
@@ -49,15 +54,13 @@
     , network-uri
     , text
     , unliftio-core
-  exposed-modules:
-      Antiope.SQS
-  other-modules:
-      Paths_antiope_sqs
   default-language: Haskell2010
 
 test-suite antiope-sqs-test
   type: exitcode-stdio-1.0
   main-is: Spec.hs
+  other-modules:
+      Paths_antiope_sqs
   hs-source-dirs:
       test
   default-extensions: BangPatterns GeneralizedNewtypeDeriving OverloadedStrings TupleSections
@@ -81,6 +84,4 @@
     , network-uri
     , text
     , unliftio-core
-  other-modules:
-      Paths_antiope_sqs
   default-language: Haskell2010
diff --git a/src/Antiope/SQS/Messages.hs b/src/Antiope/SQS/Messages.hs
new file mode 100644
--- /dev/null
+++ b/src/Antiope/SQS/Messages.hs
@@ -0,0 +1,45 @@
+{-# LANGUAGE DeriveGeneric #-}
+
+module Antiope.SQS.Messages
+  ( Many (..)
+  , SqsMessage (..)
+  ) where
+
+import Data.Aeson   as Aeson
+import Data.Text    (Text)
+import GHC.Generics (Generic)
+
+import qualified Data.Aeson.Types   as Aeson
+import qualified Data.Text.Encoding as T
+
+newtype Many a = Many { records :: [a] } deriving (Show, Eq, Generic)
+
+instance FromJSON a => FromJSON (Many a) where
+  parseJSON =
+    withObject "Many" $ \obj ->
+      Many <$> obj .: "Records"
+
+instance ToJSON a => ToJSON (Many a) where
+  toJSON (Many a) =
+    object ["Records" Aeson..= a]
+
+data SqsMessage a = SqsMessage
+  { senderId      :: !(Maybe Text)
+  , messageId     :: !(Maybe Text)
+  , md5OfBody     :: !(Maybe Text)
+  , receiptHandle :: !(Maybe Text)
+  , body          :: !(Maybe a)
+  } deriving (Show, Eq, Generic)
+
+instance FromJSON a => FromJSON (SqsMessage a) where
+  parseJSON = withObject "SqsMessage" $ \obj -> SqsMessage
+    <$> obj .:? "SenderId"
+    <*> obj .:? "MessageId"
+    <*> obj .:? "Md5OfBody"
+    <*> obj .:? "ReceiptHandle"
+    <*> decodeEscaped obj "Body"
+
+decodeEscaped :: FromJSON b => Object -> Text -> Aeson.Parser b
+decodeEscaped o t =
+  (o .: t) >>= (either fail pure . eitherDecodeStrict . T.encodeUtf8)
+
