diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,13 @@
+# New in 0.1.3.0
+
+- `mset` command with batch processing.
+
+# Fixed in 0.1.1.1
+
+- Zero cases for hmget and hmset are now handled correctly (previously, empty data was sent to Redis, resulting in an error)
+
+# New in 0.1.1
+
+- Added `del`, `persist`, `expire`, and `expireat`.
+- Added ASCII `~~>` alias for query
+
diff --git a/Setup.hs b/Setup.hs
deleted file mode 100644
--- a/Setup.hs
+++ /dev/null
@@ -1,2 +0,0 @@
-import Distribution.Simple
-main = defaultMain
diff --git a/hlrdb-core.cabal b/hlrdb-core.cabal
--- a/hlrdb-core.cabal
+++ b/hlrdb-core.cabal
@@ -1,5 +1,5 @@
 name:                hlrdb-core
-version:             0.1.2.2
+version:             0.1.3.0
 synopsis:            High-level Redis Database Core API
 description:         A library for type-driven interaction with Redis
 license:             MIT
@@ -11,6 +11,7 @@
 cabal-version:       2.0
 homepage:            https://github.com/identicalsnowflake/hlrdb-core
 bug-reports:         https://github.com/identicalsnowflake/hlrdb-core/issues
+extra-source-files: CHANGELOG.md
 
 source-repository head
   type:     git
@@ -27,17 +28,16 @@
     , HLRDB.Structures.Set
     , HLRDB.Structures.SSet
     , HLRDB.Internal
-  build-depends:
-      base >= 4.9 && < 5.0
-    , bytestring ^>= 0.10.8.2
-    , hashable ^>= 1.2.6.1
-    , hedis ^>= 0.10.1
-    , lens (>= 4.16 && <= 4.18) || ^>= 4.18
-    , mtl ^>= 2.2.2
-    , profunctors ^>= 5.2.2
-    , random ^>= 1.1
-    , time (>=1.6 && <1.9.1) || ^>= 1.9.1
-    , unordered-containers ^>= 0.2.8.0
+  build-depends: base >= 4.9 && < 5.0
+               , bytestring ^>= 0.10.8.2
+               , hashable (>= 1.2.6.1 && < 1.2.7.0) || ^>= 1.2.7.0
+               , hedis (>= 0.10.1 && <= 0.11.0) || ^>= 0.11.0
+               , lens (>= 4.16 && <= 4.17) || ^>= 4.17
+               , mtl ^>= 2.2.2
+               , profunctors (>= 5.2.2 && <= 5.3) || ^>= 5.3
+               , random ^>= 1.1
+               , time (>=1.6 && <1.9.1) || ^>= 1.9.2
+               , unordered-containers (>= 0.2.8.0 && < 0.2.10.0) || ^>= 0.2.10.0
   hs-source-dirs:      src
   default-language:    Haskell2010
   default-extensions:
diff --git a/src/HLRDB/Core.hs b/src/HLRDB/Core.hs
--- a/src/HLRDB/Core.hs
+++ b/src/HLRDB/Core.hs
@@ -12,6 +12,9 @@
        , mget
        , set
        , set'
+       , liftqs
+       , mset
+       , setex
        , incr
        , incrby
        , decr
diff --git a/src/HLRDB/Internal.hs b/src/HLRDB/Internal.hs
--- a/src/HLRDB/Internal.hs
+++ b/src/HLRDB/Internal.hs
@@ -17,6 +17,8 @@
        , readInt
        , Int64
        , runIdentity
+       , MSET(..)
+       , HLRDB.Internal.splitWith
        ) where
 
 import Data.Functor.Identity
@@ -131,4 +133,22 @@
     end _ 0 _ = 0
     end True _ n = negate n
     end _ _ n = n
+
+type DList a = ([ a ] -> [ a ])
+
+-- | Aggregated @mset@ query
+newtype MSET = MSET { runMSET :: DList (ByteString , Maybe ByteString) }
+
+instance Semigroup MSET where
+  (<>) (MSET as) (MSET bs) = MSET (as . bs)
+
+instance Monoid MSET where
+  mempty = MSET $ (<>) []
+
+splitWith :: (a -> Either b c) -> [ a ] -> ([ b ] , [ c ])
+splitWith f = foldr g mempty
+  where
+    g x (as , bs) = case f x of
+      Left a -> (a : as , bs)
+      Right b -> (as , b : bs)
 
