diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,116 @@
+## <small>0.4.7 (2023-09-14)</small>
+
+* Add CHANGELOG.md ([8bfd48b](https://github.com/finn-no/unleash-client-haskell/commit/8bfd48b))
+* Don't do -O2 ([eb8850b](https://github.com/finn-no/unleash-client-haskell/commit/eb8850b))
+* Set version to 0.4.7 ([20b23d9](https://github.com/finn-no/unleash-client-haskell/commit/20b23d9))
+* unleash-client-haskell-core: 0.8.9 ([997d8e3](https://github.com/finn-no/unleash-client-haskell/commit/997d8e3))
+
+
+
+## <small>0.4.6 (2023-09-14)</small>
+
+* Downgrade Cabal to 3.0 ([cf24d8b](https://github.com/finn-no/unleash-client-haskell/commit/cf24d8b))
+* Set version to 0.4.6 ([8c813a9](https://github.com/finn-no/unleash-client-haskell/commit/8c813a9))
+* unleash-client-haskell-core: 0.8.7 ([4ae98c3](https://github.com/finn-no/unleash-client-haskell/commit/4ae98c3))
+* Version bound all deps ([d518273](https://github.com/finn-no/unleash-client-haskell/commit/d518273))
+
+
+
+## <small>0.4.5 (2023-09-13)</small>
+
+* Add some more package properties ([5042c05](https://github.com/finn-no/unleash-client-haskell/commit/5042c05))
+* Set version to 0.4.5 ([4cf0364](https://github.com/finn-no/unleash-client-haskell/commit/4cf0364))
+* unleash-client-haskell-core: 0.8.6 ([33571e5](https://github.com/finn-no/unleash-client-haskell/commit/33571e5))
+
+
+
+## <small>0.4.4 (2023-02-27)</small>
+
+* Set version to 0.4.4 ([a064750](https://github.com/finn-no/unleash-client-haskell/commit/a064750))
+
+
+
+## <small>0.4.3 (2023-02-27)</small>
+
+* Set version to 0.8.5 ([4d4aec2](https://github.com/finn-no/unleash-client-haskell/commit/4d4aec2))
+* unleash-client-haskell-core: 0.8.5 ([b82fffa](https://github.com/finn-no/unleash-client-haskell/commit/b82fffa))
+
+
+
+## <small>0.4.2 (2023-02-27)</small>
+
+* Build with -haddock ([8433559](https://github.com/finn-no/unleash-client-haskell/commit/8433559))
+* Set version to 0.4.2 ([822623a](https://github.com/finn-no/unleash-client-haskell/commit/822623a))
+
+
+
+## <small>0.4.1 (2023-02-06)</small>
+
+* Add Haddock docs ([f909f98](https://github.com/finn-no/unleash-client-haskell/commit/f909f98))
+* Set version to 0.4.1 ([8bbfd00](https://github.com/finn-no/unleash-client-haskell/commit/8bbfd00))
+* Update README.md ([4121c0e](https://github.com/finn-no/unleash-client-haskell/commit/4121c0e))
+* Update README.md ([d68a358](https://github.com/finn-no/unleash-client-haskell/commit/d68a358))
+* Update README.md ([cf91866](https://github.com/finn-no/unleash-client-haskell/commit/cf91866))
+* Update README.md ([1766f05](https://github.com/finn-no/unleash-client-haskell/commit/1766f05))
+* Update unleash-haskell-client-core to 0.8.2 ([1c73d01](https://github.com/finn-no/unleash-client-haskell/commit/1c73d01))
+* Update unleash-haskell-client-core to 0.8.4 ([39904a0](https://github.com/finn-no/unleash-client-haskell/commit/39904a0))
+
+
+
+## 0.4.0 (2023-01-27)
+
+* Re-export some constructors from unleash-client-haskell-core ([dd0d7f5](https://github.com/finn-no/unleash-client-haskell/commit/dd0d7f5))
+* Set to version 0.4.0 ([aef1428](https://github.com/finn-no/unleash-client-haskell/commit/aef1428))
+* Update README.md ([2c94f80](https://github.com/finn-no/unleash-client-haskell/commit/2c94f80))
+* Update README.md ([77a8c4c](https://github.com/finn-no/unleash-client-haskell/commit/77a8c4c))
+* Update README.md ([9e9efbd](https://github.com/finn-no/unleash-client-haskell/commit/9e9efbd))
+
+
+
+## <small>0.3.1 (2023-01-26)</small>
+
+* Move HttpClient from unleash-client-haskell-core ([7d85ac5](https://github.com/finn-no/unleash-client-haskell/commit/7d85ac5))
+* Refactor ([2a34192](https://github.com/finn-no/unleash-client-haskell/commit/2a34192))
+* Refactor HttpClient ([fbf5957](https://github.com/finn-no/unleash-client-haskell/commit/fbf5957))
+* Set version to 0.3.1 ([319428c](https://github.com/finn-no/unleash-client-haskell/commit/319428c))
+* Support TLS by default ([f596f51](https://github.com/finn-no/unleash-client-haskell/commit/f596f51))
+* Update README.md ([8dcc937](https://github.com/finn-no/unleash-client-haskell/commit/8dcc937))
+* Update README.md ([0e2d998](https://github.com/finn-no/unleash-client-haskell/commit/0e2d998))
+
+
+
+## 0.3.0 (2023-01-26)
+
+* Set to version 0.3.0 ([24dcdfd](https://github.com/finn-no/unleash-client-haskell/commit/24dcdfd))
+* Use MonadReader ([4a4e8d6](https://github.com/finn-no/unleash-client-haskell/commit/4a4e8d6))
+
+
+
+## 0.2.0 (2023-01-25)
+
+* Add apiKey to Config ([32d43c3](https://github.com/finn-no/unleash-client-haskell/commit/32d43c3))
+* Set to version 0.2.0 ([e225ea6](https://github.com/finn-no/unleash-client-haskell/commit/e225ea6))
+* Example: Die if registration fails ([8e5e021](https://github.com/finn-no/unleash-client-haskell/commit/8e5e021))
+
+
+
+## <small>0.1.1 (2023-01-25)</small>
+
+* Set to version 0.1.1 ([6ec7b50](https://github.com/finn-no/unleash-client-haskell/commit/6ec7b50))
+* Use unleash-client-haskell-core 0.7.0 ([6071600](https://github.com/finn-no/unleash-client-haskell/commit/6071600))
+
+
+
+## 0.1.0 (2023-01-25)
+
+* Add variant getters ([ffddec4](https://github.com/finn-no/unleash-client-haskell/commit/ffddec4))
+* Initial commit ([32df140](https://github.com/finn-no/unleash-client-haskell/commit/32df140))
+* Misc. cleanup ([67eaac3](https://github.com/finn-no/unleash-client-haskell/commit/67eaac3))
+* Update and add comments ([255096d](https://github.com/finn-no/unleash-client-haskell/commit/255096d))
+* Update comments ([f31eda2](https://github.com/finn-no/unleash-client-haskell/commit/f31eda2))
+* Update README.md ([a39d5a2](https://github.com/finn-no/unleash-client-haskell/commit/a39d5a2))
+* Update README.md ([2d0601c](https://github.com/finn-no/unleash-client-haskell/commit/2d0601c))
+* Update README.md ([0885486](https://github.com/finn-no/unleash-client-haskell/commit/0885486))
+* Update unleash-client-haskell-core ([4f1f503](https://github.com/finn-no/unleash-client-haskell/commit/4f1f503))
+* Use record-dot-preprocessor ([fd68143](https://github.com/finn-no/unleash-client-haskell/commit/fd68143))
+* Use unleash-client-haskell-core at 0.6.5 tag ([a8d2040](https://github.com/finn-no/unleash-client-haskell/commit/a8d2040))
diff --git a/README.md b/README.md
new file mode 100644
--- /dev/null
+++ b/README.md
@@ -0,0 +1,41 @@
+# unleash-client-haskell
+
+This is an [Unleash](https://www.getunleash.io/) client SDK for Haskell.
+
+`Unleash.Client` provides a stateful HTTP client with functions and types for:
+
+- Client configuration
+- Client registration
+- Fetching feature toggles
+- Fetching variants
+- Sending metrics
+
+You'll need to spawn threads and handle errors yourself. This is demonstrated in the [example application](example/Main.hs).
+
+`unleash-client-haskell` is a (working) work in progress and the API is likely to change.
+
+See [unleash-client-haskell-core](https://github.com/finn-no/unleash-client-haskell-core) for a bare-bones feature toggle evaluation library.
+
+## Build
+
+```
+nix-build
+```
+
+## Maintainers
+
+- [Even Brenden](mailto:uch@anythingexternal.com)
+- [Eirik Meland](mailto:eirik.meland@gmail.com)
+
+## Dependencies
+
+- aeson (BSD-3-Clause)
+- containers (BSD-3-Clause)
+- http-client-tls (MIT)
+- http-media (MIT)
+- servant (BSD-3-Clause)
+- servant-client (BSD-3-Clause)
+- text (BSD-2-Clause)
+- time (BSD-2-Clause)
+- unleash-client-haskell-core (MIT)
+- unliftio (MIT)
diff --git a/example/Main.hs b/example/Main.hs
new file mode 100644
--- /dev/null
+++ b/example/Main.hs
@@ -0,0 +1,92 @@
+{- |
+Module      : Main
+Copyright   : Copyright © FINN.no AS, Inc. All rights reserved.
+License     : MIT
+Stability   : experimental
+
+Example application that uses unleash-client-haskell. Spawns a state poller thread that updates the feature toggles, a metrics sender thread, and an application that continuously reads a feature toggle. The application will block until the first feature toggle set is received.
+-}
+module Main where
+
+import Control.Concurrent (threadDelay)
+import Control.Monad (forever)
+import Control.Monad.Reader (ReaderT (runReaderT))
+import Control.Monad.Reader.Class (asks)
+import Data.Foldable (traverse_)
+import Data.Text (Text)
+import qualified Data.Text as T
+import Data.Void (Void)
+import Servant.Client (BaseUrl (BaseUrl), Scheme (Http))
+import System.Exit (die)
+import Unleash (emptyContext)
+import Unleash.Client (
+    HasUnleash (..),
+    UnleashConfig (..),
+    isEnabled,
+    makeUnleashConfig,
+    pollToggles,
+    pushMetrics,
+    registerClient,
+ )
+import UnliftIO
+
+unleashServer :: BaseUrl
+unleashServer = BaseUrl Http "your-unleash-server" 80 mempty
+
+secretKey :: Maybe Text
+secretKey = Nothing
+
+featureToggle :: Text
+featureToggle = "your-feature-toggle"
+
+type Program a = ReaderT AppConfig IO a
+
+data AppConfig = AppConfig {unleashConfig :: UnleashConfig}
+
+instance HasUnleash AppConfig where
+    getUnleashConfig = unleashConfig
+
+main :: IO ()
+main = do
+    config <- makeUnleashConfig "unleash-client-haskell-example" "localhost" unleashServer secretKey
+    runReaderT program (AppConfig config)
+
+program :: Program ()
+program = do
+    registerApplication
+    let threads = [statePoller, metricsPusher, application]
+    runConcurrently $ traverse_ Concurrently threads
+
+application :: Program Void
+application =
+    forever do
+        enabled <- isEnabled featureToggle emptyContext
+        liftIO . putStrLn $ T.unpack featureToggle <> " is " <> (if enabled then "enabled" else "disabled")
+        liftIO . threadDelay $ 2 * 1000 * 1000
+
+registerApplication :: Program ()
+registerApplication = do
+    registerClient
+        >>= liftIO . \case
+            Left error -> die $ "Could not register application (" <> show error <> ")"
+            Right _ -> putStrLn "Application registered"
+
+statePoller :: Program Void
+statePoller = do
+    config <- asks getUnleashConfig
+    forever do
+        pollToggles
+            >>= liftIO . \case
+                Left error -> putStrLn $ "Could not get state (" <> show error <> ")"
+                Right _ -> putStrLn "State received"
+        liftIO . threadDelay $ config.statePollIntervalInSeconds * 1000 * 1000
+
+metricsPusher :: Program Void
+metricsPusher = do
+    config <- asks getUnleashConfig
+    forever do
+        liftIO . threadDelay $ config.metricsPushIntervalInSeconds * 1000 * 1000
+        pushMetrics
+            >>= liftIO . \case
+                Left error -> putStrLn $ "Could not send metrics (" <> show error <> ")"
+                Right _ -> putStrLn "Metrics sent"
diff --git a/src/Unleash/Client.hs b/src/Unleash/Client.hs
new file mode 100644
--- /dev/null
+++ b/src/Unleash/Client.hs
@@ -0,0 +1,205 @@
+{- |
+Module      : Unleash.Client
+Copyright   : Copyright © FINN.no AS, Inc. All rights reserved.
+License     : MIT
+Stability   : experimental
+
+Functions and types that constitute an Unleash client SDK.
+
+This module re-exports select constructors from [unleash-client-haskell-core](https://github.com/finn-no/unleash-client-haskell-core).
+-}
+module Unleash.Client (
+    makeUnleashConfig,
+    UnleashConfig (..),
+    HasUnleash (..),
+    registerClient,
+    pollToggles,
+    pushMetrics,
+    isEnabled,
+    tryIsEnabled,
+    getVariant,
+    tryGetVariant,
+    -- Re-exports
+    Context (..),
+    emptyContext,
+    VariantResponse (..),
+) where
+
+import Control.Concurrent.MVar
+import Control.Monad (unless, void)
+import Control.Monad.IO.Class (MonadIO (liftIO))
+import Control.Monad.Reader (MonadReader, asks)
+import Data.Text (Text)
+import Data.Time (UTCTime, getCurrentTime)
+import Network.HTTP.Client.TLS (newTlsManager)
+import Servant.Client (BaseUrl, ClientEnv, ClientError, mkClientEnv)
+import Unleash (Context (..), Features, MetricsPayload (..), RegisterPayload (..), VariantResponse (..), emptyContext, emptyVariantResponse, featureGetVariant, featureIsEnabled)
+import Unleash.Internal.HttpClient (getAllClientFeatures, register, sendMetrics)
+
+-- | Smart constructor for Unleash client configuration. Initializes the mutable variables properly.
+makeUnleashConfig ::
+    MonadIO m =>
+    -- | Application name.
+    Text ->
+    -- | Instance identifier.
+    Text ->
+    -- | Unleash server base URL.
+    BaseUrl ->
+    -- | API key for authorization.
+    Maybe Text ->
+    -- | Configuration instance.
+    m UnleashConfig
+makeUnleashConfig applicationName instanceId serverUrl apiKey = do
+    state <- liftIO newEmptyMVar
+    metrics <- liftIO $ newMVar mempty
+    metricsBucketStart <- liftIO $ getCurrentTime >>= newMVar
+    manager <- newTlsManager
+    let clientEnv = mkClientEnv manager serverUrl
+    pure $
+        UnleashConfig
+            { applicationName = applicationName,
+              instanceId = instanceId,
+              state = state,
+              statePollIntervalInSeconds = 4,
+              metrics = metrics,
+              metricsBucketStart = metricsBucketStart,
+              metricsPushIntervalInSeconds = 8,
+              apiKey = apiKey,
+              httpClientEnvironment = clientEnv
+            }
+
+-- | Unleash client configuration. Use the smart constructor or make sure the mutable metrics variables are not empty.
+data UnleashConfig = UnleashConfig
+    { -- | Application name.
+      applicationName :: Text,
+      -- | Instance identifier.
+      instanceId :: Text,
+      -- | Full client feature set state.
+      state :: MVar Features,
+      -- | Feature set state update interval.
+      statePollIntervalInSeconds :: Int,
+      -- | Collected metrics state.
+      metrics :: MVar [(Text, Bool)],
+      -- | Current metrics bucket start time.
+      metricsBucketStart :: MVar UTCTime,
+      -- | Metrics sending interval.
+      metricsPushIntervalInSeconds :: Int,
+      -- | API key for authorization.
+      apiKey :: Maybe Text,
+      -- | HTTP client environment.
+      httpClientEnvironment :: ClientEnv
+    }
+
+-- | Reader monad convenience class. Use this to get an Unleash configuration from inside of an application configuration (for example).
+class HasUnleash r where
+    getUnleashConfig :: r -> UnleashConfig
+
+-- | Register client for the Unleash server. Call this on application startup before calling the state poller and metrics pusher functions.
+registerClient :: (HasUnleash r, MonadReader r m, MonadIO m) => m (Either ClientError ())
+registerClient = do
+    config <- asks getUnleashConfig
+    now <- liftIO getCurrentTime
+    let registrationPayload :: RegisterPayload
+        registrationPayload =
+            RegisterPayload
+                { appName = config.applicationName,
+                  instanceId = config.instanceId,
+                  started = now,
+                  intervalSeconds = config.metricsPushIntervalInSeconds
+                }
+    void <$> register config.httpClientEnvironment config.apiKey registrationPayload
+
+-- | Fetch the most recent feature toggle set from the Unleash server. Meant to be run every statePollIntervalInSeconds. Non-blocking.
+pollToggles :: (HasUnleash r, MonadReader r m, MonadIO m) => m (Either ClientError ())
+pollToggles = do
+    config <- asks getUnleashConfig
+    eitherFeatures <- getAllClientFeatures config.httpClientEnvironment config.apiKey
+    either (const $ pure ()) (updateState config.state) eitherFeatures
+    pure . void $ eitherFeatures
+    where
+        updateState state value = do
+            isUpdated <- liftIO $ tryPutMVar state value
+            liftIO . unless isUpdated . void $ swapMVar state value
+
+-- | Push metrics to the Unleash server. Meant to be run every metricsPushIntervalInSeconds. Blocks if the mutable metrics variables are empty.
+pushMetrics :: (HasUnleash r, MonadReader r m, MonadIO m) => m (Either ClientError ())
+pushMetrics = do
+    config <- asks getUnleashConfig
+    now <- liftIO getCurrentTime
+    lastBucketStart <- liftIO $ swapMVar config.metricsBucketStart now
+    bucket <- liftIO $ swapMVar config.metrics mempty
+    let metricsPayload =
+            MetricsPayload
+                { appName = config.applicationName,
+                  instanceId = config.instanceId,
+                  start = lastBucketStart,
+                  stop = now,
+                  toggles = bucket
+                }
+    void <$> sendMetrics config.httpClientEnvironment config.apiKey metricsPayload
+
+-- | Check if a feature is enabled or not. Blocks until first feature toggle set is received. Blocks if the mutable metrics variables are empty.
+isEnabled ::
+    (HasUnleash r, MonadReader r m, MonadIO m) =>
+    -- | Feature toggle name.
+    Text ->
+    -- | Client context.
+    Context ->
+    -- | Whether or not the feature toggle is enabled.
+    m Bool
+isEnabled feature context = do
+    config <- asks getUnleashConfig
+    state <- liftIO . readMVar $ config.state
+    enabled <- featureIsEnabled state feature context
+    liftIO $ modifyMVar_ config.metrics (\info -> pure $ (feature, enabled) : info)
+    pure enabled
+
+-- | Check if a feature is enabled or not. Returns false for all toggles until first toggle set is received. Blocks if the mutable metrics variables are empty.
+tryIsEnabled ::
+    (HasUnleash r, MonadReader r m, MonadIO m) =>
+    -- | Feature toggle name.
+    Text ->
+    -- | Client context.
+    Context ->
+    -- | Whether or not the feature toggle is enabled.
+    m Bool
+tryIsEnabled feature context = do
+    config <- asks getUnleashConfig
+    maybeState <- liftIO . tryReadMVar $ config.state
+    case maybeState of
+        Just state -> do
+            enabled <- featureIsEnabled state feature context
+            liftIO $ modifyMVar_ config.metrics (\info -> pure $ (feature, enabled) : info)
+            pure enabled
+        Nothing -> pure False
+
+-- | Get a variant. Blocks until first feature toggle set is received.
+getVariant ::
+    (HasUnleash r, MonadReader r m, MonadIO m) =>
+    -- | Feature toggle name.
+    Text ->
+    -- | Client context.
+    Context ->
+    -- | Variant.
+    m VariantResponse
+getVariant feature context = do
+    config <- asks getUnleashConfig
+    state <- liftIO . readMVar $ config.state
+    featureGetVariant state feature context
+
+-- | Get a variant. Returns an empty variant until first toggle set is received.
+tryGetVariant ::
+    (HasUnleash r, MonadReader r m, MonadIO m) =>
+    -- | Feature toggle name.
+    Text ->
+    -- | Client context.
+    Context ->
+    -- | Variant.
+    m VariantResponse
+tryGetVariant feature context = do
+    config <- asks getUnleashConfig
+    maybeState <- liftIO . tryReadMVar $ config.state
+    case maybeState of
+        Just state -> do
+            featureGetVariant state feature context
+        Nothing -> pure emptyVariantResponse
diff --git a/src/Unleash/Internal/HttpClient.hs b/src/Unleash/Internal/HttpClient.hs
new file mode 100644
--- /dev/null
+++ b/src/Unleash/Internal/HttpClient.hs
@@ -0,0 +1,95 @@
+{- |
+Module      : Unleash.Internal.HttpClient
+Copyright   : Copyright © FINN.no AS, Inc. All rights reserved.
+License     : MIT
+Stability   : experimental
+-}
+module Unleash.Internal.HttpClient (
+    getAllClientFeatures,
+    register,
+    sendMetrics,
+) where
+
+import Control.Monad.IO.Class (MonadIO, liftIO)
+import Data.Aeson (ToJSON, encode)
+import qualified Data.List.NonEmpty as NE
+import Data.Map.Strict (Map, fromListWith)
+import Data.Proxy (Proxy (..))
+import Data.Text (Text)
+import qualified Data.Text as T
+import Data.Version (showVersion)
+import qualified Network.HTTP.Media as M
+import Paths_unleash_client_haskell (version)
+import Servant.API (Accept (contentTypes), Get, Header, JSON, MimeRender (mimeRender), NoContent, PostNoContent, ReqBody, type (:<|>) (..), type (:>))
+import Servant.Client (ClientEnv, ClientError, client, runClientM)
+import Unleash.Internal.DomainTypes (Features, fromJsonFeatures, supportedStrategies)
+import Unleash.Internal.JsonTypes (FullMetricsBucket (..), FullMetricsPayload (..), FullRegisterPayload (..), MetricsPayload, RegisterPayload, YesAndNoes (..))
+import qualified Unleash.Internal.JsonTypes as UJT
+
+type Register = "api" :> "client" :> "register" :> Header "Authorization" Text :> Header "Content-Type" Text :> ReqBody '[CustomJSON] FullRegisterPayload :> PostNoContent
+type GetAllClientFeatures = "api" :> "client" :> "features" :> Header "Authorization" Text :> Get '[JSON] UJT.Features
+type SendMetrics = "api" :> "client" :> "metrics" :> Header "Authorization" Text :> ReqBody '[CustomJSON] FullMetricsPayload :> PostNoContent
+type Api = GetAllClientFeatures :<|> SendMetrics :<|> Register
+
+getAllClientFeatures' :<|> sendMetrics' :<|> register' = client api
+
+api :: Proxy Api
+api = Proxy
+
+type ApiKey = Text
+
+data CustomJSON = CustomJSON
+
+-- Remove charset=utf-8 because older versions of Unleash (e.g. 3.17.4) does not recognize it
+instance Accept CustomJSON where
+    contentTypes _ =
+        "application" M.// "json"
+            NE.:| ["application" M.// "json"]
+
+instance {-# OVERLAPPABLE #-} ToJSON a => MimeRender CustomJSON a where
+    mimeRender _ = encode
+
+register :: MonadIO m => ClientEnv -> Maybe ApiKey -> RegisterPayload -> m (Either ClientError NoContent)
+register clientEnv apiKey registerPayload = do
+    let fullRegisterPayload =
+            FullRegisterPayload
+                { appName = registerPayload.appName,
+                  instanceId = registerPayload.instanceId,
+                  sdkVersion = "unleash-client-haskell:" <> (T.pack . showVersion) version,
+                  strategies = supportedStrategies,
+                  started = registerPayload.started,
+                  interval = registerPayload.intervalSeconds * 1000
+                }
+    liftIO $ runClientM (register' apiKey (Just "application/json") fullRegisterPayload) clientEnv
+
+getAllClientFeatures :: MonadIO m => ClientEnv -> Maybe ApiKey -> m (Either ClientError Features)
+getAllClientFeatures clientEnv apiKey = do
+    eitherFeatures <- liftIO $ runClientM (getAllClientFeatures' apiKey) clientEnv
+    pure $ fromJsonFeatures <$> eitherFeatures
+
+sendMetrics :: MonadIO m => ClientEnv -> Maybe ApiKey -> MetricsPayload -> m (Either ClientError NoContent)
+sendMetrics clientEnv apiKey metricsPayload = do
+    liftIO $ runClientM (sendMetrics' apiKey fullMetricsPayload) clientEnv
+    where
+        fullMetricsPayload :: FullMetricsPayload
+        fullMetricsPayload =
+            FullMetricsPayload
+                { appName = metricsPayload.appName,
+                  instanceId = metricsPayload.instanceId,
+                  bucket =
+                    FullMetricsBucket
+                        { start = metricsPayload.start,
+                          stop = metricsPayload.stop,
+                          toggles = makeMapOfYesAndNoes metricsPayload.toggles
+                        }
+                }
+        makeMapOfYesAndNoes :: [(Text, Bool)] -> Map Text YesAndNoes
+        makeMapOfYesAndNoes tuples = do
+            let withSingletonLists :: [(Text, [Bool])] = (\(k, v) -> (k, [v])) <$> tuples
+            let asMap :: (Map Text [Bool]) = fromListWith (++) withSingletonLists
+            boolsToYesAndNoes <$> asMap
+        boolsToYesAndNoes :: [Bool] -> YesAndNoes
+        boolsToYesAndNoes bools = do
+            let yes = length $ filter id bools
+            let no = length bools - yes
+            YesAndNoes yes no
diff --git a/unleash-client-haskell.cabal b/unleash-client-haskell.cabal
new file mode 100644
--- /dev/null
+++ b/unleash-client-haskell.cabal
@@ -0,0 +1,83 @@
+cabal-version: 3.0
+
+name: unleash-client-haskell
+version: 0.4.7
+synopsis: Unleash feature toggle client
+description:
+    This is an [Unleash](https://www.getunleash.io/) client SDK for Haskell.
+
+    'Unleash.Client' provides a stateful HTTP client with functions and types for:
+
+        - Client configuration
+        - Client registration
+        - Fetching feature toggles
+        - Fetching variants
+        - Sending metrics
+
+    See [the source repo](https://github.com/finn-no/unleash-client-haskell) for more information and example usage.
+category: Web
+author: FINN.no
+homepage: https://github.com/finn-no/unleash-client-haskell
+maintainer: See README.md
+license: MIT
+copyright: Copyright © FINN.no AS, Inc. All rights reserved.
+extra-doc-files: CHANGELOG.md
+extra-source-files: README.md
+
+common all
+    build-depends:
+        base >=4.7 && <5,
+        record-dot-preprocessor >= 0.2.15 && < 0.3,
+        record-hasfield >= 1.0 && < 1.1
+    default-extensions:
+        BlockArguments
+        DeriveFoldable
+        DeriveFunctor
+        DeriveTraversable
+        DerivingStrategies
+        LambdaCase
+        OverloadedStrings
+        RankNTypes
+        ScopedTypeVariables
+    ghc-options:
+        -W
+        -Wno-unused-top-binds
+        -Wredundant-constraints
+        -Wunused-packages
+        -haddock
+        -F -pgmF=record-dot-preprocessor
+    default-language: Haskell2010
+
+library
+    import: all
+    exposed-modules:
+        Unleash.Client,
+    other-modules:
+        Unleash.Internal.HttpClient
+        Paths_unleash_client_haskell
+    autogen-modules:
+        Paths_unleash_client_haskell
+    hs-source-dirs: src
+    build-depends:
+        aeson >= 2.0.3 && < 2.1,
+        containers >= 0.6.4 && < 0.7,
+        http-client-tls >= 0.3.6 && < 0.4,
+        http-media >= 0.8.0 && < 0.9,
+        mtl >= 2.2.2 && < 2.3,
+        servant >= 0.19.1 && < 0.20,
+        servant-client >= 0.19 && < 0.20,
+        text >= 1.2.5 && < 1.3,
+        time >= 1.9.3 && < 1.10,
+        unleash-client-haskell-core >= 0.8.9 && < 0.9
+
+executable example
+    import: all
+    main-is: Main.hs
+    hs-source-dirs: example
+    build-depends:
+        mtl,
+        servant-client,
+        text,
+        unleash-client-haskell,
+        unleash-client-haskell-core,
+        unliftio >= 0.2.23 && < 0.3
