diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,4 +1,8 @@
-## [_Unreleased_](https://github.com/freckle/freckle-app/compare/v1.17.0.0...main)
+## [_Unreleased_](https://github.com/freckle/freckle-app/compare/v1.17.1.0...main)
+
+## [v1.17.1.0](https://github.com/freckle/freckle-app/compare/v1.17.0.0...v1.17.1.0)
+
+- Support (and require) `hw-kafka-client` `>= 5`
 
 ## [v1.17.0.0](https://github.com/freckle/freckle-app/compare/v1.16.0.0...v1.17.0.0)
 
diff --git a/freckle-app.cabal b/freckle-app.cabal
--- a/freckle-app.cabal
+++ b/freckle-app.cabal
@@ -1,6 +1,6 @@
 cabal-version:      1.18
 name:               freckle-app
-version:            1.17.0.0
+version:            1.17.1.0
 license:            MIT
 license-file:       LICENSE
 maintainer:         Freckle Education
@@ -148,7 +148,7 @@
         http-conduit >=2.3.5,
         http-link-header >=1.2.1,
         http-types >=0.12.3,
-        hw-kafka-client >=4.0.3 && <5.0.0,
+        hw-kafka-client >=5.0.0,
         immortal >=0.3,
         lens >=5.1.1,
         memcache >=0.3.0.1,
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
@@ -28,6 +28,7 @@
 import Freckle.App.OpenTelemetry
 import Kafka.Producer
 import qualified OpenTelemetry.Trace as Trace
+import UnliftIO (withRunInIO)
 import Yesod.Core.Lens
 import Yesod.Core.Types (HandlerData)
 
@@ -91,13 +92,13 @@
   -> KafkaProducerPoolConfig
   -> IO (Pool KafkaProducer)
 createKafkaProducerPool addresses KafkaProducerPoolConfig {..} =
-  Pool.newPool $
-    Pool.setNumStripes (Just kafkaProducerPoolConfigStripes) $
-      Pool.defaultPoolConfig
-        mkProducer
-        closeProducer
-        (realToFrac kafkaProducerPoolConfigIdleTimeout)
-        kafkaProducerPoolConfigSize
+  Pool.newPool
+    $ Pool.setNumStripes (Just kafkaProducerPoolConfigStripes)
+    $ Pool.defaultPoolConfig
+      mkProducer
+      closeProducer
+      (realToFrac kafkaProducerPoolConfigIdleTimeout)
+      kafkaProducerPoolConfigSize
  where
   mkProducer =
     either
@@ -122,26 +123,21 @@
   logDebugNS "kafka" $ "Producing Kafka events" :# ["events" .= values]
   view kafkaProducerPoolL >>= \case
     NullKafkaProducerPool -> pure ()
-    KafkaProducerPool producerPool -> do
-      errors <-
-        liftIO $
-          Pool.withResource producerPool $ \producer ->
-            produceMessageBatch producer $
-              toList $
-                mkProducerRecord <$> values
-      unless (null errors) $
-        logErrorNS "kafka" $
-          "Failed to send events" :# ["errors" .= fmap (tshow . snd) errors]
+    KafkaProducerPool producerPool ->
+      withRunInIO $ \run ->
+        Pool.withResource producerPool $ \producer ->
+          for_ @NonEmpty values $ \value -> do
+            mError <- liftIO $ produceMessage producer $ mkProducerRecord value
+            for_ @Maybe mError $ \e ->
+              run $ logErrorNS "kafka" $ "Failed to send event" :# ["error" .= tshow e]
  where
   mkProducerRecord value =
     ProducerRecord
       { prTopic
       , prPartition = UnassignedPartition
       , prKey = Just $ toStrict $ encode $ keyF value
-      , prValue =
-          Just $
-            toStrict $
-              encode value
+      , prValue = Just $ toStrict $ encode value
+      , prHeaders = mempty
       }
 
   traced =
diff --git a/package.yaml b/package.yaml
--- a/package.yaml
+++ b/package.yaml
@@ -1,5 +1,5 @@
 name: freckle-app
-version: 1.17.0.0
+version: 1.17.1.0
 maintainer: Freckle Education
 category: Utils
 github: freckle/freckle-app
@@ -112,7 +112,7 @@
     - http-conduit >= 2.3.5 # addToRequestQueryString
     - http-link-header
     - http-types
-    - hw-kafka-client < 5.0.0
+    - hw-kafka-client
     - immortal
     - lens
     - memcache