diff --git a/src/HLRDB/Primitives/Aggregate.hs b/src/HLRDB/Primitives/Aggregate.hs
--- a/src/HLRDB/Primitives/Aggregate.hs
+++ b/src/HLRDB/Primitives/Aggregate.hs
@@ -9,12 +9,16 @@
        , aggregatePair
        , remember
        , runT
+
+       -- | Aggregate, atomic multi-set query (as in setting multiple things in a single query)
+       , MSET
        ) where
 
 import Data.Profunctor
 import Data.Profunctor.Traversing
 import Control.Lens hiding (Traversing)
 import Data.ByteString
+import HLRDB.Internal (MSET)
 
 
 -- | Abstract representation for aggregation.
@@ -50,7 +54,7 @@
 
 -- | Reify aggregation into a target functor.
 {-# INLINE runT #-}
-runT :: (Functor f) => ([x] -> f [y]) -> T x y a b -> a -> f b
+runT :: Functor f => ([x] -> f [y]) -> T x y a b -> a -> f b
 runT i (T t) = unsafePartsOf t i
 
 
diff --git a/src/HLRDB/Structures/Basic.hs b/src/HLRDB/Structures/Basic.hs
--- a/src/HLRDB/Structures/Basic.hs
+++ b/src/HLRDB/Structures/Basic.hs
@@ -7,6 +7,7 @@
 import HLRDB.Primitives.Redis
 import HLRDB.Internal
 import Data.ByteString.Char8 (pack)
+import qualified Data.HashMap.Strict as HM
 
 
 -- | Simple get command. Works on @RedisBasic a b@ and @RedisIntegral a b@.
@@ -44,6 +45,36 @@
 set' (RKeyValue (E k e _)) a b = liftRedis $ case e (Just b) of
   Just bs -> ignore $ Redis.set (k a) bs
   Nothing -> ignore $ del [ k a ]
+
+-- | Construct a query to be used with @mset@. The @MSET@ type is a @Monoid@, so you may combine many of these together before executing the batch with the @mset@ command.
+liftqs :: RedisStructure (BASIC w) a b -> (a , b) -> MSET
+liftqs (RKeyValue (E k e _)) (a , b) = MSET $ (<>) [ (k a , e b) ]
+liftqs (RKeyValueInteger k e _) (a , b) = MSET $ (<>) [ (k a , Just $ pack (show (e b))) ]
+
+-- | Execute a @MSET@ query.
+mset :: MonadRedis m => MSET -> m ()
+mset = go . flip runMSET []
+  where
+    -- need this hashmap to/from in order to make sure deleting a value after setting it
+    -- performs correctly.
+    go xs = case (splitWith f . HM.toList . HM.fromList) xs of
+      (as , bs) -> mdel' as >> mset' bs >> pure ()
+      where
+        f (x , Nothing) = Left x
+        f (x , Just y) = Right (x , y)
+
+    mdel' [] = pure 0
+    mdel' xs = unwrap $ liftRedis $ Redis.del xs
+    
+    mset' [] = pure undefined
+    mset' xs = unwrap $ liftRedis $ Redis.mset xs
+
+-- | Set a value together with a given expiration timeout (in seconds).
+setex :: MonadRedis m => RedisStructure (BASIC w) a b -> a -> Integer -> b -> m ()
+setex (RKeyValue (E k e _)) a t b = liftRedis $ case e b of
+  Just bs -> ignore $ Redis.setex (k a) t bs
+  Nothing -> ignore $ del [ k a ]
+setex (RKeyValueInteger k e _) a t i = liftRedis $ ignore $ Redis.setex (k a) t (pack $ show (e i))
 
 -- | Increment an Integer in Redis. Empty values are treated as 0.
 incr :: MonadRedis m => RedisIntegral a b -> a -> m b
