diff --git a/server/src/main/helm/templates/deployment.yaml b/server/src/main/helm/templates/deployment.yaml
index 874205860d1689b61d879b4b154621a5dfa57337..f70d00df638dc54df685ac47cc30a0bee74046d1 100644
--- a/server/src/main/helm/templates/deployment.yaml
+++ b/server/src/main/helm/templates/deployment.yaml
@@ -121,6 +121,11 @@ spec:
         - name: SPRING_SECURITY_SAML2_RELYINGPARTY_REGISTRATION_BAYERNID_ENTITY-ID
           value: {{ required ".Values.antragsraum.saml.entityID must be set" .Values.antragsraum.saml.entityID }}
 
+        - name: SPRING_SERVLET_MULTIPART_MAX-FILE-SIZE
+          value: {{.Values.antragsraum.servlet.maxFileSize }}
+        - name: SPRING_SERVLET_MULTIPART_MAX-REQUEST-SIZE
+          value: {{.Values.antragsraum.servlet.maxRequestSize }}
+
         - name: OZGCLOUD_JWT_SECRET
           valueFrom:
             secretKeyRef:
diff --git a/server/src/main/helm/values.yaml b/server/src/main/helm/values.yaml
index c048a4516ad24a0eb06a18a41ae66c39422f4156..ddea7310a55a48d50ade5c970e73a0d46acdb0e3 100644
--- a/server/src/main/helm/values.yaml
+++ b/server/src/main/helm/values.yaml
@@ -30,4 +30,9 @@ image:
     repo: dockerproxy.ozg-sh.de
     name: bitnami/kubectl
     tag: latest
-replicaCount: 1
\ No newline at end of file
+replicaCount: 1
+
+antragsraum:
+  servlet:
+    maxRequestSize: 50MB
+    maxFileSize: 30MB
diff --git a/server/src/main/java/de/ozgcloud/antragsraum/SecurityConfiguration.java b/server/src/main/java/de/ozgcloud/antragsraum/SecurityConfiguration.java
index 22d5858da91b5398bdfc300baca1523795069b01..77cf8627f99d762fed6a68edd572fd767a0efc83 100644
--- a/server/src/main/java/de/ozgcloud/antragsraum/SecurityConfiguration.java
+++ b/server/src/main/java/de/ozgcloud/antragsraum/SecurityConfiguration.java
@@ -34,6 +34,8 @@ import org.springframework.security.saml2.provider.service.web.authentication.Sa
 import org.springframework.security.web.SecurityFilterChain;
 import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
 import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
+import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler;
+import org.springframework.security.web.header.writers.ReferrerPolicyHeaderWriter;
 import org.springframework.web.servlet.config.annotation.CorsRegistry;
 import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
 
