diff --git a/README.md b/README.md
index d2ea50a598bbed4ca82c6f68b2041b92a00ebb96..d423a7ca100716ec5fd2f4227c7ed755bd0f31a0 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,34 @@
 # OSIv2-Postfach-Anbindung für OZG-Cloud-Nachrichten
 
 Anbindung des OSIv2-Postfachs für die OZG-Cloud.
+
+
+
+## Client-Authentifizierung beim Servicekonto
+
+Die Client-Authentifizierung beim Authentication-Server (Servicekonto) erfolgt über den OAuth2-Client-Credentials-Flow (siehe [RFC 6749, Sec. 1.3.4](https://www.rfc-editor.org/rfc/rfc6749#section-1.3.4))
+mit `client_id` und `client_secret` in Verbindung mit einem Resource-URI-Parameter (siehe [RFC 8707](https://datatracker.ietf.org/doc/html/rfc8707)), der den Zugriff des Clients auf den Resource-Server (Postfach-Facade) einschränkt. 
+
+Der Resource-Server liest die Resource-URI aus dem `aud`-Claim (siehe [RFC 9068, Sec. 3](https://datatracker.ietf.org/doc/html/rfc9068#section-3)).
+
+### Beispiel:
+
+```bash
+curl -v --output auth_response.json \
+     -H "Content-Type: application/x-www-form-urlencoded" \
+     --data-urlencode "grant_type=client_credentials" \
+     --data-urlencode "client_id=OZG-Kopfstelle" \
+     --data-urlencode "client_secret=${SH_STAGE_CLIENT_SECRET}" \
+     --data-urlencode "scope=default access_urn:dataport:osi:sh:stage:ozgkopfstelle" \
+     --data-urlencode "resource=urn:dataport:osi:postfach:rz2:stage:sh" \
+     https://idp.serviceportal-stage.schleswig-holstein.de/webidp2/connect/token
+```
+
+**Beobachtungen:**
+- <small>Mit einem ungültigen `resource`-Parameter kommt ein `invalid_target`-Fehler bei der Token-Erstellung.</small>
+- <small>Ohne `resource`-Parameter (d.h. ohne `aud`-Claim) kommt `401 Unauthorized` von der Postfach-Facade.</small>
+- <small>Ohne `default`-Scope kommt ein `invalid_target`-Fehler bei der Token-Erstellung</small>
+- <small>Ohne `access_urn:dataport:osi:sh:stage:ozgkopfstelle` kommt ein `Internal Server Error 500` von der Postfach-Facade.</small>
+
+
+
diff --git a/pom.xml b/pom.xml
index b8874701f0eb9377c132afa254d160877636b402..e856dcd920bbca1893a1f8fe7ebf3704ec1a03b9 100644
--- a/pom.xml
+++ b/pom.xml
@@ -6,7 +6,7 @@
 	<parent>
 		<groupId>de.ozgcloud.common</groupId>
 		<artifactId>ozgcloud-common-parent</artifactId>
-		<version>4.0.1</version>
+		<version>4.6.0</version>
 	</parent>
 
 	<groupId>de.ozgcloud.osiv2</groupId>
@@ -17,9 +17,8 @@
 	<description>OSIv2-Postfach-Anbindung für OZG-Cloud-Nachrichten</description>
 
 	<properties>
-		<api-lib.version>0.13.0</api-lib.version>
+		<api-lib.version>0.14.0</api-lib.version>
 		<nachrichten-manager.version>2.14.0</nachrichten-manager.version>
-		<testcontainers-keycloak.version>3.2.0</testcontainers-keycloak.version>
 		<mockserver-client.version>5.15.0</mockserver-client.version>
 	</properties>
 	<dependencies>
@@ -61,23 +60,12 @@
 			<type>test-jar</type>
 			<scope>test</scope>
 		</dependency>
-		<dependency>
-			<groupId>com.github.dasniko</groupId>
-			<artifactId>testcontainers-keycloak</artifactId>
-			<version>${testcontainers-keycloak.version}</version>
-			<scope>test</scope>
-		</dependency>
 		<dependency>
 			<groupId>org.testcontainers</groupId>
 			<artifactId>testcontainers</artifactId>
 			<version>${testcontainers.version}</version>
 			<scope>test</scope>
 		</dependency>
-		<dependency>
-			<groupId>org.junit.jupiter</groupId>
-			<artifactId>junit-jupiter-engine</artifactId>
-			<scope>test</scope>
-		</dependency>
 		<dependency>
 			<groupId>org.mock-server</groupId>
 			<artifactId>mockserver-client-java</artifactId>
@@ -90,13 +78,6 @@
 			<scope>test</scope>
 		</dependency>
 
-		<dependency>
-			<groupId>org.springframework.cloud</groupId>
-			<artifactId>spring-cloud-starter-contract-stub-runner</artifactId>
-			<version>4.1.2</version>
-			<scope>test</scope>
-		</dependency>
-
 		<!-- commons -->
 		<dependency>
 			<groupId>org.apache.commons</groupId>
diff --git a/src/main/java/de/ozgcloud/nachrichten/postfach/osiv2/OsiPostfachRemoteService.java b/src/main/java/de/ozgcloud/nachrichten/postfach/osiv2/OsiPostfachRemoteService.java
index 3e7704cbece229f55cc21654f4f5eba368d8ac1c..c26429d8a9fb9e1812f92fa4b96ede47c3d0e280 100644
--- a/src/main/java/de/ozgcloud/nachrichten/postfach/osiv2/OsiPostfachRemoteService.java
+++ b/src/main/java/de/ozgcloud/nachrichten/postfach/osiv2/OsiPostfachRemoteService.java
@@ -3,13 +3,17 @@ package de.ozgcloud.nachrichten.postfach.osiv2;
 import java.util.stream.Stream;
 
 import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
 import org.springframework.stereotype.Service;
 import org.springframework.web.reactive.function.client.WebClient;
 
 import de.ozgcloud.nachrichten.postfach.PostfachNachricht;
 import de.ozgcloud.nachrichten.postfach.PostfachRemoteService;
+import lombok.extern.log4j.Log4j2;
 
 @Service
+@ConditionalOnProperty("ozgcloud.osiv2-postfach.enabled")
+@Log4j2
 public record OsiPostfachRemoteService(
 		@Qualifier("osi2PostfachWebClient") WebClient webClient
 ) implements PostfachRemoteService {
@@ -18,9 +22,10 @@ public record OsiPostfachRemoteService(
 	@Override
 	public void sendMessage(PostfachNachricht nachricht) {
 		webClient.get()
-				.uri("/dummy")
+				.uri("/_metrics")
 				.retrieve()
 				.bodyToMono(String.class)
+				.doOnNext(metricsString -> LOG.info("Metrics: {}", metricsString))
 				.block();
 		// TODO
 	}
diff --git a/src/main/java/de/ozgcloud/nachrichten/postfach/osiv2/config/OsiPostfachProperties.java b/src/main/java/de/ozgcloud/nachrichten/postfach/osiv2/config/OsiPostfachProperties.java
new file mode 100644
index 0000000000000000000000000000000000000000..44fd8ef163bbfae627be0353f5346af4a4944dbc
--- /dev/null
+++ b/src/main/java/de/ozgcloud/nachrichten/postfach/osiv2/config/OsiPostfachProperties.java
@@ -0,0 +1,55 @@
+package de.ozgcloud.nachrichten.postfach.osiv2.config;
+
+import jakarta.annotation.Nullable;
+
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.context.annotation.Configuration;
+
+import lombok.Getter;
+import lombok.Setter;
+
+@Getter
+@Setter
+@Configuration
+@ConfigurationProperties(prefix = OsiPostfachProperties.PREFIX)
+public class OsiPostfachProperties {
+
+	static final String PREFIX = "ozgcloud.osiv2-postfach";
+
+	private boolean enabled;
+
+	@Getter
+	@Setter
+	@Configuration
+	@ConfigurationProperties(prefix = ApiConfiguration.PREFIX)
+	static class ApiConfiguration {
+		static final String PREFIX = OsiPostfachProperties.PREFIX + ".api";
+
+		private String resource;
+		private String url;
+		private String tenant;
+		private String nameIdentifier;
+	}
+
+	/**
+	 * HTTP proxy configuration. To use basic HTTP authentication configure username and password. (See <a
+	 * href="https://github.com/reactor/reactor-netty/blob/3b74fcf2f5b2bdd129636e9c55608f0187ef7bd4/reactor-netty-core/src/main/java/reactor/netty/transport/ProxyProvider.java#L142">ProxyProvider</a>)
+	 */
+	@Getter
+	@Setter
+	@Configuration
+	@ConfigurationProperties(prefix = ProxyConfiguration.PREFIX)
+	static class ProxyConfiguration {
+		static final String PREFIX = OsiPostfachProperties.PREFIX + ".http-proxy";
+
+		private boolean enabled;
+
+		private String host;
+		private Integer port;
+
+		@Nullable
+		private String username = null;
+		@Nullable
+		private String password = null;
+	}
+}
diff --git a/src/main/java/de/ozgcloud/nachrichten/postfach/osiv2/config/WebClientConfiguration.java b/src/main/java/de/ozgcloud/nachrichten/postfach/osiv2/config/WebClientConfiguration.java
index 454f52e0b89cf51700af6a0ed6a723972c1958e7..7faa48b12d86fae1d3e8dd0c6c8463d699e0b169 100644
--- a/src/main/java/de/ozgcloud/nachrichten/postfach/osiv2/config/WebClientConfiguration.java
+++ b/src/main/java/de/ozgcloud/nachrichten/postfach/osiv2/config/WebClientConfiguration.java
@@ -1,37 +1,56 @@
 package de.ozgcloud.nachrichten.postfach.osiv2.config;
 
-import java.util.Objects;
-
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
-import org.springframework.context.annotation.Primary;
-import org.springframework.core.env.Environment;
+import org.springframework.http.client.reactive.ReactorClientHttpConnector;
 import org.springframework.security.oauth2.client.AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager;
 import org.springframework.security.oauth2.client.InMemoryReactiveOAuth2AuthorizedClientService;
+import org.springframework.security.oauth2.client.ReactiveOAuth2AuthorizedClientProvider;
 import org.springframework.security.oauth2.client.ReactiveOAuth2AuthorizedClientProviderBuilder;
+import org.springframework.security.oauth2.client.endpoint.WebClientReactiveClientCredentialsTokenResponseClient;
 import org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository;
 import org.springframework.security.oauth2.client.web.reactive.function.client.ServerOAuth2AuthorizedClientExchangeFilterFunction;
+import org.springframework.util.LinkedMultiValueMap;
+import org.springframework.util.MultiValueMap;
 import org.springframework.web.reactive.function.client.WebClient;
 
+import lombok.RequiredArgsConstructor;
+import reactor.netty.http.client.HttpClient;
+import reactor.netty.transport.ProxyProvider;
+
 @Configuration
+@RequiredArgsConstructor
+@ConditionalOnProperty("ozgcloud.osiv2-postfach.enabled")
 public class WebClientConfiguration {
 
+	private final OsiPostfachProperties.ApiConfiguration apiConfiguration;
+	private final OsiPostfachProperties.ProxyConfiguration proxyConfiguration;
+
 	@Bean("osi2PostfachWebClient")
 	public WebClient osi2PostfachWebClient(
-			ServerOAuth2AuthorizedClientExchangeFilterFunction serverOAuth2AuthorizedClientExchangeFilterFunction,
-			Environment environment) {
-		var url = Objects.requireNonNull(
-				environment.getProperty("ozgcloud.osiv2-postfach.api.url"),
-				"ozgcloud.osiv2-postfach.api.url is not set");
+			ReactiveClientRegistrationRepository clientRegistrations) {
 		return WebClient.builder()
-				.baseUrl(url)
-				.filter(serverOAuth2AuthorizedClientExchangeFilterFunction)
+				.baseUrl(apiConfiguration.getUrl())
+				.clientConnector(new ReactorClientHttpConnector(httpClient()))
+				.filter(serverOAuth2AuthorizedClientExchangeFilterFunction(clientRegistrations))
 				.build();
 	}
 
-	@Bean
-	@Primary
-	ServerOAuth2AuthorizedClientExchangeFilterFunction serverOAuth2AuthorizedClientExchangeFilterFunction(
+	@SuppressWarnings("ConstantConditions")
+	private HttpClient httpClient() {
+		var webClient = HttpClient.create();
+		return proxyConfiguration.isEnabled() ? webClient
+				.proxy(proxy -> proxy
+						.type(ProxyProvider.Proxy.HTTP)
+						.host(proxyConfiguration.getHost())
+						.port(proxyConfiguration.getPort())
+						.username(proxyConfiguration.getUsername())
+						.password(username -> proxyConfiguration.getPassword())
+				) : webClient;
+	}
+
+	private ServerOAuth2AuthorizedClientExchangeFilterFunction serverOAuth2AuthorizedClientExchangeFilterFunction(
 			ReactiveClientRegistrationRepository clientRegistrations) {
 
 		var oauth = new ServerOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager(clientRegistrations));
@@ -39,19 +58,46 @@ public class WebClientConfiguration {
 		return oauth;
 	}
 
-	AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager authorizedClientManager(
+	private AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager authorizedClientManager(
 			ReactiveClientRegistrationRepository clientRegistrations) {
 		var clientService = new InMemoryReactiveOAuth2AuthorizedClientService(
 				clientRegistrations);
 		var authorizedClientManager = new AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager(
 				clientRegistrations, clientService);
 
-		authorizedClientManager.setAuthorizedClientProvider(
-				ReactiveOAuth2AuthorizedClientProviderBuilder.builder()
-						.clientCredentials()
-						.build());
+		authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider());
 
 		return authorizedClientManager;
 	}
 
+	private ReactiveOAuth2AuthorizedClientProvider authorizedClientProvider() {
+		return ReactiveOAuth2AuthorizedClientProviderBuilder.builder()
+				.clientCredentials(builder ->
+						builder.accessTokenResponseClient(clientCredentialsTokenResponseClient())
+				)
+				.build();
+	}
+
+	private WebClientReactiveClientCredentialsTokenResponseClient clientCredentialsTokenResponseClient() {
+		var client = new WebClientReactiveClientCredentialsTokenResponseClient();
+		configureHttpClientForTokenRequests(client);
+		configureParametersForTokenRequests(client);
+		return client;
+	}
+
+	private void configureHttpClientForTokenRequests(WebClientReactiveClientCredentialsTokenResponseClient client) {
+		client.setWebClient(WebClient.builder()
+				.clientConnector(new ReactorClientHttpConnector(httpClient()))
+				.build());
+	}
+
+	private void configureParametersForTokenRequests(WebClientReactiveClientCredentialsTokenResponseClient client) {
+		client.addParametersConverter(source -> {
+			MultiValueMap<String, String> parameters = new LinkedMultiValueMap<>();
+			// Pass a resource indicator parameter https://datatracker.ietf.org/doc/html/rfc8707
+			parameters.add("resource", apiConfiguration.getResource());
+			return parameters;
+		});
+	}
+
 }
diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml
index a93d8dad377c3600a25688a2d493602a3e1ba5d8..172d0ad8a7a1e127b094913f30707cc9e805b69e 100644
--- a/src/main/resources/application.yml
+++ b/src/main/resources/application.yml
@@ -10,12 +10,21 @@ spring:
           osi2:
             client-id: 'OZG-Kopfstelle'
             client-secret: 'changeme'
-            scope: default, access_urn:some:scope:for:ozgkopfstelle
+            scope: default, access_urn:dataport:osi:sh:stage:ozgkopfstelle
             authorization-grant-type: 'client_credentials'
+            client-authentication-method: client_secret_post
         provider:
           osi2:
-            token-uri: http://localhost:8080/realms/master/protocol/openid-connect/token
+            token-uri: 'https://idp.serviceportal-stage.schleswig-holstein.de/webidp2/connect/token'
 ozgcloud:
   osiv2-postfach:
+    enabled: true
     api:
-      url: 'replaceme'
+      resource: 'urn:dataport:osi:postfach:rz2:stage:sh'
+      url: 'https://api-gateway-stage.dataport.de:443/api/osi_postfach/1.0.0'
+      tenant: 'SH'
+      name-identifier: 'ozgkopfstelle'
+    http-proxy:
+      enabled: true
+      host: 127.0.0.1
+      port: 3128
diff --git a/src/test/java/de/ozgcloud/nachrichten/postfach/osiv2/OsiPostfachRemoteServiceITCase.java b/src/test/java/de/ozgcloud/nachrichten/postfach/osiv2/OsiPostfachRemoteServiceITCase.java
index 4633ec32a3c03cf9491201380ffdf23ef4e73736..c1f97ce6707679c77aa262b661607912dd62c457 100644
--- a/src/test/java/de/ozgcloud/nachrichten/postfach/osiv2/OsiPostfachRemoteServiceITCase.java
+++ b/src/test/java/de/ozgcloud/nachrichten/postfach/osiv2/OsiPostfachRemoteServiceITCase.java
@@ -1,6 +1,8 @@
 package de.ozgcloud.nachrichten.postfach.osiv2;
 
+import static de.ozgcloud.nachrichten.postfach.osiv2.factory.JwtFactory.*;
 import static org.assertj.core.api.Assertions.*;
+import static org.mockserver.matchers.Times.*;
 import static org.mockserver.model.HttpRequest.*;
 import static org.mockserver.model.HttpResponse.*;
 
@@ -10,17 +12,23 @@ import org.junit.jupiter.api.Nested;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.extension.RegisterExtension;
 import org.mockserver.client.MockServerClient;
-import org.mockserver.matchers.Times;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.context.ActiveProfiles;
 import org.springframework.test.context.DynamicPropertyRegistry;
 import org.springframework.test.context.DynamicPropertySource;
+import org.springframework.test.context.TestPropertySource;
 
 import de.ozgcloud.nachrichten.postfach.PostfachNachricht;
-import de.ozgcloud.nachrichten.postfach.osiv2.extension.JwtParser;
+import de.ozgcloud.nachrichten.postfach.osiv2.extension.Jwt;
 import de.ozgcloud.nachrichten.postfach.osiv2.extension.OsiMockServerExtension;
+import lombok.SneakyThrows;
 
 @SpringBootTest(classes = TestApplication.class, webEnvironment = SpringBootTest.WebEnvironment.NONE)
+@ActiveProfiles("itcase")
+@TestPropertySource(properties = {
+		"ozgcloud.osiv2-postfach.http-proxy.enabled=false",
+})
 public class OsiPostfachRemoteServiceITCase {
 
 	@RegisterExtension
@@ -36,46 +44,53 @@ public class OsiPostfachRemoteServiceITCase {
 
 	@DynamicPropertySource
 	static void dynamicProperties(DynamicPropertyRegistry registry) {
-		registry.add("spring.security.oauth2.client.provider.osi2.token-uri", OSI_MOCK_SERVER_EXTENSION::getTokenUri);
-		registry.add("ozgcloud.osiv2-postfach.api.url", OSI_MOCK_SERVER_EXTENSION::getPostfachMockServerUrl);
+		registry.add("spring.security.oauth2.client.provider.osi2.token-uri", OSI_MOCK_SERVER_EXTENSION::getAccessTokenUrl);
+		registry.add("spring.security.oauth2.client.registration.osi2.scope", () -> CLIENT_SCOPES);
+		registry.add("spring.security.oauth2.client.registration.osi2.client-id", () -> CLIENT_ID);
+		registry.add("ozgcloud.osiv2-postfach.api.url", OSI_MOCK_SERVER_EXTENSION::getPostfachFacadeUrl);
+		registry.add("ozgcloud.osiv2-postfach.api.resource", () -> RESOURCE_URN);
 	}
 
-	private MockServerClient mockServerClient;
+	private MockServerClient mockClient;
 
 	@BeforeEach
+	@SneakyThrows
 	public void setup() {
-		mockServerClient = OSI_MOCK_SERVER_EXTENSION.getMockServerClient();
-		mockServerClient
-				.when(
-						request()
-								.withMethod("GET")
-								.withPath("/dummy"),
-						Times.exactly(1)
-				)
-				.respond(
-						response()
-								.withStatusCode(200)
-				);
+		mockClient = OSI_MOCK_SERVER_EXTENSION.getMockClient();
 	}
 
 	@DisplayName("send message")
 	@Nested
 	class TestSendMessage {
+
 		@DisplayName("should send dummy request with jwt")
 		@Test
 		void shouldSendDummyRequestWithJwt() {
+			mockClient
+					.when(
+							request()
+									.withMethod("GET")
+									.withPath("/_metrics"),
+							exactly(1)
+					)
+					.respond(
+							response()
+									.withStatusCode(200)
+					);
+
 			osiPostfachRemoteService.sendMessage(postfachNachricht);
 
-			var requests = mockServerClient.retrieveRecordedRequests(
+			var requests = mockClient.retrieveRecordedRequests(
 					request()
 							.withMethod("GET")
-							.withPath("/dummy")
+							.withPath("/_metrics")
 			);
 			assertThat(requests).hasSize(1);
-			String clientId = JwtParser.parseBody(
+			var jwt = Jwt.parseAuthHeaderValue(
 					requests[0].getHeader("Authorization").getFirst()
-			).read("$.client_id");
-			assertThat(clientId).isEqualTo("OZG-Kopfstelle");
+			);
+			assertThat(jwt.body().read("$.client_id", String.class)).isEqualTo(CLIENT_ID);
+			assertThat(jwt.body().read("$.aud", String.class)).isEqualTo(RESOURCE_URN);
 		}
 
 	}
diff --git a/src/test/java/de/ozgcloud/nachrichten/postfach/osiv2/OsiPostfachRemoteServiceRemoteITCase.java b/src/test/java/de/ozgcloud/nachrichten/postfach/osiv2/OsiPostfachRemoteServiceRemoteITCase.java
new file mode 100644
index 0000000000000000000000000000000000000000..b85d5225eecb974c503e3e1a628fe25a2032fa33
--- /dev/null
+++ b/src/test/java/de/ozgcloud/nachrichten/postfach/osiv2/OsiPostfachRemoteServiceRemoteITCase.java
@@ -0,0 +1,69 @@
+package de.ozgcloud.nachrichten.postfach.osiv2;
+
+import static org.assertj.core.api.Assertions.*;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.context.ActiveProfiles;
+import org.springframework.test.context.DynamicPropertyRegistry;
+import org.springframework.test.context.DynamicPropertySource;
+
+import de.ozgcloud.nachrichten.postfach.PostfachNachricht;
+
+@SpringBootTest(classes = TestApplication.class, webEnvironment = SpringBootTest.WebEnvironment.NONE)
+@ActiveProfiles("itcase")
+@EnabledIfEnvironmentVariable(named = "SH_STAGE_CLIENT_SECRET", matches = ".+")
+public class OsiPostfachRemoteServiceRemoteITCase {
+
+	@Autowired
+	private OsiPostfachRemoteService osiPostfachRemoteService;
+
+	private static final String MESSAGE_ID = "message-id";
+	private final PostfachNachricht postfachNachricht = PostfachNachricht.builder()
+			.messageId(MESSAGE_ID)
+			.build();
+
+	@DynamicPropertySource
+	static void dynamicProperties(DynamicPropertyRegistry registry) {
+		registry.add(
+				"spring.security.oauth2.client.registration.osi2.client-secret",
+				() -> System.getenv("SH_STAGE_CLIENT_SECRET")
+		);
+		registry.add(
+				"ozgcloud.osiv2-postfach.http-proxy.host",
+				() -> matchProxyRegex(System.getenv("HTTP_PROXY")).group(1)
+		);
+		registry.add(
+				"ozgcloud.osiv2-postfach.http-proxy.port",
+				() -> matchProxyRegex(System.getenv("HTTP_PROXY")).group(2)
+		);
+	}
+
+	private static Matcher matchProxyRegex(String text) {
+		var matcher = Pattern.compile("([0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+):([0-9]+)").matcher(text);
+		if (matcher.find()) {
+			return matcher;
+		}
+		throw new IllegalArgumentException("Proxy host and port not found in '%s'".formatted(text));
+	}
+
+	@DisplayName("send message")
+	@Nested
+	class TestSendMessage {
+
+		@DisplayName("should not fail")
+		@Test
+		void shouldNotFail() {
+			assertThatCode(() -> osiPostfachRemoteService.sendMessage(postfachNachricht))
+					.doesNotThrowAnyException();
+		}
+
+	}
+}
diff --git a/src/test/java/de/ozgcloud/nachrichten/postfach/osiv2/extension/Jwt.java b/src/test/java/de/ozgcloud/nachrichten/postfach/osiv2/extension/Jwt.java
new file mode 100644
index 0000000000000000000000000000000000000000..327c91effe0823e3d7fb7c41e3a7a2c706a11eac
--- /dev/null
+++ b/src/test/java/de/ozgcloud/nachrichten/postfach/osiv2/extension/Jwt.java
@@ -0,0 +1,89 @@
+package de.ozgcloud.nachrichten.postfach.osiv2.extension;
+
+import static de.ozgcloud.nachrichten.postfach.osiv2.factory.JsonUtil.*;
+
+import java.io.Serializable;
+import java.util.Map;
+
+import org.bouncycastle.util.encoders.Base64;
+
+import com.jayway.jsonpath.JsonPath;
+import com.jayway.jsonpath.ReadContext;
+
+import lombok.SneakyThrows;
+
+public record Jwt(String token) {
+
+	public record JwtParts(ReadContext header, ReadContext body, byte[] signature) {
+	}
+
+	@SneakyThrows
+	public static JwtParts parseAuthHeaderValue(String authorizationHeaderValue) {
+		return new Jwt(discardBearerPrefix(authorizationHeaderValue)).parse();
+
+	}
+
+	public String scope() {
+		return parse().body().read("$.scope");
+	}
+
+	public Integer issuedAt() {
+		return parse().body().read("$.iat", Integer.class);
+	}
+
+	public Integer expireTime() {
+		return parse().body().read("$.exp", Integer.class);
+	}
+
+	public JwtParts parse() {
+		var jwtParts = splitIntoHeaderAndBodyAndSignature(this.token);
+		return new JwtParts(
+				parseJsonPath(base64UrlDecode(jwtParts[0])),
+				parseJsonPath(base64UrlDecode(jwtParts[1])),
+				base64UrlDecode(jwtParts[2])
+		);
+
+	}
+
+	private static ReadContext parseJsonPath(byte[] bytes) {
+		return JsonPath.parse(new String(bytes));
+	}
+
+	private static String discardBearerPrefix(String authorizationHeaderValue) {
+		return authorizationHeaderValue.substring("Bearer ".length());
+	}
+
+	private static String[] splitIntoHeaderAndBodyAndSignature(String jwt) {
+		var jwtParts = jwt.split("\\.");
+		if (jwtParts.length != 3) {
+			throw new IllegalArgumentException("Invalid JWT token");
+		}
+		return jwtParts;
+	}
+
+	private static byte[] base64UrlDecode(String input) {
+		// Replace URL-safe characters
+		String base64 = input.replace('-', '+').replace('_', '/');
+		// Add padding if necessary
+		int padding = 4 - (base64.length() % 4);
+		if (padding < 4) {
+			base64 += "=".repeat(padding);
+		}
+		return Base64.decode(base64);
+	}
+
+	@SneakyThrows
+	public static Jwt create(Map<String, Serializable> header, Map<String, Serializable> body, byte[] signature) {
+		var headerJson = toJson(header);
+		var bodyJson = toJson(body);
+		var token = base64UrlEncode(headerJson) + "." + base64UrlEncode(bodyJson) + "." + base64UrlEncode(new String(signature));
+		return new Jwt(token);
+	}
+
+	private static String base64UrlEncode(String input) {
+		var base64 = new String(Base64.encode(input.getBytes()));
+		return base64.replace('+', '-').replace('/', '_').replace("=", "");
+
+	}
+
+}
diff --git a/src/test/java/de/ozgcloud/nachrichten/postfach/osiv2/extension/JwtParser.java b/src/test/java/de/ozgcloud/nachrichten/postfach/osiv2/extension/JwtParser.java
deleted file mode 100644
index 85c18edb1889ecdebdb31dc6f32317df77594ffc..0000000000000000000000000000000000000000
--- a/src/test/java/de/ozgcloud/nachrichten/postfach/osiv2/extension/JwtParser.java
+++ /dev/null
@@ -1,47 +0,0 @@
-package de.ozgcloud.nachrichten.postfach.osiv2.extension;
-
-import org.eclipse.jgit.util.Base64;
-
-import com.jayway.jsonpath.JsonPath;
-import com.jayway.jsonpath.ReadContext;
-
-import lombok.SneakyThrows;
-
-public class JwtParser {
-
-	@SneakyThrows
-	public static ReadContext parseBody(String authorizationHeaderValue) {
-		var jwtParts = splitIntoSignatureAndHeaderAndBody(
-				discardBearerPrefix(authorizationHeaderValue)
-		);
-		var bodyPart = jwtParts[1];
-		return parseJsonPartFromUrlEncodedBase64(bodyPart);
-	}
-
-	private static ReadContext parseJsonPartFromUrlEncodedBase64(String base64EncodedPayload) {
-		return JsonPath.parse(new String(base64UrlDecode(base64EncodedPayload)));
-	}
-
-	private static String discardBearerPrefix(String authorizationHeaderValue) {
-		return authorizationHeaderValue.substring("Bearer ".length());
-	}
-
-	private static String[] splitIntoSignatureAndHeaderAndBody(String jwt) {
-		var jwtParts =  jwt.split("\\.");
-		if (jwtParts.length != 3) {
-			throw new IllegalArgumentException("Invalid JWT token");
-		}
-		return jwtParts;
-	}
-
-	private static byte[] base64UrlDecode(String input) {
-		// Replace URL-safe characters
-		String base64 = input.replace('-', '+').replace('_', '/');
-		// Add padding if necessary
-		int padding = 4 - (base64.length() % 4);
-		if (padding < 4) {
-			base64 += "=".repeat(padding);
-		}
-		return Base64.decode(base64);
-	}
-}
diff --git a/src/test/java/de/ozgcloud/nachrichten/postfach/osiv2/extension/JwtParserTest.java b/src/test/java/de/ozgcloud/nachrichten/postfach/osiv2/extension/JwtTest.java
similarity index 66%
rename from src/test/java/de/ozgcloud/nachrichten/postfach/osiv2/extension/JwtParserTest.java
rename to src/test/java/de/ozgcloud/nachrichten/postfach/osiv2/extension/JwtTest.java
index 9e5ab356ae8e7447395cecbd7839a42d0bd83820..0552893306658033825e6d21df1714a18e392a37 100644
--- a/src/test/java/de/ozgcloud/nachrichten/postfach/osiv2/extension/JwtParserTest.java
+++ b/src/test/java/de/ozgcloud/nachrichten/postfach/osiv2/extension/JwtTest.java
@@ -2,18 +2,35 @@ package de.ozgcloud.nachrichten.postfach.osiv2.extension;
 
 import static org.assertj.core.api.Assertions.*;
 
+import java.util.Map;
+
 import org.junit.jupiter.api.DisplayName;
 import org.junit.jupiter.api.Test;
 
-class JwtParserTest {
+class JwtTest {
 
 	@DisplayName("should parse")
 	@Test
 	void shouldParse() {
 		var headerValue = "Bearer eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJ1aV9EaHVXUzdocFhzV3dZTHRlOHFIRkR4bnNFYldlVmJBZ0pzaWpsWGw4In0.eyJleHAiOjE3MzEwNzA5NTEsImlhdCI6MTczMTA3MDg5MSwianRpIjoiZTFjNWE4YjEtZWEyYS00Mzg5LTkyNDQtZWE5Mjc4M2IyZDA1IiwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDozMjkyNy9yZWFsbXMvbWFzdGVyIiwic3ViIjoiNTg1MzdjMGQtMzU3MS00MDExLWIxM2ItZDY1MGZjOGUwZjQ0IiwidHlwIjoiQmVhcmVyIiwiYXpwIjoiT1pHLUtvcGZzdGVsbGUiLCJzY29wZSI6ImFjY2Vzc191cm46c29tZTpzY29wZTpmb3I6b3pna29wZnN0ZWxsZSBkZWZhdWx0IiwiY2xpZW50SG9zdCI6IjE3Mi4xNy4wLjEiLCJjbGllbnRBZGRyZXNzIjoiMTcyLjE3LjAuMSIsImNsaWVudF9pZCI6Ik9aRy1Lb3Bmc3RlbGxlIn0.MRGusCVssO-fHRp8-tEcdQWE7QVi3P0iHdmO4rGUwj_17KtHzQAT8ShZEVvE8oL-y-XKAPh7eT9will3oON1qhW6GHbZk5Xds4P5u8D0iHNl8nCSi_YS122v9Q1gwPrwPtVH26AKrdNM_YYv0AzT63gOVUoK4YY4jLhow3Uid2AVr2OMNAtcSPMysHXS1VeQRrhOm33JF_WVlguIHNjRpvRqCULkwywBRXDJm2mHOohkXFf10nM3ORAlmeElJCZa7Lg0zeg3q957Z9Mv5KbZA1X_QiHR5qpaDvimn0R_TTCZTGWM00GfyEHi2UU1s2ZfBeZTLOTNg2MUuDgA1cI7CQ";
-		var context = JwtParser.parseBody(headerValue);
+		var context = Jwt.parseAuthHeaderValue(headerValue);
 
-		String value = context.read("$.client_id");
+		String value = context.body().read("$.client_id");
 		assertThat(value).isEqualTo("OZG-Kopfstelle");
 	}
+
+	@DisplayName("should create")
+	@Test
+	void shouldCreate() {
+		var jwt = Jwt.create(Map.of(
+				"alg", "RS256"
+		), Map.of(
+				"sub", "58537c0d-3571-4011-b13b-d650fc8e0f44"
+		), "signature".getBytes());
+
+		var jwtParts = jwt.parse();
+		assertThat(jwtParts.header().read("$.alg", String.class)).isEqualTo("RS256");
+		assertThat(jwtParts.body().read("$.sub", String.class)).isEqualTo("58537c0d-3571-4011-b13b-d650fc8e0f44");
+		assertThat(jwtParts.signature()).isEqualTo("signature".getBytes());
+	}
 }
diff --git a/src/test/java/de/ozgcloud/nachrichten/postfach/osiv2/extension/OsiMockServerExtension.java b/src/test/java/de/ozgcloud/nachrichten/postfach/osiv2/extension/OsiMockServerExtension.java
index ad7c5601652fb2bd9ba5d6ab64b67e2636b30734..1c745f5ae52029bd7719b4d9f4a9860e4997d55f 100644
--- a/src/test/java/de/ozgcloud/nachrichten/postfach/osiv2/extension/OsiMockServerExtension.java
+++ b/src/test/java/de/ozgcloud/nachrichten/postfach/osiv2/extension/OsiMockServerExtension.java
@@ -1,107 +1,111 @@
 package de.ozgcloud.nachrichten.postfach.osiv2.extension;
 
-import static org.assertj.core.api.Assertions.*;
-
-import java.util.List;
-import java.util.function.Supplier;
-
-import jakarta.ws.rs.core.Response;
+import static de.ozgcloud.nachrichten.postfach.osiv2.factory.JwtFactory.*;
+import static org.mockserver.matchers.Times.*;
+import static org.mockserver.model.Header.*;
+import static org.mockserver.model.HttpRequest.*;
+import static org.mockserver.model.HttpResponse.*;
+import static org.mockserver.model.Parameter.*;
+import static org.mockserver.model.ParameterBody.*;
 
 import org.junit.jupiter.api.extension.AfterAllCallback;
 import org.junit.jupiter.api.extension.AfterEachCallback;
 import org.junit.jupiter.api.extension.BeforeAllCallback;
+import org.junit.jupiter.api.extension.BeforeEachCallback;
 import org.junit.jupiter.api.extension.ExtensionContext;
-import org.keycloak.admin.client.resource.RealmResource;
-import org.keycloak.representations.idm.ClientRepresentation;
-import org.keycloak.representations.idm.ClientScopeRepresentation;
 import org.mockserver.client.MockServerClient;
 import org.testcontainers.containers.MockServerContainer;
+import org.testcontainers.containers.output.OutputFrame;
 import org.testcontainers.utility.DockerImageName;
 
-import dasniko.testcontainers.keycloak.KeycloakContainer;
+import de.ozgcloud.nachrichten.postfach.osiv2.factory.JwtFactory;
 import lombok.Getter;
 import lombok.RequiredArgsConstructor;
+import lombok.extern.log4j.Log4j2;
 
+@Log4j2
 @Getter
 @RequiredArgsConstructor
-public class OsiMockServerExtension implements BeforeAllCallback, AfterAllCallback, AfterEachCallback {
-
-	private MockServerClient mockServerClient;
-	private MockServerContainer mockServerContainer;
-	private KeycloakContainer keycloakContainer;
+public class OsiMockServerExtension implements BeforeAllCallback, AfterAllCallback, BeforeEachCallback, AfterEachCallback {
+
+	private MockServerClient mockClient;
+	private final MockServerContainer mockServerContainer = new MockServerContainer(DockerImageName.parse("mockserver/mockserver")
+			.withTag("mockserver-5.15.0"))
+			.withLogConsumer(outputFrame -> {
+				var line = outputFrame.getUtf8String().stripTrailing();
+				if (outputFrame.getType() == OutputFrame.OutputType.STDERR) {
+					LOG.error(line);
+				} else {
+					LOG.info(line);
+				}
+			});
 
 	@Override
 	public void beforeAll(ExtensionContext context) {
-		setupPostfachMockServer();
-		setupKeycloak();
+		setupMockServer();
 	}
 
 	@Override
 	public void afterEach(ExtensionContext context) {
-		mockServerClient.reset();
+		mockClient.reset();
 	}
 
 	@Override
 	public void afterAll(ExtensionContext context) {
 		mockServerContainer.stop();
-		mockServerClient.stop();
-		keycloakContainer.stop();
+		mockClient.stop();
 	}
 
-	private void setupPostfachMockServer() {
-		mockServerContainer = new MockServerContainer(DockerImageName.parse("mockserver/mockserver")
-				.withTag("mockserver-5.15.0"));
+	private void setupMockServer() {
 		mockServerContainer.start();
-		mockServerClient = new MockServerClient(mockServerContainer.getHost(), mockServerContainer.getServerPort());
+		mockClient = new MockServerClient(
+				mockServerContainer.getHost(),
+				mockServerContainer.getServerPort()
+		);
 	}
 
-	private void setupKeycloak() {
-		keycloakContainer = new KeycloakContainer("quay.io/keycloak/keycloak:24.0.5");
-		keycloakContainer.start();
-		try (var keycloak = keycloakContainer.getKeycloakAdminClient()) {
-			keycloak.tokenManager().getAccessToken();
-			var masterRealm = keycloak.realm("master");
-			var clientScopes = List.of("default", "access_urn:some:scope:for:ozgkopfstelle");
-			clientScopes.forEach(scope -> createClientScope(masterRealm, scope));
-			createPostfachClient(masterRealm, clientScopes);
-		}
+	public String getAccessTokenUrl() {
+		return getMockServerUrl() + "/access-token";
 	}
 
-	private void createPostfachClient(RealmResource realmResource, List<String> clientScopes) {
-		var clients = realmResource.clients();
-		var postfach = new ClientRepresentation();
-		postfach.setClientId("OZG-Kopfstelle");
-		postfach.setSecret("changeme");
-		postfach.setOptionalClientScopes(clientScopes);
-		postfach.setServiceAccountsEnabled(true);
-		postfach.setEnabled(true);
-		verifyResponseOk(() -> clients.create(postfach));
+	public String getPostfachFacadeUrl() {
+		return getMockServerUrl();
 	}
 
-	private void createClientScope(RealmResource realmResource, String scopeName) {
-		var clientScopes = realmResource.clientScopes();
-		var clientScopeRepresentation = new ClientScopeRepresentation();
-		clientScopeRepresentation.setName(scopeName);
-		clientScopeRepresentation.setProtocol("openid-connect");
-		verifyResponseOk(() -> clientScopes.create(clientScopeRepresentation));
+	private String getMockServerUrl() {
+		return "http://" + mockClient.remoteAddress().getHostName() + ":" + mockClient.remoteAddress().getPort();
 	}
 
-	private void verifyResponseOk(Supplier<Response> responseSupplier) {
-		try (var response = responseSupplier.get()) {
-			assertThat(response.getStatus()).isEqualTo(201);
-		}
-	}
-
-	public String getTokenUri() {
-		return getAuthProtocolUrl() + "/token";
-	}
-
-	public String getAuthProtocolUrl() {
-		return keycloakContainer.getAuthServerUrl() + "/realms/master/protocol/openid-connect";
-	}
-
-	public String getPostfachMockServerUrl() {
-		return "http://" + mockServerClient.remoteAddress().getHostName() + ":" + mockServerClient.remoteAddress().getPort();
+	@Override
+	public void beforeEach(ExtensionContext context) {
+		mockClient
+				.when(
+						request()
+								.withMethod("POST")
+								.withPath("/access-token")
+								.withHeaders(
+										header("Content-Type", "application/x-www-form-urlencoded;charset=UTF-8")
+								)
+								.withBody(
+										params(
+												param("grant_type", "client_credentials"),
+												param("client_id", CLIENT_ID),
+												param("client_secret", "changeme"),
+												param("scope", String.join(" ", CLIENT_SCOPES)),
+												param("resource", RESOURCE_URN)
+										)
+								),
+						exactly(1)
+				)
+				.respond(
+						response()
+								.withStatusCode(200)
+								.withHeader("Content-Type", "application/json")
+								.withBody(
+										JwtFactory.createTokenResponse(
+												JwtFactory.createAccessTokenExampleWithExpireIn(900)
+										)
+								)
+				);
 	}
-
 }
diff --git a/src/test/java/de/ozgcloud/nachrichten/postfach/osiv2/factory/JsonUtil.java b/src/test/java/de/ozgcloud/nachrichten/postfach/osiv2/factory/JsonUtil.java
new file mode 100644
index 0000000000000000000000000000000000000000..5353606ac693ef6d98d03dc58d77d900c4943279
--- /dev/null
+++ b/src/test/java/de/ozgcloud/nachrichten/postfach/osiv2/factory/JsonUtil.java
@@ -0,0 +1,14 @@
+package de.ozgcloud.nachrichten.postfach.osiv2.factory;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+import lombok.SneakyThrows;
+
+public class JsonUtil {
+	private static final ObjectMapper jsonMapper = new ObjectMapper();
+
+	@SneakyThrows
+	public static String toJson(Object object) {
+		return jsonMapper.writeValueAsString(object);
+	}
+}
diff --git a/src/test/java/de/ozgcloud/nachrichten/postfach/osiv2/factory/JwtFactory.java b/src/test/java/de/ozgcloud/nachrichten/postfach/osiv2/factory/JwtFactory.java
new file mode 100644
index 0000000000000000000000000000000000000000..c5e7fcd8adf6948ceffdf3420183a4395897850f
--- /dev/null
+++ b/src/test/java/de/ozgcloud/nachrichten/postfach/osiv2/factory/JwtFactory.java
@@ -0,0 +1,51 @@
+package de.ozgcloud.nachrichten.postfach.osiv2.factory;
+
+import static de.ozgcloud.nachrichten.postfach.osiv2.factory.JsonUtil.*;
+
+import java.util.List;
+import java.util.Map;
+
+
+import de.ozgcloud.nachrichten.postfach.osiv2.extension.Jwt;
+import lombok.SneakyThrows;
+
+public class JwtFactory {
+
+	public static final String CLIENT_ID = "ITCase OZG Kopfstelle";
+	public static final String RESOURCE_URN = "urn:dataport:osi:postfach:rz2:itcase:sh";
+	public static final List<String> CLIENT_SCOPES = List.of("default", "access_urn:itcase:scope:ozgkopfstelle");
+
+	public static Jwt createAccessTokenExampleWithExpireIn(Integer expireInSeconds) {
+		var nowEpochSeconds = System.currentTimeMillis() / 1000;
+		return Jwt.create(
+				Map.of(
+						"alg", "RS256",
+						"kid", "...",
+						"x5t", "...",
+						"typ", "at+jwt"
+				),
+				Map.of(
+						"iss", "https://idp.serviceportal-stage.schleswig-holstein.de/webidp2",
+						"nbf", nowEpochSeconds,
+						"iat", nowEpochSeconds,
+						"exp", nowEpochSeconds + expireInSeconds,
+						"aud", RESOURCE_URN,
+						"scope", String.join(" ", CLIENT_SCOPES),
+						"client_id", CLIENT_ID,
+						"client_tenant", "urn:osp:names:realm:stage:sh",
+						"jti", "..."
+				),
+				"signature".getBytes());
+	}
+
+	@SneakyThrows
+	public static String createTokenResponse(Jwt accessToken) {
+		return toJson(Map.of(
+				"access_token", accessToken.token(),
+				"token_type", "Bearer",
+				"expires_in", accessToken.expireTime() - accessToken.issuedAt(),
+				"scope", accessToken.scope()
+		));
+	}
+
+}