diff --git a/.gitignore b/.gitignore
index 717ae33934d1360293488519341c3cddb19a77ac..13bb7b5923d234f0509c1b4c9d3c410210cc529f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -152,3 +152,4 @@ devenv.local.nix
 # pre-commit
 .pre-commit-config.yaml
 
+/Session.vim
diff --git a/.gitlab-ci.yaml b/.gitlab-ci.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..e88d621f210edd8fc094a72ad27c6483077ad5ab
--- /dev/null
+++ b/.gitlab-ci.yaml
@@ -0,0 +1,64 @@
+image: docker-registry.schukai.com:443/nixos-ci-devenv:latest
+
+services:
+  - docker:dind
+
+
+variables:
+  # The repo name as used in
+  # https://github.com/nix-community/NUR/blob/master/repos.json
+  NIXOS_VERSION: "23.05"
+  NIXPKGS_ALLOW_UNFREE: "1"
+  NIXPKGS_ALLOW_INSECURE: "1"
+  DOCKER_DRIVER: overlay2
+  GIT_DEPTH: 10
+
+
+stages:
+  - build
+  - deploy
+
+before_script:
+  - nix shell nixpkgs#coreutils-full -c mkdir -p /certs/client/
+  - nix shell nixpkgs#coreutils-full -c ln -fs /etc/ssl/certs/ca-bundle.crt /certs/client/ca.pem
+  - echo > .env-gitlab-ci
+  - variables=("HOME=$HOME" "CI_COMMIT_REF_NAME=$CI_COMMIT_REF_NAME" "CI_REPOSITORY_URL=$CI_REPOSITORY_URL" "GITLAB_TOKEN=$GITLAB_TOKEN" "CI_JOB_TOKEN=$CI_JOB_TOKEN" "GITLAB_USER_EMAIL=$GITLAB_USER_EMAIL" "GITLAB_USER_NAME=\"$GITLAB_USER_NAME\"" "CI_REGISTRY_USER=$CI_REGISTRY_USER" "CI_PROJECT_ID=$CI_PROJECT_ID" "CI_PROJECT_DIR=$CI_PROJECT_DIR" "CI_API_V4_URL=$CI_API_V4_URL" "CI_PROJECT_NAME=$CI_PROJECT_NAME" "CI_COMMIT_SHORT_SHA=$CI_COMMIT_SHORT_SHA"); for var in "${variables[@]}"; do echo "$var" >> .env-gitlab-ci; done
+  - cat .env-gitlab-ci
+
+after_script:
+  - if [ -f .env-gitlab-ci ]; then rm .env-gitlab-ci; fi
+
+build:
+  stage: build
+  tags:
+    - nixos
+  script:
+    - devenv shell build-app
+
+  cache:
+    - key: nixos
+      paths:
+        - /nix/store
+  
+  artifacts:
+    paths:
+      - dist
+
+deploy:
+  stage: deploy
+  tags:
+    - nixos
+  script:
+    - devenv shell -c deploy-app
+
+  when: on_success
+
+  cache:
+    - key: nixos
+      paths:
+        - /nix/store
+
+
+  artifacts:
+    paths:
+      - dist   
diff --git a/LICENSE b/LICENSE
index f335543f280632ca05a6ffee71ac9a6a8286c792..8aa3080082f117124214e2d4c96b5f14d6fbf54c 100644
--- a/LICENSE
+++ b/LICENSE
@@ -10,4 +10,5 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU Affero General Public License for more details.
 
 You should have received a copy of the GNU Affero General Public License
