diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,4 +1,12 @@
-## [_Unreleased_](https://github.com/freckle/freckle-kafka/compare/v0.0.0.4...main)
+## [_Unreleased_](https://github.com/freckle/freckle-kafka/compare/v0.1.0.0...main)
+
+## [v0.1.0.0](https://github.com/freckle/freckle-kafka/compare/v0.0.0.4...v0.1.0.0)
+
+- Add `withKafkaProducerPool`, which creates a Kafka producer pool and
+  closes it with `Pool.destroyAllResources` once the given action
+  completes. Prefer this to `createKafkaProducerPool`, which leaves the
+  pool open for the caller to close.
+- Generalize `createKafkaProducerPool` from `IO` to any `MonadIO`
 
 ## [v0.0.0.4](https://github.com/freckle/freckle-kafka/tree/v0.0.0.4)
 
diff --git a/freckle-kafka.cabal b/freckle-kafka.cabal
--- a/freckle-kafka.cabal
+++ b/freckle-kafka.cabal
@@ -1,6 +1,6 @@
 cabal-version:      1.18
 name:               freckle-kafka
-version:            0.0.0.4
+version:            0.1.0.0
 license:            MIT
 license-file:       LICENSE
 maintainer:         Freckle Education
diff --git a/library/Freckle/App/Kafka/Producer.hs b/library/Freckle/App/Kafka/Producer.hs
--- a/library/Freckle/App/Kafka/Producer.hs
+++ b/library/Freckle/App/Kafka/Producer.hs
@@ -7,6 +7,7 @@
   , envKafkaProducerPoolConfig
   , KafkaProducerPool (..)
   , HasKafkaProducerPool (..)
+  , withKafkaProducerPool
   , createKafkaProducerPool
   , produceKeyedOn
   , produce
@@ -23,7 +24,7 @@
   )
 import Control.Exception.Annotated.UnliftIO qualified as Annotated
 import Control.Lens (Lens', lens, view)
-import Control.Monad.IO.Class (liftIO)
+import Control.Monad.IO.Class (MonadIO, liftIO)
 import Control.Monad.Reader (MonadReader)
 import Data.Aeson (ToJSON, encode)
 import Data.ByteString.Lazy qualified as BSL
@@ -40,7 +41,7 @@
 import OpenTelemetry.Trace (SpanKind (..), defaultSpanArguments)
 import OpenTelemetry.Trace qualified as Trace
 import OpenTelemetry.Trace.Monad (MonadTracer, inSpan)
-import UnliftIO (MonadUnliftIO, withRunInIO)
+import UnliftIO (MonadUnliftIO, bracket, withRunInIO)
 import Yesod.Core.Types (HandlerData (..), RunHandlerEnv (..))
 
 envKafkaBrokerAddresses
@@ -104,18 +105,37 @@
 siteL :: Lens' (RunHandlerEnv child site) site
 siteL = lens rheSite $ \x y -> x {rheSite = y}
 
+-- | Create a Kafka producer pool, closing it once the given action
+--   completes
+withKafkaProducerPool
+  :: MonadUnliftIO m
+  => NonEmpty BrokerAddress
+  -> KafkaProducerPoolConfig
+  -> (Pool KafkaProducer -> m a)
+  -> m a
+withKafkaProducerPool addresses config =
+  bracket
+    (createKafkaProducerPool addresses config)
+    (liftIO . Pool.destroyAllResources)
+
+-- | Create a Kafka producer pool for the given broker addresses and config
+--
+-- Prefer 'withKafkaProducerPool', which closes the pool with
+-- 'Pool.destroyAllResources' once the given action completes.
 createKafkaProducerPool
-  :: NonEmpty BrokerAddress
+  :: MonadIO m
+  => NonEmpty BrokerAddress
   -> KafkaProducerPoolConfig
-  -> IO (Pool KafkaProducer)
+  -> m (Pool KafkaProducer)
 createKafkaProducerPool addresses config =
-  Pool.newPool $
-    Pool.setNumStripes (Just $ kafkaProducerPoolConfigStripes config) $
-      Pool.defaultPoolConfig
-        mkProducer
-        closeProducer
-        (realToFrac $ kafkaProducerPoolConfigIdleTimeout config)
-        (kafkaProducerPoolConfigSize config)
+  liftIO $
+    Pool.newPool $
+      Pool.setNumStripes (Just $ kafkaProducerPoolConfigStripes config) $
+        Pool.defaultPoolConfig
+          mkProducer
+          closeProducer
+          (realToFrac $ kafkaProducerPoolConfigIdleTimeout config)
+          (kafkaProducerPoolConfigSize config)
  where
   mkProducer =
     either
diff --git a/package.yaml b/package.yaml
--- a/package.yaml
+++ b/package.yaml
@@ -1,5 +1,5 @@
 name: freckle-kafka
-version: 0.0.0.4
+version: 0.1.0.0
 maintainer: Freckle Education
 category: Database
 github: freckle/freckle-kafka
