diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,20 @@
+# Changelog
+
+All notable changes to `bluefin-postgresql` will be documented in this file.
+
+The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
+and this project adheres to [Haskell Package Versioning Policy](https://pvp.haskell.org).
+
+## [Unreleased]
+
+## [0.1.0.0] - 27.02.2026
+
+### Added
+
+- First edition of the package, ready for feedback.
+- 100% documentation coverage.
+- Reasonably detailed READMEs.
+- CI that builds and tests the packages for each version of GHC in the `tested-with` field.
+
+[unreleased]: https://github.com/fpringle/bluefin-postgresql/compare/bluefin-postgresql-0.1.0.0...HEAD
+[0.1.0.0]: https://github.com/fpringle/bluefin-postgresql/releases/tag/bluefin-postgresql-0.1.0.0
diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,30 @@
+Copyright (c) 2025, Frederick Pringle
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+
+    * Redistributions in binary form must reproduce the above
+      copyright notice, this list of conditions and the following
+      disclaimer in the documentation and/or other materials provided
+      with the distribution.
+
+    * Neither the name of Frederick Pringle nor the names of other
+      contributors may be used to endorse or promote products derived
+      from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/README.md b/README.md
new file mode 100644
--- /dev/null
+++ b/README.md
@@ -0,0 +1,105 @@
+# bluefin-postgresql
+
+This package provides a `bluefin` effect for [postgresql-simple](https://hackage.haskell.org/package/postgresql-simple)'s `Connection` type.
+
+It defines a dynamic effect to allow effectful functions to use a `Connection`, without worrying about where that `Connection` comes from.
+
+For a higher-level effect library using [Opaleye](https://hackage.haskell.org/package/opaleye), see [bluefin-opaleye](https://github.com/fpringle/bluefin-postgresql/blob/main/bluefin-opaleye#readme).
+
+## Effectful functions
+
+In the `WithConnection` effect we can always request a `Connection` and use it as we normally
+would:
+
+```haskell
+import Bluefin.PostgreSQL as BP
+import qualified Database.PostgreSQL.Simple as PSQL
+
+insertAndList ::
+  (e :> es, e1 :> es) =>
+  WithConnection e ->
+  IOE e1 ->
+  Eff es [User]
+insertAndList wc ioe = BP.withConnection wc $ \conn -> do
+  effIO ioe $ PSQL.execute conn "insert into users (first_name) values (?)" ["Nuala"]
+  effIO ioe $ PSQL.query conn "select * from users where first_name in ?" $ PSQL.Only $ PSQL.In ["Anna", "Boris", "Carla"]
+```
+
+In fact, for convenience we also define lifted versions of all of the query/execute
+functions from `postgresql-simple`, so we can completely forget about `Connection` and rewrite the above to:
+
+```haskell
+
+import Bluefin.PostgreSQL
+
+insertAndList ::
+  (e :> es, e1 :> es) =>
+  WithConnection e ->
+  IOE e1 ->
+  Eff es [User]
+insertAndList wc ioe = do
+  BP.execute wc ioe "insert into users (first_name) values (?)" ["Nuala"]
+  BP.query wc ioe "select * from users where first_name in ?" $ PSQL.Only $ PSQL.In ["Anna", "Boris", "Carla"]
+```
+
+The same goes for other functions:
+
+```haskell
+-- use a transaction
+insertAndListCarefully ::
+  (e :> es, e1 :> es) =>
+  WithConnection e ->
+  IOE e1 ->
+  Eff es [User]
+insertAndListCarefully wc ioe = BP.withTransaction wc ioe $ insertAndList wc ioe
+
+-- stream + fold over results (in Eff)
+countUsersIneffeciently ::
+  (e :> es, e1 :> es) =>
+  WithConnection e ->
+  IOE e1 ->
+  Eff es Int
+countUsersIneffeciently wc ioe =
+  BP.fold_ wc ioe "select * from users" 0 $ \acc (row :: User) -> do
+    effIO ioe . putStrLn $ "User: " <> show row
+    pure $ acc + 1
+```
+
+## Interpreters
+
+The simplest way of running the `WithConnection` effect is by just providing a `Connection`, which we can get in the normal ways:
+
+```haskell
+import Bluefin.PostgreSQL as BP
+import qualified Database.PostgreSQL.Simple as PSQL
+
+usingConnection :: IO ()
+usingConnection =
+  runEff $ \ioe ->
+    bracket (effIO ioe $ PSQL.connectPostgreSQL "") (effIO ioe . PSQL.close) $ \conn ->
+      BP.runWithConnection conn $ \wc -> insertAndListCarefully wc ioe >>= effIO ioe . print
+
+usingConnectInfo :: IO ()
+usingConnectInfo =
+  runEff $ \ioe ->
+    BP.runWithConnectInfo ioe PSQL.defaultConnectInfo $ \wc ->
+      insertAndListCarefully wc ioe >>= effIO ioe . print
+```
+
+Alternatively, we can use a connection pool (from [resource-pool](https://hackage.haskell.org/package/resource-pool)
+and [unliftio-pool](https://hackage.haskell.org/package/unliftio-pool)), which is much better suited to
+long-running processes like servers.
+
+```haskell
+import Bluefin.PostgreSQL as BP
+import qualified Database.PostgreSQL.Simple as PSQL
+import qualified UnliftIO.Pool as P
+
+usingConnectionPool :: IO ()
+usingConnectionPool = do
+  poolCfg <- P.mkDefaultPoolConfig (PSQL.connectPostgreSQL "") PSQL.close 5.0 10
+  pool <- P.newPool poolCfg
+  runEff $ \ioe ->
+    BP.runWithConnectionPool ioe pool $ \wc ->
+      insertAndListCarefully wc ioe >>= effIO ioe . print
+```
diff --git a/bluefin-postgresql.cabal b/bluefin-postgresql.cabal
new file mode 100644
--- /dev/null
+++ b/bluefin-postgresql.cabal
@@ -0,0 +1,79 @@
+cabal-version:      3.0
+name:               bluefin-postgresql
+version:            0.1.0.0
+synopsis:
+  bluefin support for mid-level PostgreSQL operations.
+description:
+  See the README for an overview, or the documentation in 'Bluefin.PostgreSQL'.
+license:            BSD-3-Clause
+license-file:       LICENSE
+author:             Frederick Pringle
+maintainer:         frederick.pringle@fpringle.com
+copyright:          Copyright(c) Frederick Pringle 2025
+homepage:           https://github.com/fpringle/bluefin-postgresql
+build-type:         Simple
+category:           Database
+extra-doc-files:    README.md
+                    CHANGELOG.md
+
+tested-with:
+    GHC == 8.10.7
+  , GHC == 9.0.2
+  , GHC == 9.2.4
+  , GHC == 9.2.8
+  , GHC == 9.4.2
+  , GHC == 9.4.5
+  , GHC == 9.6.1
+  , GHC == 9.6.7
+  , GHC == 9.8.2
+  , GHC == 9.10.2
+
+flag enable-pool
+  description: Enable support for connection pools using unliftio-pool.
+               You can disable this for a lighter dependency footprint if
+               you don't need support for connection pools.
+  default: True
+  manual: False
+
+common warnings
+  ghc-options: -Wall -Wno-unused-do-bind -Wunused-packages
+
+  if flag(enable-pool)
+    cpp-options: -DPOOL
+
+common deps
+  build-depends:
+    , base >= 4 && < 5
+    , bluefin >= 0.0.11 && < 0.0.18
+    , postgresql-simple >= 0.7 && < 0.8
+
+  if flag(enable-pool)
+    build-depends:
+      , unliftio-pool >= 0.4.1 && < 0.5
+
+common extensions
+  default-extensions:
+    DataKinds
+    FlexibleContexts
+    GADTs
+    LambdaCase
+    RankNTypes
+    ScopedTypeVariables
+    TypeApplications
+    TypeFamilies
+    TypeOperators
+
+library
+  import:
+      warnings
+    , deps
+    , extensions
+  exposed-modules:
+      Bluefin.PostgreSQL
+      Bluefin.PostgreSQL.Connection
+  if flag(enable-pool)
+    exposed-modules:
+      Bluefin.PostgreSQL.Connection.Pool
+  -- other-extensions:
+  hs-source-dirs:   src
+  default-language: Haskell2010
diff --git a/src/Bluefin/PostgreSQL.hs b/src/Bluefin/PostgreSQL.hs
new file mode 100644
--- /dev/null
+++ b/src/Bluefin/PostgreSQL.hs
@@ -0,0 +1,384 @@
+{-# LANGUAGE CPP #-}
+
+module Bluefin.PostgreSQL
+  ( -- * Effect
+    WithConnection
+  , withConnection
+
+    -- ** Interpreters
+  , runWithConnection
+
+#if POOL
+  , runWithConnectionPool
+#endif
+
+    -- * Lifted versions of functions from Database.PostgreSQL.Simple
+
+    -- ** Queries that return results
+  , query
+  , query_
+  , queryWith
+  , queryWith_
+
+    -- ** Statements that do not return results
+  , execute
+  , execute_
+  , executeMany
+
+    -- ** Transaction handling
+  , withTransaction
+  , withSavepoint
+  , begin
+  , commit
+  , rollback
+
+    -- ** Queries that stream results
+  , fold
+  , foldWithOptions
+  , fold_
+  , foldWithOptions_
+  , forEach
+  , forEach_
+  , returning
+  , foldWith
+  , foldWithOptionsAndParser
+  , foldWith_
+  , foldWithOptionsAndParser_
+  , forEachWith
+  , forEachWith_
+  , returningWith
+  )
+where
+
+import Data.Int (Int64)
+import qualified Database.PostgreSQL.Simple as PSQL
+import qualified Database.PostgreSQL.Simple.FromRow as PSQL
+import Bluefin.Eff
+import Bluefin.IO
+import Bluefin.PostgreSQL.Connection as Conn
+import GHC.Stack
+#if POOL
+import Bluefin.PostgreSQL.Connection.Pool as Pool
+#endif
+
+-- | Lifted 'PSQL.query'.
+query ::
+  (HasCallStack, e :> es, e1 :> es, PSQL.ToRow q, PSQL.FromRow r) =>
+  WithConnection e ->
+  IOE e1 ->
+  PSQL.Query ->
+  q ->
+  Eff es [r]
+query wc ioe q row = withConnection wc $ \conn -> effIO ioe (PSQL.query conn q row)
+
+
+-- | Lifted 'PSQL.query_'.
+query_ ::
+  (HasCallStack, e :> es, e1 :> es, PSQL.FromRow r) =>
+  WithConnection e ->
+  IOE e1 ->
+  PSQL.Query ->
+  Eff es [r]
+query_ wc ioe row = withConnection wc $ \conn -> effIO ioe (PSQL.query_ conn row)
+
+-- | Lifted 'PSQL.queryWith'.
+queryWith ::
+  (HasCallStack, e :> es, e1 :> es, PSQL.ToRow q) =>
+  WithConnection e ->
+  IOE e1 ->
+  PSQL.RowParser r ->
+  PSQL.Query ->
+  q ->
+  Eff es [r]
+queryWith wc ioe parser q row =
+  withConnection wc $ \conn -> effIO ioe (PSQL.queryWith parser conn q row)
+
+-- | Lifted 'PSQL.queryWith_'.
+queryWith_ ::
+  (HasCallStack, e :> es, e1 :> es) =>
+  WithConnection e ->
+  IOE e1 ->
+  PSQL.RowParser r ->
+  PSQL.Query ->
+  Eff es [r]
+queryWith_ wc ioe parser row =
+  withConnection wc $ \conn -> effIO ioe (PSQL.queryWith_ parser conn row)
+
+-- | Lifted 'PSQL.execute'.
+execute ::
+  (HasCallStack, e :> es, e1 :> es, PSQL.ToRow q) =>
+  WithConnection e ->
+  IOE e1 ->
+  PSQL.Query ->
+  q ->
+  Eff es Int64
+execute wc ioe q row = withConnection wc $ \conn -> effIO ioe (PSQL.execute conn q row)
+
+-- | Lifted 'PSQL.execute_'.
+execute_ ::
+  (HasCallStack, e :> es, e1 :> es) =>
+  WithConnection e ->
+  IOE e1 ->
+  PSQL.Query ->
+  Eff es Int64
+execute_ wc ioe row = withConnection wc $ \conn -> effIO ioe (PSQL.execute_ conn row)
+
+-- | Lifted 'PSQL.executeMany'.
+executeMany ::
+  (HasCallStack, e :> es, e1 :> es, PSQL.ToRow q) =>
+  WithConnection e ->
+  IOE e1 ->
+  PSQL.Query ->
+  [q] ->
+  Eff es Int64
+executeMany wc ioe q rows = withConnection wc $ \conn -> effIO ioe (PSQL.executeMany conn q rows)
+
+-- | Lifted 'PSQL.withTransaction'.
+withTransaction ::
+  (HasCallStack, e :> es, e1 :> es) => 
+  WithConnection e ->
+  IOE e1 ->
+  Eff es a -> 
+  Eff es a
+withTransaction wc ioe f =
+  unliftWithConn wc ioe $ \conn unlift ->
+    PSQL.withTransaction conn (unlift f)
+
+-- | Lifted 'PSQL.withSavepoint'.
+withSavepoint ::
+  (HasCallStack, e :> es, e1 :> es) => 
+  WithConnection e ->
+  IOE e1 ->
+  Eff es a -> Eff es a
+withSavepoint wc ioe f =
+  unliftWithConn wc ioe $ \conn unlift ->
+    PSQL.withSavepoint conn (unlift f)
+
+-- | Lifted 'PSQL.begin'.
+begin ::
+  (HasCallStack, e :> es, e1 :> es) => 
+  WithConnection e ->
+  IOE e1 ->
+  Eff es ()
+begin wc ioe = withConnection wc $ effIO ioe . PSQL.begin
+
+-- | Lifted 'PSQL.commit'.
+commit ::
+  (HasCallStack, e :> es, e1 :> es) => 
+  WithConnection e ->
+  IOE e1 ->
+  Eff es ()
+commit wc ioe = withConnection wc $ effIO ioe . PSQL.commit
+
+-- | Lifted 'PSQL.rollback'.
+rollback ::
+  (HasCallStack, e :> es, e1 :> es) => 
+  WithConnection e ->
+  IOE e1 ->
+  Eff es ()
+rollback wc ioe = withConnection wc $ effIO ioe . PSQL.rollback
+
+(...) :: (a -> b) -> (t1 -> t2 -> a) -> t1 -> t2 -> b
+unlift ... f = \a' row -> unlift $ f a' row
+
+unliftWithConn ::
+  (HasCallStack, e :> es, e1 :> es) =>
+  WithConnection e ->
+  IOE e1 ->
+  (PSQL.Connection -> (forall b. Eff es b -> IO b) -> IO a) ->
+  Eff es a
+unliftWithConn wc ioe f =
+  withConnection wc $ \conn ->
+    withEffToIO_ ioe $ \unlift ->
+      f conn unlift
+{-# INLINE unliftWithConn #-}
+
+-- | Lifted 'PSQL.fold'.
+fold ::
+  (HasCallStack, e :> es, e1 :> es, PSQL.FromRow row, PSQL.ToRow params) =>
+  WithConnection e ->
+  IOE e1 ->
+  PSQL.Query ->
+  params ->
+  a ->
+  (a -> row -> Eff es a) ->
+  Eff es a
+fold wc ioe q params a f =
+  unliftWithConn wc ioe $ \conn unlift ->
+    PSQL.fold conn q params a (unlift ... f)
+
+-- | Lifted 'PSQL.foldWithOptions'.
+foldWithOptions ::
+  (HasCallStack, e :> es, e1 :> es, PSQL.FromRow row, PSQL.ToRow params) =>
+  WithConnection e ->
+  IOE e1 ->
+  PSQL.FoldOptions ->
+  PSQL.Query ->
+  params ->
+  a ->
+  (a -> row -> Eff es a) ->
+  Eff es a
+foldWithOptions wc ioe opts q params a f =
+  unliftWithConn wc ioe $ \conn unlift ->
+    PSQL.foldWithOptions opts conn q params a (unlift ... f)
+
+-- | Lifted 'PSQL.fold_'.
+fold_ ::
+  (HasCallStack, e :> es, e1 :> es, PSQL.FromRow row) =>
+  WithConnection e ->
+  IOE e1 ->
+  PSQL.Query ->
+  a ->
+  (a -> row -> Eff es a) ->
+  Eff es a
+fold_ wc ioe q a f =
+  unliftWithConn wc ioe $ \conn unlift ->
+    PSQL.fold_ conn q a (unlift ... f)
+
+-- | Lifted 'PSQL.foldWithOptions_'.
+foldWithOptions_ ::
+  (HasCallStack, e :> es, e1 :> es, PSQL.FromRow row) =>
+  WithConnection e ->
+  IOE e1 ->
+  PSQL.FoldOptions ->
+  PSQL.Query ->
+  a ->
+  (a -> row -> Eff es a) ->
+  Eff es a
+foldWithOptions_ wc ioe opts q a f =
+  unliftWithConn wc ioe $ \conn unlift ->
+    PSQL.foldWithOptions_ opts conn q a (unlift ... f)
+
+-- | Lifted 'PSQL.forEach'.
+forEach ::
+  (HasCallStack, e :> es, e1 :> es, PSQL.FromRow r, PSQL.ToRow q) =>
+  WithConnection e ->
+  IOE e1 ->
+  PSQL.Query ->
+  q ->
+  (r -> Eff es ()) ->
+  Eff es ()
+forEach wc ioe q row forR =
+  unliftWithConn wc ioe $ \conn unlift ->
+    PSQL.forEach conn q row (unlift . forR)
+
+-- | Lifted 'PSQL.forEach_'.
+forEach_ ::
+  (HasCallStack, e :> es, e1 :> es, PSQL.FromRow r) =>
+  WithConnection e ->
+  IOE e1 ->
+  PSQL.Query ->
+  (r -> Eff es ()) ->
+  Eff es ()
+forEach_ wc ioe q forR =
+  unliftWithConn wc ioe $ \conn unlift ->
+    PSQL.forEach_ conn q (unlift . forR)
+
+-- | Lifted 'PSQL.returning'.
+returning ::
+  (HasCallStack, e :> es, e1 :> es, PSQL.ToRow q, PSQL.FromRow r) =>
+  WithConnection e ->
+  IOE e1 ->
+  PSQL.Query ->
+  [q] ->
+  Eff es [r]
+returning wc ioe q rows = withConnection wc $ \conn -> effIO ioe $ PSQL.returning conn q rows
+
+-- | Lifted 'PSQL.foldWith'.
+foldWith ::
+  (HasCallStack, e :> es, e1 :> es, PSQL.ToRow params) =>
+  WithConnection e ->
+  IOE e1 ->
+  PSQL.RowParser row ->
+  PSQL.Query ->
+  params ->
+  a ->
+  (a -> row -> Eff es a) ->
+  Eff es a
+foldWith wc ioe parser q params a f =
+  unliftWithConn wc ioe $ \conn unlift ->
+    PSQL.foldWith parser conn q params a (unlift ... f)
+
+-- | Lifted 'PSQL.foldWithOptionsAndParser'.
+foldWithOptionsAndParser ::
+  (HasCallStack, e :> es, e1 :> es, PSQL.ToRow params) =>
+  WithConnection e ->
+  IOE e1 ->
+  PSQL.FoldOptions ->
+  PSQL.RowParser row ->
+  PSQL.Query ->
+  params ->
+  a ->
+  (a -> row -> Eff es a) ->
+  Eff es a
+foldWithOptionsAndParser wc ioe opts parser q params a f =
+  unliftWithConn wc ioe $ \conn unlift ->
+    PSQL.foldWithOptionsAndParser opts parser conn q params a (unlift ... f)
+
+-- | Lifted 'PSQL.foldWith_'.
+foldWith_ ::
+  (HasCallStack, e :> es, e1 :> es) =>
+  WithConnection e ->
+  IOE e1 ->
+  PSQL.RowParser row ->
+  PSQL.Query ->
+  a ->
+  (a -> row -> Eff es a) ->
+  Eff es a
+foldWith_ wc ioe parser q a f =
+  unliftWithConn wc ioe $ \conn unlift ->
+    PSQL.foldWith_ parser conn q a (unlift ... f)
+
+-- | Lifted 'PSQL.foldWithOptionsAndParser_'.
+foldWithOptionsAndParser_ ::
+  (HasCallStack, e :> es, e1 :> es) =>
+  WithConnection e ->
+  IOE e1 ->
+  PSQL.FoldOptions ->
+  PSQL.RowParser row ->
+  PSQL.Query ->
+  a ->
+  (a -> row -> Eff es a) ->
+  Eff es a
+foldWithOptionsAndParser_ wc ioe opts parser q a f =
+  unliftWithConn wc ioe $ \conn unlift ->
+    PSQL.foldWithOptionsAndParser_ opts parser conn q a (unlift ... f)
+
+-- | Lifted 'PSQL.forEachWith'.
+forEachWith ::
+  (HasCallStack, e :> es, e1 :> es, PSQL.ToRow q) =>
+  WithConnection e ->
+  IOE e1 ->
+  PSQL.RowParser r ->
+  PSQL.Query ->
+  q ->
+  (r -> Eff es ()) ->
+  Eff es ()
+forEachWith wc ioe parser q row forR =
+  unliftWithConn wc ioe $ \conn unlift ->
+    PSQL.forEachWith parser conn q row (unlift . forR)
+
+-- | Lifted 'PSQL.forEachWith_'.
+forEachWith_ ::
+  (HasCallStack, e :> es, e1 :> es) =>
+  WithConnection e ->
+  IOE e1 ->
+  PSQL.RowParser r ->
+  PSQL.Query ->
+  (r -> Eff es ()) ->
+  Eff es ()
+forEachWith_ wc ioe parser row forR =
+  unliftWithConn wc ioe $ \conn unlift ->
+    PSQL.forEachWith_ parser conn row (unlift . forR)
+
+-- | Lifted 'PSQL.returningWith'.
+returningWith ::
+  (HasCallStack, e :> es, e1 :> es, PSQL.ToRow q) =>
+  WithConnection e ->
+  IOE e1 ->
+  PSQL.RowParser r ->
+  PSQL.Query ->
+  [q] ->
+  Eff es [r]
+returningWith wc ioe parser q rows =
+  withConnection wc $ \conn -> effIO ioe $ PSQL.returningWith parser conn q rows
diff --git a/src/Bluefin/PostgreSQL/Connection.hs b/src/Bluefin/PostgreSQL/Connection.hs
new file mode 100644
--- /dev/null
+++ b/src/Bluefin/PostgreSQL/Connection.hs
@@ -0,0 +1,74 @@
+module Bluefin.PostgreSQL.Connection
+  ( -- * Effect
+    WithConnection (..)
+  , withConnection
+
+    -- * Interpret with a single Connection
+  , runWithConnection
+  , runWithConnectInfo
+  )
+where
+
+import Bluefin.Compound
+import Bluefin.Eff
+import Bluefin.IO
+import qualified Database.PostgreSQL.Simple as PSQL
+import GHC.Stack
+
+{- | A dynamic effect that lets us use a database 'PSQL.Connection',
+without specifying __how__ that connection is supplied.
+
+For example, we might want to just provide a connection and let the interpreter
+use that for the whole procedure (see 'Bluefin.PostgreSQL.Connection.runWithConnection').
+
+Or, we might want to create a connection pool which provides connections when
+the interpreter asks for them (see "Bluefin.PostgreSQL.Connection.Pool").
+-}
+newtype WithConnection (e :: Effects) = MkWithConnection
+  { withConnectionImpl :: forall e' a. (PSQL.Connection -> Eff e' a) -> Eff (e' :& e) a
+  -- ^ Use a 'PSQL.Connection' provided by an interpreter.
+  }
+
+instance Handle WithConnection where
+  mapHandle h =
+    MkWithConnection
+      { withConnectionImpl = useImplUnder . withConnectionImpl h
+      }
+
+-- | Use a 'PSQL.Connection' provided by an interpreter.
+withConnection :: (e :> es) => WithConnection e -> (PSQL.Connection -> Eff es a) -> Eff es a
+withConnection w f = makeOp (withConnectionImpl (mapHandle w) f)
+
+{- | Run a t'WithConnection' effect by simply supplying a 'PSQL.Connection'.
+The connection will be kept alive for the whole duration of the procedure,
+which might not be want you want for long-running processes. If so, see
+"Bluefin.PostgreSQL.Connection.Pool".
+-}
+runWithConnection ::
+  PSQL.Connection ->
+  (forall e. WithConnection e -> Eff (e :& es) a) ->
+  Eff es a
+runWithConnection conn k =
+  useImplIn
+    k
+    MkWithConnection
+      { withConnectionImpl = \f -> useImpl (f conn)
+      }
+
+{- | Run a t'WithConnection' effect using a 'PSQL.ConnectInfo'.
+The 'PSQL.ConnectInfo' will be used to create a 'PSQL.Connection' which will be
+kept alive for the whole duration of the procedure, which might not be want you want
+for long-running processes. If so, see "Bluefin.PostgreSQL.Connection.Pool".
+
+'PSQL.withConnect' will handle opening and closing the 'PSQL.Connection'.
+-}
+runWithConnectInfo ::
+  (HasCallStack, e1 :> es) =>
+  IOE e1 ->
+  PSQL.ConnectInfo ->
+  (forall e. WithConnection e -> Eff (e :& es) a) ->
+  Eff es a
+runWithConnectInfo ioe connInfo k =
+  withEffToIO_ ioe $ \unlift ->
+    PSQL.withConnect connInfo $ \conn ->
+      unlift $ runWithConnection conn k
diff --git a/src/Bluefin/PostgreSQL/Connection/Pool.hs b/src/Bluefin/PostgreSQL/Connection/Pool.hs
new file mode 100644
--- /dev/null
+++ b/src/Bluefin/PostgreSQL/Connection/Pool.hs
@@ -0,0 +1,37 @@
+module Bluefin.PostgreSQL.Connection.Pool
+  ( -- * Interpret with a Connection pool
+    runWithConnectionPool
+
+    -- * Re-export
+  , module Pool
+  )
+where
+
+import Bluefin.Compound
+import Bluefin.Eff
+import Bluefin.IO
+import Bluefin.PostgreSQL.Connection
+import qualified Database.PostgreSQL.Simple as PSQL
+import GHC.Stack
+import UnliftIO.Pool as Pool
+
+{- | Rather than keeping one connection alive and re-using it for the whole
+process, we might want to create a 'Pool' of connections and only "ask" for
+one when we need it. This function uses "UnliftIO.Pool" to do just that.
+-}
+runWithConnectionPool ::
+  (HasCallStack, e1 :> es) =>
+  IOE e1 ->
+  Pool.Pool PSQL.Connection ->
+  (forall e. WithConnection e -> Eff (e :& es) a) ->
+  Eff es a
+runWithConnectionPool ioe pool k =
+  useImplIn
+    k
+    MkWithConnection
+      { withConnectionImpl = \f ->
+          runEffReader ioe $
+            Pool.withResource pool $
+              \conn -> effReader $
+                \_ -> useImpl (f conn)
+      }