-along with this program.  If not, see <https://www.gnu.org/licenses/>.
\ No newline at end of file
+along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
diff --git a/README.md b/README.md
index 4333b476d3c9a5425ae3d06919977db07127cea6..c89e28fe64979f1d07a3df02f273ebe0cdc5adf1 100644
--- a/README.md
+++ b/README.md
@@ -325,7 +325,7 @@ func main() {
    var h configuration.EventHook
    h = &ChangeEventHandler{
      callback: func(event configuration.ChangeEvent) {
-      log := event.Changlog
+      log := event.Changelog
       msg = fmt.Sprintf("Change from %s to %s", log[0].From, log[0].To)
       fmt.Println(msg)
       closeChan <- true
@@ -349,6 +349,33 @@ func main() {
 
 ```
 
+### Environment variables
+
+The configuration can also be loaded from environment variables. This is useful if you want to
+define the configuration via environment variables. For example, if you want to use Docker.
+
+With the `InitFromEnv(prefix)` function, you can set the configuration from environment variables.
+You must call this function manually. The definition of the environment variables must be
+done in the following format:
+
+```go
+   config := struct {
+      Host string `env:"HOST"`
+   }{
+      Host: "localhost",
+   }
+
+   s := configuration.New(config)
+   
+   s.InitFromEnv("APP")
+   
+```
+
+In this example, the environment variable `APP_HOST` is used. The prefix is used to
+to avoid collisions with other environment variables. You can also use an empty string
+as a prefix. Then the environment variables are used directly.
+
+
 ### Error handling
 
 If an error occurs, it is returned by the function `Errors()`. The errors can be handled as usual.
diff --git a/Taskfile.yml b/Taskfile.yml
new file mode 100644
index 0000000000000000000000000000000000000000..7febe83e7b63269468299d999d7aac31118ef44f
--- /dev/null
+++ b/Taskfile.yml
@@ -0,0 +1,48 @@
+# https://taskfile.dev
+
+version: '3'
+
+tasks:
+  default:
+    cmds:
+      - task --list-all
+    silent: true
+  test:
+    desc: Execute unit tests in Go.
+    cmds:  
+      - echo "Execute unit tests in Go."
+      - go test -cover -v ./...
+
+  test-fuzz:
+    desc: Conduct fuzzing tests.#
+    cmds:  
+      - echo "Conduct fuzzing tests."
+      - go test -v -fuzztime=30s -fuzz=Fuzz ./...
+
+  add-licenses:
+    desc: Attach license headers to Go files.
+    cmds:
+      - echo "Attach license headers to Go files."
+      - addlicense -c "schukai GmbH" -s -l "AGPL-3.0" ./*.go
+    silent: true
+
+  check:
+    desc: Confirm repository status.
+    cmds:
+      - git diff-index --quiet HEAD || (echo "There are uncommitted changes after running make. Please commit or stash them before running make."; exit 1)
+    silent: true
+
+  build:
+    desc: Compile the application,
+    aliases:
+      - b
+    vars:
+      DEVENV_ROOT:
+        sh: echo "$DEVENV_ROOT"
+    
+    cmds:
+      - devenv shell build-app
+    sources:
+      - source/**/*.go
+      - source/**/*.mod
+      - dist/**      
diff --git a/api.go b/api.go
index b2a340d02c15dc67e298f13bd4e31fbe23dacefa..b8638a53270667b0f2c1232a049161bc9dfc51bc 100644
--- a/api.go
+++ b/api.go
@@ -7,7 +7,7 @@ import (
 	"github.com/imdario/mergo"
 )
 
-// NewSetting creates a new configuration setting
+// New NewSetting creates a new configuration setting
 // with the given defaults.
 func New[C any](defaults C) *Settings[C] {
 
@@ -39,7 +39,7 @@ func New[C any](defaults C) *Settings[C] {
 	return s
 }
 
-// Set the mnemonic
+// SetMnemonic Set the mnemonic
 // The mnemonic is used to identify the configuration in the configuration file
 func (s *Settings[C]) SetMnemonic(mnemonic string) *Settings[C] {
 
@@ -58,7 +58,7 @@ func (s *Settings[C]) SetMnemonic(mnemonic string) *Settings[C] {
 	return s
 }
 
-// Config() returns the configuration
+// Config returns the configuration
 // Remember that the configuration is a copy of the original configuration.
 // Changes to the configuration will not be reflected in the original configuration.
 func (s *Settings[C]) Config() C {
diff --git a/change-handler.go b/change-handler.go
index d6e66fcd0d06a074bbfabab8e1438f6c47d58aa2..721a745dcd992ff702fdbff31f0b9ce6046ee9d4 100644
--- a/change-handler.go
+++ b/change-handler.go
@@ -8,7 +8,7 @@ import (
 )
 
 type ChangeEvent struct {
-	Changlog diff.Changelog
+	Changelog diff.Changelog
 }
 
 type ChangeHook interface {
@@ -44,7 +44,7 @@ func (s *Settings[C]) RemoveOnChangeHook(hook ChangeHook) *Settings[C] {
 
 func (s *Settings[C]) notifyChangeHooks(changelog diff.Changelog) *Settings[C] {
 	for _, h := range s.hooks.change {
-		go h.Handle(ChangeEvent{Changlog: changelog})
+		go h.Handle(ChangeEvent{Changelog: changelog})
 	}
 	return s
 }
diff --git a/change.go b/change.go
index c3000e42ffdbf355b4462560201707f3ff04bf93..7027cc0b5218e8ad36fe70e8981a89782f1aab78 100644
--- a/change.go
+++ b/change.go
@@ -27,6 +27,7 @@ func (s *Settings[C]) setConfigInternal(config C, lock bool) *Settings[C] {
 	defer func() {
 
 		if len(changelog) > 0 {
+			s.notifyPostprocessingHooks(changelog)
 			s.notifyChangeHooks(changelog)
 		}
 
diff --git a/change_test.go b/change_test.go
index 6dea9f6ff847e7bd7bd13abbf4cf8d9e6f5d268d..94d9368b925dca4f20e65b46ff0eab148b2de2ff 100644
--- a/change_test.go
+++ b/change_test.go
@@ -63,7 +63,7 @@ func TestReadmeExample(t *testing.T) {
 	var h ChangeHook
 	h = &ChangeEventHandler{
 		Callback: func(event ChangeEvent) {
-			log := event.Changlog
+			log := event.Changelog
 			msg = fmt.Sprintf("Change from %s to %s", log[0].From, log[0].To)
 			// for Readme
 			//fmt.Println(msg)
@@ -91,7 +91,7 @@ func TestReadmeExample(t *testing.T) {
 
 }
 
-func TestCangeOnChange(t *testing.T) {
+func TestChangeOnChange(t *testing.T) {
 
 	defaults := ConfigStruct2{
 		A: "",
diff --git a/configuration.iml b/configuration.iml
index 2aabe1403a0edfad0e889e4ef2242811eb6dbf65..11dced775143a48129a88c078710d3bedeaaa89d 100644
--- a/configuration.iml
+++ b/configuration.iml
@@ -11,7 +11,9 @@
   </component>
   <component name="NewModuleRootManager" inherit-compiler-output="true">
     <exclude-output />
-    <content url="file://$MODULE_DIR$" />
+    <content url="file://$MODULE_DIR$">
+      <excludeFolder url="file://$MODULE_DIR$/licenses" />
+    </content>
     <orderEntry type="inheritedJdk" />
     <orderEntry type="sourceFolder" forTests="false" />
   </component>
diff --git a/devenv.lock b/devenv.lock
index 8262950770534d1c20c0fb05622cdb40a6efbc14..8993ca89c49f0009e157e0fa07774b143c214b2d 100644
--- a/devenv.lock
+++ b/devenv.lock
@@ -3,11 +3,11 @@
     "devenv": {
       "locked": {
         "dir": "src/modules",
-        "lastModified": 1690831896,
-        "narHash": "sha256-k4Cb2/Yx2kL8TFuVejwEk4R0J+5sxbydAj2IZBKZg7o=",
+        "lastModified": 1692003204,
+        "narHash": "sha256-gO2DXwXuArjpywgtRTDb3aKscWMbnI7YwFaqvV46yv0=",
         "owner": "cachix",
         "repo": "devenv",
-        "rev": "e91205acb792f6a49ea53ab04c04fbc2a85039cd",
+        "rev": "ade3ae522baf366296598e232b7b063d81740bbb",
         "type": "github"
       },
       "original": {
@@ -74,16 +74,16 @@
     },
     "nixpkgs": {
       "locked": {
-        "lastModified": 1691188534,
-        "narHash": "sha256-oXjS9GrZar+sB8j2KYzWw3dW62jYFhnjOsnO5+D+B3s=",
-        "owner": "NixOS",
+        "lastModified": 1691950488,
+        "narHash": "sha256-iUNEeudc4dGjx+HsHccnGiuZUVE/nhjXuQ1DVCsHIUY=",
+        "owner": "nixos",
         "repo": "nixpkgs",
-        "rev": "5767e7b931f2e6ee7f582d564b8665095c059f3b",
+        "rev": "720e61ed8de116eec48d6baea1d54469b536b985",
         "type": "github"
       },
       "original": {
-        "owner": "NixOS",
-        "ref": "nixpkgs-unstable",
+        "owner": "nixos",
+        "ref": "nixos-23.05",
         "repo": "nixpkgs",
         "type": "github"
       }
@@ -104,6 +104,21 @@
         "type": "github"
       }
     },
+    "nixpkgs_2": {
+      "locked": {
+        "lastModified": 1691950488,
+        "narHash": "sha256-iUNEeudc4dGjx+HsHccnGiuZUVE/nhjXuQ1DVCsHIUY=",
+        "owner": "NixOS",
+        "repo": "nixpkgs",
+        "rev": "720e61ed8de116eec48d6baea1d54469b536b985",
+        "type": "github"
+      },
+      "original": {
+        "id": "nixpkgs",
+        "ref": "nixos-23.05",
+        "type": "indirect"
+      }
+    },
     "pre-commit-hooks": {
       "inputs": {
         "flake-compat": "flake-compat",
@@ -115,11 +130,11 @@
         "nixpkgs-stable": "nixpkgs-stable"
       },
       "locked": {
-        "lastModified": 1691093055,
-        "narHash": "sha256-sjNWYpDHc6vx+/M0WbBZKltR0Avh2S43UiDbmYtfHt0=",
+        "lastModified": 1691747570,
+        "narHash": "sha256-J3fnIwJtHVQ0tK2JMBv4oAmII+1mCdXdpeCxtIsrL2A=",
         "owner": "cachix",
         "repo": "pre-commit-hooks.nix",
-        "rev": "ebb43bdacd1af8954d04869c77bc3b61fde515e4",
+        "rev": "c5ac3aa3324bd8aebe8622a3fc92eeb3975d317a",
         "type": "github"
       },
       "original": {
@@ -132,7 +147,8 @@
       "inputs": {
         "devenv": "devenv",
         "nixpkgs": "nixpkgs",
-        "pre-commit-hooks": "pre-commit-hooks"
+        "pre-commit-hooks": "pre-commit-hooks",
+        "version": "version"
       }
     },
     "systems": {
@@ -149,6 +165,24 @@
         "repo": "default",
         "type": "github"
       }
+    },
+    "version": {
+      "inputs": {
+        "nixpkgs": "nixpkgs_2"
+      },
+      "locked": {
+        "lastModified": 1690668568,
+        "narHash": "sha256-jzixQKFFW4oxO0S4GYqbkFCXzhBd6com6Z9+MtVKakU=",
+        "ref": "refs/heads/master",
+        "rev": "3838f03165b726e47d586c04a1821749375e1001",
+        "revCount": 37,
+        "type": "git",
+        "url": "https://gitlab.schukai.com/oss/utilities/version.git"
+      },
+      "original": {
+        "type": "git",
+        "url": "https://gitlab.schukai.com/oss/utilities/version.git"
+      }
     }
   },
   "root": "root",
diff --git a/devenv.nix b/devenv.nix
index 9fc608ad6cea4bad996182a6d122b5ec65e6806a..cef457e62cf8fabfd66ff7e8895df4dde7164f9c 100644
--- a/devenv.nix
+++ b/devenv.nix
@@ -1,19 +1,161 @@
-{ pkgs, ... }:
+{ pkgs, inputs, phps, lib, config, modulesPath,... }:
 
 {
-  # https://devenv.sh/basics/
+
+  env.APP_NAME = "configuration";
 
   # https://devenv.sh/packages/
-  packages = [ pkgs.git ];
+  packages = [
+    inputs.version.defaultPackage."${builtins.currentSystem}"
+    pkgs.git
+    pkgs.gcc12
+    pkgs.go-task
+    pkgs.blackbox
+    pkgs.blackbox-terminal
+    pkgs.jq
+    pkgs.delve
+    pkgs.gdlv
+    pkgs.wget
+    pkgs.glab
+    pkgs.unixtools.xxd
+    pkgs.libffi
+    pkgs.zlib
+    pkgs.procps
+    pkgs.php81Extensions.xdebug
+    pkgs.ranger
+    pkgs.meld
+    pkgs.gnused
+    pkgs.coreutils-full
+    pkgs.gnugrep
+    pkgs.gnumake
+    pkgs.util-linux
+    pkgs.httpie
+    pkgs.netcat
+    pkgs.memcached
+    pkgs.fd    
+  ];
+
 
   # https://devenv.sh/languages/
-  languages.go.enable = true;
+  # languages.nix.enable = true;
+  languages = {
+    go = { enable = true; };
+  };
+  
+   difftastic.enable = true;
+
+    
+  # This script is executed when the app is built
+  # You can use it to build the app  
+  scripts.build-app.exec =  ''
+#!${pkgs.bash}/bin/bash
+#set -euo pipefail
+set -x
+
+PATH="''${PATH}":${pkgs.coreutils}/bin
+PATH="''${PATH}":${pkgs.findutils}/bin
+PATH="''${PATH}":${pkgs.jq}/bin/
+PATH="''${PATH}":${pkgs.rsync}/bin/
+PATH="''${PATH}":${pkgs.bash}/bin/
+PATH="''${PATH}":${pkgs.curl}/bin/
+PATH="''${PATH}":${pkgs.moreutils}/bin/
+PATH="''${PATH}":${pkgs.gnutar}/bin
+PATH="''${PATH}":${pkgs.gzip}/bin/
+PATH="''${PATH}":${pkgs.procps}/bin/
+PATH="''${PATH}":${pkgs.exa}/bin/
+PATH="''${PATH}":${pkgs.git}/bin/
+PATH="''${PATH}":${pkgs.gnugrep}/bin/
+PATH="''${PATH}":${inputs.version.defaultPackage."${builtins.currentSystem}"}/bin/
+
+export -f PATH
+
+task test
+
+'';
+
+  # This scritp is used to deploy the app to the gitlab registry
+  # It is used by the gitlab-ci.yml file
+  # The environment variables are set in the gitlab project settings
+  scripts.deploy-app.exec =  ''
+#!${pkgs.bash}/bin/bash
+
+PATH="''${PATH}":${pkgs.coreutils}/bin
+PATH="''${PATH}":${pkgs.jq}/bin/
+PATH="''${PATH}":${pkgs.curl}/bin/
+PATH="''${PATH}":${pkgs.moreutils}/bin/
+PATH="''${PATH}":${pkgs.gnutar}/bin
+PATH="''${PATH}":${pkgs.gzip}/bin/
+PATH="''${PATH}":${pkgs.exa}/bin/
+PATH="''${PATH}":${pkgs.git}/bin/
+PATH="''${PATH}":${inputs.version.defaultPackage."${builtins.currentSystem}"}/bin/
+
+export PATH
+
+if [[ -f .env-gitlab-ci ]]; then
+    source .env-gitlab-ci
+    rm .env-gitlab-ci
+fi
+
+set -x
+## if $HOME not set, set it to current directory
+if [[ -z "''${HOME}" ]]; then
+    HOME=$(pwd)
+fi
+
+export HOME
+
+git config user.email "''${GITLAB_USER_EMAIL}"
+git config user.name "''${GITLAB_USER_NAME:-"Gitlab CI"}"
+git config pull.rebase true
+git config http.sslVerify "false"
+git remote set-url origin https://pad:''${GITLAB_TOKEN}@''${CI_REPOSITORY_URL#*@}
+
+git fetch --all --tags --unshallow
+git reset --hard origin/master
+git checkout $CI_COMMIT_REF_NAME
+git pull origin $CI_COMMIT_REF_NAME
+
+if [ ! -z "''${CI_PROJECT_DIR}" ]; then
+    echo "CI_PROJECT_DIR is set, using it as project root."
+    project_root=$(realpath "''${CI_PROJECT_DIR}")/
+elif [ ! -z "''${DEVENV_ROOT}" ]; then
+    echo "DEVENV_ROOT is set, using it as project root."
+    project_root=$(realpath "''${DEVENV_ROOT}")/
+else
+    echo "Error: DEVENV_ROOT or CI_PROJECT_DIR environment variables are not set."
+    exit 1  
+fi
+
+if [ ! -d "''${project_root}" ]; then
+    echo "Error: Project root directory does not seem to be valid."
+    echo "Check the DEVENV_ROOT or CI_PROJECT_DIR environment variables."
+    exit 1
+fi
+
+if [ -z "'CI_JOB_TOKEN" ]; then
+    echo "Error: CI_JOB_TOKEN variable is not set."
+    exit 1
+fi
+
+git --no-pager log --decorate=short --pretty=oneline
+
+## the version should be the same as in the build task
+if ! version auto --git --verbose
+then
+    echo "ERROR: Could not update version."
+    exit 1
+fi 
+
+git --no-pager log --decorate=short --pretty=oneline
+
+gitVersion=v$(version predict)
+
+
+git push -o ci.skip origin ''${CI_COMMIT_REF_NAME} --tags
+
+echo "done"
 
-  # https://devenv.sh/pre-commit-hooks/
-  # pre-commit.hooks.shellcheck.enable = true;
+'';
 
-  # https://devenv.sh/processes/
-  # processes.ping.exec = "ping example.com";
 
-  # See full reference at https://devenv.sh/reference/options/
 }
diff --git a/devenv.yaml b/devenv.yaml
index c7cb5cedadc04e17a1703f298db573f43c79cad8..525a6f02f172bb9108a23c335a4ced3e80f78502 100644
--- a/devenv.yaml
+++ b/devenv.yaml
@@ -1,3 +1,7 @@
 inputs:
   nixpkgs:
-    url: github:NixOS/nixpkgs/nixpkgs-unstable
+    url: github:nixos/nixpkgs/nixos-23.05
+
+  version:
+    url: git+https://gitlab.schukai.com/oss/utilities/version.git
+    flake: true
diff --git a/error-handler.go b/error-handler.go
index 5eb3752cd00c401595b2de66442a98b12ef7a699..fee0f7cd73e802faf16da24168876e1c2ffa67d0 100644
--- a/error-handler.go
+++ b/error-handler.go
@@ -10,13 +10,13 @@ type ErrorHook interface {
 	Handle(event ErrorEvent)
 }
 
-// OnChange registers a hook that is called when the configuration changes.
+// OnError registers a hook that is called when the configuration changes.
 func (s *Settings[C]) OnError(hook ErrorHook) *Settings[C] {
 	s.hooks.error = append(s.hooks.error, hook)
 	return s
 }
 
-// HasOnChangeHook returns true if there are registered hooks.
+// HasOnErrorHook returns true if there are registered hooks.
 func (s *Settings[C]) HasOnErrorHook(hook ErrorHook) *Settings[C] {
 	for _, h := range s.hooks.error {
 		if h == hook {
@@ -26,7 +26,7 @@ func (s *Settings[C]) HasOnErrorHook(hook ErrorHook) *Settings[C] {
 	return s
 }
 
-// RemoveOnChangeHook removes a change hook from the list of hooks.
+// RemoveOnErrorHook removes a change hook from the list of hooks.
 func (s *Settings[C]) RemoveOnErrorHook(hook ErrorHook) *Settings[C] {
 	for i, h := range s.hooks.error {
 		if h == hook {
diff --git a/error-handler_test.go b/error-handler_test.go
index 88bb7980cf6b3d83fefcb07baaa95e64dc66df1e..4b63e8979a385abde61733a50be920d1f1778728 100644
--- a/error-handler_test.go
+++ b/error-handler_test.go
@@ -59,13 +59,13 @@ func TestAddRemoveErrorHook(t *testing.T) {
 	s.OnError(h)
 
 	if len(s.hooks.error) != 1 {
-		t.Error("Expected 1 got ", len(s.hooks.change))
+		t.Error("Expected 1 got ", len(s.hooks.error))
 	}
 
 	s.RemoveOnErrorHook(h)
 
 	if len(s.hooks.error) != 0 {
-		t.Error("Expected 0 got ", len(s.hooks.change))
+		t.Error("Expected 0 got ", len(s.hooks.error))
 	}
 
 }
diff --git a/error.go b/error.go
index cb7d68877c2d45c357557ccf42218c73a2ba3177..4ee39a6b7cb8e19fe700acfed43b7376df4d9526 100644
--- a/error.go
+++ b/error.go
@@ -69,14 +69,14 @@ func newUnsupportedTypeError(t reflect.Type) UnsupportedTypeError {
 	return UnsupportedTypeError(errors.New("type " + t.String() + " is not supported"))
 }
 
-// At the reflect level, some types are not supported
+// UnsupportedReflectKindError At the reflect level, some types are not supported
 type UnsupportedReflectKindError error
 
 func newUnsupportedReflectKindError(t reflect.Type) UnsupportedReflectKindError {
 	return UnsupportedReflectKindError(errors.New("type " + t.String() + " is not supported"))
 }
 
-// This error indicates that the flag is not found
+// FlagNotFoundError This error indicates that the flag is not found
 type FlagNotFoundError error
 
 func newFlagNotFoundError(name string) FlagNotFoundError {
diff --git a/file.go b/file.go
index 297efb2d84ca833750d81283c9cc5e6467fdf67e..b934f0e0edf4d28fc150c1190986f0aa43ea3f92 100644
--- a/file.go
+++ b/file.go
@@ -34,7 +34,7 @@ func (s *Settings[c]) HasFile(file string) bool {
 	return false
 }
 
-// AddFiles adds a file to the list of files to import
+// AddFile adds a file to the list of files to import
 func (s *Settings[C]) AddFile(file string, format ...Format) *Settings[C] {
 
 	var f Format
@@ -96,12 +96,12 @@ func initFileBackend(files *fileBackend) {
 	files.fs = fs.FS(internalFS{})
 }
 
-// Path returns the configuration directory
+// Directories returns the list of directories to search for configuration files
 func (s *Settings[C]) Directories() []string {
 	return s.files.directories
 }
 
-// AddPath adds a directory to the configuration directory
+// AddDirectory adds a directory to the list of directories to search for configuration files
 func (s *Settings[C]) AddDirectory(d string) *Settings[C] {
 	s.Lock()
 	defer s.Unlock()
@@ -148,7 +148,7 @@ func (s *Settings[C]) sanitizeDirectories() {
 
 }
 
-// Set all configuration directories
+// SetDirectories sets the list of directories to search for configuration files
 func (s *Settings[C]) SetDirectories(d []string) *Settings[C] {
 	s.Lock()
 	defer s.Unlock()
@@ -158,7 +158,7 @@ func (s *Settings[C]) SetDirectories(d []string) *Settings[C] {
 	return s
 }
 
-// Add the current working directory to the configuration directory
+// AddWorkingDirectory adds the current working directory to the list of directories to search for configuration files
 func (s *Settings[C]) AddWorkingDirectory() *Settings[C] {
 	s.Lock()
 	defer s.Unlock()
@@ -181,7 +181,7 @@ func (s *Settings[C]) AddWorkingDirectory() *Settings[C] {
 	return s
 }
 
-// Add the Unix etc directory to the configuration directory
+// AddEtcDirectory adds the /etc directory to the list of directories to search for configuration files
 func (s *Settings[C]) AddEtcDirectory() *Settings[C] {
 	s.Lock()
 	defer s.Unlock()
@@ -211,7 +211,7 @@ func (s *Settings[C]) AddEtcDirectory() *Settings[C] {
 	return s
 }
 
-// Add the user configuration directory to the configuration directory
+// AddUserConfigDirectory Add the user configuration directory to the configuration directory
 // The mnemonic must be set for this function
 func (s *Settings[C]) AddUserConfigDirectory() *Settings[C] {
 
@@ -242,7 +242,7 @@ func (s *Settings[C]) AddUserConfigDirectory() *Settings[C] {
 	return s
 }
 
-// Add the current working directory, the user configuration directory
+// SetDefaultDirectories Add the current working directory, the user configuration directory
 // and the Unix etc directory to the configuration directory
 func (s *Settings[C]) SetDefaultDirectories() *Settings[C] {
 	s.AddWorkingDirectory().
@@ -269,7 +269,7 @@ func (s *Settings[C]) SetFileFormat(format Format) *Settings[C] {
 	return s
 }
 
-// Set the file name without extension
+// SetFileName sets the name of the configuration file
 func (s *Settings[C]) SetFileName(name string) *Settings[C] {
 
 	errorCount := len(s.errors)
diff --git a/go.mod b/go.mod
index 768bc9d1b4f619cbeac1cb70a6023903370347a6..a4e546ef7d838c02d31431e9e257a0d2c12aa29a 100644
--- a/go.mod
+++ b/go.mod
@@ -1,6 +1,6 @@
 module gitlab.schukai.com/oss/libraries/go/application/configuration
 
-go 1.19
+go 1.20
 
 require (
 	github.com/fsnotify/fsnotify v1.6.0
@@ -13,7 +13,7 @@ require (
 	gitlab.schukai.com/oss/libraries/go/application/xflags v1.9.0
 	gitlab.schukai.com/oss/libraries/go/network/http-negotiation v1.3.0
 	gitlab.schukai.com/oss/libraries/go/utilities/pathfinder v0.5.2
-	golang.org/x/exp v0.0.0-20230801115018-d63ba01acd4b
+	golang.org/x/exp v0.0.0-20230811145659-89c5cff77bcb
 	gopkg.in/yaml.v3 v3.0.1
 )
 
diff --git a/go.sum b/go.sum
index d5905a812c8c674d31139b2be43b572c6b24b3f3..2bcbd7a068e92963f98bde693dbc487753999a49 100644
--- a/go.sum
+++ b/go.sum
@@ -71,6 +71,10 @@ golang.org/x/exp v0.0.0-20230307190834-24139beb5833 h1:SChBja7BCQewoTAU7IgvucQKM
 golang.org/x/exp v0.0.0-20230307190834-24139beb5833/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
 golang.org/x/exp v0.0.0-20230801115018-d63ba01acd4b h1:r+vk0EmXNmekl0S0BascoeeoHk/L7wmaW2QF90K+kYI=
 golang.org/x/exp v0.0.0-20230801115018-d63ba01acd4b/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc=
+golang.org/x/exp v0.0.0-20230810033253-352e893a4cad h1:g0bG7Z4uG+OgH2QDODnjp6ggkk1bJDsINcuWmJN1iJU=
+golang.org/x/exp v0.0.0-20230810033253-352e893a4cad/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc=
+golang.org/x/exp v0.0.0-20230811145659-89c5cff77bcb h1:mIKbk8weKhSeLH2GmUTrvx8CjkyJmnU1wFmg59CUjFA=
+golang.org/x/exp v0.0.0-20230811145659-89c5cff77bcb/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc=
 golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
 golang.org/x/net v0.0.0-20221014081412-f15817d10f9b h1:tvrvnPFcdzp294diPnrdZZZ8XUt2Tyj7svb7X52iDuU=
 golang.org/x/net v0.0.0-20221014081412-f15817d10f9b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
diff --git a/handler.go b/handler.go
index b5d5a1f813d8ea10b64ea6fba5b8037843cc3cb0..3c6937f3e05c99ae9a4ee9141e35e6c80fe22d38 100644
--- a/handler.go
+++ b/handler.go
@@ -18,3 +18,11 @@ type ErrorEventHandler struct {
 func (c *ErrorEventHandler) Handle(event ErrorEvent) {
 	c.Callback(event)
 }
+
+type PostprocessingHandler struct {
+	Callback func(event PostprocessingEvent)
+}
+
+func (c *PostprocessingHandler) Handle(event PostprocessingEvent) {
+	c.Callback(event)
+}
diff --git a/postprocessing-handler.go b/postprocessing-handler.go
new file mode 100644
index 0000000000000000000000000000000000000000..912750a64c4efb81861c23b8af05996c2c7a612f
--- /dev/null
+++ b/postprocessing-handler.go
@@ -0,0 +1,50 @@
+// Copyright 2022 schukai GmbH
+// SPDX-License-Identifier: AGPL-3.0
+
+package configuration
+
+import (
+	"github.com/r3labs/diff/v3"
+)
+
+type PostprocessingEvent struct {
+	Changelog diff.Changelog
+}
+
+type PostprocessingHook interface {
+	Handle(event PostprocessingEvent)
+}
+
+// OnPostprocessing registers a hook that is called when the configuration changes.
+func (s *Settings[C]) OnPostprocessing(hook PostprocessingHook) *Settings[C] {
+	s.hooks.postprocessing = append(s.hooks.postprocessing, hook)
+	return s
+}
+
+// HasOnPostprocessingHook returns true if there are registered hooks.
+func (s *Settings[C]) HasOnPostprocessingHook(hook PostprocessingHook) *Settings[C] {
+	for _, h := range s.hooks.postprocessing {
+		if h == hook {
+			break
+		}
+	}
+	return s
+}
+
+// RemoveOnPostprocessingHook removes a change hook from the list of hooks.
+func (s *Settings[C]) RemoveOnPostprocessingHook(hook PostprocessingHook) *Settings[C] {
+	for i, h := range s.hooks.postprocessing {
+		if h == hook {
+			s.hooks.postprocessing = append(s.hooks.postprocessing[:i], s.hooks.postprocessing[i+1:]...)
+			break
+		}
+	}
+	return s
+}
+
+func (s *Settings[C]) notifyPostprocessingHooks(changelog diff.Changelog) *Settings[C] {
+	for _, h := range s.hooks.postprocessing {
+		h.Handle(PostprocessingEvent{Changelog: changelog})
+	}
+	return s
+}
diff --git a/postprocessing-handler_test.go b/postprocessing-handler_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..86e87e1e01e368f6246cd7c8464625091b4102ab
--- /dev/null
+++ b/postprocessing-handler_test.go
@@ -0,0 +1,95 @@
+// Copyright 2022 schukai GmbH
+// SPDX-License-Identifier: AGPL-3.0
+
+package configuration
+
+import (
+	"bytes"
+	"io"
+	"testing"
+)
+
+type mockTestPostprocessingEventHandler struct {
+	ChangeHook
+}
+
+func (m *mockTestPostprocessingEventHandler) Handle(event PostprocessingEvent) {
+	// do nothing
+
+}
+
+func TestAddRemovePostprocessingHook(t *testing.T) {
+
+	config := struct {
+		Host string
+	}{
+		Host: "localhost",
+	}
+
+	s := New(config)
+
+	var h PostprocessingHook
+	h = &mockTestPostprocessingEventHandler{}
+	s.OnPostprocessing(h)
+
+	if len(s.hooks.postprocessing) != 1 {
+		t.Error("Expected 1 got ", len(s.hooks.postprocessing))
+	}
+
+	s.RemoveOnPostprocessingHook(h)
+
+	if len(s.hooks.postprocessing) != 0 {
+		t.Error("Expected 0 got ", len(s.hooks.postprocessing))
+	}
+
+}
+
+func TestPostprocessingOnChange(t *testing.T) {
+
+	defaults := ConfigStruct2{
+		A: "",
+		B: false,
+		C: nil,
+		D: nil,
+		F: 0,
+		G: "",
+	}
+
+	s := New(defaults)
+	s.SetMnemonic("test")
+
+	var buf bytes.Buffer
+	buf.WriteString("A: a\n")
+	r := (io.Reader)(&buf)
+
+	s.AddReader(r, Yaml)
+
+	s.Import()
+
+	if s.HasErrors() {
+		t.Error("Expected not error", s.Errors())
+	}
+
+	c := s.Config()
+	if c.A != "a" {
+		t.Error("Expected \"a\" got ", c)
+	}
+
+	counter := 0
+	var p PostprocessingHook
+	p = &PostprocessingHandler{
+		Callback: func(event PostprocessingEvent) {
+			counter++
+		},
+	}
+
+	s.OnPostprocessing(p)
+
+	c.A = "x"
+	s.SetConfig(c)
+
+	if counter != 1 {
+		t.Error("Expected 1 got ", counter)
+	}
+
+}
diff --git a/settings.go b/settings.go
index 62a9d095cb54c61a7ab64a79c3ffbcf49058004a..a76b270811f4a5866a7acdafeddf92955b07c370 100644
--- a/settings.go
+++ b/settings.go
@@ -4,10 +4,11 @@
 package configuration
 
 import (
-	"github.com/fsnotify/fsnotify"
 	"reflect"
 	"strconv"
 	"sync"
+
+	"github.com/fsnotify/fsnotify"
 )
 
 type fileWatch struct {
@@ -28,8 +29,9 @@ type Settings[C any] struct {
 	mnemonic      string
 	importCounter int
 	hooks         struct {
-		change []ChangeHook
-		error  []ErrorHook
+		change         []ChangeHook
+		error          []ErrorHook
+		postprocessing []PostprocessingHook
 	}
 
 	fileWatch fileWatch
diff --git a/stream.go b/stream.go
index 4ad5621aa39a7ebabf1dc6596ae407036cd2ae37..7cde59c86e0c0cc914a9209bb65b7cccc9798b73 100644
--- a/stream.go
+++ b/stream.go
@@ -10,35 +10,13 @@ type reader struct {
 	reader io.Reader
 }
 
-//type writer struct {
-//	format Format
-//	reader io.Writer
-//}
-
 type streamBackend struct {
 	readers []reader
-	//writers []writer
 }
 
-//type StreamOption struct {
-//}
-
-//func (s *Settings[C]) AddStream(stream io.ReadWriter, format Format) *Settings[C] {
-//	return s.
-//		AddReader(stream, format)
-//		//AddWriter(stream, format)
-//}
-
 func (s *Settings[C]) AddReader(r io.Reader, format Format) *Settings[C] {
 	s.Lock()
 	defer s.Unlock()
 	s.stream.readers = append(s.stream.readers, reader{format, r})
 	return s
 }
-
-//func (s *Settings[C]) AddWriter(w io.Writer, format Format) *Settings[C] {
-//	s.Lock()
-//	defer s.Unlock()
-//	s.stream.writers = append(s.stream.writers, writer{format, w})
-//	return s
-//}
diff --git a/stream_test.go b/stream_test.go
index f6fe7243c3884c4404147d410074a4b9eb875c95..da9e215744273dbbd7974a82d12d8adf5d154267 100644
--- a/stream_test.go
+++ b/stream_test.go
@@ -8,7 +8,7 @@ import (
 	"io"
 	"testing"
 )
-
+ 
 func TestAddReader(t *testing.T) {
 	var config ConfigStruct2
 	s := New(&config)
diff --git a/watch_test.go b/watch_test.go
index 494970bf736c336d8dfee0437b459cffd451634c..91e4bfff5a795f4af7bbe6d6d9fec939214060cf 100644
--- a/watch_test.go
+++ b/watch_test.go
@@ -73,8 +73,8 @@ func TestMultiChange(t *testing.T) {
 	var h ChangeHook
 	h = &ChangeEventHandler{
 		Callback: func(event ChangeEvent) {
-			result = append(result, event.Changlog[0].To.(string))
-			signal <- event.Changlog[0].To.(string)
+			result = append(result, event.Changelog[0].To.(string))
+			signal <- event.Changelog[0].To.(string)
 		},
 	}
 
@@ -167,8 +167,8 @@ func TestWatch(t *testing.T) {
 	var h ChangeHook
 	h = &ChangeEventHandler{
 		Callback: func(event ChangeEvent) {
-			assert.Equal(t, event.Changlog[0].From, "localhost")
-			assert.Equal(t, event.Changlog[0].To, "example.org")
+			assert.Equal(t, event.Changelog[0].From, "localhost")
+			assert.Equal(t, event.Changelog[0].To, "example.org")
 			signal <- true
 		},
 	}