@@ -53,6 +55,7 @@ import lombok.RequiredArgsConstructor;
 public class SecurityConfiguration {
 	private static final String OPTIONS = "OPTIONS";
 	private static final String GET = "GET";
+	private static final String POLICY_DIRECTIVES = "object-src 'none'; child-src 'self'; frame-ancestors 'none'; base-uri 'none'; upgrade-insecure-requests; require-trusted-types-for 'script'";
 
 	@NonNull
 	private final BayernIdSaml2Extension bayernIdSaml2Extension;
@@ -70,6 +73,11 @@ public class SecurityConfiguration {
 		return new SecurityProvider();
 	}
 
+	@Bean
+	public SecurityContextLogoutHandler logoutHandler() {
+		return new SecurityContextLogoutHandler();
+	}
+
 	@Bean
 	Saml2AuthenticationRequestResolver authenticationRequestResolver(RelyingPartyRegistrationRepository registrations) {
 		var registrationResolver = new DefaultRelyingPartyRegistrationResolver(registrations);
@@ -91,7 +99,11 @@ public class SecurityConfiguration {
 			.requestMatchers("/api/**").authenticated()
 			.anyRequest().denyAll())
 		  .addFilterBefore(tokenAuthenticationFilter, UsernamePasswordAuthenticationFilter.class)
-		  .csrf(AbstractHttpConfigurer::disable);
+		  .csrf(AbstractHttpConfigurer::disable)
+		  .headers(headers -> headers.contentSecurityPolicy(
+			csp -> csp.policyDirectives(POLICY_DIRECTIVES)))
+		  .headers(headers -> headers.referrerPolicy(
+			referrerPolicyConfig -> referrerPolicyConfig.policy(ReferrerPolicyHeaderWriter.ReferrerPolicy.NO_REFERRER)));
 
 		http.saml2Login(samlLogin -> samlLogin.successHandler(getSamlUrlAuthenticationSuccessHandler()))
 		  .saml2Logout(Customizer.withDefaults())
diff --git a/server/src/main/java/de/ozgcloud/antragsraum/attachments/FileRestClient.java b/server/src/main/java/de/ozgcloud/antragsraum/attachments/FileRestClient.java
index de48e9faee253c06dda24fe03085c8458284ce83..bfde3830060e9a10f1123032e12ce9898e28f2e7 100644
--- a/server/src/main/java/de/ozgcloud/antragsraum/attachments/FileRestClient.java
+++ b/server/src/main/java/de/ozgcloud/antragsraum/attachments/FileRestClient.java
@@ -48,9 +48,12 @@ import lombok.extern.log4j.Log4j2;
 @Log4j2
 @Component
 class FileRestClient {
-	private static final String FIND_FILE_METADATA_API = "/api/v1/file/metadata/{fileId}";
-	private static final String GET_FILE_CONTENT_API = "/api/v1/file/content/{fileId}";
+	static final String FIND_FILE_METADATA_API = "/api/v1/file/metadata/{fileId}";
+	static final String GET_FILE_CONTENT_API = "/api/v1/file/content/{fileId}";
 	static final String UPLOAD_FILE_API = "/api/v1/file";
+	public static final String FILE_KEY = "file";
+	public static final String VORGANG_ID_KEY = "vorgangId";
+	static final String NO_FILE_ID_RECEIVED_MESSAGE = "No fileId received";
 
 	private final RestClient restClient;
 
@@ -74,7 +77,7 @@ class FileRestClient {
 			if (Objects.nonNull(response)) {
 				fileIdFuture.complete(response);
 			} else {
-				fileIdFuture.completeExceptionally(new IllegalStateException("No fileId received"));
+				fileIdFuture.completeExceptionally(new IllegalStateException(NO_FILE_ID_RECEIVED_MESSAGE));
 			}
 		} catch (Exception e) {
 			fileIdFuture.completeExceptionally(e);
@@ -83,19 +86,16 @@ class FileRestClient {
 
 	private String sendFile(OzgUploadFile uploadFile, String address) {
 		MultiValueMap<String, Object> requestBody = new LinkedMultiValueMap<>();
-		requestBody.add("file", uploadFile.file().getResource());
-		requestBody.add("vorgangId", uploadFile.vorgangId());
+		requestBody.add(FILE_KEY, uploadFile.file().getResource());
+		requestBody.add(VORGANG_ID_KEY, uploadFile.vorgangId());
 
-		var fileId = restClient
+		return restClient
 		  .post()
 		  .uri(UPLOAD_FILE_API)
 		  .header(X_GRPC_ADDRESS, RestClientUtils.sanitizeAddress(address))
 		  .contentType(MediaType.MULTIPART_FORM_DATA)
 		  .body(requestBody)
 		  .retrieve().toEntity(String.class).getBody();
-		LOG.info("received {}", fileId);
-
-		return fileId;
 	}
 
 	void downloadFileContent(final String fileId, final OutputStream out, final String address) {
diff --git a/server/src/main/java/de/ozgcloud/antragsraum/attachments/VirusScannerClient.java b/server/src/main/java/de/ozgcloud/antragsraum/attachments/VirusScannerClient.java
index f2ac82063ad66c8bb1d77e3bcce3409422352276..4085c550fd58f8bd9597731b265cea39e6941109 100755
--- a/server/src/main/java/de/ozgcloud/antragsraum/attachments/VirusScannerClient.java
+++ b/server/src/main/java/de/ozgcloud/antragsraum/attachments/VirusScannerClient.java
@@ -57,7 +57,7 @@ class VirusScannerClient {
 		Set<String> viruses = new HashSet<>();
 
 		if (Objects.nonNull(file)) {
-			LOG.info("Scanning file {} for viruses using ClamAV at {}", file.getOriginalFilename(), clamAVScanUrl);
+			LOG.debug("Scanning file {} for viruses using ClamAV at {}", file.getOriginalFilename(), clamAVScanUrl);
 
 			MultiValueMap<String, Object> requestBody = new LinkedMultiValueMap<>();
 			requestBody.add(FILE_REQUEST_KEY, file.getResource());
diff --git a/server/src/main/java/de/ozgcloud/antragsraum/command/CommandRestClient.java b/server/src/main/java/de/ozgcloud/antragsraum/command/CommandRestClient.java
index e25c388d258eb28792286e7e4f55860dde4a76a7..8bb9b294d01055b9bbf6ac6549e07b08c4007f56 100644
--- a/server/src/main/java/de/ozgcloud/antragsraum/command/CommandRestClient.java
+++ b/server/src/main/java/de/ozgcloud/antragsraum/command/CommandRestClient.java
@@ -34,7 +34,7 @@ import lombok.extern.log4j.Log4j2;
 @Log4j2
 @Component
 class CommandRestClient {
-	private static final String COMMAND_URI = "/api/v1/command/{commandId}";
+	static final String COMMAND_URI = "/api/v1/command/{commandId}";
 
 	private final RestClient restClient;
 
diff --git a/server/src/main/java/de/ozgcloud/antragsraum/common/InvalidFileAdvice.java b/server/src/main/java/de/ozgcloud/antragsraum/common/InvalidFileAdvice.java
index 375e174891763498e30b3b9ef01bae4e8e9c776d..e24bea7c2cd28b6e85cf79cc0d37d40c86a69552 100755
--- a/server/src/main/java/de/ozgcloud/antragsraum/common/InvalidFileAdvice.java
+++ b/server/src/main/java/de/ozgcloud/antragsraum/common/InvalidFileAdvice.java
@@ -21,7 +21,7 @@
 package de.ozgcloud.antragsraum.common;
 
 import jakarta.annotation.Priority;
-import lombok.extern.log4j.Log4j2;
+
 import org.springframework.http.HttpHeaders;
 import org.springframework.http.HttpStatus;
 import org.springframework.http.ResponseEntity;
@@ -30,13 +30,15 @@ import org.springframework.web.bind.annotation.ExceptionHandler;
 import org.springframework.web.context.request.WebRequest;
 import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;
 
+import lombok.extern.log4j.Log4j2;
+
 @ControllerAdvice
 @Log4j2
 @Priority(value = 0)
 public class InvalidFileAdvice extends ResponseEntityExceptionHandler {
-    @ExceptionHandler(value = {InvalidFileTypeException.class})
-    protected ResponseEntity<Object> handleVirusFound(RuntimeException ex, WebRequest request) {
-        LOG.error("Uploaded file is not valid.", ex);
-        return handleExceptionInternal(ex, "File not acceptable", new HttpHeaders(), HttpStatus.NOT_ACCEPTABLE, request);
-    }
+	@ExceptionHandler(value = { InvalidFileTypeException.class })
+	protected ResponseEntity<Object> handleInvalidFile(RuntimeException ex, WebRequest request) {
+		LOG.error("Uploaded file is not valid.", ex);
+		return handleExceptionInternal(ex, "File not acceptable", new HttpHeaders(), HttpStatus.NOT_ACCEPTABLE, request);
+	}
 }
diff --git a/server/src/main/java/de/ozgcloud/antragsraum/common/logging/RepositoryAspectPointcut.java b/server/src/main/java/de/ozgcloud/antragsraum/common/logging/RepositoryAspectPointcut.java
new file mode 100644
index 0000000000000000000000000000000000000000..47f0bcdda4fca053d060bed639c87e17a27441aa
--- /dev/null
+++ b/server/src/main/java/de/ozgcloud/antragsraum/common/logging/RepositoryAspectPointcut.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2022 Das Land Schleswig-Holstein vertreten durch den
+ * Ministerpräsidenten des Landes Schleswig-Holstein
+ * Staatskanzlei
+ * Abteilung Digitalisierung und zentrales IT-Management der Landesregierung
+ *
+ * Lizenziert unter der EUPL, Version 1.2 oder - sobald
+ * diese von der Europäischen Kommission genehmigt wurden -
+ * Folgeversionen der EUPL ("Lizenz");
+ * Sie dürfen dieses Werk ausschließlich gemäß
+ * dieser Lizenz nutzen.
+ * Eine Kopie der Lizenz finden Sie hier:
+ *
+ * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
+ *
+ * Sofern nicht durch anwendbare Rechtsvorschriften
+ * gefordert oder in schriftlicher Form vereinbart, wird
+ * die unter der Lizenz verbreitete Software "so wie sie
+ * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN -
+ * ausdrücklich oder stillschweigend - verbreitet.
+ * Die sprachspezifischen Genehmigungen und Beschränkungen
+ * unter der Lizenz sind dem Lizenztext zu entnehmen.
+ */
+package de.ozgcloud.antragsraum.common.logging;
+
+import org.aspectj.lang.annotation.Pointcut;
+
+public class RepositoryAspectPointcut {
+
+	@Pointcut("execution(public * org.springframework.data.repository.Repository+.*(..))")
+	void anyPublicRepositoryMethod() {
+		// aspect pointcut - no implementation needed
+	}
+}
diff --git a/server/src/main/java/de/ozgcloud/antragsraum/common/logging/RepositoryLoggingAspect.java b/server/src/main/java/de/ozgcloud/antragsraum/common/logging/RepositoryLoggingAspect.java
new file mode 100644
index 0000000000000000000000000000000000000000..efb41906f2b1771743030b3fd3fdbd6d50aaa9c9
--- /dev/null
+++ b/server/src/main/java/de/ozgcloud/antragsraum/common/logging/RepositoryLoggingAspect.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (c) 2024.   Das Land Schleswig-Holstein vertreten durch den Ministerpräsidenten
+ * des Landes Schleswig-Holstein Staatskanzlei Abteilung Digitalisierung und zentrales IT-Management der Landesregierung.
+ *
+ * Lizenziert unter der EUPL, Version 1.2 oder - sobald
+ * diese von der Europäischen Kommission genehmigt wurden -
+ * Folgeversionen der EUPL ("Lizenz");
+ * Sie dürfen dieses Werk ausschließlich gemäß
+ * dieser Lizenz nutzen.
+ * Eine Kopie der Lizenz finden Sie hier:
+ *
+ * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
+ *
+ * Sofern nicht durch anwendbare Rechtsvorschriften
+ * gefordert oder in schriftlicher Form vereinbart, wird
+ * die unter der Lizenz verbreitete Software "so wie sie
+ * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN -
+ * ausdrücklich oder stillschweigend - verbreitet.
+ * Die sprachspezifischen Genehmigungen und Beschränkungen
+ * unter der Lizenz sind dem Lizenztext zu entnehmen.
+ */
+package de.ozgcloud.antragsraum.common.logging;
+
+import org.aspectj.lang.JoinPoint;
+import org.aspectj.lang.annotation.Aspect;
+import org.aspectj.lang.annotation.Before;
+import org.springframework.stereotype.Component;
+
+import de.ozgcloud.common.logging.AspectLoggingUtils;
+
+@Aspect
+@Component
+public class RepositoryLoggingAspect extends RepositoryAspectPointcut {
+
+	@Before("anyPublicRepositoryMethod()")
+	public void onRepositoryMethod(JoinPoint joinPoint) {
+		AspectLoggingUtils.log(joinPoint);
+	}
+}
\ No newline at end of file
diff --git a/server/src/main/java/de/ozgcloud/antragsraum/nachricht/Nachricht.java b/server/src/main/java/de/ozgcloud/antragsraum/nachricht/Nachricht.java
index cd43487035b44a0f60aab17490a6b9a627c6343c..6d762929c6627ff46c0185b01c58e75cb594c938 100644
--- a/server/src/main/java/de/ozgcloud/antragsraum/nachricht/Nachricht.java
+++ b/server/src/main/java/de/ozgcloud/antragsraum/nachricht/Nachricht.java
@@ -100,4 +100,23 @@ record Nachricht(
 	type = "String"
   )
   String status) {
+
+	@Override
+	public String toString() {
+		return "Nachricht{" +
+		  "id='" + id + '\'' +
+		  ", nachrichtEventId='" + nachrichtEventId + '\'' +
+		  ", answeredAt=" + answeredAt +
+		  ", sendAt=" + sendAt +
+		  ", title='" + title + '\'' +
+		  ", message=<...masked for privacy..>" +
+		  ", postfachId='" + postfachId + '\'' +
+		  ", vorgangId='" + vorgangId + '\'' +
+		  ", replyOption=" + replyOption +
+		  ", attachments=" + attachments +
+		  ", accessible=" + accessible +
+		  ", trustLevel='" + trustLevel + '\'' +
+		  ", status='" + status + '\'' +
+		  '}';
+	}
 }
diff --git a/server/src/main/java/de/ozgcloud/antragsraum/nachricht/NachrichtHeaderMapper.java b/server/src/main/java/de/ozgcloud/antragsraum/nachricht/NachrichtHeaderMapper.java
index 25c943faa4422982d4119fde729967e2e34f6252..032a097056123aa9e662f0375df4b78221aa6bed 100644
--- a/server/src/main/java/de/ozgcloud/antragsraum/nachricht/NachrichtHeaderMapper.java
+++ b/server/src/main/java/de/ozgcloud/antragsraum/nachricht/NachrichtHeaderMapper.java
@@ -24,6 +24,8 @@ import java.time.DateTimeException;
 import java.time.LocalDateTime;
 import java.time.ZoneOffset;
 
+import org.apache.commons.lang3.StringUtils;
+
 import de.ozgcloud.antragsraum.proxy.AntragraumproxyGrpcRueckfrageHead;
 import lombok.AccessLevel;
 import lombok.NoArgsConstructor;
@@ -37,14 +39,17 @@ class NachrichtHeaderMapper {
 		try {
 			timestamp = LocalDateTime.parse(dateString).toEpochSecond(ZoneOffset.UTC) * 1000;
 		} catch (DateTimeException e) {
-			LOG.warn("Invalid date [{}] received for Nachricht. Setting it to 0 (=1970-01-01)", dateString);
+			if (StringUtils.isEmpty(dateString)) {
+				LOG.debug("Empty date received for Nachricht. Setting it to 0 (=1970-01-01)");
+			} else {
+				LOG.warn("Invalid date [{}] received for Nachricht. Setting it to 0 (=1970-01-01)", dateString);
+			}
 		}
 
 		return timestamp;
 	}
 
 	static NachrichtHeader fromRueckfrageHead(final AntragraumproxyGrpcRueckfrageHead rueckfrageHead, String nachrichtEventId) {
-		LOG.info("Mapping RueckfrageHead [{}] for NachrichtEvent [{}] to NachrichtHeader", rueckfrageHead, nachrichtEventId);
 		var builder = NachrichtHeader.builder()
 		  .vorgangId(rueckfrageHead.getVorgangId())
 		  .id(rueckfrageHead.getId())
diff --git a/server/src/main/java/de/ozgcloud/antragsraum/nachricht/NachrichtMapper.java b/server/src/main/java/de/ozgcloud/antragsraum/nachricht/NachrichtMapper.java
index 88c9e6ca9582fd25e37621dbec660708f3404b69..a118da2f45e0537d03bf6173cf7e869c72a7db43 100644
--- a/server/src/main/java/de/ozgcloud/antragsraum/nachricht/NachrichtMapper.java
+++ b/server/src/main/java/de/ozgcloud/antragsraum/nachricht/NachrichtMapper.java
@@ -57,14 +57,17 @@ class NachrichtMapper {
 		try {
 			timestamp = LocalDateTime.parse(dateString).toEpochSecond(ZoneOffset.UTC) * 1000;
 		} catch (DateTimeException e) {
-			LOG.warn("Invalid date [{}] received for Nachricht. Setting it to 0 (=1970-01-01)", dateString);
+			if (StringUtils.isEmpty(dateString)) {
+				LOG.debug("Empty date received for Nachricht. Setting it to 0 (=1970-01-01)");
+			} else {
+				LOG.warn("Invalid date [{}] received for Nachricht. Setting it to 0 (=1970-01-01)", dateString);
+			}
 		}
 
 		return timestamp;
 	}
 
 	public Nachricht fromRestRueckfrage(final AntragraumproxyGrpcRueckfrage rueckfrage, final String id) {
-		LOG.info("Mapping RestRueckfrage [{}] for NachrichtEvent [{}] to Nachricht", rueckfrage, id);
 		var builder = Nachricht.builder()
 		  .vorgangId(rueckfrage.getVorgangId())
 		  .id(rueckfrage.getId())
diff --git a/server/src/main/java/de/ozgcloud/antragsraum/nachricht/NachrichtenRemoteService.java b/server/src/main/java/de/ozgcloud/antragsraum/nachricht/NachrichtenRemoteService.java
index fef102a521f04f1f9ca7b48e16bc2d447faf3c9d..3fb1be48928fdafb737c6d1771a0d35dc34366da 100644
--- a/server/src/main/java/de/ozgcloud/antragsraum/nachricht/NachrichtenRemoteService.java
+++ b/server/src/main/java/de/ozgcloud/antragsraum/nachricht/NachrichtenRemoteService.java
@@ -21,6 +21,7 @@
 package de.ozgcloud.antragsraum.nachricht;
 
 import java.util.List;
+import java.util.Objects;
 import java.util.Optional;
 
 import org.springframework.stereotype.Service;
@@ -30,7 +31,6 @@ import de.ozgcloud.antragsraum.events.NachrichtEvent;
 import de.ozgcloud.antragsraum.proxy.AntragraumproxyGrpcSendRueckfrageAnswerResponse;
 import de.ozgcloud.antragsraum.security.AuthenticationHelper;
 import io.grpc.StatusRuntimeException;
-import lombok.NonNull;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.log4j.Log4j2;
 
@@ -38,17 +38,17 @@ import lombok.extern.log4j.Log4j2;
 @Log4j2
 @RequiredArgsConstructor
 class NachrichtenRemoteService {
-	private final @NonNull NachrichtenRestClient nachrichtenRestClient;
-	private final @NonNull NachrichtMapper nachrichtMapper;
-	private final @NonNull AntragraumProxyMapper antragraumProxyMapper;
+	private final NachrichtenRestClient nachrichtenRestClient;
+	private final NachrichtMapper nachrichtMapper;
+	private final AntragraumProxyMapper antragraumProxyMapper;
 
 	List<NachrichtHeader> findRueckfrageHeads(NachrichtEvent event) {
 		try {
 			var reply = nachrichtenRestClient.getFindRueckfragen(event.postfachId(), AuthenticationHelper.getSamlToken(), event.address());
 			var rueckfrageHeads = reply.getRueckfrageHead();
-			LOG.info("{} RueckfrageHeads für Postfach ID {} erhalten", rueckfrageHeads.size(), event.postfachId());
 
-			return rueckfrageHeads.stream().map(rueckfrageHead -> NachrichtHeaderMapper.fromRueckfrageHead(rueckfrageHead, event.id())).toList();
+			return Objects.nonNull(rueckfrageHeads) ? rueckfrageHeads.stream()
+			  .map(rueckfrageHead -> NachrichtHeaderMapper.fromRueckfrageHead(rueckfrageHead, event.id())).toList() : List.of();
 		} catch (StatusRuntimeException e) {
 			LOG.error("Error beim Laden der RueckfrageHeads für das Postfach {}", event.postfachId(), e);
 		}
@@ -60,9 +60,8 @@ class NachrichtenRemoteService {
 		try {
 			var reply = nachrichtenRestClient.getGetRueckfrage(rueckfrageId, AuthenticationHelper.getSamlToken(), event.address());
 			var rueckfrage = reply.getRueckfrage();
-			LOG.info("Rueckfrage [{}] für ID {} erhalten", rueckfrage, rueckfrageId);
 
-			return Optional.of(nachrichtMapper.fromRestRueckfrage(rueckfrage, event.id()));
+			return Objects.nonNull(rueckfrage) ? Optional.of(nachrichtMapper.fromRestRueckfrage(rueckfrage, event.id())) : Optional.empty();
 		} catch (StatusRuntimeException e) {
 			LOG.error("Error beim Laden der Rueckfrage mit ID {}", rueckfrageId, e);
 		}
diff --git a/server/src/main/java/de/ozgcloud/antragsraum/nachricht/NachrichtenService.java b/server/src/main/java/de/ozgcloud/antragsraum/nachricht/NachrichtenService.java
index 4957d99651f5c2944e7b85fb5abad73bff17cfe4..b1a9b37f695f2cfd90675e5cd719aabe8e747f28 100644
--- a/server/src/main/java/de/ozgcloud/antragsraum/nachricht/NachrichtenService.java
+++ b/server/src/main/java/de/ozgcloud/antragsraum/nachricht/NachrichtenService.java
@@ -27,10 +27,15 @@ import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.*;
 import java.util.Collection;
 import java.util.List;
 
+import jakarta.validation.ConstraintViolationException;
+import jakarta.validation.Validator;
+
 import org.springframework.stereotype.Service;
+import org.springframework.validation.annotation.Validated;
 
 import de.ozgcloud.antragsraum.command.CommandReference;
 import de.ozgcloud.antragsraum.common.NotAccessibleException;
+import de.ozgcloud.antragsraum.common.TechnicalException;
 import de.ozgcloud.antragsraum.events.NachrichtEvent;
 import de.ozgcloud.antragsraum.events.NachrichtEventService;
 import lombok.NonNull;
@@ -39,12 +44,14 @@ import lombok.extern.log4j.Log4j2;
 
 @Log4j2
 @Service
+@Validated
 @RequiredArgsConstructor
 class NachrichtenService {
 	static final String RUECKFRAGE_LINK_RELATIONSHIP_NAME = "rueckfrage";
 
 	private final @NonNull NachrichtEventService nachrichtEventService;
 	private final @NonNull NachrichtenRemoteService nachrichtenRemoteService;
+	private final @NonNull Validator validator;
 
 	List<RueckfrageHeader> getRueckfrageHeadersOfPostfach(String postfachId) {
 		var headers = RueckfrageHeaderMapper.fromNachrichtHeaders(getNachrichtHeadersOfPostfach(postfachId));
@@ -91,8 +98,18 @@ class NachrichtenService {
 	}
 
 	CommandReference sendRueckfrageAnswer(final ReplyNachricht nachricht) {
+		validate(nachricht);
+
 		var nachrichtEvent = nachrichtEventService.getNachrichtEventById(nachricht.nachrichtEventId());
 
 		return nachrichtenRemoteService.sendAnswer(nachricht, nachrichtEvent.address());
 	}
+
+	private void validate(final ReplyNachricht nachricht) {
+		var validationResult = validator.validate(nachricht);
+
+		if (!validationResult.isEmpty()) {
+			throw new TechnicalException(new ConstraintViolationException(validationResult));
+		}
+	}
 }
diff --git a/server/src/main/java/de/ozgcloud/antragsraum/nachricht/ReplyNachricht.java b/server/src/main/java/de/ozgcloud/antragsraum/nachricht/ReplyNachricht.java
index 22d1db48c6ef1c9edf758e66faaeb6a68b27c953..6c712bb36bcb222275c89a2e163f74c33d404966 100644
--- a/server/src/main/java/de/ozgcloud/antragsraum/nachricht/ReplyNachricht.java
+++ b/server/src/main/java/de/ozgcloud/antragsraum/nachricht/ReplyNachricht.java
@@ -25,8 +25,6 @@ import java.util.List;
 import jakarta.validation.constraints.NotEmpty;
 import jakarta.validation.constraints.Size;
 
-import org.springframework.validation.annotation.Validated;
-
 import com.fasterxml.jackson.annotation.JsonInclude;
 
 import de.ozgcloud.antragsraum.attachments.OzgFile;
@@ -35,24 +33,26 @@ import lombok.Builder;
 
 @JsonInclude(JsonInclude.Include.NON_NULL)
 @Builder(toBuilder = true)
-@Validated
 record ReplyNachricht(
+  @Size(max = 36, message = "An id is too long")
   @Schema(description = "The id of the postfach",
 	name = "postfachId",
 	type = "string",
 	example = "28721c6f-b78f-4d5c-a048-19fd2fc429d2"
   )
   String postfachId,
+  @Size(max = 24, message = "An id id too long")
   @Schema(description = "The id of the NachrichtEvent in the InfoManager the reply belongs to",
 	name = "nachrichtEventId",
 	type = "string",
 	example = "60af924b4f1a2560298b4567"
   )
   String nachrichtEventId,
-  @Schema(description = "The encoded address of the OZG-Cloud instanz the reply belongs to",
+  @Size(max = 24, message = "An id is too long")
+  @Schema(description = "The id of the Rückfrage",
 	name = "id",
 	type = "string",
-	example = "c3RhdGljOi8vdXRvcGlhLnNoLm96Zy1jbG91ZC5kZTo5MDkw"
+	example = "66af924b4f1a2560298b45ab"
   )
   @NotEmpty
   String id,
@@ -61,8 +61,20 @@ record ReplyNachricht(
 	type = "string",
 	example = "Im Anhang sind die Dokumente"
   )
-  @Size(max = 500000, message = "message too long")
+  @Size(max = 100_000, message = "Message text too long")
   String message,
   @Schema(description = "The list of the attached file ids. Only the file id is used when sending the reply, The other values are set by the backend")
+  @Size(max = 20)
   List<OzgFile> attachments) {
+
+	@Override
+	public String toString() {
+		return "ReplyNachricht{" +
+		  "postfachId='" + postfachId + '\'' +
+		  ", nachrichtEventId='" + nachrichtEventId + '\'' +
+		  ", id='" + id + '\'' +
+		  ", attachments=" + attachments +
+		  ", message=<...masked for privacy..>" +
+		  '}';
+	}
 }
diff --git a/server/src/main/java/de/ozgcloud/antragsraum/nachricht/Rueckfrage.java b/server/src/main/java/de/ozgcloud/antragsraum/nachricht/Rueckfrage.java
index 59717183afe5c98c86a254ead54cad318818ec1e..b4d28c83bf61673b91420dc13ba1509b3684aa3a 100644
--- a/server/src/main/java/de/ozgcloud/antragsraum/nachricht/Rueckfrage.java
+++ b/server/src/main/java/de/ozgcloud/antragsraum/nachricht/Rueckfrage.java
@@ -63,4 +63,16 @@ class Rueckfrage {
 	  type = "ReplyNachricht"
 	)
 	List<ReplyNachricht> antworten;
+
+	@Override
+	public String toString() {
+		return "Rueckfrage{" +
+		  "title='" + title + '\'' +
+		  ", status='" + status + '\'' +
+		  ", nachricht=<...masked for privacy..>" +
+		  ", accessible=" + accessible +
+		  ", trustLevel='" + trustLevel + '\'' +
+		  ", antworten=<...masked for privacy..>" +
+		  '}';
+	}
 }
diff --git a/server/src/main/java/de/ozgcloud/antragsraum/nachricht/RueckfrageHeaderMapper.java b/server/src/main/java/de/ozgcloud/antragsraum/nachricht/RueckfrageHeaderMapper.java
index a69bb75c3e63bb9efcedf89b1223a57af4248937..bddbc759e27ed37a944ea46994bedb132b263e19 100644
--- a/server/src/main/java/de/ozgcloud/antragsraum/nachricht/RueckfrageHeaderMapper.java
+++ b/server/src/main/java/de/ozgcloud/antragsraum/nachricht/RueckfrageHeaderMapper.java
@@ -37,7 +37,6 @@ class RueckfrageHeaderMapper {
 	}
 
 	private static RueckfrageHeader fromNachrichtHeader(NachrichtHeader nachrichtHeader) {
-		LOG.info("Mapping NachrichtHeader [{}] to RueckfrageHeader", nachrichtHeader);
 		return RueckfrageHeader.builder()
 		  .title(nachrichtHeader.title())
 		  .nachrichtHeader(nachrichtHeader)
diff --git a/server/src/main/java/de/ozgcloud/antragsraum/nachricht/RueckfrageMapper.java b/server/src/main/java/de/ozgcloud/antragsraum/nachricht/RueckfrageMapper.java
index 6bc1c707b2a09166962422a0547c4473151eba2f..d147533bde77d21fb1a2385df12dcbaaba24e714 100644
--- a/server/src/main/java/de/ozgcloud/antragsraum/nachricht/RueckfrageMapper.java
+++ b/server/src/main/java/de/ozgcloud/antragsraum/nachricht/RueckfrageMapper.java
@@ -32,7 +32,6 @@ class RueckfrageMapper {
 	static Rueckfrage fromNachricht(Nachricht nachricht) {
 		var clerkNachricht = nachricht.toBuilder().replyNachrichten(null).build();
 
-		LOG.info("Mapping Nachricht [{}] to Rueckfrage", nachricht);
 		return Rueckfrage.builder()
 		  .title(nachricht.title())
 		  .nachricht(clerkNachricht)
diff --git a/server/src/main/java/de/ozgcloud/antragsraum/security/AuthenticationController.java b/server/src/main/java/de/ozgcloud/antragsraum/security/AuthenticationController.java
index c6a4e7d12675b146c54e96bc356e112aee3361ce..4bb26808b2b6fdd104747a3703164c3edfce0ec7 100644
--- a/server/src/main/java/de/ozgcloud/antragsraum/security/AuthenticationController.java
+++ b/server/src/main/java/de/ozgcloud/antragsraum/security/AuthenticationController.java
@@ -20,10 +20,15 @@
 
 package de.ozgcloud.antragsraum.security;
 
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+
 import org.springframework.http.HttpStatus;
 import org.springframework.http.MediaType;
 import org.springframework.http.ResponseEntity;
 import org.springframework.security.core.annotation.AuthenticationPrincipal;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler;
 import org.springframework.web.bind.annotation.GetMapping;
 import org.springframework.web.bind.annotation.PostMapping;
 import org.springframework.web.bind.annotation.RequestBody;
@@ -40,6 +45,7 @@ class AuthenticationController {
 
 	private final InMemoryUserDetailService userDetailsService;
 	private final JwtTokenProvider tokenProvider;
+	private final SecurityContextLogoutHandler logoutHandler;
 
 	@PostMapping(path = "/authenticate", produces = MediaType.TEXT_PLAIN_VALUE, consumes = MediaType.APPLICATION_JSON_VALUE)
 	ResponseEntity<String> login(@RequestBody AuthCode data) {
@@ -68,8 +74,13 @@ class AuthenticationController {
 	}
 
 	@GetMapping(path = "/logout")
-	ResponseEntity<String> logout(@AuthenticationPrincipal User user) {
-		userDetailsService.logout(user);
+	ResponseEntity<String> logout(HttpServletRequest request, HttpServletResponse response, @AuthenticationPrincipal User user) {
+		if (user != null) {
+			userDetailsService.logout(user);
+		}
+
+		var authentication = SecurityContextHolder.getContext().getAuthentication();
+		logoutHandler.logout(request, response, authentication);
 
 		return ResponseEntity.ok("Success");
 	}
diff --git a/server/src/main/java/de/ozgcloud/antragsraum/security/AuthenticationHelper.java b/server/src/main/java/de/ozgcloud/antragsraum/security/AuthenticationHelper.java
index c7d9ceb8bb1e560f27745b3dcc0d4fce53a48498..6f20f2ffd44b48379d2517fbe28d4c56e03c971c 100644
--- a/server/src/main/java/de/ozgcloud/antragsraum/security/AuthenticationHelper.java
+++ b/server/src/main/java/de/ozgcloud/antragsraum/security/AuthenticationHelper.java
@@ -57,8 +57,6 @@ public class AuthenticationHelper {
 			tokenValue = user.getSamlToken();
 		}
 
-		LOG.debug("Returning SAML Token:[{}]", tokenValue);
-
 		return tokenValue;
 	}
 }
diff --git a/server/src/main/java/de/ozgcloud/antragsraum/security/JwtTokenFilter.java b/server/src/main/java/de/ozgcloud/antragsraum/security/JwtTokenFilter.java
index 84fb44283d5a860543c67f598bc4acf53782e07e..866c00c3968dca9881b5790ed5be38b66d3c42bc 100644
--- a/server/src/main/java/de/ozgcloud/antragsraum/security/JwtTokenFilter.java
+++ b/server/src/main/java/de/ozgcloud/antragsraum/security/JwtTokenFilter.java
@@ -20,16 +20,15 @@
 
 package de.ozgcloud.antragsraum.security;
 
+import java.io.IOException;
+import java.util.NoSuchElementException;
+import java.util.Optional;
+
 import jakarta.servlet.FilterChain;
 import jakarta.servlet.ServletException;
 import jakarta.servlet.http.HttpServletRequest;
 import jakarta.servlet.http.HttpServletResponse;
-import java.io.IOException;
-import java.util.NoSuchElementException;
-import java.util.Optional;
-import lombok.NonNull;
-import lombok.RequiredArgsConstructor;
-import lombok.extern.log4j.Log4j2;
+
 import org.springframework.security.access.AccessDeniedException;
 import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
 import org.springframework.security.core.Authentication;
@@ -41,53 +40,56 @@ import org.springframework.stereotype.Component;
 import org.springframework.util.StringUtils;
 import org.springframework.web.filter.OncePerRequestFilter;
 
+import lombok.NonNull;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.log4j.Log4j2;
+
 @Log4j2
 @RequiredArgsConstructor
 @Component
 public class JwtTokenFilter extends OncePerRequestFilter {
-    public static final String TOKEN_HEADER = "Authorization";
-    public static final String TOKEN_PREFIX = "Bearer ";
+	public static final String TOKEN_HEADER = "Authorization";
+	public static final String TOKEN_PREFIX = "Bearer ";
 
-    private final UserDetailsService userDetailsService;
-    private final JwtTokenVerifier jwtTokenVerifier;
+	private final UserDetailsService userDetailsService;
+	private final JwtTokenVerifier jwtTokenVerifier;
 
-    @Override
-    protected void doFilterInternal(@NonNull HttpServletRequest request, @NonNull HttpServletResponse response,
-                                    @NonNull FilterChain chain) throws ServletException, IOException {
-        try {
-            getJwtFromRequest(request)
-                .flatMap(jwtTokenVerifier::validate).flatMap(jwsParser -> getJwtFromRequest(request)
-                    .flatMap(token -> jwtTokenVerifier.getJws(jwsParser, token)))
-                .ifPresent(jws -> setUserToSecurityContext(
-                    createAuthentication(request, jws.getPayload().getSubject(), getJwtFromRequest(request))));
-        } catch (NoSuchElementException | IllegalStateException e) {
-            LOG.debug("Cannot set user authentication", e);
-            throw new AccessDeniedException("User not found or invalid token");
-        }
+	@Override
+	protected void doFilterInternal(@NonNull HttpServletRequest request, @NonNull HttpServletResponse response,
+	  @NonNull FilterChain chain) throws ServletException, IOException {
+		try {
+			getJwtFromRequest(request)
+			  .flatMap(jwtTokenVerifier::validate).flatMap(jwsParser -> getJwtFromRequest(request)
+				.flatMap(token -> jwtTokenVerifier.getJws(jwsParser, token)))
+			  .ifPresent(jws -> setUserToSecurityContext(
+				createAuthentication(request, jws.getPayload().getSubject(), getJwtFromRequest(request))));
+		} catch (NoSuchElementException | IllegalStateException e) {
+			LOG.debug("Cannot set user authentication", e);
+			throw new AccessDeniedException("User not found or invalid token");
+		}
 
-        chain.doFilter(request, response);
-    }
+		chain.doFilter(request, response);
+	}
 
-    Authentication createAuthentication(@NonNull HttpServletRequest request, String username,
-                                        Optional<String> tokenOptional) {
-        UserDetails userDetails = userDetailsService.loadUserByUsername(username);
-        String token = tokenOptional.orElse("");
-        UsernamePasswordAuthenticationToken authentication =
-            new UsernamePasswordAuthenticationToken(userDetails, token, userDetails.getAuthorities());
-        authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
+	Authentication createAuthentication(@NonNull HttpServletRequest request, String username, Optional<String> tokenOptional) {
+		UserDetails userDetails = userDetailsService.loadUserByUsername(username);
+		String token = tokenOptional.orElse("");
+		UsernamePasswordAuthenticationToken authentication =
+		  new UsernamePasswordAuthenticationToken(userDetails, token, userDetails.getAuthorities());
+		authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
 
-        return authentication;
-    }
+		return authentication;
+	}
 
-    void setUserToSecurityContext(Authentication authentication) {
-        SecurityContextHolder.getContext().setAuthentication(authentication);
-    }
+	void setUserToSecurityContext(Authentication authentication) {
+		SecurityContextHolder.getContext().setAuthentication(authentication);
+	}
 
-    Optional<String> getJwtFromRequest(HttpServletRequest request) {
-        String tokenHeader = request.getHeader(TOKEN_HEADER);
-        if (StringUtils.hasText(tokenHeader) && tokenHeader.startsWith(TOKEN_PREFIX)) {
-            return Optional.of(tokenHeader.replace(TOKEN_PREFIX, "").trim());
-        }
-        return Optional.empty();
-    }
+	Optional<String> getJwtFromRequest(HttpServletRequest request) {
+		String tokenHeader = request.getHeader(TOKEN_HEADER);
+		if (StringUtils.hasText(tokenHeader) && tokenHeader.startsWith(TOKEN_PREFIX)) {
+			return Optional.of(tokenHeader.replace(TOKEN_PREFIX, "").trim());
+		}
+		return Optional.empty();
+	}
 }
diff --git a/server/src/main/resources/application-local.yml b/server/src/main/resources/application-local.yml
index dd87a88dc3cbdae321115bfcc17871c9faa65746..7faf55c9133054c16a6ef664f0a9b94160aa1237 100644
--- a/server/src/main/resources/application-local.yml
+++ b/server/src/main/resources/application-local.yml
@@ -36,16 +36,16 @@ spring:
       relyingparty:
         registration:
           bayernid:
-            entity-id: http://mock-idp
+            entity-id: https://sso.dev.by.ozg-cloud.de/realms/by-antragsraum-idp
             signing:
               credentials:
-                - private-key-location: "classpath:/mujina-local.key"
-                  certificate-location: "classpath:/mujina-local.crt"
+                - private-key-location: "classpath:/bayernid-dev-sign.key"
+                  certificate-location: "classpath:/bayernid-dev-sign.crt"
             decryption:
               credentials:
-                - private-key-location: "classpath:/mujina-local.key"
-                  certificate-location: "classpath:/mujina-local.crt"
+                - private-key-location: "classpath:/bayernid-dev-sign.key"
+                  certificate-location: "classpath:/bayernid-dev-sign.crt"
             assertingparty:
               singlesignon:
                 sign-request: true
-              metadata-uri: "http://localhost:8088/metadata"
\ No newline at end of file
+              metadata-uri: "classpath:/metadata/keycloak-saml-metadata.xml"
\ No newline at end of file
diff --git a/server/src/main/resources/metadata/okta-idp.xml b/server/src/main/resources/metadata/okta-idp.xml
deleted file mode 100644
index 2b294cce4fa05fb4e1b32e86fdd9180ba0728ad9..0000000000000000000000000000000000000000
--- a/server/src/main/resources/metadata/okta-idp.xml
+++ /dev/null
@@ -1,41 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<md:EntityDescriptor entityID="http://www.okta.com/exkeeichkhdEpMLcK5d7"
-                     xmlns:md="urn:oasis:names:tc:SAML:2.0:metadata">
-    <md:IDPSSODescriptor WantAuthnRequestsSigned="false"
-                         protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
-        <md:KeyDescriptor use="signing">
-            <ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
-                <ds:X509Data>
-                    <ds:X509Certificate>MIIDqDCCApCgAwIBAgIGAYz3208uMA0GCSqGSIb3DQEBCwUAMIGUMQswCQYDVQQGEwJVUzETMBEG
-                        A1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNU2FuIEZyYW5jaXNjbzENMAsGA1UECgwET2t0YTEU
-                        MBIGA1UECwwLU1NPUHJvdmlkZXIxFTATBgNVBAMMDGRldi0wNDg0OTQ4NzEcMBoGCSqGSIb3DQEJ
-                        ARYNaW5mb0Bva3RhLmNvbTAeFw0yNDAxMTEwOTI4NTRaFw0zNDAxMTEwOTI5NTRaMIGUMQswCQYD
-                        VQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNU2FuIEZyYW5jaXNjbzENMAsG
-                        A1UECgwET2t0YTEUMBIGA1UECwwLU1NPUHJvdmlkZXIxFTATBgNVBAMMDGRldi0wNDg0OTQ4NzEc
-                        MBoGCSqGSIb3DQEJARYNaW5mb0Bva3RhLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC
-                        ggEBAMYySzBZX6VmleW0+259bgvQdfruEGwbS1Jg0TKmBTO4gIhBZzyaQY5kq1EkuVrrg3TMnF0G
-                        Gws2LjF99t4L93d4wBX0WrrehEttvPgiz3qMcjlgqbynM1zTw9w/TbO61lFf6kcwaSu6wbTFeMwB
-                        l5WYBRPg/LcUU0Qddw3NKuVXk0BX5iPCUP+nWczmkrpByfYPYFaMUcFxrY0NnMCQ6/VcrZfPN2yu
-                        ZX+fAV2Bx56o4QD73PCB6XXlOLo6p3PlqGpIMf/NDdBB3ACtIbQuc+l7Bm8dvjZOyV665I6jq9ZP
-                        MOX8Qo8SKIZz2Hh9AvHSwIKVW4aKTKxQ0LQ+I78Xr9UCAwEAATANBgkqhkiG9w0BAQsFAAOCAQEA
-                        Y+A+hCMIfjICOzdu6cD8AOrm2PIo1zcMR+mmUKt1l+Gw8gku//uIRc55N+Gga6/rYl6Og2fe+uip
-                        qzQMjXTtmizI+unukVZeJDkfy8sBwNvuikHs2no0HpS7uqgb1gvVKhvyelmD6UsW0mef0b+QRJJU
-                        rNn3LBGYYw4sB3aKTQlmhPE2M7dKbV3N6dxjPneZf4lNSEhsXFBnAEcxczqy37SFCj5JcDInmwwF
-                        2gC0dVqEL7S+JFy6xAyDfn4iBoO+DqgMS6OOAN6kvvR5PB+RYnTqT1lWfcqkeHR9fMfpEGPlxgJ7
-                        MrgVs3cAR5kJKpSbdNyoBWswRQlm+jX2wHJK0Q==
-                    </ds:X509Certificate>
-                </ds:X509Data>
-            </ds:KeyInfo>
-        </md:KeyDescriptor>
-        <md:SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
-                                Location="https://dev-04849487.okta.com/app/dev-04849487_antragsraumsaml_1/exkeeichkhdEpMLcK5d7/slo/saml"/>
-        <md:SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"
-                                Location="https://dev-04849487.okta.com/app/dev-04849487_antragsraumsaml_1/exkeeichkhdEpMLcK5d7/slo/saml"/>
-        <md:NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified</md:NameIDFormat>
-        <md:NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress</md:NameIDFormat>
-        <md:SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
-                                Location="https://dev-04849487.okta.com/app/dev-04849487_antragsraumsaml_1/exkeeichkhdEpMLcK5d7/sso/saml"/>
-        <md:SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"
-                                Location="https://dev-04849487.okta.com/app/dev-04849487_antragsraumsaml_1/exkeeichkhdEpMLcK5d7/sso/saml"/>
-    </md:IDPSSODescriptor>
-</md:EntityDescriptor>
\ No newline at end of file
diff --git a/server/src/test/helm/deployment_env_test.yaml b/server/src/test/helm/deployment_env_test.yaml
index 9a9a7c7d5df4fe0988dbce806ffc71792e8cfb3f..d53e5e80f52e9a26ab9970466cab569d41f0d1af 100644
--- a/server/src/test/helm/deployment_env_test.yaml
+++ b/server/src/test/helm/deployment_env_test.yaml
@@ -192,4 +192,34 @@ tests:
           path: spec.template.spec.containers[0].env
           content: 
              name: SPRING_SECURITY_SAML2_RELYINGPARTY_REGISTRATION_BAYERNID_ENTITY-ID
-             value: https://sso.dev.de/realms/by-antragsraum-idp
\ No newline at end of file
+             value: https://sso.dev.de/realms/by-antragsraum-idp
+
+  - it: should contain default spring servlet envs 
+    asserts:
+      - contains:
+          path: spec.template.spec.containers[0].env
+          content: 
+            name: SPRING_SERVLET_MULTIPART_MAX-FILE-SIZE
+            value: 30MB
+      - contains:
+          path: spec.template.spec.containers[0].env
+          content: 
+            name: SPRING_SERVLET_MULTIPART_MAX-REQUEST-SIZE
+            value: 50MB
+  - it: should set spring servlet envs 
+    set: 
+      antragsraum:
+        servlet:
+          maxRequestSize: 40MB
+          maxFileSize: 35MB
+    asserts:
+      - contains:
+          path: spec.template.spec.containers[0].env
+          content: 
+            name: SPRING_SERVLET_MULTIPART_MAX-FILE-SIZE
+            value: 35MB
+      - contains:
+          path: spec.template.spec.containers[0].env
+          content: 
+            name: SPRING_SERVLET_MULTIPART_MAX-REQUEST-SIZE
+            value: 40MB
\ No newline at end of file
diff --git a/server/src/test/java/de/ozgcloud/antragsraum/SecurityConfigurationTest.java b/server/src/test/java/de/ozgcloud/antragsraum/SecurityConfigurationTest.java
index 8ba32440719497f65c3c470778c4bb8898fd553d..cd4b8c89ddaed198489fb792692d79632c81731b 100644
--- a/server/src/test/java/de/ozgcloud/antragsraum/SecurityConfigurationTest.java
+++ b/server/src/test/java/de/ozgcloud/antragsraum/SecurityConfigurationTest.java
@@ -31,6 +31,7 @@ import org.mockito.junit.jupiter.MockitoExtension;
 import org.springframework.security.config.annotation.web.builders.HttpSecurity;
 import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository;
 import org.springframework.security.saml2.provider.service.web.authentication.Saml2AuthenticationRequestResolver;
+import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler;
 import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
 
 import de.ozgcloud.antragsraum.security.AntragsraumLogoutSuccessHandler;
@@ -40,6 +41,7 @@ import de.ozgcloud.antragsraum.security.BayernIdSaml2Extension;
 import de.ozgcloud.antragsraum.security.InMemoryUserDetailService;
 import de.ozgcloud.antragsraum.security.JwtTokenFilter;
 import de.ozgcloud.antragsraum.security.SamlUrlAuthenticationSuccessHandler;
+import de.ozgcloud.antragsraum.security.SecurityProvider;
 
 @ExtendWith(MockitoExtension.class)
 class SecurityConfigurationTest {
@@ -65,6 +67,20 @@ class SecurityConfigurationTest {
 		securityConfiguration = new SecurityConfiguration(bayernIdSaml2Extension, properties, userDetailsService, tokenFilter, corsProperties);
 	}
 
+	@Test
+	void shouldCreateSecurityProvider() {
+		SecurityProvider securityProvider = securityConfiguration.securityProvider();
+
+		assertThat(securityProvider).isNotNull();
+	}
+
+	@Test
+	void shouldCreateLogoutHandler() {
+		SecurityContextLogoutHandler logoutHandler = securityConfiguration.logoutHandler();
+
+		assertThat(logoutHandler).isNotNull();
+	}
+
 	@Nested
 	class TestHttpSecurity {
 		private HttpSecurity httpSecurity;
@@ -77,6 +93,7 @@ class SecurityConfigurationTest {
 			when(httpSecurity.saml2Logout(any())).thenReturn(httpSecurity);
 			when(httpSecurity.addFilterBefore(any(), any())).thenReturn(httpSecurity);
 			when(httpSecurity.csrf(any())).thenReturn(httpSecurity);
+			when(httpSecurity.headers(any())).thenReturn(httpSecurity);
 		}
 
 		@Test
@@ -99,6 +116,13 @@ class SecurityConfigurationTest {
 
 			verify(httpSecurity).saml2Logout(any());
 		}
+
+		@Test
+		void shouldSetupHeaders() throws Exception {
+			securityConfiguration.filterChain(httpSecurity);
+
+			verify(httpSecurity, times(2)).headers(any());
+		}
 	}
 
 	@Nested
@@ -123,8 +147,10 @@ class SecurityConfigurationTest {
 		void setUp() throws Exception {
 			when(security.authorizeHttpRequests(any())).thenReturn(security);
 			when(security.addFilterBefore(any(), any())).thenReturn(security);
+			when(security.headers(any())).thenReturn(security);
 			when(security.saml2Login(any())).thenReturn(security);
 			when(security.saml2Logout(any())).thenReturn(security);
+			when(security.csrf(any())).thenReturn(security);
 		}
 
 		@Test
diff --git a/server/src/test/java/de/ozgcloud/antragsraum/attachments/FileControllerTestConfiguration.java b/server/src/test/java/de/ozgcloud/antragsraum/attachments/FileControllerTestConfiguration.java
index a705fab72494e0c4296199d064f862e32ce26fd0..006f96223eda9846b73a87120795f8541414d0c1 100644
--- a/server/src/test/java/de/ozgcloud/antragsraum/attachments/FileControllerTestConfiguration.java
+++ b/server/src/test/java/de/ozgcloud/antragsraum/attachments/FileControllerTestConfiguration.java
@@ -36,10 +36,10 @@ import de.ozgcloud.antragsraum.WebConfiguration;
 @EnableAsync
 @ComponentScan(value = { "de.ozgcloud.antragsraum.attachments", "de.ozgcloud.antragsraum.common" },
   excludeFilters = @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE,
-	classes = FileRemoteServiceTestConfiguration.class))
+	classes = { FileRemoteServiceTestConfiguration.class, FileRestClientTestConfiguration.class }))
 @Configuration
 public class FileControllerTestConfiguration {
-	
+
 	RestClient.Builder restClientBuilder() {
 		return RestClient.builder().baseUrl("http://localhost:8080");
 	}
diff --git a/server/src/test/java/de/ozgcloud/antragsraum/attachments/FileRemoteServiceTestConfiguration.java b/server/src/test/java/de/ozgcloud/antragsraum/attachments/FileRemoteServiceTestConfiguration.java
index 196a35a9000ff34802f8fd26c87c437fb4ca0d73..532bc9f603d52860ceab5392bd79cb7ec5b74c34 100644
--- a/server/src/test/java/de/ozgcloud/antragsraum/attachments/FileRemoteServiceTestConfiguration.java
+++ b/server/src/test/java/de/ozgcloud/antragsraum/attachments/FileRemoteServiceTestConfiguration.java
@@ -34,7 +34,7 @@ import de.ozgcloud.antragsraum.events.NachrichtEventService;
 
 @ComponentScan(value = { "de.ozgcloud.antragsraum.attachments", "de.ozgcloud.antragsraum.common" },
   excludeFilters = @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE,
-	classes = { FileControllerTestConfiguration.class, FileController.class }))
+	classes = { FileControllerTestConfiguration.class, FileController.class, FileRestClientTestConfiguration.class }))
 @Configuration
 public class FileRemoteServiceTestConfiguration {
 	@Bean
diff --git a/server/src/test/java/de/ozgcloud/antragsraum/attachments/FileRestClientTest.java b/server/src/test/java/de/ozgcloud/antragsraum/attachments/FileRestClientTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..45af2c4e9e4efcae7fded3f6becc5cfe972ca858
--- /dev/null
+++ b/server/src/test/java/de/ozgcloud/antragsraum/attachments/FileRestClientTest.java
@@ -0,0 +1,153 @@
+/*
+ * Copyright (c) 2024.   Das Land Schleswig-Holstein vertreten durch den Ministerpräsidenten
+ * des Landes Schleswig-Holstein Staatskanzlei Abteilung Digitalisierung und zentrales IT-Management der Landesregierung.
+ *
+ * Lizenziert unter der EUPL, Version 1.2 oder - sobald
+ * diese von der Europäischen Kommission genehmigt wurden -
+ * Folgeversionen der EUPL ("Lizenz");
+ * Sie dürfen dieses Werk ausschließlich gemäß
+ * dieser Lizenz nutzen.
+ * Eine Kopie der Lizenz finden Sie hier:
+ *
+ * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
+ *
+ * Sofern nicht durch anwendbare Rechtsvorschriften
+ * gefordert oder in schriftlicher Form vereinbart, wird
+ * die unter der Lizenz verbreitete Software "so wie sie
+ * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN -
+ * ausdrücklich oder stillschweigend - verbreitet.
+ * Die sprachspezifischen Genehmigungen und Beschränkungen
+ * unter der Lizenz sind dem Lizenztext zu entnehmen.
+ */
+
+package de.ozgcloud.antragsraum.attachments;
+
+import static de.ozgcloud.antragsraum.attachments.FileRestClient.*;
+import static org.assertj.core.api.Assertions.*;
+import static org.mockito.Mockito.*;
+import static org.springframework.test.web.client.match.MockRestRequestMatchers.*;
+import static org.springframework.test.web.client.response.MockRestResponseCreators.*;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.UUID;
+import java.util.concurrent.CompletableFuture;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.web.client.RestClientTest;
+import org.springframework.boot.test.web.client.MockServerRestClientCustomizer;
+import org.springframework.http.MediaType;
+import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
+import org.springframework.test.web.client.MockRestServiceServer;
+import org.springframework.web.client.RestClient;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+import de.ozgcloud.antragsraum.common.TechnicalException;
+import de.ozgcloud.antragsraum.proxy.AntragraumproxyGrpcFindFilesResponse;
+
+@SpringJUnitConfig(classes = { FileRestClientTestConfiguration.class })
+@RestClientTest(FileRestClient.class)
+class FileRestClientTest {
+	final static String ADDRESS = "http://localhost:8382";
+	public static final String FILE_ID = "test";
+	private FileRestClient fileRestClient;
+
+	@Autowired
+	private RestClient.Builder restClientBuilder;
+
+	@Autowired
+	private MockServerRestClientCustomizer customizer;
+
+	private MockRestServiceServer server;
+
+	private final ObjectMapper objectMapper = new ObjectMapper();
+
+	@BeforeEach
+	void setupRestClient() {
+		customizer.customize(restClientBuilder);
+		server = customizer.getServer(restClientBuilder);
+		fileRestClient = new FileRestClient(restClientBuilder.build());
+	}
+
+	@Nested
+	class TestLoadingFileMetadata {
+		private final String fileFileUri = ADDRESS + FIND_FILE_METADATA_API.replace("{fileId}", FILE_ID);
+
+		@BeforeEach
+		void setup() throws JsonProcessingException {
+			server.expect(requestTo(fileFileUri))
+			  .andRespond(withSuccess(objectMapper.writeValueAsString(createFindFileMetadataResponse()), MediaType.APPLICATION_JSON));
+		}
+
+		private AntragraumproxyGrpcFindFilesResponse createFindFileMetadataResponse() {
+			return AntragraumproxyGrpcFindFilesResponseTestFactory.create();
+		}
+
+		@Test
+		void shouldFindFileMetadata() {
+			var fileMetadata = fileRestClient.findBinaryFilesMetaData(FILE_ID, ADDRESS + FIND_FILE_METADATA_API);
+
+			assertThat(fileMetadata).isNotNull();
+		}
+	}
+
+	@Nested
+	class TestUploadingFile {
+
+		@Test
+		void shouldUploadFile() throws JsonProcessingException {
+			server.expect(requestTo(ADDRESS + UPLOAD_FILE_API))
+			  .andRespond(withSuccess(objectMapper.writeValueAsString(UUID.randomUUID().toString()), MediaType.APPLICATION_JSON));
+
+			CompletableFuture<String> fileId = new CompletableFuture<>();
+			fileRestClient.uploadFile(OzgUploadFileTestFactory.create(), ADDRESS + UPLOAD_FILE_API, fileId);
+
+			assertThat(fileId).isCompleted();
+		}
+
+		@Test
+		void shouldHandleEmptyUploadFile() {
+			server.expect(requestTo(ADDRESS + UPLOAD_FILE_API))
+			  .andRespond(withSuccess("", MediaType.APPLICATION_JSON));
+
+			CompletableFuture<String> fileId = new CompletableFuture<>();
+			fileRestClient.uploadFile(OzgUploadFileTestFactory.create(), ADDRESS + UPLOAD_FILE_API, fileId);
+
+			assertThat(fileId).withFailMessage(NO_FILE_ID_RECEIVED_MESSAGE).isCompletedExceptionally();
+		}
+	}
+
+	@Nested
+	class TestDownloadFileContent {
+		private final String fileContentUri = ADDRESS + GET_FILE_CONTENT_API.replace("{fileId}", FILE_ID);
+
+		@BeforeEach
+		void setup() throws IOException {
+			server.expect(requestTo(fileContentUri))
+			  .andRespond(withSuccess(OzgUploadFileTestFactory.MULTIPART_FILE.getBytes(), MediaType.APPLICATION_OCTET_STREAM));
+		}
+
+		@Test
+		void shouldDownloadFileContent() throws IOException {
+			var out = new ByteArrayOutputStream();
+			fileRestClient.downloadFileContent(FILE_ID, out, ADDRESS + GET_FILE_CONTENT_API);
+			var res = out.toByteArray();
+
+			assertThat(res).isEqualTo(OzgUploadFileTestFactory.MULTIPART_FILE.getBytes());
+		}
+
+		@Test
+		void shouldDownloadFileContentIoException() {
+			var out = mock(ByteArrayOutputStream.class);
+			doThrow(IOException.class).when(out).write(any(byte[].class), anyInt(), anyInt());
+
+			assertThatExceptionOfType(TechnicalException.class).isThrownBy(
+			  () -> fileRestClient.downloadFileContent(FILE_ID, out, ADDRESS + GET_FILE_CONTENT_API));
+		}
+	}
+}
\ No newline at end of file
diff --git a/server/src/test/java/de/ozgcloud/antragsraum/attachments/FileRestClientTestConfiguration.java b/server/src/test/java/de/ozgcloud/antragsraum/attachments/FileRestClientTestConfiguration.java
new file mode 100644
index 0000000000000000000000000000000000000000..73d964c9b5375a27e8ba58413ea843d2324bb41a
--- /dev/null
+++ b/server/src/test/java/de/ozgcloud/antragsraum/attachments/FileRestClientTestConfiguration.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (c) 2023-2024.   Das Land Schleswig-Holstein vertreten durch den Ministerpräsidenten
+ * des Landes Schleswig-Holstein Staatskanzlei Abteilung Digitalisierung und zentrales IT-Management der Landesregierung.
+ *
+ * Lizenziert unter der EUPL, Version 1.2 oder - sobald
+ * diese von der Europäischen Kommission genehmigt wurden -
+ * Folgeversionen der EUPL ("Lizenz");
+ * Sie dürfen dieses Werk ausschließlich gemäß
+ * dieser Lizenz nutzen.
+ * Eine Kopie der Lizenz finden Sie hier:
+ *
+ * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
+ *
+ * Sofern nicht durch anwendbare Rechtsvorschriften
+ * gefordert oder in schriftlicher Form vereinbart, wird
+ * die unter der Lizenz verbreitete Software "so wie sie
+ * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN -
+ * ausdrücklich oder stillschweigend - verbreitet.
+ * Die sprachspezifischen Genehmigungen und Beschränkungen
+ * unter der Lizenz sind dem Lizenztext zu entnehmen.
+ */
+package de.ozgcloud.antragsraum.attachments;
+
+import jakarta.validation.Validator;
+
+import org.springframework.boot.test.mock.mockito.MockBean;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.ComponentScan;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.FilterType;
+import org.springframework.scheduling.annotation.EnableAsync;
+import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
+import org.springframework.web.client.RestClient;
+
+import de.ozgcloud.antragsraum.WebConfiguration;
+import de.ozgcloud.antragsraum.events.NachrichtEventService;
+
+@EnableAsync
+@ComponentScan(value = { "de.ozgcloud.antragsraum.attachments", "de.ozgcloud.antragsraum.common" },
+  excludeFilters = @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE,
+	classes = { FileControllerTestConfiguration.class, FileController.class, FileRemoteServiceTestConfiguration.class }))
+@Configuration
+public class FileRestClientTestConfiguration {
+	@MockBean
+	FileService fileService;
+
+	@MockBean
+	NachrichtEventService nachrichtEventService;
+
+	@Bean
+	public RestClient.Builder restClientBuilder() {
+		return RestClient.builder().baseUrl(FileRestClientTest.ADDRESS);
+	}
+
+	@Bean(name = WebConfiguration.NACHRICHT_REST_CLIENT)
+	RestClient fileRestClient() {
+		return restClientBuilder().build();
+	}
+
+	@Bean
+	public Validator defaultValidator() {
+		return new LocalValidatorFactoryBean();
+	}
+
+	@MockBean
+	VirusScannerClient virusScannerClient;
+
+}
diff --git a/server/src/test/java/de/ozgcloud/antragsraum/attachments/FileValidatorTest.java b/server/src/test/java/de/ozgcloud/antragsraum/attachments/FileValidatorTest.java
index 00a40704d540c5ead0256ed8ca0cd0d692c266ca..97ef3384b172d94a275d632bc07c5c5e15c3f4d1 100644
--- a/server/src/test/java/de/ozgcloud/antragsraum/attachments/FileValidatorTest.java
+++ b/server/src/test/java/de/ozgcloud/antragsraum/attachments/FileValidatorTest.java
@@ -70,13 +70,10 @@ class FileValidatorTest {
 
 	@Nested
 	class TestFileContentTypeIsValid {
-		@BeforeEach
-		void init() {
-			when(fileProperties.getBlockedContentTypes()).thenReturn(new String[] { INVALID_CONTENT_TYPE });
-		}
-
 		@Test
 		void shouldBeValid() {
+			when(fileProperties.getBlockedContentTypes()).thenReturn(new String[] { INVALID_CONTENT_TYPE });
+
 			var result = validator.fileContentTypeIsValid(CONTENT_TYPE);
 
 			assertThat(result).isTrue();
@@ -84,10 +81,19 @@ class FileValidatorTest {
 
 		@Test
 		void shouldBeInvalid() {
+			when(fileProperties.getBlockedContentTypes()).thenReturn(new String[] { INVALID_CONTENT_TYPE });
+
 			var result = validator.fileContentTypeIsValid(INVALID_CONTENT_TYPE);
 
 			assertThat(result).isFalse();
 		}
+
+		@Test
+		void shouldBeInvalidBecauseNotSet() {
+			var result = validator.fileContentTypeIsValid(null);
+
+			assertThat(result).isFalse();
+		}
 	}
 
 	@Nested
diff --git a/server/src/test/java/de/ozgcloud/antragsraum/command/CommandControllerTestConfiguration.java b/server/src/test/java/de/ozgcloud/antragsraum/command/CommandControllerTestConfiguration.java
index b02268cf28a716e0093fced02455c87577014b77..a2d0064a158a0e4f45f9e96485a0376792bd06c8 100644
--- a/server/src/test/java/de/ozgcloud/antragsraum/command/CommandControllerTestConfiguration.java
+++ b/server/src/test/java/de/ozgcloud/antragsraum/command/CommandControllerTestConfiguration.java
@@ -28,12 +28,15 @@ import static org.mockito.Mockito.*;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.ComponentScan;
 import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.FilterType;
 import org.springframework.scheduling.annotation.EnableAsync;
 import org.springframework.web.servlet.config.annotation.EnableWebMvc;
 
 @EnableWebMvc
 @EnableAsync
-@ComponentScan(value = { "de.ozgcloud.antragsraum.command", "de.ozgcloud.antragsraum.common" })
+@ComponentScan(value = { "de.ozgcloud.antragsraum.command", "de.ozgcloud.antragsraum.common" },
+  excludeFilters = @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE,
+	classes = { CommandRestClientTestConfiguration.class, CommandRemoteServiceTestConfiguration.class }))
 @Configuration
 public class CommandControllerTestConfiguration {
 	@Bean
diff --git a/server/src/test/java/de/ozgcloud/antragsraum/command/CommandRemoteServiceTestConfiguration.java b/server/src/test/java/de/ozgcloud/antragsraum/command/CommandRemoteServiceTestConfiguration.java
index ef94b3b59946692d5a9b21fe28cd71180fef2f86..7287f61a8b30a51d20ffb55778b98e0b3d8bd187 100644
--- a/server/src/test/java/de/ozgcloud/antragsraum/command/CommandRemoteServiceTestConfiguration.java
+++ b/server/src/test/java/de/ozgcloud/antragsraum/command/CommandRemoteServiceTestConfiguration.java
@@ -32,7 +32,7 @@ import de.ozgcloud.antragsraum.events.NachrichtEventService;
 @EnableAsync
 @ComponentScan(value = { "de.ozgcloud.antragsraum.command", "de.ozgcloud.antragsraum.common" },
   excludeFilters = @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE,
-	classes = { CommandController.class }))
+	classes = { CommandController.class, CommandRestClientTestConfiguration.class }))
 @Configuration
 public class CommandRemoteServiceTestConfiguration {
 
diff --git a/server/src/test/java/de/ozgcloud/antragsraum/command/CommandRestClientTest.java b/server/src/test/java/de/ozgcloud/antragsraum/command/CommandRestClientTest.java
index 507d97e308a44485fb0e6bc9643350a369f760e3..0a99eff18ed770c6f34b8d255f3542aede8d704d 100644
--- a/server/src/test/java/de/ozgcloud/antragsraum/command/CommandRestClientTest.java
+++ b/server/src/test/java/de/ozgcloud/antragsraum/command/CommandRestClientTest.java
@@ -21,33 +21,59 @@
  */
 package de.ozgcloud.antragsraum.command;
 
-import static org.mockito.Mockito.*;
+import static de.ozgcloud.antragsraum.command.CommandRestClient.*;
+import static org.assertj.core.api.Assertions.*;
+import static org.springframework.test.web.client.match.MockRestRequestMatchers.*;
+import static org.springframework.test.web.client.response.MockRestResponseCreators.*;
 
 import java.util.UUID;
 
+import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
-import org.junit.jupiter.api.extension.ExtendWith;
-import org.mockito.InjectMocks;
-import org.mockito.Spy;
-import org.mockito.junit.jupiter.MockitoExtension;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.web.client.RestClientTest;
+import org.springframework.boot.test.web.client.MockServerRestClientCustomizer;
+import org.springframework.http.MediaType;
+import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
+import org.springframework.test.web.client.MockRestServiceServer;
+import org.springframework.web.client.RestClient;
 
-import de.ozgcloud.antragsraum.proxy.AntragraumproxyGrpcCommand;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
 
-@ExtendWith(MockitoExtension.class)
+@SpringJUnitConfig(classes = { CommandRestClientTestConfiguration.class })
+@RestClientTest(CommandRestClient.class)
 class CommandRestClientTest {
-	private final static String SERVICE_ADDRESS = "static://localhost:9090";
+	final static String ADDRESS = "http://localhost:8382";
 	private static final String COMMAND_ID = UUID.randomUUID().toString();
 
-	@Spy
-	@InjectMocks
-	private CommandRestClient restClient;
+	private CommandRestClient commandRestClient;
+
+	@Autowired
+	private RestClient.Builder restClientBuilder;
+
+	@Autowired
+	private MockServerRestClientCustomizer customizer;
+
+	private final ObjectMapper objectMapper = new ObjectMapper();
+
+	private final String commandApi = ADDRESS + COMMAND_URI.replace("{commandId}", COMMAND_ID);
+
+	@BeforeEach
+	void setup() throws JsonProcessingException {
+		customizer.customize(restClientBuilder);
+		MockRestServiceServer server = customizer.getServer(restClientBuilder);
+		commandRestClient = new CommandRestClient(restClientBuilder.build());
+
+		server.expect(requestTo(commandApi))
+		  .andRespond(
+			withSuccess(objectMapper.writeValueAsString(AntragraumproxyGrpcCommandTestFactory.createCommand()), MediaType.APPLICATION_JSON));
+	}
 
 	@Test
 	void shouldCallGetCommand() {
-		doReturn(mock(AntragraumproxyGrpcCommand.class)).when(restClient).getCommand(any(), any());
-
-		restClient.getCommand(COMMAND_ID, SERVICE_ADDRESS);
+		var command = commandRestClient.getCommand(COMMAND_ID, ADDRESS + COMMAND_URI);
 
-		verify(restClient).getCommand(any(), any());
+		assertThat(command.getId()).isEqualTo(AntragraumproxyGrpcCommandTestFactory.ID);
 	}
 }
\ No newline at end of file
diff --git a/server/src/test/java/de/ozgcloud/antragsraum/command/CommandRestClientTestConfiguration.java b/server/src/test/java/de/ozgcloud/antragsraum/command/CommandRestClientTestConfiguration.java
new file mode 100644
index 0000000000000000000000000000000000000000..ae11e9f710cdc32b336f9e41e8d98c508745c557
--- /dev/null
+++ b/server/src/test/java/de/ozgcloud/antragsraum/command/CommandRestClientTestConfiguration.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (c) 2023-2024.   Das Land Schleswig-Holstein vertreten durch den Ministerpräsidenten
+ * des Landes Schleswig-Holstein Staatskanzlei Abteilung Digitalisierung und zentrales IT-Management der Landesregierung.
+ *
+ * Lizenziert unter der EUPL, Version 1.2 oder - sobald
+ * diese von der Europäischen Kommission genehmigt wurden -
+ * Folgeversionen der EUPL ("Lizenz");
+ * Sie dürfen dieses Werk ausschließlich gemäß
+ * dieser Lizenz nutzen.
+ * Eine Kopie der Lizenz finden Sie hier:
+ *
+ * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
+ *
+ * Sofern nicht durch anwendbare Rechtsvorschriften
+ * gefordert oder in schriftlicher Form vereinbart, wird
+ * die unter der Lizenz verbreitete Software "so wie sie
+ * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN -
+ * ausdrücklich oder stillschweigend - verbreitet.
+ * Die sprachspezifischen Genehmigungen und Beschränkungen
+ * unter der Lizenz sind dem Lizenztext zu entnehmen.
+ */
+package de.ozgcloud.antragsraum.command;
+
+import org.springframework.boot.test.mock.mockito.MockBean;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.ComponentScan;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.FilterType;
+import org.springframework.scheduling.annotation.EnableAsync;
+import org.springframework.web.client.RestClient;
+
+import de.ozgcloud.antragsraum.WebConfiguration;
+
+@EnableAsync
+@ComponentScan(value = { "de.ozgcloud.antragsraum.command", "de.ozgcloud.antragsraum.common" },
+  excludeFilters = @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE,
+	classes = { CommandControllerTestConfiguration.class, CommandController.class, CommandRemoteServiceTestConfiguration.class }))
+@Configuration
+public class CommandRestClientTestConfiguration {
+
+	@Bean
+	public RestClient.Builder restClientBuilder() {
+		return RestClient.builder().baseUrl(CommandRestClientTest.ADDRESS);
+	}
+
+	@Bean(name = WebConfiguration.NACHRICHT_REST_CLIENT)
+	RestClient commandRestClient() {
+		return restClientBuilder().build();
+	}
+
+	@MockBean
+	CommandService commandService;
+
+}
diff --git a/server/src/test/java/de/ozgcloud/antragsraum/nachricht/NachrichtMapperTest.java b/server/src/test/java/de/ozgcloud/antragsraum/nachricht/NachrichtMapperTest.java
index a8210912f787379296705cc1098024a68b8e9805..882721adbbcb05259680c8735c5f092b4624cec0 100644
--- a/server/src/test/java/de/ozgcloud/antragsraum/nachricht/NachrichtMapperTest.java
+++ b/server/src/test/java/de/ozgcloud/antragsraum/nachricht/NachrichtMapperTest.java
@@ -48,6 +48,15 @@ class NachrichtMapperTest {
 	@InjectMocks
 	private NachrichtMapper mapper;
 
+	@Test
+	void shouldMapEmptyAttachmentFields() {
+		var rueckfrage = AntragraumproxyGrpcRueckfrageTestFactory.createRueckfrage();
+		rueckfrage.setAttachmentFileId(null);
+		var nachricht = mapper.fromRestRueckfrage(rueckfrage, NachrichtTestFactory.NACHRICHT_EVENT_ID);
+
+		assertThat(nachricht.attachments()).isEmpty();
+	}
+
 	@Nested
 	class TestFromRestRueckfrage {
 		private final AntragraumproxyGrpcRueckfrage rueckfrage = AntragraumproxyGrpcRueckfrageTestFactory.createRueckfrage();
@@ -120,6 +129,15 @@ class NachrichtMapperTest {
 			assertThat(nachricht.answeredAt()).isZero();
 		}
 
+		@Test
+		void shouldMapEmptyAnsweredAt() {
+			var rueckfrage = AntragraumproxyGrpcRueckfrageTestFactory.createRueckfrage();
+			rueckfrage.setAnsweredAt("");
+			var nachricht = mapper.fromRestRueckfrage(rueckfrage, NachrichtTestFactory.NACHRICHT_EVENT_ID);
+
+			assertThat(nachricht.answeredAt()).isZero();
+		}
+
 		@Test
 		void shouldMapAttachments() {
 			var nachricht = mapper.fromRestRueckfrage(AntragraumproxyGrpcRueckfrageTestFactory.createRueckfrage(),
diff --git a/server/src/test/java/de/ozgcloud/antragsraum/nachricht/NachrichtServiceTestConfiguration.java b/server/src/test/java/de/ozgcloud/antragsraum/nachricht/NachrichtServiceTestConfiguration.java
new file mode 100644
index 0000000000000000000000000000000000000000..69b632c38601e06fb675ec4a35cd2d301b1d375a
--- /dev/null
+++ b/server/src/test/java/de/ozgcloud/antragsraum/nachricht/NachrichtServiceTestConfiguration.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (c) 2023-2024.
+ * Das Land Schleswig-Holstein vertreten durch den Ministerpräsidenten des Landes Schleswig-Holstein Staatskanzlei Abteilung Digitalisierung und zentrales IT-Management der Landesregierung
+ *
+ * Lizenziert unter der EUPL, Version 1.2 oder - sobald
+ * diese von der Europäischen Kommission genehmigt wurden -
+ * Folgeversionen der EUPL ("Lizenz");
+ * Sie dürfen dieses Werk ausschließlich gemäß
+ * dieser Lizenz nutzen.
+ * Eine Kopie der Lizenz finden Sie hier:
+ *
+ * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
+ *
+ * Sofern nicht durch anwendbare Rechtsvorschriften
+ * gefordert oder in schriftlicher Form vereinbart, wird
+ * die unter der Lizenz verbreitete Software "so wie sie
+ * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN -
+ * ausdrücklich oder stillschweigend - verbreitet.
+ * Die sprachspezifischen Genehmigungen und Beschränkungen
+ * unter der Lizenz sind dem Lizenztext zu entnehmen.
+ */
+package de.ozgcloud.antragsraum.nachricht;
+
+import jakarta.validation.Validator;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.ComponentScan;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.FilterType;
+import org.springframework.scheduling.annotation.EnableAsync;
+import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
+import org.springframework.web.client.RestClient;
+import org.springframework.web.servlet.config.annotation.EnableWebMvc;
+
+import de.ozgcloud.antragsraum.WebConfiguration;
+
+@EnableWebMvc
+@EnableAsync
+@ComponentScan(value = { "de.ozgcloud.antragsraum.nachricht", "de.ozgcloud.antragsraum.common" },
+  excludeFilters = @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE,
+	classes = { NachrichtenRemoteServiceTestConfiguration.class, NachrichtenRestClientTestConfiguration.class,
+	  NachrichtenControllerTestConfiguration.class }))
+@Configuration
+public class NachrichtServiceTestConfiguration {
+	@Bean(name = WebConfiguration.NACHRICHT_REST_CLIENT)
+	public RestClient restClient() {
+		return RestClient.builder()
+		  .baseUrl("http//localhost:8080")
+		  .build();
+	}
+
+	@Bean
+	public Validator defaultValidator() {
+		return new LocalValidatorFactoryBean();
+	}
+}
diff --git a/server/src/test/java/de/ozgcloud/antragsraum/nachricht/NachrichtTest.java b/server/src/test/java/de/ozgcloud/antragsraum/nachricht/NachrichtTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..200b613a84fa15bcc10bd25b3685b304af9b3fc2
--- /dev/null
+++ b/server/src/test/java/de/ozgcloud/antragsraum/nachricht/NachrichtTest.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) 2024.   Das Land Schleswig-Holstein vertreten durch den Ministerpräsidenten
+ * des Landes Schleswig-Holstein Staatskanzlei Abteilung Digitalisierung und zentrales IT-Management der Landesregierung.
+ *
+ * Lizenziert unter der EUPL, Version 1.2 oder - sobald
+ * diese von der Europäischen Kommission genehmigt wurden -
+ * Folgeversionen der EUPL ("Lizenz");
+ * Sie dürfen dieses Werk ausschließlich gemäß
+ * dieser Lizenz nutzen.
+ * Eine Kopie der Lizenz finden Sie hier:
+ *
+ * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
+ *
+ * Sofern nicht durch anwendbare Rechtsvorschriften
+ * gefordert oder in schriftlicher Form vereinbart, wird
+ * die unter der Lizenz verbreitete Software "so wie sie
+ * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN -
+ * ausdrücklich oder stillschweigend - verbreitet.
+ * Die sprachspezifischen Genehmigungen und Beschränkungen
+ * unter der Lizenz sind dem Lizenztext zu entnehmen.
+ */
+
+package de.ozgcloud.antragsraum.nachricht;
+
+import static org.assertj.core.api.Assertions.*;
+
+import org.junit.jupiter.api.Test;
+
+class NachrichtTest {
+	private final Nachricht nachricht = NachrichtTestFactory.createNachricht();
+
+	@Test
+	void shouldNotPrintMessageText() {
+		String nachrichtString = nachricht.toString();
+
+		assertThat(nachrichtString).doesNotContain(NachrichtTestFactory.TEXT);
+	}
+}
\ No newline at end of file
diff --git a/server/src/test/java/de/ozgcloud/antragsraum/nachricht/NachrichtenControllerITCase.java b/server/src/test/java/de/ozgcloud/antragsraum/nachricht/NachrichtenControllerITCase.java
index fdd47a4fb0026427804cb10fdf39411f07936f43..c50db1e212f58992e3f1ebed9230a78279349a92 100644
--- a/server/src/test/java/de/ozgcloud/antragsraum/nachricht/NachrichtenControllerITCase.java
+++ b/server/src/test/java/de/ozgcloud/antragsraum/nachricht/NachrichtenControllerITCase.java
@@ -29,6 +29,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
 import java.nio.charset.Charset;
 import java.util.List;
 
+import org.apache.commons.lang3.RandomStringUtils;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Nested;
 import org.junit.jupiter.api.Test;
@@ -209,14 +210,6 @@ public class NachrichtenControllerITCase {
 			  .andExpect(jsonPath("$.nachrichtEventId").value(CommandReferenceTestFactory.NACHRICHT_EVENT_ID));
 		}
 
-		@Test
-		@WithMockUser
-		void shouldNotSendNachrichtBlankId() throws Exception {
-			var invalidMsg = ReplyNachrichtTestFactory.createBuilder().id("").build();
-
-			performPutRequest(mapper.writeValueAsString(invalidMsg)).andExpect(status().isBadRequest());
-		}
-
 		@Test
 		@WithMockUser
 		void shouldNotSendNachrichtWithoutAddress() throws Exception {
@@ -233,6 +226,49 @@ public class NachrichtenControllerITCase {
 			performPutRequest(mapper.writeValueAsString(msg)).andExpect(status().isInternalServerError());
 		}
 
+		@Nested
+		class TestSendingInvalidReply {
+			@Test
+			@WithMockUser
+			void shouldNotSendNachrichtBlankId() throws Exception {
+				var invalidMsg = ReplyNachrichtTestFactory.createBuilder().id("").build();
+
+				performPutRequest(mapper.writeValueAsString(invalidMsg)).andExpect(status().isBadRequest());
+			}
+
+			@Test
+			@WithMockUser
+			void shouldHandleInvalidId() throws Exception {
+				var invalidMsg = msg.toBuilder().id("id_that_is_too_long_123456789").build();
+
+				performPutRequest(mapper.writeValueAsString(invalidMsg)).andExpect(status().isBadRequest());
+			}
+
+			@Test
+			@WithMockUser
+			void shouldHandleInvalidNachrichtEventId() throws Exception {
+				var invalidMsg = msg.toBuilder().nachrichtEventId("id_that_is_too_long_123456789").build();
+
+				performPutRequest(mapper.writeValueAsString(invalidMsg)).andExpect(status().isBadRequest());
+			}
+
+			@Test
+			@WithMockUser
+			void shouldHandleInvalidPostfachId() throws Exception {
+				var invalidMsg = msg.toBuilder().id("postfach_id_that_is_too_long_123456789_123456789").build();
+
+				performPutRequest(mapper.writeValueAsString(invalidMsg)).andExpect(status().isBadRequest());
+			}
+
+			@Test
+			@WithMockUser
+			void shouldHandleInvalidMessage() throws Exception {
+				var invalidMsg = msg.toBuilder().message(RandomStringUtils.random(500001, true, false)).build();
+
+				performPutRequest(mapper.writeValueAsString(invalidMsg)).andExpect(status().isBadRequest());
+			}
+		}
+
 		ResultActions performPutRequest(String body) throws Exception {
 			return mockMvc.perform(
 			  put(NachrichtenController.PATH + "/antwort")
diff --git a/server/src/test/java/de/ozgcloud/antragsraum/nachricht/NachrichtenControllerTestConfiguration.java b/server/src/test/java/de/ozgcloud/antragsraum/nachricht/NachrichtenControllerTestConfiguration.java
index 5d5d5eff162baa5b531a38f7c90bdc1d8dba7f7b..0b87eca4ef6aa7b48974d7976d95ac82c989cdf3 100644
--- a/server/src/test/java/de/ozgcloud/antragsraum/nachricht/NachrichtenControllerTestConfiguration.java
+++ b/server/src/test/java/de/ozgcloud/antragsraum/nachricht/NachrichtenControllerTestConfiguration.java
@@ -35,7 +35,8 @@ import de.ozgcloud.antragsraum.WebConfiguration;
 @EnableAsync
 @ComponentScan(value = { "de.ozgcloud.antragsraum.nachricht", "de.ozgcloud.antragsraum.common" },
   excludeFilters = @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE,
-	classes = { NachrichtenRemoteServiceTestConfiguration.class, NachrichtenRestClientTestConfiguration.class }))
+	classes = { NachrichtenRemoteServiceTestConfiguration.class, NachrichtenRestClientTestConfiguration.class,
+	  NachrichtServiceTestConfiguration.class }))
 @Configuration
 public class NachrichtenControllerTestConfiguration {
 	@Bean(name = WebConfiguration.NACHRICHT_REST_CLIENT)
diff --git a/server/src/test/java/de/ozgcloud/antragsraum/nachricht/NachrichtenRemoteServiceTest.java b/server/src/test/java/de/ozgcloud/antragsraum/nachricht/NachrichtenRemoteServiceTest.java
index 627f0e0cad1b24a1802cd737fd2ab32a59cec471..d866a13274a88ad3014c042adaee4d1a65d5cf25 100644
--- a/server/src/test/java/de/ozgcloud/antragsraum/nachricht/NachrichtenRemoteServiceTest.java
+++ b/server/src/test/java/de/ozgcloud/antragsraum/nachricht/NachrichtenRemoteServiceTest.java
@@ -52,6 +52,7 @@ import de.ozgcloud.antragsraum.proxy.AntragraumproxyGrpcRueckfrageAnswer;
 import de.ozgcloud.antragsraum.proxy.AntragraumproxyGrpcRueckfrageHead;
 import de.ozgcloud.antragsraum.proxy.AntragraumproxyGrpcSendRueckfrageAnswerResponse;
 import de.ozgcloud.antragsraum.security.UserTestFactory;
+import io.grpc.Status;
 import io.grpc.StatusRuntimeException;
 
 @ExtendWith(MockitoExtension.class)
@@ -107,6 +108,16 @@ public class NachrichtenRemoteServiceTest {
 
 			verify(nachrichtenRestClient).getFindRueckfragen(anyString(), anyString(), anyString());
 		}
+
+		@Test
+		void shouldHandleNullResult() {
+			when(nachrichtenRestClient.getFindRueckfragen(anyString(), anyString(), anyString())).thenReturn(
+			  new AntragraumproxyGrpcFindRueckfragenResponse());
+
+			nachrichtenRemoteService.findRueckfrageHeads(nachrichtEvent);
+
+			verify(nachrichtenRestClient).getFindRueckfragen(anyString(), anyString(), anyString());
+		}
 	}
 
 	@Nested
@@ -167,6 +178,31 @@ public class NachrichtenRemoteServiceTest {
 		}
 	}
 
+	@Nested
+	class TestGetNachrichtError {
+		@Test
+		void shouldHandleNullRueckfrage() {
+			when(nachrichtenRestClient.getGetRueckfrage(anyString(), anyString(), anyString())).thenReturn(
+			  new AntragraumproxyGrpcGetRueckfrageResponse());
+
+			nachrichtenRemoteService.getRueckfrage(AntragraumproxyGrpcRueckfrageTestFactory.RUECKFRAGE_ID,
+			  NachrichtEventTestFactory.createNachrichtEvent());
+
+			verify(nachrichtenRestClient).getGetRueckfrage(anyString(), anyString(), anyString());
+		}
+
+		@Test
+		void shouldHandleError() {
+			when(nachrichtenRestClient.getGetRueckfrage(anyString(), anyString(), anyString())).thenThrow(
+			  new StatusRuntimeException(Status.CANCELLED));
+
+			nachrichtenRemoteService.getRueckfrage(AntragraumproxyGrpcRueckfrageTestFactory.RUECKFRAGE_ID,
+			  NachrichtEventTestFactory.createNachrichtEvent());
+
+			verify(nachrichtenRestClient).getGetRueckfrage(anyString(), anyString(), anyString());
+		}
+	}
+
 	@Nested
 	class TestSendAnswer {
 		private final ReplyNachricht reply = ReplyNachrichtTestFactory.create();
diff --git a/server/src/test/java/de/ozgcloud/antragsraum/nachricht/NachrichtenRemoteServiceTestConfiguration.java b/server/src/test/java/de/ozgcloud/antragsraum/nachricht/NachrichtenRemoteServiceTestConfiguration.java
index 3f5b394aad17b5b37d958034f78c2d43f64b8e64..21f2bd81161301c657450d428e0a7523e6822b53 100644
--- a/server/src/test/java/de/ozgcloud/antragsraum/nachricht/NachrichtenRemoteServiceTestConfiguration.java
+++ b/server/src/test/java/de/ozgcloud/antragsraum/nachricht/NachrichtenRemoteServiceTestConfiguration.java
@@ -22,12 +22,15 @@ package de.ozgcloud.antragsraum.nachricht;
 import static de.ozgcloud.antragsraum.nachricht.AntragraumproxyGrpcRueckfrageTestFactory.*;
 import static org.mockito.Mockito.*;
 
+import jakarta.validation.Validator;
+
 import org.springframework.boot.test.mock.mockito.MockBean;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.ComponentScan;
 import org.springframework.context.annotation.Configuration;
 import org.springframework.context.annotation.FilterType;
 import org.springframework.scheduling.annotation.EnableAsync;
+import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
 
 import de.ozgcloud.antragsraum.WebConfiguration;
 import de.ozgcloud.antragsraum.attachments.FileService;
@@ -38,7 +41,8 @@ import de.ozgcloud.antragsraum.proxy.AntragraumproxyGrpcSendRueckfrageAnswerResp
 @EnableAsync
 @ComponentScan(value = { "de.ozgcloud.antragsraum.nachricht", "de.ozgcloud.antragsraum.common" },
   excludeFilters = @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE,
-	classes = { NachrichtenControllerTestConfiguration.class, NachrichtenRestClientTestConfiguration.class, NachrichtenRestClient.class }))
+	classes = { NachrichtenControllerTestConfiguration.class, NachrichtenRestClientTestConfiguration.class, NachrichtenRestClient.class,
+	  NachrichtServiceTestConfiguration.class }))
 @Configuration
 public class NachrichtenRemoteServiceTestConfiguration {
 	@MockBean
@@ -62,4 +66,9 @@ public class NachrichtenRemoteServiceTestConfiguration {
 		return nachrichtenRestClient;
 	}
 
+	@Bean
+	public Validator defaultValidator() {
+		return new LocalValidatorFactoryBean();
+	}
+
 }
diff --git a/server/src/test/java/de/ozgcloud/antragsraum/nachricht/NachrichtenRestClientTestConfiguration.java b/server/src/test/java/de/ozgcloud/antragsraum/nachricht/NachrichtenRestClientTestConfiguration.java
index 1f5bccbc5ff9885e9dca86f407770f36b331a3fb..e39f0412df4f61466aea73e25a78437b5d91396b 100644
--- a/server/src/test/java/de/ozgcloud/antragsraum/nachricht/NachrichtenRestClientTestConfiguration.java
+++ b/server/src/test/java/de/ozgcloud/antragsraum/nachricht/NachrichtenRestClientTestConfiguration.java
@@ -21,12 +21,15 @@
  */
 package de.ozgcloud.antragsraum.nachricht;
 
+import jakarta.validation.Validator;
+
 import org.springframework.boot.test.mock.mockito.MockBean;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.ComponentScan;
 import org.springframework.context.annotation.Configuration;
 import org.springframework.context.annotation.FilterType;
 import org.springframework.scheduling.annotation.EnableAsync;
+import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
 import org.springframework.web.client.RestClient;
 
 import de.ozgcloud.antragsraum.WebConfiguration;
@@ -36,7 +39,8 @@ import de.ozgcloud.antragsraum.events.NachrichtEventService;
 @EnableAsync
 @ComponentScan(value = { "de.ozgcloud.antragsraum.nachricht", "de.ozgcloud.antragsraum.common" },
   excludeFilters = @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE,
-	classes = { NachrichtenControllerTestConfiguration.class, NachrichtenController.class, NachrichtenRemoteServiceTestConfiguration.class }))
+	classes = { NachrichtenControllerTestConfiguration.class, NachrichtenController.class, NachrichtenRemoteServiceTestConfiguration.class,
+	  NachrichtServiceTestConfiguration.class }))
 @Configuration
 public class NachrichtenRestClientTestConfiguration {
 	@MockBean
@@ -44,7 +48,7 @@ public class NachrichtenRestClientTestConfiguration {
 
 	@MockBean
 	NachrichtEventService nachrichtEventService;
-	
+
 	@Bean
 	public RestClient.Builder restClientBuilder() {
 		return RestClient.builder().baseUrl(NachrichtenRestClientTest.ADDRESS);
@@ -55,4 +59,9 @@ public class NachrichtenRestClientTestConfiguration {
 		return restClientBuilder().build();
 	}
 
+	@Bean
+	public Validator defaultValidator() {
+		return new LocalValidatorFactoryBean();
+	}
+
 }
diff --git a/server/src/test/java/de/ozgcloud/antragsraum/nachricht/NachrichtenServiceITCase.java b/server/src/test/java/de/ozgcloud/antragsraum/nachricht/NachrichtenServiceITCase.java
new file mode 100644
index 0000000000000000000000000000000000000000..ff7af87fd603945c9e75d1b5bdb7db7c0148229d
--- /dev/null
+++ b/server/src/test/java/de/ozgcloud/antragsraum/nachricht/NachrichtenServiceITCase.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright (c) 2023-2024.
+ * Das Land Schleswig-Holstein vertreten durch den Ministerpräsidenten des Landes Schleswig-Holstein Staatskanzlei Abteilung Digitalisierung und zentrales IT-Management der Landesregierung
+ *
+ * Lizenziert unter der EUPL, Version 1.2 oder - sobald
+ * diese von der Europäischen Kommission genehmigt wurden -
+ * Folgeversionen der EUPL ("Lizenz");
+ * Sie dürfen dieses Werk ausschließlich gemäß
+ * dieser Lizenz nutzen.
+ * Eine Kopie der Lizenz finden Sie hier:
+ *
+ * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
+ *
+ * Sofern nicht durch anwendbare Rechtsvorschriften
+ * gefordert oder in schriftlicher Form vereinbart, wird
+ * die unter der Lizenz verbreitete Software "so wie sie
+ * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN -
+ * ausdrücklich oder stillschweigend - verbreitet.
+ * Die sprachspezifischen Genehmigungen und Beschränkungen
+ * unter der Lizenz sind dem Lizenztext zu entnehmen.
+ */
+package de.ozgcloud.antragsraum.nachricht;
+
+import static org.assertj.core.api.Assertions.*;
+import static org.mockito.Mockito.*;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.UUID;
+
+import org.apache.commons.lang3.RandomStringUtils;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.boot.test.mock.mockito.MockBean;
+import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
+
+import de.ozgcloud.antragsraum.attachments.OzgFile;
+import de.ozgcloud.antragsraum.attachments.OzgFileTestFactory;
+import de.ozgcloud.antragsraum.command.CommandReferenceTestFactory;
+import de.ozgcloud.antragsraum.common.TechnicalException;
+import de.ozgcloud.antragsraum.events.NachrichtEventService;
+import de.ozgcloud.antragsraum.events.NachrichtEventTestFactory;
+
+@SpringBootTest
+@SpringJUnitConfig(classes = { NachrichtServiceTestConfiguration.class })
+class NachrichtenServiceITCase {
+	public static final int OVERSIZED_EVENT_ID = 25;
+	public static final int OVERSIZED_ID = 25;
+	public static final int OVERSIZED_POSTFACH_ID = UUID.randomUUID().toString().length() + 1;
+	public static final int OVERSIZED_TEXT = 100_001;
+	public static final int MAX_ATTACHMENT_COUNT = 21;
+	@Autowired
+	private NachrichtenService nachrichtenService;
+	@MockBean
+	private NachrichtEventService nachrichtEventService;
+	@MockBean
+	private NachrichtenRemoteService nachrichtenRemoteService;
+	@MockBean
+	private NachrichtMapper nachrichtMapper;
+	@MockBean
+	private NachrichtenController nachrichtenController;
+
+	@Nested
+	class TestSendNachricht {
+		@BeforeEach
+		void init() {
+			when(nachrichtEventService.getNachrichtEventById(anyString())).thenReturn(NachrichtEventTestFactory.createNachrichtEvent());
+			when(nachrichtenRemoteService.sendAnswer(any(ReplyNachricht.class), anyString())).thenReturn(CommandReferenceTestFactory.create());
+		}
+
+		@Test
+		void shouldSendValidReply() {
+			ReplyNachricht msg = ReplyNachrichtTestFactory.create();
+
+			nachrichtenService.sendRueckfrageAnswer(msg);
+
+			verify(nachrichtenRemoteService).sendAnswer(any(ReplyNachricht.class), anyString());
+		}
+
+		@Nested
+		class TestSendingInvalidReply {
+
+			@Test
+			void messageTestTooLong() {
+				ReplyNachricht msg = ReplyNachrichtTestFactory.createBuilder().message(RandomStringUtils.random(OVERSIZED_TEXT)).build();
+
+				assertThatExceptionOfType(TechnicalException.class).isThrownBy(() -> nachrichtenService.sendRueckfrageAnswer(msg))
+				  .withMessageStartingWith("TechnicalException happened! Message: message: Message text too long");
+			}
+
+			@Test
+			void messagePostfachIdTooLong() {
+				ReplyNachricht msg = ReplyNachrichtTestFactory.createBuilder().postfachId(RandomStringUtils.random(OVERSIZED_POSTFACH_ID)).build();
+
+				assertThatExceptionOfType(TechnicalException.class).isThrownBy(() -> nachrichtenService.sendRueckfrageAnswer(msg))
+				  .withMessageStartingWith("TechnicalException happened! Message: postfachId: An id is too long");
+			}
+
+			@Test
+			void messageIdTooLong() {
+				ReplyNachricht msg = ReplyNachrichtTestFactory.createBuilder().id(RandomStringUtils.random(OVERSIZED_ID)).build();
+
+				assertThatExceptionOfType(TechnicalException.class).isThrownBy(() -> nachrichtenService.sendRueckfrageAnswer(msg))
+				  .withMessageStartingWith("TechnicalException happened! Message: id: An id is too long");
+			}
+
+			@Test
+			void messageNachrichtEventIdTooLong() {
+				ReplyNachricht msg = ReplyNachrichtTestFactory.createBuilder().nachrichtEventId(RandomStringUtils.random(OVERSIZED_EVENT_ID)).build();
+
+				assertThatExceptionOfType(TechnicalException.class).isThrownBy(() -> nachrichtenService.sendRueckfrageAnswer(msg))
+				  .withMessageStartingWith("TechnicalException happened! Message: nachrichtEventId: An id id too long");
+			}
+
+			@Test
+			void messageTooManyAttachments() {
+				List<OzgFile> attachments = new ArrayList<>(MAX_ATTACHMENT_COUNT);
+				for (int i = 0; i < MAX_ATTACHMENT_COUNT; i++) {
+					attachments.add(OzgFileTestFactory.create());
+				}
+
+				ReplyNachricht msg = ReplyNachrichtTestFactory.createBuilder().attachments(attachments).build();
+
+				assertThatExceptionOfType(TechnicalException.class).isThrownBy(() -> nachrichtenService.sendRueckfrageAnswer(msg))
+				  .withMessageStartingWith("TechnicalException happened! Message: attachments: size must be between 0 and 20");
+			}
+		}
+	}
+}
\ No newline at end of file
diff --git a/server/src/test/java/de/ozgcloud/antragsraum/nachricht/NachrichtenServiceTest.java b/server/src/test/java/de/ozgcloud/antragsraum/nachricht/NachrichtenServiceTest.java
index e459afbdec26b3dad332bd966fb8d5813d0e1ebf..536d887582fb096165912c23dc0fa96de27ea71c 100644
--- a/server/src/test/java/de/ozgcloud/antragsraum/nachricht/NachrichtenServiceTest.java
+++ b/server/src/test/java/de/ozgcloud/antragsraum/nachricht/NachrichtenServiceTest.java
@@ -28,6 +28,10 @@ import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.*;
 
 import java.util.List;
 import java.util.Optional;
+import java.util.Set;
+
+import jakarta.validation.ConstraintViolation;
+import jakarta.validation.Validator;
 
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Nested;
@@ -54,6 +58,8 @@ class NachrichtenServiceTest {
 	private NachrichtEventService nachrichtEventService;
 	@Mock
 	private NachrichtenRemoteService nachrichtenRemoteService;
+	@Mock
+	private Validator validator;
 
 	@Nested
 	class TestGetRueckfrageHeadersOfPostfach {
@@ -206,4 +212,15 @@ class NachrichtenServiceTest {
 			assertThat(result).isInstanceOf(CommandReference.class);
 		}
 	}
+
+	@Nested
+	class TestValidation {
+		@Test
+		@SuppressWarnings("unchecked")
+		void shouldNotCallSendOnValidationError() {
+			when(validator.validate(any(ReplyNachricht.class))).thenReturn(Set.of(mock(ConstraintViolation.class)));
+
+			assertThatException().isThrownBy(() -> nachrichtenService.sendRueckfrageAnswer(ReplyNachrichtTestFactory.create()));
+		}
+	}
 }
\ No newline at end of file
diff --git a/server/src/test/java/de/ozgcloud/antragsraum/nachricht/ReplyNachrichtTest.java b/server/src/test/java/de/ozgcloud/antragsraum/nachricht/ReplyNachrichtTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..ca1cad65796e8514201319ec56e8c7663e9e5f0b
--- /dev/null
+++ b/server/src/test/java/de/ozgcloud/antragsraum/nachricht/ReplyNachrichtTest.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) 2024.   Das Land Schleswig-Holstein vertreten durch den Ministerpräsidenten
+ * des Landes Schleswig-Holstein Staatskanzlei Abteilung Digitalisierung und zentrales IT-Management der Landesregierung.
+ *
+ * Lizenziert unter der EUPL, Version 1.2 oder - sobald
+ * diese von der Europäischen Kommission genehmigt wurden -
+ * Folgeversionen der EUPL ("Lizenz");
+ * Sie dürfen dieses Werk ausschließlich gemäß
+ * dieser Lizenz nutzen.
+ * Eine Kopie der Lizenz finden Sie hier:
+ *
+ * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
+ *
+ * Sofern nicht durch anwendbare Rechtsvorschriften
+ * gefordert oder in schriftlicher Form vereinbart, wird
+ * die unter der Lizenz verbreitete Software "so wie sie
+ * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN -
+ * ausdrücklich oder stillschweigend - verbreitet.
+ * Die sprachspezifischen Genehmigungen und Beschränkungen
+ * unter der Lizenz sind dem Lizenztext zu entnehmen.
+ */
+
+package de.ozgcloud.antragsraum.nachricht;
+
+import static org.assertj.core.api.Assertions.*;
+
+import org.junit.jupiter.api.Test;
+
+class ReplyNachrichtTest {
+	final ReplyNachricht replyNachricht = ReplyNachrichtTestFactory.create();
+
+	@Test
+	void shouldNotPrintMessageText() {
+		String replyNachrichtText = replyNachricht.toString();
+
+		assertThat(replyNachrichtText).doesNotContain(ReplyNachrichtTestFactory.TEXT);
+	}
+}
\ No newline at end of file
diff --git a/server/src/test/java/de/ozgcloud/antragsraum/nachricht/ReplyNachrichtTestFactory.java b/server/src/test/java/de/ozgcloud/antragsraum/nachricht/ReplyNachrichtTestFactory.java
index 72225ad0b3f086a94a840ec2fd6ad93d7be30a10..ca44f4f85426e5e143179ad91a164fa8ef4e88aa 100644
--- a/server/src/test/java/de/ozgcloud/antragsraum/nachricht/ReplyNachrichtTestFactory.java
+++ b/server/src/test/java/de/ozgcloud/antragsraum/nachricht/ReplyNachrichtTestFactory.java
@@ -21,11 +21,9 @@
  */
 package de.ozgcloud.antragsraum.nachricht;
 
-import java.util.UUID;
-
 public class ReplyNachrichtTestFactory {
-	public static final String ID = UUID.randomUUID().toString();
-	public static final String NACHRICHT_EVENT_ID = UUID.randomUUID().toString();
+	public static final String ID = "60af924b4f1a2560298b4567";
+	public static final String NACHRICHT_EVENT_ID = "60af924b4f1a2560298b4567";
 	public static final String TEXT = "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam";
 
 	static ReplyNachricht create() {
diff --git a/server/src/test/java/de/ozgcloud/antragsraum/nachricht/RueckfrageTest.java b/server/src/test/java/de/ozgcloud/antragsraum/nachricht/RueckfrageTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..402647a4bba932e22064aa6f11e52e966def688b
--- /dev/null
+++ b/server/src/test/java/de/ozgcloud/antragsraum/nachricht/RueckfrageTest.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 2024.   Das Land Schleswig-Holstein vertreten durch den Ministerpräsidenten
+ * des Landes Schleswig-Holstein Staatskanzlei Abteilung Digitalisierung und zentrales IT-Management der Landesregierung.
+ *
+ * Lizenziert unter der EUPL, Version 1.2 oder - sobald
+ * diese von der Europäischen Kommission genehmigt wurden -
+ * Folgeversionen der EUPL ("Lizenz");
+ * Sie dürfen dieses Werk ausschließlich gemäß
+ * dieser Lizenz nutzen.
+ * Eine Kopie der Lizenz finden Sie hier:
+ *
+ * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
+ *
+ * Sofern nicht durch anwendbare Rechtsvorschriften
+ * gefordert oder in schriftlicher Form vereinbart, wird
+ * die unter der Lizenz verbreitete Software "so wie sie
+ * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN -
+ * ausdrücklich oder stillschweigend - verbreitet.
+ * Die sprachspezifischen Genehmigungen und Beschränkungen
+ * unter der Lizenz sind dem Lizenztext zu entnehmen.
+ */
+
+package de.ozgcloud.antragsraum.nachricht;
+
+import static org.assertj.core.api.Assertions.*;
+
+import org.junit.jupiter.api.Test;
+
+class RueckfrageTest {
+	final Rueckfrage rueckfrage = RueckfrageTestFactory.createRueckfrage();
+
+	@Test
+	void shouldNotPrintNachrichtText() {
+		String rueckfrageString = rueckfrage.toString();
+
+		assertThat(rueckfrageString).doesNotContain(NachrichtTestFactory.TEXT);
+	}
+
+	@Test
+	void shouldNotPrintAnswers() {
+		String rueckfrageString = rueckfrage.toString();
+
+		assertThat(rueckfrageString).contains("antworten=<...masked for privacy..>");
+	}
+}
\ No newline at end of file
diff --git a/server/src/test/java/de/ozgcloud/antragsraum/security/AuthenticationControllerTest.java b/server/src/test/java/de/ozgcloud/antragsraum/security/AuthenticationControllerTest.java
index 7f294ddd7cb39af587cbd4be2236884d58c5bbbc..9ab867230769f1fd56ddea5eee1a880c39c8f39a 100644
--- a/server/src/test/java/de/ozgcloud/antragsraum/security/AuthenticationControllerTest.java
+++ b/server/src/test/java/de/ozgcloud/antragsraum/security/AuthenticationControllerTest.java
@@ -34,7 +34,10 @@ import org.mockito.InjectMocks;
 import org.mockito.Mock;
 import org.mockito.Spy;
 import org.mockito.junit.jupiter.MockitoExtension;
-import org.springframework.http.HttpStatusCode;
+import org.springframework.http.HttpStatus;
+import org.springframework.mock.web.MockHttpServletRequest;
+import org.springframework.mock.web.MockHttpServletResponse;
+import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler;
 
 @ExtendWith(MockitoExtension.class)
 class AuthenticationControllerTest {
@@ -48,6 +51,9 @@ class AuthenticationControllerTest {
 	@Mock
 	private JwtTokenProvider tokenProvider;
 
+	@Mock
+	private SecurityContextLogoutHandler logoutHandler;
+
 	@Nested
 	class TestLogin {
 		private AuthCode code;
@@ -78,7 +84,7 @@ class AuthenticationControllerTest {
 		void shouldHaveStatusOk() {
 			var tokenResponse = authenticationController.login(code);
 
-			assertThat(tokenResponse.getStatusCode()).isEqualTo(HttpStatusCode.valueOf(200));
+			assertThat(tokenResponse.getStatusCode()).isEqualTo(HttpStatus.OK);
 		}
 
 		@Test
@@ -119,16 +125,16 @@ class AuthenticationControllerTest {
 		void shouldCreateToken() {
 			when(inMemoryUserDetailService.getUser(code)).thenReturn(Optional.of(UserTestFactory.create()));
 
-			var token = authenticationController.refresh(code);
+			var tokenResponse = authenticationController.refresh(code);
 
-			assertThat(token.getStatusCode()).isEqualTo(HttpStatusCode.valueOf(200));
+			assertThat(tokenResponse.getStatusCode()).isEqualTo(HttpStatus.OK);
 		}
 
 		@Test
 		void shouldNotCreateToken() {
-			var token = authenticationController.refresh(code);
+			var tokenResponse = authenticationController.refresh(code);
 
-			assertThat(token.getStatusCode()).isEqualTo(HttpStatusCode.valueOf(403));
+			assertThat(tokenResponse.getStatusCode()).isEqualTo(HttpStatus.FORBIDDEN);
 		}
 
 		@Test
@@ -140,4 +146,39 @@ class AuthenticationControllerTest {
 			verify(inMemoryUserDetailService).updateRefreshCodeOf(any(User.class));
 		}
 	}
+
+	@Nested
+	class TestLogout {
+		private final MockHttpServletRequest request = new MockHttpServletRequest();
+		private final MockHttpServletResponse response = new MockHttpServletResponse();
+		private final User user = UserTestFactory.create();
+
+		@Test
+		void shouldHaveStatusOk() {
+			var res = authenticationController.logout(request, response, user);
+
+			assertThat(res.getStatusCode()).isEqualTo(HttpStatus.OK);
+		}
+
+		@Test
+		void shouldCallUserDetailService() {
+			authenticationController.logout(request, response, user);
+
+			verify(inMemoryUserDetailService).logout(user);
+		}
+
+		@Test
+		void shouldNotCallUserDetailService() {
+			authenticationController.logout(request, response, null);
+
+			verify(inMemoryUserDetailService, never()).logout(user);
+		}
+
+		@Test
+		void shouldCallLogoutHandler() {
+			authenticationController.logout(request, response, user);
+
+			verify(logoutHandler).logout(eq(request), eq(response), any());
+		}
+	}
 }
\ No newline at end of file
diff --git a/server/src/test/java/de/ozgcloud/antragsraum/security/AuthenticationHelperTest.java b/server/src/test/java/de/ozgcloud/antragsraum/security/AuthenticationHelperTest.java
index 83872d53ece4b0871aaf944c8ce0ed6b9a601eb9..ddc9500135309d5539da86d9d516a7ab8a27cd36 100644
--- a/server/src/test/java/de/ozgcloud/antragsraum/security/AuthenticationHelperTest.java
+++ b/server/src/test/java/de/ozgcloud/antragsraum/security/AuthenticationHelperTest.java
@@ -75,7 +75,7 @@ class AuthenticationHelperTest {
 		}
 
 		@Test
-		void shouldGetEmptySamlToken() {
+		void shouldGetEmptySamlTokenBecauseWrongTokenType() {
 			var authenticated = mock(RememberMeAuthenticationToken.class);
 			var context = mock(SecurityContext.class);
 			when(context.getAuthentication()).thenReturn(authenticated);
@@ -85,5 +85,18 @@ class AuthenticationHelperTest {
 
 			assertThat(samlToken).isEqualTo(AuthenticationHelper.NO_SAML_TOKEN);
 		}
+
+		@Test
+		void shouldGetEmptySamlTokenBecauseWrongUserType() {
+			var authenticated = mock(UsernamePasswordAuthenticationToken.class);
+			var context = mock(SecurityContext.class);
+			when(authenticated.getPrincipal()).thenReturn("test");
+			when(context.getAuthentication()).thenReturn(authenticated);
+			SecurityContextHolder.setContext(context);
+
+			var samlToken = AuthenticationHelper.getSamlToken();
+
+			assertThat(samlToken).isEqualTo(AuthenticationHelper.NO_SAML_TOKEN);
+		}
 	}
 }
\ No newline at end of file