diff --git a/goofy-server/pom.xml b/goofy-server/pom.xml
new file mode 100644
index 0000000000000000000000000000000000000000..b91dd9bea9ae8b0acef721fe7f0de085bd464395
--- /dev/null
+++ b/goofy-server/pom.xml
@@ -0,0 +1,251 @@
+<!--
+
+    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.
+
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
+
+	<modelVersion>4.0.0</modelVersion>
+
+	<parent>
+		<groupId>de.itvsh.ozg</groupId>
+		<artifactId>goofy</artifactId>
+		<version>1.1.1</version>
+	</parent>
+
+	<artifactId>goofy-server</artifactId>
+	<name>Goofy Server</name>
+	<description>Projekt packaging deployment artefact</description>
+	<packaging>jar</packaging>
+
+	<properties>
+		<maven.compiler.source>${java.version}</maven.compiler.source>
+		<maven.compiler.target>${java.version}</maven.compiler.target>
+
+		<spring-boot.build-image.imageName>docker.ozg-sh.de/goofy:build-latest</spring-boot.build-image.imageName>
+	</properties>
+
+	<dependencies>
+		<!-- Spring -->
+		<dependency>
+			<groupId>org.springframework.boot</groupId>
+			<artifactId>spring-boot-starter</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>org.springframework.boot</groupId>
+			<artifactId>spring-boot-starter-log4j2</artifactId>
+		</dependency>
+
+		<dependency>
+			<groupId>org.springframework.boot</groupId>
+			<artifactId>spring-boot-starter-web</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>org.springframework.boot</groupId>
+			<artifactId>spring-boot-starter-hateoas</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>org.springframework.boot</groupId>
+			<artifactId>spring-boot-starter-validation</artifactId>
+		</dependency>
+
+		<dependency>
+			<groupId>net.devh</groupId>
+			<artifactId>grpc-client-spring-boot-starter</artifactId>
+		</dependency>
+
+		<dependency>
+			<groupId>org.springframework.boot</groupId>
+			<artifactId>spring-boot-starter-actuator</artifactId>
+		</dependency>
+
+		<dependency>
+			<groupId>org.springframework.boot</groupId>
+			<artifactId>spring-boot-starter-security</artifactId>
+		</dependency>
+
+		<dependency>
+			<groupId>org.keycloak</groupId>
+			<artifactId>keycloak-spring-boot-starter</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>org.keycloak</groupId>
+			<artifactId>keycloak-admin-client</artifactId>
+		</dependency>
+
+		<!-- jwt -->
+		<dependency>
+			<groupId>com.auth0</groupId>
+			<artifactId>java-jwt</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>io.jsonwebtoken</groupId>
+			<artifactId>jjwt</artifactId>
+		</dependency>
+
+		<!-- own projects -->
+		<dependency>
+			<groupId>de.itvsh.ozg.pluto</groupId>
+			<artifactId>pluto-interface</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>de.itvsh.ozg.pluto</groupId>
+			<artifactId>pluto-utils</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>de.itvsh.kop.common</groupId>
+			<artifactId>kop-common-pdf</artifactId>
+		</dependency>
+		
+		<!-- tools -->
+		<dependency>
+			<groupId>org.mapstruct</groupId>
+			<artifactId>mapstruct</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>org.jsoup</groupId>
+			<artifactId>jsoup</artifactId>
+		</dependency>
+
+		<!-- aspectJ -->
+		<dependency>
+			<groupId>org.aspectj</groupId>
+			<artifactId>aspectjweaver</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>org.aspectj</groupId>
+			<artifactId>aspectjrt</artifactId>
+		</dependency>
+
+		<!-- Dev -->
+		<dependency>
+			<groupId>org.projectlombok</groupId>
+			<artifactId>lombok</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>org.springframework.boot</groupId>
+			<artifactId>spring-boot-devtools</artifactId>
+			<scope>runtime</scope>
+		</dependency>
+
+		<!-- commons -->
+		<dependency>
+			<groupId>org.apache.commons</groupId>
+			<artifactId>commons-lang3</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>commons-io</groupId>
+			<artifactId>commons-io</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>commons-beanutils</groupId>
+			<artifactId>commons-beanutils</artifactId>
+		</dependency>
+
+		<!-- Test -->
+		<dependency>
+			<groupId>org.springframework.security</groupId>
+			<artifactId>spring-security-test</artifactId>
+			<scope>test</scope>
+		</dependency>
+		<dependency>
+			<groupId>de.itvsh.ozg.pluto</groupId>
+			<artifactId>pluto-utils</artifactId>
+			<type>test-jar</type>
+			<scope>test</scope>
+			<version>${pluto.version}</version>
+		</dependency>
+
+		<dependency>
+			<groupId>com.thedeanda</groupId>
+			<artifactId>lorem</artifactId>
+		</dependency>
+
+	</dependencies>
+
+	<build>
+		<finalName>${project.artifactId}</finalName>
+		<plugins>
+			<plugin>
+				<groupId>org.apache.maven.plugins</groupId>
+				<artifactId>maven-compiler-plugin</artifactId>
+			</plugin>
+			<plugin>
+				<groupId>org.springframework.boot</groupId>
+				<artifactId>spring-boot-maven-plugin</artifactId>
+				<configuration>
+					<docker>
+						<publishRegistry>
+							<username>${docker-username}</username>
+							<password>${docker-password}</password>
+							<url>${docker-url}</url>
+						</publishRegistry>
+					</docker>
+				</configuration>
+			</plugin>
+
+			<plugin>
+				<groupId>org.apache.maven.plugins</groupId>
+				<artifactId>maven-failsafe-plugin</artifactId>
+			</plugin>
+			<plugin>
+				<groupId>org.apache.maven.plugins</groupId>
+				<artifactId>maven-surefire-plugin</artifactId>
+			</plugin>
+			<plugin>
+				<groupId>org.jacoco</groupId>
+				<artifactId>jacoco-maven-plugin</artifactId>
+			</plugin>
+			<plugin>
+				<groupId>pl.project13.maven</groupId>
+				<artifactId>git-commit-id-plugin</artifactId>
+			</plugin>
+
+
+			<plugin>
+				<groupId>org.apache.maven.plugins</groupId>
+				<artifactId>maven-resources-plugin</artifactId>
+				<executions>
+					<execution>
+						<id>copy-client</id>
+						<phase>compile</phase>
+						<goals>
+							<goal>copy-resources</goal>
+						</goals>
+						<configuration>
+							<outputDirectory>${project.build.directory}/classes/META-INF/resources</outputDirectory>
+							<resources>
+								<resource>
+									<directory>../${project.parent.artifactId}-client/dist/apps/goofy/</directory>
+								</resource>
+							</resources>
+						</configuration>
+					</execution>
+				</executions>
+			</plugin>
+		</plugins>
+	</build>
+
+</project>
diff --git a/goofy-server/sonar-project.properties b/goofy-server/sonar-project.properties
new file mode 100644
index 0000000000000000000000000000000000000000..8660b9e44335323e02f6aaa513605e4a34635db9
--- /dev/null
+++ b/goofy-server/sonar-project.properties
@@ -0,0 +1,26 @@
+#
+# 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.
+#
+
+sonar.java.coveragePlugin=jacoco
+sonar.jacoco.reportPath=target/jacoco.exec
\ No newline at end of file
diff --git a/goofy-server/src/main/java/de/itvsh/goofy/CallBeanFactoryPostProcessor.java b/goofy-server/src/main/java/de/itvsh/goofy/CallBeanFactoryPostProcessor.java
new file mode 100644
index 0000000000000000000000000000000000000000..a841a0843757820f8a61694444f52f9cbe7ee6f8
--- /dev/null
+++ b/goofy-server/src/main/java/de/itvsh/goofy/CallBeanFactoryPostProcessor.java
@@ -0,0 +1,42 @@
+/*
+ * 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.itvsh.goofy;
+
+import org.springframework.beans.BeansException;
+import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
+import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
+
+import lombok.RequiredArgsConstructor;
+
+@RequiredArgsConstructor
+public class CallBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
+
+	private final CallScope callScope;
+
+	@Override
+	public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
+		beanFactory.registerScope(CallScope.SCOPE_NAME, callScope);
+	}
+
+}
diff --git a/goofy-server/src/main/java/de/itvsh/goofy/CallScope.java b/goofy-server/src/main/java/de/itvsh/goofy/CallScope.java
new file mode 100644
index 0000000000000000000000000000000000000000..316082488fc32a8e999d3795c966b6ca469adab5
--- /dev/null
+++ b/goofy-server/src/main/java/de/itvsh/goofy/CallScope.java
@@ -0,0 +1,90 @@
+/*
+ * 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.itvsh.goofy;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.springframework.beans.factory.ObjectFactory;
+import org.springframework.beans.factory.config.Scope;
+import org.springframework.core.NamedInheritableThreadLocal;
+
+import lombok.extern.log4j.Log4j2;
+
+@Log4j2
+public class CallScope implements Scope {
+
+	public static final String SCOPE_NAME = "call";
+
+	private static final ThreadLocal<Map<String, Object>> scopedObjectsHolder = new NamedInheritableThreadLocal<>("Call Context");
+	private static final ThreadLocal<Map<String, Runnable>> destructionCallbacksHolder = new NamedInheritableThreadLocal<>(
+			"Call Context Desctruction Callbacks");
+
+	public void startScope() {
+		LOG.info("START Call-Scope");
+		scopedObjectsHolder.set(new ConcurrentHashMap<>());
+		destructionCallbacksHolder.set(new ConcurrentHashMap<>());
+	}
+
+	public void endScope() {
+		scopedObjectsHolder.remove();
+		callAllDestructionCallbacks();
+		destructionCallbacksHolder.remove();
+	}
+
+	private void callAllDestructionCallbacks() {
+		destructionCallbacksHolder.get().values().forEach(Runnable::run);
+	}
+
+	@Override
+	public Object get(String name, ObjectFactory<?> objectFactory) {
+		if (!scopedObjectsHolder.get().containsKey(name)) {
+			scopedObjectsHolder.get().put(name, objectFactory.getObject());
+		}
+
+		return scopedObjectsHolder.get().get(name);
+	}
+
+	@Override
+	public Object remove(String name) {
+		destructionCallbacksHolder.get().remove(name);
+		return scopedObjectsHolder.get().remove(name);
+	}
+
+	@Override
+	public void registerDestructionCallback(String name, Runnable callback) {
+		destructionCallbacksHolder.get().put(name, callback);
+
+	}
+
+	@Override
+	public Object resolveContextualObject(String key) {
+		return scopedObjectsHolder.get().get(key);
+	}
+
+	@Override
+	public String getConversationId() {
+		return Thread.currentThread().getName();
+	}
+}
diff --git a/goofy-server/src/main/java/de/itvsh/goofy/EnvironmentController.java b/goofy-server/src/main/java/de/itvsh/goofy/EnvironmentController.java
new file mode 100644
index 0000000000000000000000000000000000000000..16a43db83b9555b0779b225a19d15a482e5772ed
--- /dev/null
+++ b/goofy-server/src/main/java/de/itvsh/goofy/EnvironmentController.java
@@ -0,0 +1,59 @@
+/*
+ * 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.itvsh.goofy;
+
+import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.*;
+
+import org.keycloak.adapters.springboot.KeycloakSpringBootProperties;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+@RestController
+@RequestMapping("/api/environment")
+public class EnvironmentController {
+
+	@Autowired
+	private KeycloakSpringBootProperties kcProperties;
+
+	@Value("${goofy.production}")
+	private boolean production = true;
+
+	@GetMapping
+	public FrontendEnvironment getFrontendEnvironment() {
+		return FrontendEnvironment.builder()//
+				.production(production)//
+				.remoteHost(apiRoot())//
+				.authServer(kcProperties.getAuthServerUrl())//
+				.clientId(kcProperties.getResource())//
+				.realm(kcProperties.getRealm())
+				.build();
+	}
+
+	private String apiRoot() {
+		return linkTo(RootController.class).toUri().toString();
+	}
+}
\ No newline at end of file
diff --git a/goofy-server/src/main/java/de/itvsh/goofy/FrontendEnvironment.java b/goofy-server/src/main/java/de/itvsh/goofy/FrontendEnvironment.java
new file mode 100644
index 0000000000000000000000000000000000000000..2a76e62af963a538fb212c95082f0f1c6e256638
--- /dev/null
+++ b/goofy-server/src/main/java/de/itvsh/goofy/FrontendEnvironment.java
@@ -0,0 +1,38 @@
+/*
+ * 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.itvsh.goofy;
+
+import lombok.Builder;
+import lombok.Getter;
+
+@Getter
+@Builder
+public class FrontendEnvironment {
+
+	private boolean production;
+	private String remoteHost;
+	private String authServer;
+	private String realm;
+	private String clientId;
+}
diff --git a/goofy-server/src/main/java/de/itvsh/goofy/GoofyServerApplication.java b/goofy-server/src/main/java/de/itvsh/goofy/GoofyServerApplication.java
new file mode 100644
index 0000000000000000000000000000000000000000..5bc352f40cf7db040d951e495dcab17093f754ac
--- /dev/null
+++ b/goofy-server/src/main/java/de/itvsh/goofy/GoofyServerApplication.java
@@ -0,0 +1,78 @@
+/*
+ * 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.itvsh.goofy;
+
+import java.util.TimeZone;
+
+import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.boot.web.servlet.FilterRegistrationBean;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.ComponentScan;
+import org.springframework.context.annotation.EnableAspectJAutoProxy;
+import org.springframework.scheduling.annotation.EnableAsync;
+import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
+import org.springframework.security.concurrent.DelegatingSecurityContextRunnable;
+import org.springframework.web.filter.ForwardedHeaderFilter;
+
+@SpringBootApplication
+@EnableAsync
+@EnableAspectJAutoProxy(proxyTargetClass = true)
+@ComponentScan({ "de.itvsh.*" })
+public class GoofyServerApplication {
+
+	public static final String GRPC_CLIENT = "pluto";
+
+	public static void main(String[] args) {
+		TimeZone.setDefault(TimeZone.getTimeZone("UTC"));
+		SpringApplication.run(GoofyServerApplication.class, args);
+	}
+
+	@Bean
+	public FilterRegistrationBean<ForwardedHeaderFilter> forwardedHeaderFilter() {
+		FilterRegistrationBean<ForwardedHeaderFilter> bean = new FilterRegistrationBean<>();
+		bean.setFilter(new ForwardedHeaderFilter());
+		return bean;
+	}
+
+	@Bean
+	public ThreadPoolTaskExecutor threadPoolTaskExecutor() {
+		ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
+
+		executor.setThreadNamePrefix("async-");
+		executor.setTaskDecorator(DelegatingSecurityContextRunnable::new);
+		return executor;
+	}
+
+	@Bean
+	public CallScope callScope() {
+		return new CallScope();
+	}
+
+	@Bean
+	public BeanFactoryPostProcessor beanFactoryPostProcessor(CallScope callScope) {
+		return new CallBeanFactoryPostProcessor(callScope);
+	}
+}
\ No newline at end of file
diff --git a/goofy-server/src/main/java/de/itvsh/goofy/JwtTokenUtil.java b/goofy-server/src/main/java/de/itvsh/goofy/JwtTokenUtil.java
new file mode 100644
index 0000000000000000000000000000000000000000..ac0bb5d7018b758d167081e698511277bd574f07
--- /dev/null
+++ b/goofy-server/src/main/java/de/itvsh/goofy/JwtTokenUtil.java
@@ -0,0 +1,129 @@
+/*
+ * 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.itvsh.goofy;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.core.authority.SimpleGrantedAuthority;
+import org.springframework.stereotype.Component;
+
+import com.auth0.jwt.JWT;
+import com.auth0.jwt.algorithms.Algorithm;
+import com.auth0.jwt.exceptions.JWTVerificationException;
+
+import de.itvsh.goofy.common.binaryfile.FileId;
+import de.itvsh.goofy.common.downloadtoken.DownloadTokenProperties;
+import de.itvsh.goofy.common.user.UserProfile;
+import io.jsonwebtoken.Claims;
+import io.jsonwebtoken.Jwts;
+import io.jsonwebtoken.SignatureAlgorithm;
+
+@Component
+public class JwtTokenUtil {
+
+	public static final String TOKEN_TYP_KEY = "typ";
+	public static final String TOKEN_TYPE = "JWT";
+	public static final String TOKEN_ISSUER = "secure-api";
+	public static final String TOKEN_AUDIENCE = "secure-app";
+	public static final String ROLE_CLAIM = "roles";
+	public static final String USERID_CLAIM = "userId";
+	public static final String FIRSTNAME_CLAIM = "firstName";
+	public static final String LASTNAME_CLAIM = "lastName";
+	public static final String FILEID_CLAIM = "fileId";
+	public static final String ORGANSIATIONSEINHEIT_IDS_CLAIM = "organisationseinheitIds";
+
+	@Autowired
+	private DownloadTokenProperties downloadTokenProperties;
+
+	@SuppressWarnings("unchecked")
+	private List<Map<String, String>> getRoleClaims(String token) {
+		List<Map<String, String>> roleClaims = new ArrayList<>();
+
+		getAllClaimsFromToken(token).ifPresent(claims -> roleClaims.addAll((List<Map<String, String>>) claims.get(ROLE_CLAIM)));
+
+		return roleClaims;
+	}
+
+	public Optional<Claims> getAllClaimsFromToken(String token) {
+		return Optional.of(Jwts.parser().setSigningKey(downloadTokenProperties.getSecret().getBytes()).parseClaimsJws(token).getBody());
+	}
+
+	public List<SimpleGrantedAuthority> getRolesFromToken(String token) {
+		List<Map<String, String>> claimsList = getRoleClaims(token);
+		return claimsList.stream()
+				.flatMap(rolesMap -> rolesMap.entrySet().stream())
+				.map(roleEntry -> new SimpleGrantedAuthority(roleEntry.getValue()))
+				.toList();
+	}
+
+	@SuppressWarnings({ "unchecked" })
+	public Collection<String> getOrganisationseinheitIdsFromToken(String token) {
+		List<String> organisationseinheitIds = new ArrayList<>();
+		getAllClaimsFromToken(token)
+				.ifPresent(claims -> {
+					if (claims.get(ORGANSIATIONSEINHEIT_IDS_CLAIM) != null) {
+						organisationseinheitIds.addAll((Collection<String>) claims.get(ORGANSIATIONSEINHEIT_IDS_CLAIM));
+					}
+				});
+		return organisationseinheitIds;
+	}
+
+	public String generateToken(FileId fileId, UserProfile user) {
+		var claims = new HashMap<String, Object>();
+		claims.put(USERID_CLAIM, user.getId().toString());
+		claims.put(FIRSTNAME_CLAIM, user.getFirstName());
+		claims.put(LASTNAME_CLAIM, user.getLastName());
+		claims.put(ROLE_CLAIM, user.getAuthorities());
+		claims.put(FILEID_CLAIM, fileId.toString());
+		claims.put(ORGANSIATIONSEINHEIT_IDS_CLAIM, user.getOrganisationseinheitIds());
+
+		return doGenerateToken(claims, user.getId().toString());
+	}
+
+	private String doGenerateToken(Map<String, Object> claims, String subject) {
+		return Jwts.builder()
+				.setClaims(claims)
+				.setSubject(subject)
+				.setHeaderParam(TOKEN_TYP_KEY, TOKEN_TYPE)
+				.setIssuer(TOKEN_ISSUER).setIssuedAt(new Date(System.currentTimeMillis()))
+				.setExpiration(new Date(System.currentTimeMillis() + downloadTokenProperties.getValidity()))
+				.setAudience(TOKEN_AUDIENCE)
+				.signWith(SignatureAlgorithm.HS512, downloadTokenProperties.getSecret().getBytes())
+				.compact();
+	}
+
+	public void verifyToken(String token) throws JWTVerificationException {
+		var algorithm = Algorithm.HMAC512(downloadTokenProperties.getSecret());
+		var verifier = JWT.require(algorithm).build();
+
+		verifier.verify(token);
+	}
+}
\ No newline at end of file
diff --git a/goofy-server/src/main/java/de/itvsh/goofy/RequestAttributes.java b/goofy-server/src/main/java/de/itvsh/goofy/RequestAttributes.java
new file mode 100644
index 0000000000000000000000000000000000000000..d7b2bf987415db144ed1a6b79c89f778399c6580
--- /dev/null
+++ b/goofy-server/src/main/java/de/itvsh/goofy/RequestAttributes.java
@@ -0,0 +1,48 @@
+/*
+ * 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.itvsh.goofy;
+
+import java.util.UUID;
+
+import org.springframework.context.annotation.Scope;
+import org.springframework.context.annotation.ScopedProxyMode;
+import org.springframework.stereotype.Component;
+
+import lombok.AccessLevel;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+
+@Component
+@Scope(scopeName = "call", proxyMode = ScopedProxyMode.TARGET_CLASS)
+@Getter
+@Builder
+@AllArgsConstructor(access = AccessLevel.PRIVATE)
+@NoArgsConstructor
+public class RequestAttributes {
+
+	@Builder.Default
+	private String requestId = UUID.randomUUID().toString();
+}
diff --git a/goofy-server/src/main/java/de/itvsh/goofy/RequestIdFilter.java b/goofy-server/src/main/java/de/itvsh/goofy/RequestIdFilter.java
new file mode 100644
index 0000000000000000000000000000000000000000..430039a8c01ee0dff2d59bc013936560af2ddd88
--- /dev/null
+++ b/goofy-server/src/main/java/de/itvsh/goofy/RequestIdFilter.java
@@ -0,0 +1,65 @@
+/*
+ * 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.itvsh.goofy;
+
+import java.io.IOException;
+
+import javax.servlet.FilterChain;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.logging.log4j.CloseableThreadContext;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+import org.springframework.web.filter.OncePerRequestFilter;
+
+import lombok.extern.log4j.Log4j2;
+
+@Component
+@Log4j2
+class RequestIdFilter extends OncePerRequestFilter {
+
+	private static final String REQUEST_ID_KEY = "requestId";
+
+	@Autowired
+	private RequestAttributes reqAttributes;
+	@Autowired
+	private CallScope callScope;
+
+	@Override
+	protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
+			throws ServletException, IOException {
+
+		callScope.startScope();
+		try (CloseableThreadContext.Instance ctc = CloseableThreadContext.put(REQUEST_ID_KEY, reqAttributes.getRequestId())) {
+			LOG.info("START of Request with ID '{}'.", reqAttributes.getRequestId());
+			filterChain.doFilter(request, response);
+		} finally {
+			LOG.info("END of Request with ID '{}'", reqAttributes.getRequestId());
+			callScope.endScope();
+		}
+	}
+
+}
diff --git a/goofy-server/src/main/java/de/itvsh/goofy/RootController.java b/goofy-server/src/main/java/de/itvsh/goofy/RootController.java
new file mode 100644
index 0000000000000000000000000000000000000000..24daf2d39e3d1586f1185d66a22124cdd3ab661e
--- /dev/null
+++ b/goofy-server/src/main/java/de/itvsh/goofy/RootController.java
@@ -0,0 +1,145 @@
+/*
+ * 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.itvsh.goofy;
+
+import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.*;
+
+import java.time.Instant;
+import java.util.Optional;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.info.BuildProperties;
+import org.springframework.hateoas.EntityModel;
+import org.springframework.hateoas.Link;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import de.itvsh.goofy.common.ModelBuilder;
+import de.itvsh.goofy.common.downloadtoken.DownloadTokenController;
+import de.itvsh.goofy.common.user.CurrentUserService;
+import de.itvsh.goofy.common.user.UserId;
+import de.itvsh.goofy.common.user.UserManagerUrlProvider;
+import de.itvsh.goofy.common.user.UserRemoteService;
+import de.itvsh.goofy.common.user.UserRole;
+import de.itvsh.goofy.system.SystemStatusService;
+import de.itvsh.goofy.vorgang.VorgangController;
+
+@RestController
+@RequestMapping(RootController.PATH)
+public class RootController {
+
+	static final String PATH = "/api"; // NOSONAR
+
+	static final String REL_VORGAENGE = "vorgaenge";
+	static final String REL_SEARCH = "search";
+	static final String REL_SEARCH_USER = "search-user-profiles";
+	static final String REL_MY_VORGAENGE = "myVorgaenge";
+	static final String REL_SEARCH_MY_VORGAENGE = "searchMyVorgaenge";
+	static final String REL_DOWNLOAD_TOKEN = "downloadToken";
+	static final String REL_CURRENT_USER = "currentUser";
+
+	@Autowired(required = false)
+	public BuildProperties buildProperties;
+	@Autowired
+	private CurrentUserService currentUserService;
+	@Autowired
+	private SystemStatusService systemStatusService;
+	@Autowired
+	private UserRemoteService internalUserIdService;
+
+	@Autowired
+	private UserManagerUrlProvider userManagerUrlProvider;
+
+	@GetMapping
+	public EntityModel<RootResource> getRootResource() {
+		var internalUserId = internalUserIdService.getUserId(currentUserService.getUserId());
+
+		var modelBuilder = ModelBuilder.fromEntity(new RootResource())
+				.ifMatch(this::hasRole).addLinks(
+						linkTo(RootController.class).withSelfRel(),
+						linkTo(VorgangController.class).withRel(REL_VORGAENGE),
+						linkTo(DownloadTokenController.class).withRel(REL_DOWNLOAD_TOKEN),
+						Link.of(userManagerUrlProvider.getUserProfileSearchTemplate(), REL_SEARCH_USER))
+				.ifMatch(this::hasRoleAndSearchServerAvailable).addLinks(
+						buildVorgangListByPageLink(REL_SEARCH, Optional.empty()));
+
+		internalUserId.ifPresent(userId -> modelBuilder
+				.ifMatch(this::hasVerwaltungRole).addLink(
+						buildVorgangListByPageLink(REL_MY_VORGAENGE, Optional.of(userId)))
+				.ifMatch(this::hasVerwaltungRoleAndSearchServerAvailable).addLink(
+						buildVorgangListByPageLink(REL_SEARCH_MY_VORGAENGE, Optional.of(userId))));
+
+		var model = modelBuilder.buildModel();
+
+		getUserProfilesUrl()
+				.ifPresent(urlTemplate -> model.add(Link.of(String.format(urlTemplate, currentUserService.getUserId()), REL_CURRENT_USER)));
+
+		return model;
+	}
+
+	private boolean hasRoleAndSearchServerAvailable() {
+		return hasRole() && systemStatusService.isSearchServerAvailable();
+	}
+
+	private boolean hasVerwaltungRoleAndSearchServerAvailable() {
+		return hasVerwaltungRole() && systemStatusService.isSearchServerAvailable();
+	}
+
+	boolean hasRole() {
+		return hasVerwaltungRole() || currentUserService.hasRole(UserRole.EINHEITLICHER_ANSPRECHPARTNER);
+	}
+
+	boolean hasVerwaltungRole() {
+		return currentUserService.hasRole(UserRole.VERWALTUNG_USER)
+				|| currentUserService.hasRole(UserRole.VERWALTUNG_POSTSTELLE);
+	}
+
+	private Link buildVorgangListByPageLink(String linkRel, Optional<UserId> assignedTo) {
+		return linkTo(methodOn(VorgangController.class).getVorgangListByPage(0, null, null, assignedTo)).withRel(linkRel);
+	}
+
+	Optional<String> getUserProfilesUrl() {
+		if (userManagerUrlProvider.isConfiguredForUserProfile()) {
+			return Optional.of(userManagerUrlProvider.getUserProfileTemplate());
+		}
+
+		return Optional.empty();
+	}
+
+	class RootResource {
+
+		public String getVersion() {
+			return buildProperties == null ? "--" : buildProperties.getVersion();
+		}
+
+		public Instant getBuildTime() {
+			return buildProperties == null ? null : buildProperties.getTime();
+		}
+
+		public String getJavaVersion() {
+			return System.getProperty("java.version");
+		}
+	}
+}
\ No newline at end of file
diff --git a/goofy-server/src/main/java/de/itvsh/goofy/SecurityConfiguration.java b/goofy-server/src/main/java/de/itvsh/goofy/SecurityConfiguration.java
new file mode 100644
index 0000000000000000000000000000000000000000..59957616d39acaa1d869fd9069c4e8005dc43b00
--- /dev/null
+++ b/goofy-server/src/main/java/de/itvsh/goofy/SecurityConfiguration.java
@@ -0,0 +1,87 @@
+/*
+ * 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.itvsh.goofy;
+
+import org.keycloak.adapters.springsecurity.KeycloakConfiguration;
+import org.keycloak.adapters.springsecurity.authentication.KeycloakAuthenticationProvider;
+import org.keycloak.adapters.springsecurity.config.KeycloakWebSecurityConfigurerAdapter;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.HttpMethod;
+import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
+import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.config.http.SessionCreationPolicy;
+import org.springframework.security.core.authority.mapping.SimpleAuthorityMapper;
+import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
+import org.springframework.security.web.authentication.session.NullAuthenticatedSessionStrategy;
+import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy;
+import org.springframework.security.web.csrf.CookieCsrfTokenRepository;
+import org.springframework.security.web.header.writers.frameoptions.XFrameOptionsHeaderWriter;
+import org.springframework.security.web.header.writers.frameoptions.XFrameOptionsHeaderWriter.XFrameOptionsMode;
+
+import de.itvsh.goofy.common.downloadtoken.DownloadTokenAuthenticationFilter;
+
+@EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true)
+@KeycloakConfiguration
+public class SecurityConfiguration extends KeycloakWebSecurityConfigurerAdapter {
+
+	@Autowired
+	private DownloadTokenAuthenticationFilter downloadTokenFilter;
+
+	@Override
+	protected void configure(HttpSecurity http) throws Exception {
+		super.configure(http);
+		http.csrf().csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse());
+
+		http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
+				.and().authorizeRequests()//
+				.antMatchers(HttpMethod.GET, "/api/environment").permitAll()//
+				.antMatchers(HttpMethod.GET, "/assets/**").permitAll()//
+				.antMatchers(HttpMethod.GET, "/vorgang/**").permitAll()//
+				.antMatchers(HttpMethod.GET, "/search/**").permitAll()//
+				.antMatchers("/api").authenticated()//
+				.antMatchers("/api/**").authenticated()//
+				.antMatchers("/actuator").permitAll()//
+				.antMatchers("/actuator/**").permitAll()//
+				.antMatchers("/").permitAll()//
+				.antMatchers("/*").permitAll()//
+				.anyRequest().denyAll();
+
+		http.headers().addHeaderWriter(new XFrameOptionsHeaderWriter(XFrameOptionsMode.SAMEORIGIN));
+		http.addFilterBefore(downloadTokenFilter, UsernamePasswordAuthenticationFilter.class);
+	}
+
+	@Autowired
+	public void configureGlobal(AuthenticationManagerBuilder auth) {
+		KeycloakAuthenticationProvider keyCloakAuthProvider = keycloakAuthenticationProvider();
+		keyCloakAuthProvider.setGrantedAuthoritiesMapper(new SimpleAuthorityMapper());
+		auth.authenticationProvider(keyCloakAuthProvider);
+	}
+
+	@Override
+	protected SessionAuthenticationStrategy sessionAuthenticationStrategy() {
+		return new NullAuthenticatedSessionStrategy();
+	}
+
+}
diff --git a/goofy-server/src/main/java/de/itvsh/goofy/WebConfig.java b/goofy-server/src/main/java/de/itvsh/goofy/WebConfig.java
new file mode 100644
index 0000000000000000000000000000000000000000..72090847f2c127e444ee734b5550b01301f2549f
--- /dev/null
+++ b/goofy-server/src/main/java/de/itvsh/goofy/WebConfig.java
@@ -0,0 +1,73 @@
+/*
+ * 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.itvsh.goofy;
+
+import java.io.IOException;
+import java.util.concurrent.TimeUnit;
+
+import org.keycloak.adapters.KeycloakConfigResolver;
+import org.keycloak.adapters.springboot.KeycloakSpringBootConfigResolver;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.core.io.Resource;
+import org.springframework.http.CacheControl;
+import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
+import org.springframework.web.servlet.resource.PathResourceResolver;
+
+@Configuration
+public class WebConfig implements WebMvcConfigurer {
+
+	private static final String RESOURCE_LOCATION = "classpath:/META-INF/resources/";
+
+	@Override
+	public void addResourceHandlers(ResourceHandlerRegistry registry) {
+
+		registry.addResourceHandler("/*.js", "/*.css", "/*.ttf", "/*.woff", "/*.woff2", "/*.eot",
+				"/**/*.svg", "/*.svf", "/*.otf", "/*.ico", "/**/*.png")
+				.addResourceLocations(RESOURCE_LOCATION)
+				.setCacheControl(CacheControl.maxAge(30, TimeUnit.DAYS).cachePublic())
+				.resourceChain(true)
+				.addResolver(new PathResourceResolver());
+
+		registry.addResourceHandler("/**/*")
+				.addResourceLocations(RESOURCE_LOCATION)
+				.setCacheControl(CacheControl.noStore())
+				.setUseLastModified(false)
+				.resourceChain(true)
+				.addResolver(new PathResourceResolver() {
+					@Override
+					protected Resource getResource(String resourcePath, Resource location) throws IOException {
+						Resource requestedResource = location.createRelative(resourcePath);
+						return requestedResource.exists() && requestedResource.isReadable() ? requestedResource
+								: super.getResource("index.html", location);
+					}
+				});
+	}
+
+	@Bean
+	public KeycloakConfigResolver keyCloakConfigResolver() {
+		return new KeycloakSpringBootConfigResolver();
+	}
+}
diff --git a/goofy-server/src/main/java/de/itvsh/goofy/attachment/AttachmentController.java b/goofy-server/src/main/java/de/itvsh/goofy/attachment/AttachmentController.java
new file mode 100644
index 0000000000000000000000000000000000000000..719334be8b9930e45df8ddfc2972f576c5ea0351
--- /dev/null
+++ b/goofy-server/src/main/java/de/itvsh/goofy/attachment/AttachmentController.java
@@ -0,0 +1,55 @@
+/*
+ * 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.itvsh.goofy.attachment;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.hateoas.CollectionModel;
+import org.springframework.hateoas.EntityModel;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import de.itvsh.goofy.common.binaryfile.BinaryFileModelAssembler;
+import de.itvsh.goofy.common.file.OzgFile;
+import de.itvsh.goofy.common.file.OzgFileService;
+
+@RestController
+@RequestMapping(AttachmentController.ATTACHMENT_PATH)
+public class AttachmentController {
+
+	static final String ATTACHMENT_PATH = "/api/attachments"; // NOSONAR
+
+	static final String PARAM_EINGANG_ID = "eingangId";
+
+	@Autowired
+	private OzgFileService fileService;
+	@Autowired
+	private BinaryFileModelAssembler modelAssembler;
+
+	@GetMapping(params = PARAM_EINGANG_ID)
+	public CollectionModel<EntityModel<OzgFile>> getAllByEingang(@RequestParam String eingangId) {
+		return modelAssembler.toCollectionModel(fileService.getAttachmentsByEingang(eingangId));
+	}
+}
\ No newline at end of file
diff --git a/goofy-server/src/main/java/de/itvsh/goofy/common/AbstractLinkedResourceDeserializer.java b/goofy-server/src/main/java/de/itvsh/goofy/common/AbstractLinkedResourceDeserializer.java
new file mode 100644
index 0000000000000000000000000000000000000000..3100089a888c3237b5ef6869c8d502101e5d9af1
--- /dev/null
+++ b/goofy-server/src/main/java/de/itvsh/goofy/common/AbstractLinkedResourceDeserializer.java
@@ -0,0 +1,135 @@
+/*
+ * 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.itvsh.goofy.common;
+
+import java.io.IOException;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.net.URLDecoder;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Objects;
+import java.util.Set;
+
+import org.apache.commons.lang3.StringUtils;
+
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.databind.BeanProperty;
+import com.fasterxml.jackson.databind.DeserializationContext;
+import com.fasterxml.jackson.databind.JavaType;
+import com.fasterxml.jackson.databind.deser.ContextualDeserializer;
+import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
+
+import de.itvsh.kop.common.datatype.StringBasedValue;
+import lombok.Getter;
+
+abstract class AbstractLinkedResourceDeserializer extends StdDeserializer<Object> implements ContextualDeserializer {
+
+	private static final long serialVersionUID = 1L;
+
+	@Getter
+	private BeanProperty beanProperty;
+
+	@Getter
+	private final JavaType targetType;
+
+	protected AbstractLinkedResourceDeserializer() {
+		super(Object.class);
+		targetType = null;
+	}
+
+	protected AbstractLinkedResourceDeserializer(BeanProperty beanProperty) {
+		super(Object.class);
+		this.beanProperty = beanProperty;
+		this.targetType = beanProperty.getType();
+	}
+
+	@Override
+	public Object deserialize(JsonParser jsonParser, DeserializationContext ctxt) throws IOException {
+		if (jsonParser.isExpectedStartArrayToken()) {
+			Collection<Object> idList = targetType.getRawClass().isAssignableFrom(Set.class) ? new HashSet<>() : new ArrayList<>();
+
+			while (!jsonParser.nextToken().isStructEnd()) {
+				idList.add(extractId(jsonParser.getText()));
+			}
+			return idList;
+		} else {
+			return extractId(jsonParser.getText());
+		}
+	}
+
+	Object extractId(String url) {
+		Class<?> type;
+		if (targetType.isCollectionLikeType()) {
+			type = targetType.getContentType().getRawClass();
+		} else {
+			type = targetType.getRawClass();
+		}
+
+		if (String.class.isAssignableFrom(type)) {
+			return extractStringId(url);
+		}
+		if (Long.class.isAssignableFrom(type) || Long.TYPE.isAssignableFrom(type)) {
+			return extractLongId(url);
+		}
+		if (StringBasedValue.class.isAssignableFrom(type)) {
+			return extractStringBasedValue(type, url);
+		}
+		return buildByBuilder(url);
+	}
+
+	abstract Object buildByBuilder(String url);
+
+	public static Long extractLongId(String uri) {
+		var trimedUri = StringUtils.trimToNull(uri);
+		if (Objects.isNull(trimedUri)) {
+			return null;
+		}
+		return Long.parseLong(URLDecoder.decode(trimedUri.substring(trimedUri.lastIndexOf('/') + 1), StandardCharsets.UTF_8));
+	}
+
+	private StringBasedValue extractStringBasedValue(Class<?> type, String url) {
+		String value = extractStringId(url);
+		Method fromMethod;
+		try {
+			fromMethod = type.getMethod("from", String.class);
+		} catch (NoSuchMethodException e) {
+			throw new IllegalStateException(
+					String.format("Cannot generate Id from type '%s'. Missing 'from' Method.", targetType.getRawClass().getSimpleName()));
+		}
+		try {
+			return (StringBasedValue) fromMethod.invoke(null, value);
+		} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
+			throw new IllegalStateException(
+					String.format("Cannot generate Id from type '%s'. Error calling 'from' Method.", targetType.getRawClass().getSimpleName()),
+					e);
+		}
+	}
+
+	public static String extractStringId(String url) {
+		return URLDecoder.decode(url.substring(url.lastIndexOf('/') + 1), StandardCharsets.UTF_8);
+	}
+}
diff --git a/goofy-server/src/main/java/de/itvsh/goofy/common/AbstractLinkedResourceSerializer.java b/goofy-server/src/main/java/de/itvsh/goofy/common/AbstractLinkedResourceSerializer.java
new file mode 100644
index 0000000000000000000000000000000000000000..29c8fd6b30413864b65bc6964efcfc30e3a6f2e0
--- /dev/null
+++ b/goofy-server/src/main/java/de/itvsh/goofy/common/AbstractLinkedResourceSerializer.java
@@ -0,0 +1,60 @@
+/*
+ * 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.itvsh.goofy.common;
+
+import java.io.IOException;
+import java.util.Collection;
+
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.databind.JsonSerializer;
+import com.fasterxml.jackson.databind.SerializerProvider;
+import com.fasterxml.jackson.databind.ser.ContextualSerializer;
+
+import de.itvsh.kop.common.errorhandling.TechnicalException;
+
+abstract class AbstractLinkedResourceSerializer extends JsonSerializer<Object> implements ContextualSerializer {
+	@Override
+	public void serialize(Object value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
+
+		if (value instanceof Collection) {
+			gen.writeStartArray();
+			((Collection<?>) value).forEach(val -> writeObject(gen, buildLink(val)));
+			gen.writeEndArray();
+		} else {
+			writeObject(gen, buildLink(value));
+		}
+	}
+
+	abstract String buildLink(Object id);
+
+	abstract IdExtractor<Object> getExtractor();
+
+	void writeObject(JsonGenerator gen, Object value) {
+		try {
+			gen.writeObject(value);
+		} catch (IOException e) {
+			throw new TechnicalException("Error writing String to json", e);
+		}
+	}
+}
diff --git a/goofy-server/src/main/java/de/itvsh/goofy/common/BaseTypesMapper.java b/goofy-server/src/main/java/de/itvsh/goofy/common/BaseTypesMapper.java
new file mode 100644
index 0000000000000000000000000000000000000000..6e103714dab75d63ca95542a2fe045652aefde59
--- /dev/null
+++ b/goofy-server/src/main/java/de/itvsh/goofy/common/BaseTypesMapper.java
@@ -0,0 +1,42 @@
+/*
+ * 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.itvsh.goofy.common;
+
+import java.time.ZonedDateTime;
+import java.util.Optional;
+
+import org.apache.commons.lang3.StringUtils;
+import org.mapstruct.Mapper;
+
+@Mapper
+public interface BaseTypesMapper {
+
+	default String fromString(String in) {
+		return StringUtils.trimToNull(in);
+	}
+
+	default ZonedDateTime mapDateTime(String inStr) {
+		return Optional.ofNullable(fromString(inStr)).map(ZonedDateTime::parse).orElse(null);
+	}
+}
diff --git a/goofy-server/src/main/java/de/itvsh/goofy/common/GrpcUtil.java b/goofy-server/src/main/java/de/itvsh/goofy/common/GrpcUtil.java
new file mode 100644
index 0000000000000000000000000000000000000000..640d522dc9c046801df440cc0d89eddd25b2c8d2
--- /dev/null
+++ b/goofy-server/src/main/java/de/itvsh/goofy/common/GrpcUtil.java
@@ -0,0 +1,54 @@
+/*
+ * 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.itvsh.goofy.common;
+
+import java.nio.charset.StandardCharsets;
+import java.util.Collection;
+import java.util.Optional;
+import java.util.stream.StreamSupport;
+
+import io.grpc.Metadata;
+import io.grpc.Metadata.Key;
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public class GrpcUtil {
+
+	public static Key<byte[]> createKeyOf(String key) {
+		return Key.of(key, Metadata.BINARY_BYTE_MARSHALLER);
+	}
+
+	public static String getFromHeaders(String key, Metadata headers) {
+		return Optional.ofNullable(headers.get(createKeyOf(key)))
+				.map(val -> new String(val, StandardCharsets.UTF_8))
+				.orElse(null);
+	}
+
+	public static Collection<String> getCollection(String key, Metadata headers) {
+		return StreamSupport.stream(headers.getAll(createKeyOf(key)).spliterator(), false)
+				.map(bytes -> new String(bytes, StandardCharsets.UTF_8))
+				.toList();
+	}
+}
\ No newline at end of file
diff --git a/goofy-server/src/main/java/de/itvsh/goofy/common/IdBuilder.java b/goofy-server/src/main/java/de/itvsh/goofy/common/IdBuilder.java
new file mode 100644
index 0000000000000000000000000000000000000000..545739c2c2e4b279b631268a607a4b4d9a206c8e
--- /dev/null
+++ b/goofy-server/src/main/java/de/itvsh/goofy/common/IdBuilder.java
@@ -0,0 +1,42 @@
+/*
+ * 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.itvsh.goofy.common;
+
+import com.fasterxml.jackson.databind.BeanProperty;
+
+import lombok.NoArgsConstructor;
+
+@NoArgsConstructor
+class IdBuilder implements ObjectBuilder<Object> {
+
+	@Override
+	public Object build(Object id) {
+		return id;
+	}
+
+	@Override
+	public ObjectBuilder<Object> constructContextAware(BeanProperty property) {
+		return new IdBuilder();
+	}
+}
diff --git a/goofy-server/src/main/java/de/itvsh/goofy/common/IdExtractor.java b/goofy-server/src/main/java/de/itvsh/goofy/common/IdExtractor.java
new file mode 100644
index 0000000000000000000000000000000000000000..6fc433669c084148897e8dbdaa83d4c3ae9fd6d1
--- /dev/null
+++ b/goofy-server/src/main/java/de/itvsh/goofy/common/IdExtractor.java
@@ -0,0 +1,29 @@
+/*
+ * 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.itvsh.goofy.common;
+
+@FunctionalInterface
+public interface IdExtractor<T> {
+	String extractId(T object);
+}
\ No newline at end of file
diff --git a/goofy-server/src/main/java/de/itvsh/goofy/common/IgnoreGrpcFields.java b/goofy-server/src/main/java/de/itvsh/goofy/common/IgnoreGrpcFields.java
new file mode 100644
index 0000000000000000000000000000000000000000..6f15affe1f440110f3c135dff984d78cb7acd1c2
--- /dev/null
+++ b/goofy-server/src/main/java/de/itvsh/goofy/common/IgnoreGrpcFields.java
@@ -0,0 +1,39 @@
+/*
+ * 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.itvsh.goofy.common;
+
+import org.mapstruct.Mapping;
+
+@Mapping(target = "mergeFrom", ignore = true)
+@Mapping(target = "clearField", ignore = true)
+@Mapping(target = "clearOneof", ignore = true)
+@Mapping(target = "mergeUnknownFields", ignore = true)
+@Mapping(target = "unknownFields", ignore = true)
+@Mapping(target = "allFields", ignore = true)
+@Mapping(target = "createdAtBytes", ignore = true)
+@Mapping(target = "createdByBytes", ignore = true)
+@Mapping(target = "createdByNameBytes", ignore = true)
+@Mapping(target = "idBytes", ignore = true)
+public @interface IgnoreGrpcFields {
+}
diff --git a/goofy-server/src/main/java/de/itvsh/goofy/common/LinkedResource.java b/goofy-server/src/main/java/de/itvsh/goofy/common/LinkedResource.java
new file mode 100644
index 0000000000000000000000000000000000000000..668e4eae7e000fedf87dbae222204277cd366bef
--- /dev/null
+++ b/goofy-server/src/main/java/de/itvsh/goofy/common/LinkedResource.java
@@ -0,0 +1,50 @@
+/*
+ * 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.itvsh.goofy.common;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
+import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.FIELD)
+@Inherited
+
+@JacksonAnnotationsInside
+@JsonSerialize(using = LinkedResourceSerializer.class)
+@JsonDeserialize(using = LinkedResourceDeserializer.class)
+public @interface LinkedResource {
+
+	Class<?> controllerClass();
+
+	Class<? extends IdExtractor<Object>> extractor() default ToStringExtractor.class;
+
+	Class<? extends ObjectBuilder<Object>> builder() default IdBuilder.class;
+}
\ No newline at end of file
diff --git a/goofy-server/src/main/java/de/itvsh/goofy/common/LinkedResourceDeserializer.java b/goofy-server/src/main/java/de/itvsh/goofy/common/LinkedResourceDeserializer.java
new file mode 100644
index 0000000000000000000000000000000000000000..9fcf9f0fe1c95f4f39e7557824ff487e8896a5c9
--- /dev/null
+++ b/goofy-server/src/main/java/de/itvsh/goofy/common/LinkedResourceDeserializer.java
@@ -0,0 +1,67 @@
+/*
+ * 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.itvsh.goofy.common;
+
+import java.lang.reflect.InvocationTargetException;
+
+import org.apache.commons.lang3.reflect.ConstructorUtils;
+
+import com.fasterxml.jackson.databind.BeanProperty;
+import com.fasterxml.jackson.databind.DeserializationContext;
+import com.fasterxml.jackson.databind.JsonDeserializer;
+
+import de.itvsh.kop.common.errorhandling.TechnicalException;
+
+public class LinkedResourceDeserializer extends AbstractLinkedResourceDeserializer {
+
+	private static final long serialVersionUID = 1L;
+
+	private LinkedResource annotation;
+
+	protected LinkedResourceDeserializer() {
+		super();
+	}
+
+	protected LinkedResourceDeserializer(BeanProperty beanProperty) {
+		super(beanProperty);
+		this.annotation = beanProperty.getAnnotation(LinkedResource.class);
+	}
+
+	@Override
+	Object buildByBuilder(String url) {
+		ObjectBuilder<?> builder;
+		try {
+			builder = ConstructorUtils.invokeConstructor(annotation.builder()).constructContextAware(getBeanProperty());
+		} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException | InstantiationException e) {
+			throw new TechnicalException("Error instanciating Builder.", e);
+		}
+		return builder.build(extractStringId(url));
+	}
+
+	@Override
+	public JsonDeserializer<?> createContextual(DeserializationContext ctxt, BeanProperty property) {
+		return new LinkedResourceDeserializer(property);
+	}
+
+}
diff --git a/goofy-server/src/main/java/de/itvsh/goofy/common/LinkedResourceSerializer.java b/goofy-server/src/main/java/de/itvsh/goofy/common/LinkedResourceSerializer.java
new file mode 100644
index 0000000000000000000000000000000000000000..0bc2e4c58dc4f6c643ea02de0582b21a6a3995b5
--- /dev/null
+++ b/goofy-server/src/main/java/de/itvsh/goofy/common/LinkedResourceSerializer.java
@@ -0,0 +1,66 @@
+/*
+ * 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.itvsh.goofy.common;
+
+import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.*;
+
+import java.lang.reflect.InvocationTargetException;
+
+import org.apache.commons.lang3.reflect.ConstructorUtils;
+
+import com.fasterxml.jackson.databind.BeanProperty;
+import com.fasterxml.jackson.databind.JsonSerializer;
+import com.fasterxml.jackson.databind.SerializerProvider;
+
+import de.itvsh.kop.common.errorhandling.TechnicalException;
+import lombok.NoArgsConstructor;
+
+@NoArgsConstructor
+public class LinkedResourceSerializer extends AbstractLinkedResourceSerializer {
+
+	private LinkedResource annotation;
+
+	private LinkedResourceSerializer(LinkedResource annotation) {
+		this.annotation = annotation;
+	}
+
+	@Override
+	public JsonSerializer<?> createContextual(SerializerProvider prov, BeanProperty property) {
+		return new LinkedResourceSerializer(property.getAnnotation(LinkedResource.class));
+	}
+
+	@Override
+	String buildLink(Object id) {
+		return linkTo(annotation.controllerClass()).slash(getExtractor().extractId(id)).toString();
+	}
+
+	@Override
+	IdExtractor<Object> getExtractor() {
+		try {
+			return ConstructorUtils.invokeConstructor(annotation.extractor());
+		} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException | InstantiationException e) {
+			throw new TechnicalException("Error instanciating IdExtractor", e);
+		}
+	}
+}
diff --git a/goofy-server/src/main/java/de/itvsh/goofy/common/LinkedUserProfileResource.java b/goofy-server/src/main/java/de/itvsh/goofy/common/LinkedUserProfileResource.java
new file mode 100644
index 0000000000000000000000000000000000000000..8a08a1898bf2a1f3dfc5b392cfab02ead1dcba2a
--- /dev/null
+++ b/goofy-server/src/main/java/de/itvsh/goofy/common/LinkedUserProfileResource.java
@@ -0,0 +1,48 @@
+/*
+ * 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.itvsh.goofy.common;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
+import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.FIELD)
+@Inherited
+
+@JacksonAnnotationsInside
+@JsonSerialize(using = LinkedUserProfileResourceSerializer.class)
+@JsonDeserialize(using = LinkedUserProfileResourceDeserializer.class)
+public @interface LinkedUserProfileResource {
+
+	Class<? extends IdExtractor<Object>> extractor() default ToStringExtractor.class;
+
+	Class<? extends ObjectBuilder<Object>> builder() default IdBuilder.class;
+}
diff --git a/goofy-server/src/main/java/de/itvsh/goofy/common/LinkedUserProfileResourceDeserializer.java b/goofy-server/src/main/java/de/itvsh/goofy/common/LinkedUserProfileResourceDeserializer.java
new file mode 100644
index 0000000000000000000000000000000000000000..a8ed967a5bd64ddb3f859ea4310a20fac949dab6
--- /dev/null
+++ b/goofy-server/src/main/java/de/itvsh/goofy/common/LinkedUserProfileResourceDeserializer.java
@@ -0,0 +1,66 @@
+/*
+ * 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.itvsh.goofy.common;
+
+import java.lang.reflect.InvocationTargetException;
+
+import org.apache.commons.lang3.reflect.ConstructorUtils;
+
+import com.fasterxml.jackson.databind.BeanProperty;
+import com.fasterxml.jackson.databind.DeserializationContext;
+import com.fasterxml.jackson.databind.JsonDeserializer;
+
+import de.itvsh.kop.common.errorhandling.TechnicalException;
+
+public class LinkedUserProfileResourceDeserializer extends AbstractLinkedResourceDeserializer {
+
+	private static final long serialVersionUID = 1L;
+
+	private LinkedUserProfileResource annotation;
+
+	protected LinkedUserProfileResourceDeserializer() {
+		super();
+	}
+
+	protected LinkedUserProfileResourceDeserializer(BeanProperty beanProperty) {
+		super(beanProperty);
+		this.annotation = beanProperty.getAnnotation(LinkedUserProfileResource.class);
+	}
+
+	@Override
+	Object buildByBuilder(String url) {
+		ObjectBuilder<?> builder;
+		try {
+			builder = ConstructorUtils.invokeConstructor(annotation.builder()).constructContextAware(getBeanProperty());
+		} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException | InstantiationException e) {
+			throw new TechnicalException("Error instanciating Builder.", e);
+		}
+		return builder.build(extractStringId(url));
+	}
+
+	@Override
+	public JsonDeserializer<?> createContextual(DeserializationContext ctxt, BeanProperty property) {
+		return new LinkedUserProfileResourceDeserializer(property);
+	}
+}
diff --git a/goofy-server/src/main/java/de/itvsh/goofy/common/LinkedUserProfileResourceSerializer.java b/goofy-server/src/main/java/de/itvsh/goofy/common/LinkedUserProfileResourceSerializer.java
new file mode 100644
index 0000000000000000000000000000000000000000..df1eea531784bf4be748500077486b6de0984d56
--- /dev/null
+++ b/goofy-server/src/main/java/de/itvsh/goofy/common/LinkedUserProfileResourceSerializer.java
@@ -0,0 +1,90 @@
+/*
+ * 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.itvsh.goofy.common;
+
+import java.io.IOException;
+import java.lang.reflect.InvocationTargetException;
+import java.util.Collection;
+
+import org.apache.commons.lang3.reflect.ConstructorUtils;
+import org.springframework.hateoas.Link;
+
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.databind.BeanProperty;
+import com.fasterxml.jackson.databind.JsonSerializer;
+import com.fasterxml.jackson.databind.SerializerProvider;
+import com.fasterxml.jackson.databind.ser.ContextualSerializer;
+
+import de.itvsh.kop.common.errorhandling.TechnicalException;
+import lombok.NoArgsConstructor;
+
+@NoArgsConstructor
+public class LinkedUserProfileResourceSerializer extends JsonSerializer<Object> implements ContextualSerializer {
+
+	private LinkedUserProfileResource annotation;
+
+	private LinkedUserProfileResourceSerializer(LinkedUserProfileResource annotation) {
+		this.annotation = annotation;
+	}
+
+	@Override
+	public JsonSerializer<?> createContextual(SerializerProvider prov, BeanProperty property) {
+		return new LinkedUserProfileResourceSerializer(property.getAnnotation(LinkedUserProfileResource.class));
+	}
+
+	String buildLink(Object id) {
+		if (UserProfileUrlProvider.isConfigured()) {
+			return Link.of(UserProfileUrlProvider.getUrl(getExtractor().extractId(id))).getHref();
+		} else {
+			return id.toString();
+		}
+	}
+
+	IdExtractor<Object> getExtractor() {
+		try {
+			return ConstructorUtils.invokeConstructor(annotation.extractor());
+		} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException | InstantiationException e) {
+			throw new TechnicalException("Error instanciating IdExtractor", e);
+		}
+	}
+
+	@Override
+	public void serialize(Object value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
+		if (value instanceof Collection) {
+			gen.writeStartArray();
+			((Collection<?>) value).forEach(val -> writeObject(gen, buildLink(val)));
+			gen.writeEndArray();
+		} else {
+			writeObject(gen, buildLink(value));
+		}
+	}
+
+	void writeObject(JsonGenerator gen, Object value) {
+		try {
+			gen.writeObject(value);
+		} catch (IOException e) {
+			throw new TechnicalException("Error writing String to json", e);
+		}
+	}
+}
\ No newline at end of file
diff --git a/goofy-server/src/main/java/de/itvsh/goofy/common/ModelBuilder.java b/goofy-server/src/main/java/de/itvsh/goofy/common/ModelBuilder.java
new file mode 100644
index 0000000000000000000000000000000000000000..b484c19bdbdda160d7e2ab085678b792580688bd
--- /dev/null
+++ b/goofy-server/src/main/java/de/itvsh/goofy/common/ModelBuilder.java
@@ -0,0 +1,259 @@
+/*
+ * 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.itvsh.goofy.common;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Field;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.function.BooleanSupplier;
+import java.util.function.Function;
+import java.util.function.Predicate;
+import java.util.function.Supplier;
+import java.util.function.UnaryOperator;
+import java.util.stream.Collectors;
+
+import org.apache.commons.collections.CollectionUtils;
+import org.apache.commons.lang3.reflect.FieldUtils;
+import org.springframework.hateoas.EntityModel;
+import org.springframework.hateoas.Link;
+import org.springframework.hateoas.server.mvc.WebMvcLinkBuilder;
+
+import lombok.RequiredArgsConstructor;
+import lombok.extern.log4j.Log4j2;
+
+@Log4j2
+public class ModelBuilder<T> {
+
+	private static final Map<Class<?>, Map<Object, List<Field>>> ANNOTATED_FIELDS_BY_ANNOTATION = new ConcurrentHashMap<>();
+
+	private final T entity;
+	private final EntityModel<T> model;
+
+	private final List<Link> links = new LinkedList<>();
+	private final List<Function<EntityModel<T>, EntityModel<T>>> mapper = new LinkedList<>();
+
+	private ModelBuilder(T entity) {
+		this.entity = entity;
+		this.model = null;
+	}
+
+	private ModelBuilder(EntityModel<T> model) {
+		this.entity = null;
+		this.model = model;
+	}
+
+	public static <T> ModelBuilder<T> fromEntity(T entity) {
+		return new ModelBuilder<>(entity);
+	}
+
+	public static <T> ModelBuilder<T> fromModel(EntityModel<T> model) {
+		return new ModelBuilder<>(model);
+	}
+
+	public ModelBuilder<T> addLink(Link link) {
+		links.add(link);
+		return this;
+	}
+
+	public ModelBuilder<T> addLinks(Link... links) {
+		this.links.addAll(Arrays.asList(links));
+		return this;
+	}
+
+	public ModelBuilder<T> addLinks(Collection<Link> links) {
+		this.links.addAll(links);
+		return this;
+	}
+
+	public ConditionalLinkAdder ifMatch(Predicate<T> predicate) {
+		return new ConditionalLinkAdder(predicate.test(Objects.isNull(entity) ? model.getContent() : entity));
+	}
+
+	public ConditionalLinkAdder ifMatch(BooleanSupplier guard) {
+		return new ConditionalLinkAdder(guard.getAsBoolean());
+	}
+
+	public ModelBuilder<T> map(UnaryOperator<EntityModel<T>> mapper) {
+		this.mapper.add(mapper);
+		return this;
+	}
+
+	public EntityModel<T> buildModel() {
+		var filteredLinks = links.stream().filter(Objects::nonNull).collect(Collectors.toSet());
+
+		EntityModel<T> buildedModel = Objects.isNull(model) ? EntityModel.of(entity) : model;
+		buildedModel = buildedModel.add(filteredLinks);
+
+		addLinkByLinkedResourceAnnotationIfMissing(buildedModel);
+		addLinkByLinkedUserProfileResourceAnnotationIfMissing(buildedModel);
+
+		return applyMapper(buildedModel);
+	}
+
+	private EntityModel<T> applyMapper(EntityModel<T> resource) {
+		Iterator<Function<EntityModel<T>, EntityModel<T>>> i = mapper.iterator();
+		EntityModel<T> result = resource;
+		while (i.hasNext()) {
+			result = i.next().apply(result);
+		}
+		return result;
+	}
+
+	private void addLinkByLinkedResourceAnnotationIfMissing(EntityModel<T> resource) {
+		getFields(LinkedResource.class).stream()
+				.filter(field -> shouldAddLink(resource, field))
+				.forEach(field -> handleLinkedResourceField(resource, field));
+	}
+
+	private void handleLinkedResourceField(EntityModel<T> resource, Field field) {
+		getEntityFieldValue(field).ifPresent(val -> resource
+				.add(WebMvcLinkBuilder.linkTo(field.getAnnotation(LinkedResource.class).controllerClass()).slash(val)
+						.withRel(sanitizeName(field.getName()))));
+	}
+
+	private void addLinkByLinkedUserProfileResourceAnnotationIfMissing(EntityModel<T> resource) {
+		getFields(LinkedUserProfileResource.class).stream()
+				.filter(field -> shouldAddLink(resource, field))
+				.forEach(field -> handleLinkedUserProfileResourceField(resource, field));
+	}
+
+	private void handleLinkedUserProfileResourceField(EntityModel<T> resource, Field field) {
+		getEntityFieldValue(field).ifPresent(val -> {
+			if (UserProfileUrlProvider.isConfigured()) {
+				resource.add(Link.of(UserProfileUrlProvider.getUrl(val)).withRel(sanitizeName(field.getName())));
+			}
+		});
+	}
+
+	private boolean shouldAddLink(EntityModel<T> resource, Field field) {
+		return !(field.getType().isArray() || Collection.class.isAssignableFrom(field.getType()) || resource.hasLink(sanitizeName(field.getName())));
+	}
+
+	private List<Field> getFields(Class<? extends Annotation> annotationClass) {
+		var fields = Optional.ofNullable(ANNOTATED_FIELDS_BY_ANNOTATION.get(getEntity().getClass()))
+				.map(fieldsByAnnotation -> fieldsByAnnotation.get(annotationClass))
+				.orElseGet(Collections::emptyList);
+
+		if (CollectionUtils.isEmpty(fields)) {
+			fields = FieldUtils.getFieldsListWithAnnotation(getEntity().getClass(), annotationClass);
+
+			updateFields(annotationClass, fields);
+		}
+		return fields;
+	}
+
+	private void updateFields(Class<? extends Annotation> annotationClass, List<Field> fields) {
+		var annotationMap = Optional.ofNullable(ANNOTATED_FIELDS_BY_ANNOTATION.get(getEntity().getClass())).orElseGet(HashMap::new);
+		annotationMap.put(annotationClass, fields);
+
+		ANNOTATED_FIELDS_BY_ANNOTATION.put(annotationClass, annotationMap);
+	}
+
+	private String sanitizeName(String fieldName) {
+		if (fieldName.endsWith("Id")) {
+			return fieldName.substring(0, fieldName.indexOf("Id"));
+		}
+		return fieldName;
+	}
+
+	private Optional<Object> getEntityFieldValue(Field field) {
+		try {
+			field.setAccessible(true); // NOSONAR
+			Optional<Object> value = Optional.ofNullable(field.get(getEntity()));
+			field.setAccessible(false);
+			return value;
+		} catch (IllegalArgumentException | IllegalAccessException e) {
+			LOG.warn("Cannot access field value of LinkedResource field.", e);
+		}
+		return Optional.empty();
+	}
+
+	private T getEntity() {
+		return Optional.ofNullable(entity == null ? model.getContent() : entity)
+				.orElseThrow(() -> new IllegalStateException("Entity must not null for ModelBuilding"));
+	}
+
+	@RequiredArgsConstructor
+	public class ConditionalLinkAdder {
+
+		public final boolean conditionFulfilled;
+
+		public ModelBuilder<T> addLink(Supplier<Link> linkSupplier) {
+			if (conditionFulfilled) {
+				addLink(linkSupplier.get());
+			}
+			return ModelBuilder.this;
+		}
+
+		public ModelBuilder<T> addLink(Function<T, Link> linkBuilder) {
+			if (conditionFulfilled) {
+				addLink(linkBuilder.apply(getEntity()));
+			}
+			return ModelBuilder.this;
+		}
+
+		public ModelBuilder<T> addLink(Link link) {
+			if (conditionFulfilled) {
+				links.add(link);
+			}
+			return ModelBuilder.this;
+		}
+
+		public ModelBuilder<T> addLinks(Link... links) {
+			if (conditionFulfilled) {
+				ModelBuilder.this.links.addAll(Arrays.asList(links));
+			}
+			return ModelBuilder.this;
+		}
+
+		@SafeVarargs
+		public final ModelBuilder<T> addLinks(Supplier<Link>... linkSuppliers) {
+			if (conditionFulfilled) {
+				for (int i = 0; i < linkSuppliers.length; i++) {
+					ModelBuilder.this.links.add(linkSuppliers[i].get());
+				}
+			}
+			return ModelBuilder.this;
+		}
+
+		public final ModelBuilder<T> addLinks(Supplier<Collection<Link>> linksSupplier) {
+			if (conditionFulfilled) {
+				linksSupplier.get().forEach(ModelBuilder.this.links::add);
+			}
+
+			return ModelBuilder.this;
+		}
+
+	}
+}
\ No newline at end of file
diff --git a/goofy-server/src/main/java/de/itvsh/goofy/common/ObjectBuilder.java b/goofy-server/src/main/java/de/itvsh/goofy/common/ObjectBuilder.java
new file mode 100644
index 0000000000000000000000000000000000000000..b5448bbb245decd6d7c11d9f3eb847bf01f1acb5
--- /dev/null
+++ b/goofy-server/src/main/java/de/itvsh/goofy/common/ObjectBuilder.java
@@ -0,0 +1,33 @@
+/*
+ * 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.itvsh.goofy.common;
+
+import com.fasterxml.jackson.databind.BeanProperty;
+
+public interface ObjectBuilder<T> {
+
+	T build(Object id);
+
+	ObjectBuilder<T> constructContextAware(BeanProperty property);
+}
diff --git a/goofy-server/src/main/java/de/itvsh/goofy/common/RegexUtil.java b/goofy-server/src/main/java/de/itvsh/goofy/common/RegexUtil.java
new file mode 100644
index 0000000000000000000000000000000000000000..a15c248f7bb4e56d1ef948e54b3bcf6a74bfa597
--- /dev/null
+++ b/goofy-server/src/main/java/de/itvsh/goofy/common/RegexUtil.java
@@ -0,0 +1,33 @@
+/*
+ * 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.itvsh.goofy.common;
+
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public class RegexUtil {
+
+	public static final String VALIDATION_EMAIL = "[a-zA-Z0-9!#$%&'*+\\/=?^_`{|}~-]+(?:\\.[a-z0-9!#$%&'*+\\/=?^_`{|}~-]+)*@(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?\\.)+[a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?";
+}
\ No newline at end of file
diff --git a/goofy-server/src/main/java/de/itvsh/goofy/common/TimeMapper.java b/goofy-server/src/main/java/de/itvsh/goofy/common/TimeMapper.java
new file mode 100644
index 0000000000000000000000000000000000000000..f2e2d8779573a36b08e00474244ab86b19ec650e
--- /dev/null
+++ b/goofy-server/src/main/java/de/itvsh/goofy/common/TimeMapper.java
@@ -0,0 +1,37 @@
+/*
+ * 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.itvsh.goofy.common;
+
+import java.time.ZonedDateTime;
+
+import org.apache.commons.lang3.StringUtils;
+import org.mapstruct.Mapper;
+
+@Mapper
+public interface TimeMapper {
+
+	default ZonedDateTime parseString(String dateTimeStr) {
+		return StringUtils.isBlank(dateTimeStr) ? null : ZonedDateTime.parse(dateTimeStr);
+	}
+}
diff --git a/goofy-server/src/main/java/de/itvsh/goofy/common/ToStringExtractor.java b/goofy-server/src/main/java/de/itvsh/goofy/common/ToStringExtractor.java
new file mode 100644
index 0000000000000000000000000000000000000000..bc2b697ccacffc9e1b0f055a317fc1746057b45e
--- /dev/null
+++ b/goofy-server/src/main/java/de/itvsh/goofy/common/ToStringExtractor.java
@@ -0,0 +1,35 @@
+/*
+ * 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.itvsh.goofy.common;
+
+import lombok.NoArgsConstructor;
+
+@NoArgsConstructor
+class ToStringExtractor implements IdExtractor<Object> {
+
+	@Override
+	public String extractId(Object object) {
+		return object.toString();
+	}
+}
diff --git a/goofy-server/src/main/java/de/itvsh/goofy/common/UserProfileUrlProvider.java b/goofy-server/src/main/java/de/itvsh/goofy/common/UserProfileUrlProvider.java
new file mode 100644
index 0000000000000000000000000000000000000000..97aacab5e2086ac32bb9de4c491fb105acedd1fb
--- /dev/null
+++ b/goofy-server/src/main/java/de/itvsh/goofy/common/UserProfileUrlProvider.java
@@ -0,0 +1,74 @@
+/*
+ * 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.itvsh.goofy.common;
+
+import java.util.Objects;
+
+import org.springframework.beans.BeansException;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ApplicationContextAware;
+import org.springframework.stereotype.Component;
+
+import com.google.common.base.Preconditions;
+
+@Component
+public class UserProfileUrlProvider implements ApplicationContextAware {
+	// TODO auf javax.validation umstellen
+	private static final String ERROR_MESSAGE = "Key %s missing, please add it to the application.yml";
+	// TODO applicationContext darf nicht static abgelegt werden - es sollte möglich
+	// sein, dass eine andere Konfiguration in der jvm läuft.
+	private static ApplicationContext applicationContext;
+	static final String URL_ROOT_KEY = "kop.user-manager.url";
+	static final String USER_PROFILES_TEMPLATE_KEY = "kop.user-manager.profile-template";
+
+	@Override
+	public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
+		UserProfileUrlProvider.setSpringContext(applicationContext);
+	}
+
+	private static void setSpringContext(ApplicationContext context) {
+		applicationContext = context;
+	}
+
+	public static boolean isConfigured() {
+		return Objects.nonNull(applicationContext.getEnvironment().getProperty(URL_ROOT_KEY))
+				&& Objects.nonNull(applicationContext.getEnvironment().getProperty(USER_PROFILES_TEMPLATE_KEY));
+	}
+
+	public static String getUrl(Object val) {
+		// TODO Abhängingkeit zu com.google.common ausbauen
+		Preconditions.checkNotNull(applicationContext, "ApplicationContext not initialized");
+
+		// TODO parameter lieber injezieren und validation verwenden
+		var rootUrl = applicationContext.getEnvironment().getProperty(URL_ROOT_KEY);
+		Preconditions.checkNotNull(rootUrl, ERROR_MESSAGE, URL_ROOT_KEY);
+
+		var template = applicationContext.getEnvironment().getProperty(USER_PROFILES_TEMPLATE_KEY);
+		Preconditions.checkNotNull(template, ERROR_MESSAGE, USER_PROFILES_TEMPLATE_KEY);
+
+		// TODO UriComponent Builder verwenden
+		var profilesUrl = rootUrl + template;
+		return String.format(profilesUrl, val);
+	}
+}
diff --git a/goofy-server/src/main/java/de/itvsh/goofy/common/ValidationMessageCodes.java b/goofy-server/src/main/java/de/itvsh/goofy/common/ValidationMessageCodes.java
new file mode 100644
index 0000000000000000000000000000000000000000..15e2dd27289f97ba18d823fc7ed03e0d93d38878
--- /dev/null
+++ b/goofy-server/src/main/java/de/itvsh/goofy/common/ValidationMessageCodes.java
@@ -0,0 +1,40 @@
+/*
+ * 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.itvsh.goofy.common;
+
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public class ValidationMessageCodes {
+
+	private static final String FIELD_PREFIX = "validation_field_";
+	public static final String FIELD_IS_EMPTY = FIELD_PREFIX + "empty";
+	public static final String FIELD_MIN_SIZE = FIELD_PREFIX + "min_size";
+	public static final String FIELD_MAX_SIZE = FIELD_PREFIX + "max_size";
+	public static final String FIELD_SIZE = FIELD_PREFIX + "size";
+	public static final String FIELD_DATE_PAST = FIELD_PREFIX + "date_past";
+	public static final String FIELD_INVALID = FIELD_PREFIX + "invalid";
+	public static final String FIELD_FILE_SIZE_EXCEEDED = FIELD_PREFIX + "file_size_exceeded";
+}
\ No newline at end of file
diff --git a/goofy-server/src/main/java/de/itvsh/goofy/common/attacheditem/VorgangAttachedItem.java b/goofy-server/src/main/java/de/itvsh/goofy/common/attacheditem/VorgangAttachedItem.java
new file mode 100644
index 0000000000000000000000000000000000000000..8354a1dfb90f7527c7b8d0274513fc760d631099
--- /dev/null
+++ b/goofy-server/src/main/java/de/itvsh/goofy/common/attacheditem/VorgangAttachedItem.java
@@ -0,0 +1,44 @@
+/*
+ * 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.itvsh.goofy.common.attacheditem;
+
+import java.util.Map;
+
+import de.itvsh.goofy.common.command.CommandBody;
+import lombok.Builder;
+import lombok.Getter;
+
+@Getter
+@Builder
+public class VorgangAttachedItem implements CommandBody {
+
+	private String id;
+	private long version;
+
+	private String client;
+	private String vorgangId;
+	private String itemName;
+
+	private Map<String, Object> item;
+}
\ No newline at end of file
diff --git a/goofy-server/src/main/java/de/itvsh/goofy/common/attacheditem/VorgangAttachedItemService.java b/goofy-server/src/main/java/de/itvsh/goofy/common/attacheditem/VorgangAttachedItemService.java
new file mode 100644
index 0000000000000000000000000000000000000000..2fa150af4b662917ac84ea2fa9bab5777bee7900
--- /dev/null
+++ b/goofy-server/src/main/java/de/itvsh/goofy/common/attacheditem/VorgangAttachedItemService.java
@@ -0,0 +1,122 @@
+/*
+ * 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.itvsh.goofy.common.attacheditem;
+
+import java.util.Map;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import de.itvsh.goofy.common.command.Command;
+import de.itvsh.goofy.common.command.CommandBody;
+import de.itvsh.goofy.common.command.CommandBodyMapper;
+import de.itvsh.goofy.common.command.CommandOrder;
+import de.itvsh.goofy.common.command.CommandService;
+import de.itvsh.goofy.common.command.CreateCommand;
+import de.itvsh.goofy.kommentar.Kommentar;
+import de.itvsh.goofy.wiedervorlage.Wiedervorlage;
+
+@Service
+public class VorgangAttachedItemService {
+
+	private static final String DONE_FIELD_NAME = "done";
+	@Autowired
+	private CommandBodyMapper commandBodyMapper;
+	@Autowired
+	private CommandService commandService;
+
+	static final String WIEDERVORLAGE_ITEM_NAME = "Wiedervorlage";
+	static final String KOMMENTAR_ITEM_NAME = "Kommentar";
+
+	public Command createNewWiedervorlage(Wiedervorlage wiedervorlage, String vorgangId) {
+		var vorgangAttachedItem = buildVorgangAttachedItem(wiedervorlage, vorgangId, WIEDERVORLAGE_ITEM_NAME);
+
+		return commandService.createCommand(buildCreateCommand(vorgangId, vorgangAttachedItem));
+	}
+
+	public Command createNewKommentar(Kommentar kommentar, String vorgangId) {
+		var vorgangAttachedItem = buildVorgangAttachedItem(kommentar, vorgangId, KOMMENTAR_ITEM_NAME);
+
+		return commandService.createCommand(buildCreateCommand(vorgangId, vorgangAttachedItem));
+	}
+
+	CreateCommand buildCreateCommand(String vorgangId, CommandBody body) {
+		return CreateCommand.builder()
+				.vorgangId(vorgangId)
+				.relationId(vorgangId)
+				.order(CommandOrder.CREATE_ATTACHED_ITEM)
+				.body(body)
+				.build();
+	}
+
+	public Command editKommentar(Kommentar kommentar, String kommentarId, long kommentarVersion) {
+		var vorgangAttachedItem = buildVorgangAttachedItem(kommentar, kommentar.getVorgangId(), KOMMENTAR_ITEM_NAME);
+
+		return commandService.createCommand(
+				buildUpdateCommand(kommentar.getVorgangId(), kommentarId, vorgangAttachedItem), kommentarVersion);
+	}
+
+	public Command editWiedervorlage(Wiedervorlage wiedervorlage, String wiedervorlageId, long version) {
+		var vorgangAttachedItem = buildVorgangAttachedItem(wiedervorlage, wiedervorlage.getVorgangId(), WIEDERVORLAGE_ITEM_NAME);
+
+		return commandService.createCommand(
+				buildUpdateCommand(wiedervorlage.getVorgangId(), wiedervorlageId, vorgangAttachedItem), version);
+	}
+
+	VorgangAttachedItem buildVorgangAttachedItem(CommandBody body, String vorgangId, String itemName) {
+		return VorgangAttachedItem.builder()
+				.vorgangId(vorgangId)
+				.itemName(itemName)
+				.item(commandBodyMapper.fromObjectToMap(body))
+				.build();
+	}
+
+	CreateCommand buildUpdateCommand(String vorgangId, String relationId, CommandBody body) {
+		return CreateCommand.builder()
+				.vorgangId(vorgangId)
+				.relationId(relationId)
+				.order(CommandOrder.UPDATE_ATTACHED_ITEM)
+				.body(body)
+				.build();
+	}
+
+	public Command setWiedervorlageDone(Wiedervorlage wiedervorlage, boolean done) {
+		return commandService.createCommand(createPatchCommand(wiedervorlage, done), wiedervorlage.getVersion());
+	}
+
+	CreateCommand createPatchCommand(Wiedervorlage wiedervorlage, boolean done) {
+		return getPatchCommandBuilder(wiedervorlage).body(createSetDoneBody(done)).relationVersion(wiedervorlage.getVersion()).build();
+	}
+
+	CreateCommand.CreateCommandBuilder getPatchCommandBuilder(Wiedervorlage wiedervorlage) {
+		return CreateCommand.builder()
+				.relationId(wiedervorlage.getId())
+				.vorgangId(wiedervorlage.getVorgangId())
+				.order(CommandOrder.PATCH_ATTACHED_ITEM);
+	}
+
+	private VorgangAttachedItem createSetDoneBody(boolean value) {
+		return VorgangAttachedItem.builder().itemName(Wiedervorlage.class.getSimpleName()).item(Map.of(DONE_FIELD_NAME, value)).build();
+	}
+}
\ No newline at end of file
diff --git a/goofy-server/src/main/java/de/itvsh/goofy/common/binaryfile/BinaryFileController.java b/goofy-server/src/main/java/de/itvsh/goofy/common/binaryfile/BinaryFileController.java
new file mode 100644
index 0000000000000000000000000000000000000000..622ff627e59da32a86efa4cc548d4f90a78780c3
--- /dev/null
+++ b/goofy-server/src/main/java/de/itvsh/goofy/common/binaryfile/BinaryFileController.java
@@ -0,0 +1,129 @@
+/*
+ * 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.itvsh.goofy.common.binaryfile;
+
+import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.*;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.List;
+import java.util.concurrent.Future;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.hateoas.CollectionModel;
+import org.springframework.hateoas.EntityModel;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.MediaType;
+import org.springframework.http.ResponseEntity;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestPart;
+import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.multipart.MultipartFile;
+import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
+
+import de.itvsh.goofy.common.file.OzgFile;
+import de.itvsh.kop.common.errorhandling.TechnicalException;
+
+@RestController
+@RequestMapping(BinaryFileController.PATH)
+public class BinaryFileController {
+
+	static final String PATH = "/api/binaryFiles"; // NOSONAR
+
+	@Autowired
+	private BinaryFileService service;
+	@Autowired
+	private BinaryFileModelAssembler modelAssembler;
+	@Autowired
+	private FileIdMapper fileIdMapper;
+
+	@GetMapping("/{fileId}")
+	public EntityModel<OzgFile> getFile(@PathVariable FileId fileId) {
+		return modelAssembler.toModel(service.getFile(fileId));
+	}
+
+	@PostMapping("/{vorgangId}/{field}/file")
+	public Future<ResponseEntity<Void>> uploadFile(@PathVariable String vorgangId, @PathVariable String field, @RequestPart MultipartFile file) {
+		var fileIdFuture = service.uploadFile(buildBinaryFileUploadRequest(vorgangId, field, file));
+
+		return fileIdFuture.thenApply(fileId -> ResponseEntity.created(linkTo(BinaryFileController.class).slash(fileId).toUri()).build());
+	}
+
+	UploadBinaryFileRequest buildBinaryFileUploadRequest(String vorgangId, String field, MultipartFile file) {
+		return UploadBinaryFileRequest.builder()
+				.vorgangId(vorgangId)
+				.field(field)
+				.uploadStream(getInputStream(file))
+				.name(file.getOriginalFilename())
+				.contentType(file.getContentType())
+				.size(file.getSize())
+				.build();
+	}
+
+	InputStream getInputStream(MultipartFile file) {
+		try {
+			return file.getInputStream();
+		} catch (IOException e) {
+			throw new TechnicalException("Error reading File data as stream.", e);
+		}
+	}
+
+	public CollectionModel<EntityModel<OzgFile>> getFiles(List<FileId> fileIds) {
+		var files = service.getFiles(mapToFileId(fileIds)).toList();
+
+		return modelAssembler.toCollectionModel(files);
+	}
+
+	private List<FileId> mapToFileId(List<FileId> fileIds) {
+		return fileIds.stream()
+				.map(fileIdMapper::toString)
+				.map(FileId::from)
+				.toList();
+	}
+
+	@PreAuthorize("@downloadAuthenticationHandler.canDownloadFile(#fileId)")
+	@GetMapping(value = "/{fileId}", produces = { MediaType.APPLICATION_OCTET_STREAM_VALUE,
+			"images/*" }, consumes = MediaType.APPLICATION_OCTET_STREAM_VALUE)
+	public ResponseEntity<StreamingResponseBody> getFileData(@PathVariable FileId fileId) {
+		var ozgFile = service.getFile(fileId);
+
+		return buildResponseEntity(ozgFile, createDownloadStreamingBody(fileId));
+	}
+
+	StreamingResponseBody createDownloadStreamingBody(FileId fileId) {
+		return out -> service.writeFileContent(fileId, out);
+	}
+
+	ResponseEntity<StreamingResponseBody> buildResponseEntity(OzgFile ozgFile, StreamingResponseBody responseBody) {
+		return ResponseEntity.ok()
+				.header(HttpHeaders.CONTENT_DISPOSITION, String.format("attachment; filename=%s", ozgFile.getName()))
+				.contentLength(ozgFile.getSize())
+				.contentType(MediaType.valueOf(ozgFile.getContentType()))
+				.body(responseBody);
+	}
+}
\ No newline at end of file
diff --git a/goofy-server/src/main/java/de/itvsh/goofy/common/binaryfile/BinaryFileDownloadStreamObserver.java b/goofy-server/src/main/java/de/itvsh/goofy/common/binaryfile/BinaryFileDownloadStreamObserver.java
new file mode 100644
index 0000000000000000000000000000000000000000..853dac311eaeb1c9dff2e5c7c15e1697444133aa
--- /dev/null
+++ b/goofy-server/src/main/java/de/itvsh/goofy/common/binaryfile/BinaryFileDownloadStreamObserver.java
@@ -0,0 +1,64 @@
+/*
+ * 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.itvsh.goofy.common.binaryfile;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.concurrent.CompletableFuture;
+
+import de.itvsh.kop.common.errorhandling.TechnicalException;
+import de.itvsh.ozg.pluto.grpc.binaryFile.GrpcGetBinaryFileDataResponse;
+import io.grpc.stub.StreamObserver;
+import lombok.AccessLevel;
+import lombok.RequiredArgsConstructor;
+
+@RequiredArgsConstructor(access = AccessLevel.PROTECTED)
+class BinaryFileDownloadStreamObserver implements StreamObserver<GrpcGetBinaryFileDataResponse> {
+
+	private final CompletableFuture<Boolean> streamFuture;
+	private final OutputStream out;
+
+	@Override
+	public void onNext(GrpcGetBinaryFileDataResponse response) {
+		writeToStream(response.getFileContent().toByteArray());
+	}
+
+	void writeToStream(byte[] contentPart) {
+		try {
+			out.write(contentPart);
+		} catch (IOException e) {
+			throw new TechnicalException("Download file error writing on outputstream", e);
+		}
+	}
+
+	@Override
+	public void onError(Throwable t) {
+		streamFuture.completeExceptionally(t);
+	}
+
+	@Override
+	public void onCompleted() {
+		streamFuture.complete(true);
+	}
+}
diff --git a/goofy-server/src/main/java/de/itvsh/goofy/common/binaryfile/BinaryFileMaxSizeConstraint.java b/goofy-server/src/main/java/de/itvsh/goofy/common/binaryfile/BinaryFileMaxSizeConstraint.java
new file mode 100644
index 0000000000000000000000000000000000000000..1634bda97152baa66dbe4050471dacbd8498f1ff
--- /dev/null
+++ b/goofy-server/src/main/java/de/itvsh/goofy/common/binaryfile/BinaryFileMaxSizeConstraint.java
@@ -0,0 +1,52 @@
+/*
+ * 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.itvsh.goofy.common.binaryfile;
+
+import static java.lang.annotation.ElementType.*;
+import static java.lang.annotation.RetentionPolicy.*;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import javax.validation.Constraint;
+import javax.validation.Payload;
+
+import de.itvsh.goofy.common.ValidationMessageCodes;
+
+@Constraint(validatedBy = { UploadBinaryFileSizeValidator.class })
+@Target({ FIELD, METHOD, PARAMETER, ANNOTATION_TYPE, TYPE_USE })
+@Retention(RUNTIME)
+@Documented
+public @interface BinaryFileMaxSizeConstraint {
+	String message() default ValidationMessageCodes.FIELD_FILE_SIZE_EXCEEDED;
+
+	Class<?>[] groups() default {};
+
+	Class<? extends Payload>[] payload() default {};
+
+	int max() default UploadBinaryFileSizeValidator.DEFAULT_MAX_SIZE;
+
+	String unit() default UploadBinaryFileSizeValidator.DEFAULT_UNIT_STRING;
+}
\ No newline at end of file
diff --git a/goofy-server/src/main/java/de/itvsh/goofy/common/binaryfile/BinaryFileModelAssembler.java b/goofy-server/src/main/java/de/itvsh/goofy/common/binaryfile/BinaryFileModelAssembler.java
new file mode 100644
index 0000000000000000000000000000000000000000..db7613cc2b040b135046914a58aca170cbd6d84c
--- /dev/null
+++ b/goofy-server/src/main/java/de/itvsh/goofy/common/binaryfile/BinaryFileModelAssembler.java
@@ -0,0 +1,57 @@
+/*
+ * 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.itvsh.goofy.common.binaryfile;
+
+import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.*;
+
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import org.springframework.hateoas.CollectionModel;
+import org.springframework.hateoas.EntityModel;
+import org.springframework.hateoas.server.RepresentationModelAssembler;
+import org.springframework.stereotype.Component;
+
+import de.itvsh.goofy.common.ModelBuilder;
+import de.itvsh.goofy.common.file.OzgFile;
+
+@Component
+public class BinaryFileModelAssembler implements RepresentationModelAssembler<OzgFile, EntityModel<OzgFile>> {
+
+	static final String REL_DOWNLOAD = "download";
+
+	@Override
+	public EntityModel<OzgFile> toModel(OzgFile file) {
+		var selfLink = linkTo(BinaryFileController.class).slash(file.getId());
+
+		return ModelBuilder.fromEntity(file).addLink(selfLink.withSelfRel())
+				.addLink(linkTo(BinaryFileController.class).slash(file.getId()).withRel(REL_DOWNLOAD))
+				.buildModel();
+	}
+
+	public CollectionModel<EntityModel<OzgFile>> toCollectionModel(Stream<OzgFile> entities) {
+		return CollectionModel.of(entities.map(this::toModel).collect(Collectors.toList()),
+				linkTo(BinaryFileController.class).withSelfRel());
+	}
+}
\ No newline at end of file
diff --git a/goofy-server/src/main/java/de/itvsh/goofy/common/binaryfile/BinaryFileProperties.java b/goofy-server/src/main/java/de/itvsh/goofy/common/binaryfile/BinaryFileProperties.java
new file mode 100644
index 0000000000000000000000000000000000000000..2f309fa3d5387c3a19a206beab9b6b0a118183d8
--- /dev/null
+++ b/goofy-server/src/main/java/de/itvsh/goofy/common/binaryfile/BinaryFileProperties.java
@@ -0,0 +1,46 @@
+/*
+ * 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.itvsh.goofy.common.binaryfile;
+
+import java.util.Collections;
+import java.util.Map;
+
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.util.unit.DataSize;
+
+import lombok.Getter;
+import lombok.Setter;
+
+@Getter
+@Setter
+@Configuration
+@ConfigurationProperties(BinaryFileProperties.PREFIX)
+class BinaryFileProperties {
+
+	static final String PREFIX = "kop.upload";
+
+	private Map<String, DataSize> maxFileSize = Collections.emptyMap();
+
+}
\ No newline at end of file
diff --git a/goofy-server/src/main/java/de/itvsh/goofy/common/binaryfile/BinaryFileRemoteService.java b/goofy-server/src/main/java/de/itvsh/goofy/common/binaryfile/BinaryFileRemoteService.java
new file mode 100644
index 0000000000000000000000000000000000000000..9267bb00fc4fac00d2b71a73b17f546f3eef3da6
--- /dev/null
+++ b/goofy-server/src/main/java/de/itvsh/goofy/common/binaryfile/BinaryFileRemoteService.java
@@ -0,0 +1,168 @@
+/*
+ * 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.itvsh.goofy.common.binaryfile;
+
+import java.io.OutputStream;
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.stream.Stream;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import com.google.protobuf.ByteString;
+
+import de.itvsh.goofy.common.callcontext.ContextService;
+import de.itvsh.goofy.common.file.OzgFile;
+import de.itvsh.goofy.common.file.OzgFileMapper;
+import de.itvsh.kop.common.errorhandling.TechnicalException;
+import de.itvsh.ozg.pluto.grpc.binaryFile.BinaryFileServiceGrpc.BinaryFileServiceBlockingStub;
+import de.itvsh.ozg.pluto.grpc.binaryFile.BinaryFileServiceGrpc.BinaryFileServiceStub;
+import de.itvsh.ozg.pluto.grpc.binaryFile.GrpcBinaryFilesRequest;
+import de.itvsh.ozg.pluto.grpc.binaryFile.GrpcFindFilesResponse;
+import de.itvsh.ozg.pluto.grpc.binaryFile.GrpcGetBinaryFileDataRequest;
+import de.itvsh.ozg.pluto.grpc.binaryFile.GrpcUploadBinaryFileMetaData;
+import de.itvsh.ozg.pluto.grpc.binaryFile.GrpcUploadBinaryFileRequest;
+import de.itvsh.ozg.pluto.grpc.file.GrpcOzgFile;
+import net.devh.boot.grpc.client.inject.GrpcClient;
+
+@Service
+class BinaryFileRemoteService {
+
+	@GrpcClient("pluto")
+	private BinaryFileServiceBlockingStub serviceStub;
+	@GrpcClient("pluto")
+	private BinaryFileServiceStub asyncServiceStub;
+	@Autowired
+	private OzgFileMapper mapper;
+	@Autowired
+	private FileIdMapper fileIdMapper;
+
+	@Autowired
+	private ContextService contextService;
+
+	static final int CHUNK_SIZE = 255 * 1024;
+
+	public OzgFile getFile(FileId fileId) {
+		var response = serviceStub.findBinaryFilesMetaData(buildGrpcBinaryFileRequest(fileId));
+
+		return mapOzgFile(response.getFile(0));
+	}
+
+	private OzgFile mapOzgFile(GrpcOzgFile file) {
+		return mapper.toFile(file);
+	}
+
+	GrpcBinaryFilesRequest buildGrpcBinaryFileRequest(FileId fileId) {
+		return GrpcBinaryFilesRequest.newBuilder()
+				.addFileId(fileId.toString())
+				.setContext(contextService.createCallContext())
+				.build();
+	}
+
+	public CompletableFuture<FileId> uploadFile(UploadBinaryFileRequest uploadBinaryFileRequest) {
+		var fileIdFuture = new CompletableFuture<FileId>();
+		var responseObserver = createUploadBinaryFileObserver(fileIdFuture, uploadBinaryFileRequest);
+
+		asyncServiceStub.uploadBinaryFileAsStream(responseObserver);
+
+		return fileIdFuture;
+	}
+
+	BinaryFileUploadStreamObserver createUploadBinaryFileObserver(CompletableFuture<FileId> fileIdFuture, UploadBinaryFileRequest uploadBinaryFileRequest) {
+		var metadataRequest = buildMetaDataRequest(uploadBinaryFileRequest);
+		var streamer = new ChunkedFileSender<>(uploadBinaryFileRequest.getUploadStream(), CHUNK_SIZE, this::buildChunkRequest, metadataRequest);
+		return new BinaryFileUploadStreamObserver(fileIdFuture, streamer);
+	}
+
+	GrpcUploadBinaryFileRequest buildMetaDataRequest(UploadBinaryFileRequest uploadBinaryFileRequest) {
+		return GrpcUploadBinaryFileRequest.newBuilder()
+				.setMetadata(GrpcUploadBinaryFileMetaData.newBuilder()
+						.setContext(contextService.createCallContext())
+						.setVorgangId(uploadBinaryFileRequest.getVorgangId())
+						.setField(uploadBinaryFileRequest.getField())
+						.setContentType(uploadBinaryFileRequest.getContentType())
+						.setSize(uploadBinaryFileRequest.getSize())
+						.setFileName(uploadBinaryFileRequest.getName()))
+				.build();
+	}
+
+	GrpcUploadBinaryFileRequest buildChunkRequest(byte[] bytes) {
+		return GrpcUploadBinaryFileRequest.newBuilder().setFileContent((ByteString.copyFrom(bytes))).build();
+	}
+
+	public Stream<OzgFile> getFiles(List<FileId> fileIds) {
+		var response = serviceStub.findBinaryFilesMetaData(buildBinaryFilesRequest(fileIds));
+
+		return mapGrpcFindFilesRespones(response);
+	}
+
+	private Stream<OzgFile> mapGrpcFindFilesRespones(GrpcFindFilesResponse response) {
+		return response.getFileList().stream().map(mapper::toFile);
+	}
+
+	GrpcBinaryFilesRequest buildBinaryFilesRequest(List<FileId> fileIds) {
+		return GrpcBinaryFilesRequest.newBuilder()
+				.addAllFileId(mapFileIds(fileIds))
+				.setContext(contextService.createCallContext())
+				.build();
+	}
+
+	private List<String> mapFileIds(List<FileId> fileIds) {
+		return fileIds.stream().map(fileIdMapper::toString).toList();
+	}
+
+	public void writeFileContent(FileId fileId, OutputStream out) {
+		var streamFuture = new CompletableFuture<Boolean>();
+		var responseObserver = createDownloadBinaryFileObserver(streamFuture, out);
+
+		asyncServiceStub.getBinaryFileContent(buildGrpcGetBinaryFileDataRequest(fileId), responseObserver);
+
+		waitUntilFutureToComplete(streamFuture);
+	}
+
+	void waitUntilFutureToComplete(CompletableFuture<Boolean> streamFuture) {
+		try {
+			streamFuture.get(10, TimeUnit.MINUTES);
+		} catch (InterruptedException e) {
+			Thread.currentThread().interrupt();
+			throw new TechnicalException(e.getMessage(), e);
+		} catch (ExecutionException | TimeoutException e) {
+			throw new TechnicalException(e.getMessage(), e);
+		}
+	}
+
+	BinaryFileDownloadStreamObserver createDownloadBinaryFileObserver(CompletableFuture<Boolean> streamFuture, OutputStream out) {
+		return new BinaryFileDownloadStreamObserver(streamFuture, out);
+	}
+
+	private GrpcGetBinaryFileDataRequest buildGrpcGetBinaryFileDataRequest(FileId fileId) {
+		return GrpcGetBinaryFileDataRequest.newBuilder()
+				.setContext(contextService.createCallContext())
+				.setFileId(fileIdMapper.toString(fileId)).build();
+	}
+}
\ No newline at end of file
diff --git a/goofy-server/src/main/java/de/itvsh/goofy/common/binaryfile/BinaryFileService.java b/goofy-server/src/main/java/de/itvsh/goofy/common/binaryfile/BinaryFileService.java
new file mode 100644
index 0000000000000000000000000000000000000000..8349b5fdc3331432655dbd8b38412891cd122691
--- /dev/null
+++ b/goofy-server/src/main/java/de/itvsh/goofy/common/binaryfile/BinaryFileService.java
@@ -0,0 +1,61 @@
+/*
+ * 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.itvsh.goofy.common.binaryfile;
+
+import java.io.OutputStream;
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+import java.util.stream.Stream;
+
+import javax.validation.Valid;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.validation.annotation.Validated;
+
+import de.itvsh.goofy.common.file.OzgFile;
+
+@Service
+@Validated
+public class BinaryFileService {
+
+	@Autowired
+	private BinaryFileRemoteService remoteService;
+
+	public CompletableFuture<FileId> uploadFile(@Valid UploadBinaryFileRequest uploadBinaryFileRequest) {
+		return remoteService.uploadFile(uploadBinaryFileRequest);
+	}
+
+	public Stream<OzgFile> getFiles(List<FileId> fileIds) {
+		return remoteService.getFiles(fileIds);
+	}
+
+	public void writeFileContent(FileId fileId, OutputStream out) {
+		remoteService.writeFileContent(fileId, out);
+	}
+
+	public OzgFile getFile(FileId fileId) {
+		return remoteService.getFile(fileId);
+	}
+}
\ No newline at end of file
diff --git a/goofy-server/src/main/java/de/itvsh/goofy/common/binaryfile/BinaryFileUploadStreamObserver.java b/goofy-server/src/main/java/de/itvsh/goofy/common/binaryfile/BinaryFileUploadStreamObserver.java
new file mode 100644
index 0000000000000000000000000000000000000000..733d348bcde1149f6a20456046796fab881f7657
--- /dev/null
+++ b/goofy-server/src/main/java/de/itvsh/goofy/common/binaryfile/BinaryFileUploadStreamObserver.java
@@ -0,0 +1,66 @@
+/*
+ * 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.itvsh.goofy.common.binaryfile;
+
+import java.util.concurrent.CompletableFuture;
+
+import de.itvsh.ozg.pluto.grpc.binaryFile.GrpcUploadBinaryFileRequest;
+import de.itvsh.ozg.pluto.grpc.binaryFile.GrpcUploadBinaryFileResponse;
+import io.grpc.stub.ClientCallStreamObserver;
+import io.grpc.stub.ClientResponseObserver;
+import lombok.AccessLevel;
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+
+@RequiredArgsConstructor(access = AccessLevel.PROTECTED)
+class BinaryFileUploadStreamObserver implements ClientResponseObserver<GrpcUploadBinaryFileRequest, GrpcUploadBinaryFileResponse> {
+
+	private final CompletableFuture<FileId> fileIdFuture;
+	private final ChunkedFileSender<GrpcUploadBinaryFileRequest> fileStreamer;
+
+	@Getter
+	private String fileId;
+
+	private ClientCallStreamObserver<GrpcUploadBinaryFileRequest> requestObserver;
+
+	@Override
+	public void beforeStart(ClientCallStreamObserver<GrpcUploadBinaryFileRequest> requestStream) {
+		this.requestObserver = requestStream;
+		requestObserver.setOnReadyHandler(() -> fileStreamer.sendChunkTo(requestObserver));
+	}
+	@Override
+	public void onNext(GrpcUploadBinaryFileResponse response) {
+		fileId = response.getFileId();
+	}
+
+	@Override
+	public void onError(Throwable t) {
+		fileIdFuture.completeExceptionally(t);
+	}
+
+	@Override
+	public void onCompleted() {
+		fileIdFuture.complete(FileId.from(fileId));
+	}
+}
\ No newline at end of file
diff --git a/goofy-server/src/main/java/de/itvsh/goofy/common/binaryfile/ChunkedFileSender.java b/goofy-server/src/main/java/de/itvsh/goofy/common/binaryfile/ChunkedFileSender.java
new file mode 100644
index 0000000000000000000000000000000000000000..66044344b6cd624362f40db816b31ed3a0fd394b
--- /dev/null
+++ b/goofy-server/src/main/java/de/itvsh/goofy/common/binaryfile/ChunkedFileSender.java
@@ -0,0 +1,84 @@
+/*
+ * 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.itvsh.goofy.common.binaryfile;
+
+import de.itvsh.kop.common.errorhandling.TechnicalException;
+import io.grpc.stub.CallStreamObserver;
+import lombok.AllArgsConstructor;
+import org.apache.commons.io.IOUtils;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.function.Function;
+
+@AllArgsConstructor
+class ChunkedFileSender<T> {
+
+	private final AtomicBoolean hasUploadFile = new AtomicBoolean(true);
+	private final InputStream uploadStream;
+	private final int chunkSize;
+	private final Function<byte[], T> buildChunkRequest;
+	private T requestMetadata;
+
+	public void sendChunkTo(CallStreamObserver<T> streamObserver) {
+		if (hasUploadFile.get()) {
+			sendMetadata(streamObserver);
+			int size = sendNextChunk(streamObserver);
+			if (size < chunkSize) {
+				handleFileEndReached(streamObserver);
+			}
+		}
+	}
+
+	private void sendMetadata(CallStreamObserver<T> streamObserver) {
+		if(requestMetadata != null) {
+			streamObserver.onNext(requestMetadata);
+			requestMetadata = null;
+		}
+	}
+
+	private int sendNextChunk(CallStreamObserver<T> streamObserver) {
+		byte[] content = readFromStream();
+		var size = content.length;
+		if (size > 0) {
+			streamObserver.onNext(buildChunkRequest.apply(content));
+		}
+		return size;
+	}
+
+	private byte[] readFromStream() {
+		try {
+			return uploadStream.readNBytes(chunkSize);
+		} catch (IOException e) {
+			throw new TechnicalException("Error on sending a single chunk", e);
+		}
+	}
+
+	private void handleFileEndReached(CallStreamObserver<T> streamObserver) {
+		IOUtils.closeQuietly(uploadStream);
+		streamObserver.onCompleted();
+		hasUploadFile.getAndSet(false);
+	}
+}
diff --git a/goofy-server/src/main/java/de/itvsh/goofy/common/binaryfile/DownloadAuthenticationHandler.java b/goofy-server/src/main/java/de/itvsh/goofy/common/binaryfile/DownloadAuthenticationHandler.java
new file mode 100644
index 0000000000000000000000000000000000000000..0dd43b57380ce87d98073d1b8b089ee2ded65b97
--- /dev/null
+++ b/goofy-server/src/main/java/de/itvsh/goofy/common/binaryfile/DownloadAuthenticationHandler.java
@@ -0,0 +1,49 @@
+/*
+ * 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.itvsh.goofy.common.binaryfile;
+
+import java.util.Objects;
+
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.stereotype.Component;
+
+@Component
+public class DownloadAuthenticationHandler {
+
+	public boolean canDownloadFile(FileId fileId) {
+		return check(fileId, SecurityContextHolder.getContext().getAuthentication());
+	}
+
+	boolean check(FileId fileId, Authentication auth) {
+		if (auth instanceof UsernamePasswordAuthenticationToken userPasswordToken) {
+			GoofyUserWithFileId user = (GoofyUserWithFileId) userPasswordToken.getPrincipal();
+			return Objects.nonNull(fileId) && fileId.equals(user.getFileId()) && auth.isAuthenticated();
+		}
+
+		return auth.isAuthenticated();
+	}
+
+}
diff --git a/goofy-server/src/main/java/de/itvsh/goofy/common/binaryfile/DynamicViolationParameter.java b/goofy-server/src/main/java/de/itvsh/goofy/common/binaryfile/DynamicViolationParameter.java
new file mode 100644
index 0000000000000000000000000000000000000000..f4eca332a06c39cc2368ebeb2296852ec6c06816
--- /dev/null
+++ b/goofy-server/src/main/java/de/itvsh/goofy/common/binaryfile/DynamicViolationParameter.java
@@ -0,0 +1,38 @@
+/*
+ * 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.itvsh.goofy.common.binaryfile;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import lombok.Builder;
+import lombok.Getter;
+
+@Builder
+@Getter
+public class DynamicViolationParameter {
+
+	@Builder.Default
+	private Map<String, Object> map = new HashMap<>();
+}
\ No newline at end of file
diff --git a/goofy-server/src/main/java/de/itvsh/goofy/common/binaryfile/FileId.java b/goofy-server/src/main/java/de/itvsh/goofy/common/binaryfile/FileId.java
new file mode 100644
index 0000000000000000000000000000000000000000..6ce48433f9ddc92118f355b2db182965c95b4c28
--- /dev/null
+++ b/goofy-server/src/main/java/de/itvsh/goofy/common/binaryfile/FileId.java
@@ -0,0 +1,47 @@
+/*
+ * 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.itvsh.goofy.common.binaryfile;
+
+import java.util.UUID;
+
+import de.itvsh.kop.common.datatype.StringBasedValue;
+import lombok.EqualsAndHashCode;
+
+@EqualsAndHashCode(callSuper = true)
+public class FileId extends StringBasedValue {
+
+	private static final long serialVersionUID = 1L;
+
+	FileId(String fileId) {
+		super(fileId);
+	}
+
+	public static FileId createNew() {
+		return from(UUID.randomUUID().toString());
+	}
+
+	public static FileId from(String fileId) {
+		return new FileId(fileId);
+	}
+}
\ No newline at end of file
diff --git a/goofy-server/src/main/java/de/itvsh/goofy/common/binaryfile/FileIdMapper.java b/goofy-server/src/main/java/de/itvsh/goofy/common/binaryfile/FileIdMapper.java
new file mode 100644
index 0000000000000000000000000000000000000000..16895669c84d1f75cc9226a3d5bc6ece286728dc
--- /dev/null
+++ b/goofy-server/src/main/java/de/itvsh/goofy/common/binaryfile/FileIdMapper.java
@@ -0,0 +1,38 @@
+/*
+ * 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.itvsh.goofy.common.binaryfile;
+
+import org.mapstruct.Mapper;
+
+@Mapper
+public interface FileIdMapper {
+
+	default FileId toFileId(String fileId) {
+		return FileId.from(fileId);
+	}
+
+	default String toString(FileId fileId) {
+		return fileId.toString();
+	}
+}
\ No newline at end of file
diff --git a/goofy-server/src/main/java/de/itvsh/goofy/common/binaryfile/GoofyUserWithFileId.java b/goofy-server/src/main/java/de/itvsh/goofy/common/binaryfile/GoofyUserWithFileId.java
new file mode 100644
index 0000000000000000000000000000000000000000..f3156b59eacd6b043ba29455e99b8716c2368972
--- /dev/null
+++ b/goofy-server/src/main/java/de/itvsh/goofy/common/binaryfile/GoofyUserWithFileId.java
@@ -0,0 +1,46 @@
+/*
+ * 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.itvsh.goofy.common.binaryfile;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Objects;
+
+import org.springframework.security.core.GrantedAuthority;
+
+import de.itvsh.goofy.common.user.UserProfile;
+import lombok.Builder;
+import lombok.Getter;
+
+@Builder
+@Getter
+public class GoofyUserWithFileId {
+	private UserProfile user;
+
+	private FileId fileId;
+
+	public Collection<GrantedAuthority> getAuthorities() {
+		return Objects.nonNull(user) ? user.getAuthorities() : List.of();
+	}
+}
\ No newline at end of file
diff --git a/goofy-server/src/main/java/de/itvsh/goofy/common/binaryfile/UploadBinaryFileRequest.java b/goofy-server/src/main/java/de/itvsh/goofy/common/binaryfile/UploadBinaryFileRequest.java
new file mode 100644
index 0000000000000000000000000000000000000000..c3297d3d57a116230f54dc74ce56dfd6e66c8375
--- /dev/null
+++ b/goofy-server/src/main/java/de/itvsh/goofy/common/binaryfile/UploadBinaryFileRequest.java
@@ -0,0 +1,46 @@
+/*
+ * 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.itvsh.goofy.common.binaryfile;
+
+import java.io.InputStream;
+
+import org.springframework.validation.annotation.Validated;
+
+import lombok.Builder;
+import lombok.Getter;
+
+@Getter
+@Builder
+@Validated
+@BinaryFileMaxSizeConstraint
+class UploadBinaryFileRequest {
+
+	private String vorgangId;
+	private String field;
+
+	private String name;
+	private String contentType;
+	private long size;
+	private InputStream uploadStream;
+}
diff --git a/goofy-server/src/main/java/de/itvsh/goofy/common/binaryfile/UploadBinaryFileSizeValidator.java b/goofy-server/src/main/java/de/itvsh/goofy/common/binaryfile/UploadBinaryFileSizeValidator.java
new file mode 100644
index 0000000000000000000000000000000000000000..c7aebf3c23d222540a87baa284c9e5691f364af4
--- /dev/null
+++ b/goofy-server/src/main/java/de/itvsh/goofy/common/binaryfile/UploadBinaryFileSizeValidator.java
@@ -0,0 +1,78 @@
+/*
+ * 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.itvsh.goofy.common.binaryfile;
+
+import java.util.Map;
+import java.util.Optional;
+
+import javax.validation.ConstraintValidator;
+import javax.validation.ConstraintValidatorContext;
+
+import org.hibernate.validator.constraintvalidation.HibernateConstraintValidatorContext;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.util.unit.DataSize;
+import org.springframework.util.unit.DataUnit;
+
+class UploadBinaryFileSizeValidator implements ConstraintValidator<BinaryFileMaxSizeConstraint, UploadBinaryFileRequest> {
+
+	static final String DEFAULT_UNIT_STRING = "MB";
+	static final DataUnit DEFAULT_UNIT = DataUnit.fromSuffix(DEFAULT_UNIT_STRING);
+
+	static final int DEFAULT_MAX_SIZE = 40;
+
+	private final DataSize defaultDataSize = DataSize.of(DEFAULT_MAX_SIZE, DEFAULT_UNIT);
+
+	@Autowired
+	private BinaryFileProperties binaryFileProperties;
+
+	@Override
+	public boolean isValid(UploadBinaryFileRequest uploadRequest, ConstraintValidatorContext context) {
+		var maxValidSize = getMaxValidSize(uploadRequest.getField());
+
+		setDynamicPayload(buildDynamicPayload(maxValidSize), context);
+
+		return isFileSizeValid(DataSize.ofBytes(uploadRequest.getSize()), maxValidSize);
+	}
+
+	private boolean isFileSizeValid(DataSize uploadFileSize, DataSize maxFileSize) {
+		return uploadFileSize.toBytes() <= maxFileSize.toBytes();
+	}
+
+	DataSize getMaxValidSize(String field) {
+		return Optional.ofNullable(binaryFileProperties.getMaxFileSize().get(field)).orElseGet(() -> defaultDataSize);
+	}
+
+	private void setDynamicPayload(DynamicViolationParameter dynamicViolationParameter, ConstraintValidatorContext context) {
+		var unwrappedContext = getUnwrappedContext(HibernateConstraintValidatorContext.class, context);
+		unwrappedContext.withDynamicPayload(dynamicViolationParameter);
+	}
+
+	<T> T getUnwrappedContext(Class<T> unwrapTo, ConstraintValidatorContext context) {
+		return context.unwrap(unwrapTo);
+	}
+
+	private DynamicViolationParameter buildDynamicPayload(DataSize maxFileSize) {
+		return DynamicViolationParameter.builder().map(Map.of("max", maxFileSize.toMegabytes())).build();
+	}
+}
\ No newline at end of file
diff --git a/goofy-server/src/main/java/de/itvsh/goofy/common/callcontext/CallContextAttachingInterceptor.java b/goofy-server/src/main/java/de/itvsh/goofy/common/callcontext/CallContextAttachingInterceptor.java
new file mode 100644
index 0000000000000000000000000000000000000000..df61fb0e7cfd2e4bb2627c8f03cb50ff4643be76
--- /dev/null
+++ b/goofy-server/src/main/java/de/itvsh/goofy/common/callcontext/CallContextAttachingInterceptor.java
@@ -0,0 +1,60 @@
+/*
+ * 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.itvsh.goofy.common.callcontext;
+
+import org.springframework.beans.factory.annotation.Autowired;
+
+import io.grpc.CallOptions;
+import io.grpc.Channel;
+import io.grpc.ClientCall;
+import io.grpc.ClientInterceptor;
+import io.grpc.ForwardingClientCall.SimpleForwardingClientCall;
+import io.grpc.Metadata;
+import io.grpc.MethodDescriptor;
+import net.devh.boot.grpc.client.interceptor.GrpcGlobalClientInterceptor;
+
+@GrpcGlobalClientInterceptor
+public class CallContextAttachingInterceptor implements ClientInterceptor {
+
+	@Autowired
+	private ContextService contextService;
+
+	@Override
+	public <A, B> ClientCall<A, B> interceptCall(MethodDescriptor<A, B> method, CallOptions callOptions, Channel next) {
+		return new CallContextAttachingClientCall<>(next.newCall(method, callOptions));
+	}
+
+	final class CallContextAttachingClientCall<A, B> extends SimpleForwardingClientCall<A, B> {
+
+		protected CallContextAttachingClientCall(ClientCall<A, B> delegate) {
+			super(delegate);
+		}
+
+		@Override
+		public void start(Listener<B> responseListener, Metadata headers) {
+			headers.merge(contextService.buildCallContextMetadata());
+			super.start(responseListener, headers);
+		}
+	}
+}
\ No newline at end of file
diff --git a/goofy-server/src/main/java/de/itvsh/goofy/common/callcontext/CallContextMapper.java b/goofy-server/src/main/java/de/itvsh/goofy/common/callcontext/CallContextMapper.java
new file mode 100644
index 0000000000000000000000000000000000000000..2bd03f5100ee719f2c99fef9afa463b011b1b3ae
--- /dev/null
+++ b/goofy-server/src/main/java/de/itvsh/goofy/common/callcontext/CallContextMapper.java
@@ -0,0 +1,41 @@
+/*
+ * 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.itvsh.goofy.common.callcontext;
+
+import org.mapstruct.Mapper;
+import org.springframework.beans.factory.annotation.Autowired;
+
+import de.itvsh.ozg.pluto.grpc.command.GrpcCallContext;
+
+@Mapper
+public abstract class CallContextMapper {
+
+	@Autowired
+	private ContextService contextService;
+
+	public GrpcCallContext createCallContext() {
+		return contextService.createCallContext();
+	}
+
+}
diff --git a/goofy-server/src/main/java/de/itvsh/goofy/common/callcontext/ContextService.java b/goofy-server/src/main/java/de/itvsh/goofy/common/callcontext/ContextService.java
new file mode 100644
index 0000000000000000000000000000000000000000..11be31cbfbb0bee279081ffdc558d45118d00203
--- /dev/null
+++ b/goofy-server/src/main/java/de/itvsh/goofy/common/callcontext/ContextService.java
@@ -0,0 +1,106 @@
+/*
+ * 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.itvsh.goofy.common.callcontext;
+
+import static de.itvsh.goofy.common.GrpcUtil.*;
+
+import java.util.Objects;
+import java.util.Optional;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.ApplicationContext;
+import org.springframework.stereotype.Service;
+
+import de.itvsh.goofy.RequestAttributes;
+import de.itvsh.goofy.common.user.CurrentUserService;
+import de.itvsh.goofy.common.user.UserRole;
+import de.itvsh.ozg.pluto.grpc.command.GrpcCallContext;
+import de.itvsh.ozg.pluto.grpc.command.GrpcUser;
+import io.grpc.Metadata;
+import lombok.extern.log4j.Log4j2;
+
+@Service
+@Log4j2
+public class ContextService {
+
+	static final String KEY_USER_ID = "USER_ID-bin";
+	static final String KEY_USER_NAME = "USER_NAME-bin";
+	static final String KEY_CLIENT_NAME = "CLIENT_NAME-bin";
+	static final String KEY_REQUEST_ID = "REQUEST_ID-bin";
+	static final String KEY_ACCESS_LIMITED_ORGAID = "ACCESS_LIMITED_TO_ORGANISATORISCHEEINHEITENID-bin";
+	static final String KEY_ACCESS_LIMITED = "ACCESS_LIMITED-bin";
+
+	@Autowired
+	private ApplicationContext context;
+
+	@Autowired
+	private CurrentUserService userService;
+	@Autowired(required = false)
+	private RequestAttributes reqAttributes;
+
+	public GrpcCallContext createCallContext() {
+		return GrpcCallContext.newBuilder()
+				.setClient(context.getId())
+				.setUser(createUser())
+				.setRequestId(getRequestId())
+				.build();
+	}
+
+	private GrpcUser createUser() {
+		var goofyUser = userService.getUser();
+
+		return GrpcUser.newBuilder()
+				.setId(goofyUser.getId().toString())
+				.setName(goofyUser.getFullName())
+				.build();
+	}
+
+	Metadata buildCallContextMetadata() {
+		var user = userService.getUser();
+		var metadata = new Metadata();
+
+		Optional.ofNullable(user.getId()).map(id -> id.toString().getBytes())
+				.ifPresentOrElse(bytes -> metadata.put(createKeyOf(KEY_USER_ID), bytes), () -> LOG.warn("Missing value 'userid'"));
+		Optional.ofNullable(user.getFullName()).map(String::getBytes)
+				.ifPresentOrElse(bytes -> metadata.put(createKeyOf(KEY_USER_NAME), bytes), () -> LOG.warn("Missing value 'fullname'"));
+		Optional.ofNullable(context.getId()).map(String::getBytes)
+				.ifPresentOrElse(bytes -> metadata.put(createKeyOf(KEY_CLIENT_NAME), bytes), () -> LOG.warn("Missing value 'client name'"));
+		user.getOrganisationseinheitIds().stream().map(String::getBytes)
+				.forEach(bytes -> metadata.put(createKeyOf(KEY_ACCESS_LIMITED_ORGAID), bytes));
+		metadata.put(createKeyOf(KEY_ACCESS_LIMITED), isOrganisationEinheitenIdCheckNecessary());
+
+		return metadata;
+	}
+
+	private byte[] isOrganisationEinheitenIdCheckNecessary() {
+		var hasRoleWithNoCheckNecessary = userService.hasRole(UserRole.VERWALTUNG_POSTSTELLE)
+				|| userService.hasRole(UserRole.EINHEITLICHER_ANSPRECHPARTNER);
+
+		return Boolean.toString(!hasRoleWithNoCheckNecessary).getBytes();
+	}
+
+	String getRequestId() {
+		return Objects.isNull(reqAttributes) ? "- no request scope -" : reqAttributes.getRequestId();
+	}
+}
\ No newline at end of file
diff --git a/goofy-server/src/main/java/de/itvsh/goofy/common/clientattribute/ClientAttributeRemoteService.java b/goofy-server/src/main/java/de/itvsh/goofy/common/clientattribute/ClientAttributeRemoteService.java
new file mode 100644
index 0000000000000000000000000000000000000000..7b730e3c9954a5b5d24f597dc263c41eb8be7304
--- /dev/null
+++ b/goofy-server/src/main/java/de/itvsh/goofy/common/clientattribute/ClientAttributeRemoteService.java
@@ -0,0 +1,42 @@
+/*
+ * 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.itvsh.goofy.common.clientattribute;
+
+import org.springframework.stereotype.Service;
+
+import de.itvsh.goofy.GoofyServerApplication;
+import de.itvsh.ozg.pluto.grpc.clientAttribute.ClientAttributeServiceGrpc.ClientAttributeServiceBlockingStub;
+import de.itvsh.ozg.pluto.grpc.clientAttribute.GrpcUpdateClientAttributeRequest;
+import net.devh.boot.grpc.client.inject.GrpcClient;
+
+@Service
+class ClientAttributeRemoteService {
+	@GrpcClient(GoofyServerApplication.GRPC_CLIENT)
+	private ClientAttributeServiceBlockingStub service;
+
+	void resetPostfachNachricht(GrpcUpdateClientAttributeRequest request) {
+		service.update(request);
+	}
+
+}
diff --git a/goofy-server/src/main/java/de/itvsh/goofy/common/clientattribute/ClientAttributeService.java b/goofy-server/src/main/java/de/itvsh/goofy/common/clientattribute/ClientAttributeService.java
new file mode 100644
index 0000000000000000000000000000000000000000..31aa06ee96ab1eab2cb8127650a66567f446c0f8
--- /dev/null
+++ b/goofy-server/src/main/java/de/itvsh/goofy/common/clientattribute/ClientAttributeService.java
@@ -0,0 +1,61 @@
+/*
+ * 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.itvsh.goofy.common.clientattribute;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import de.itvsh.ozg.pluto.grpc.clientAttribute.GrpcClientAttribute;
+import de.itvsh.ozg.pluto.grpc.clientAttribute.GrpcClientAttributeValue;
+import de.itvsh.ozg.pluto.grpc.clientAttribute.GrpcUpdateClientAttributeRequest;
+
+@Service
+public class ClientAttributeService {
+	public static final String HAS_NEW_POSTFACH_NACHRICHT_ATTRIBUTE_NAME = "hasNewPostfachNachricht";
+	static final String ORIGINAL_CLIENT_NAME = "KopNachrichtenManager";
+	@Autowired
+	private ClientAttributeRemoteService remoteService;
+
+	public void resetPostfachNachricht(String vorgangId) {
+
+		remoteService.resetPostfachNachricht(buildResetNewPostfachNachricht(vorgangId));
+	}
+
+	GrpcUpdateClientAttributeRequest buildResetNewPostfachNachricht(String vorgangId) {
+		return buildRequest(vorgangId, false, HAS_NEW_POSTFACH_NACHRICHT_ATTRIBUTE_NAME);
+	}
+
+	private GrpcUpdateClientAttributeRequest buildRequest(String vorgangId, boolean value, String attributeName) {
+		return GrpcUpdateClientAttributeRequest.newBuilder()
+				.setVorgangId(vorgangId)
+				.setAttribute(
+						GrpcClientAttribute.newBuilder().setValue(
+								GrpcClientAttributeValue.newBuilder().setBoolValue(value).build())
+								.setClientName(ORIGINAL_CLIENT_NAME)
+								.setAttributeName(attributeName)
+								.build())
+				.build();
+	}
+
+}
diff --git a/goofy-server/src/main/java/de/itvsh/goofy/common/command/Command.java b/goofy-server/src/main/java/de/itvsh/goofy/common/command/Command.java
new file mode 100644
index 0000000000000000000000000000000000000000..40daa57c05fdba912585e1637538374de06dd0a5
--- /dev/null
+++ b/goofy-server/src/main/java/de/itvsh/goofy/common/command/Command.java
@@ -0,0 +1,69 @@
+/*
+ * 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.itvsh.goofy.common.command;
+
+import java.time.ZonedDateTime;
+import java.util.Map;
+
+import com.fasterxml.jackson.annotation.JsonIgnore;
+
+import de.itvsh.goofy.common.LinkedUserProfileResource;
+import de.itvsh.goofy.common.user.UserId;
+import de.itvsh.goofy.vorgang.forwarding.RedirectRequest;
+import lombok.AccessLevel;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+
+@Builder(toBuilder = true)
+@Getter
+@AllArgsConstructor(access = AccessLevel.PACKAGE)
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public class Command {
+
+	@JsonIgnore
+	private String id;
+	private ZonedDateTime createdAt;
+	private ZonedDateTime finishedAt;
+
+	@JsonIgnore
+	@LinkedUserProfileResource
+	private UserId createdBy;
+	private String createdByName;
+
+	@JsonIgnore
+	private CommandStatus status;
+	private String errorMessage;
+
+	@JsonIgnore
+	private String vorgangId;
+	@JsonIgnore
+	private String relationId;
+	private CommandOrder order;
+
+	private RedirectRequest redirectRequest;
+
+	private Map<String, ?> body;
+}
\ No newline at end of file
diff --git a/goofy-server/src/main/java/de/itvsh/goofy/common/command/CommandBody.java b/goofy-server/src/main/java/de/itvsh/goofy/common/command/CommandBody.java
new file mode 100644
index 0000000000000000000000000000000000000000..30bbda1d46cec622e188071b163285ee1f938a0b
--- /dev/null
+++ b/goofy-server/src/main/java/de/itvsh/goofy/common/command/CommandBody.java
@@ -0,0 +1,45 @@
+/*
+ * 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.itvsh.goofy.common.command;
+
+import com.fasterxml.jackson.annotation.JsonSubTypes;
+import com.fasterxml.jackson.annotation.JsonSubTypes.Type;
+
+import de.itvsh.goofy.kommentar.Kommentar;
+import de.itvsh.goofy.postfach.PostfachMail;
+import de.itvsh.goofy.vorgang.AssignUserCommandBody;
+import de.itvsh.goofy.vorgang.forwarding.RedirectRequest;
+import de.itvsh.goofy.wiedervorlage.Wiedervorlage;
+
+@JsonSubTypes({
+		@Type(value = PostfachMail.class, name = "SEND_POSTFACH_MAIL"),
+		@Type(value = PostfachMail.class, name = "SEND_POSTFACH_NACHRICHT"),
+		@Type(value = AssignUserCommandBody.class, name = "ASSIGN_USER"),
+		@Type(value = RedirectRequest.class, name = "REDIRECT_VORGANG"),
+		@Type(value = Wiedervorlage.class, name = "WIEDERVORLAGE"),
+		@Type(value = Kommentar.class, name = "KOMMENTAR")
+})
+public interface CommandBody {
+
+}
diff --git a/goofy-server/src/main/java/de/itvsh/goofy/common/command/CommandBodyMapper.java b/goofy-server/src/main/java/de/itvsh/goofy/common/command/CommandBodyMapper.java
new file mode 100644
index 0000000000000000000000000000000000000000..908b48b9e319741807eeed671f6504265a05d57c
--- /dev/null
+++ b/goofy-server/src/main/java/de/itvsh/goofy/common/command/CommandBodyMapper.java
@@ -0,0 +1,124 @@
+/*
+ * 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.itvsh.goofy.common.command;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
+
+import org.apache.commons.beanutils.BeanMap;
+import org.apache.commons.lang3.StringUtils;
+import org.mapstruct.Mapper;
+
+import de.itvsh.ozg.pluto.grpc.command.GrpcCommandBody;
+import de.itvsh.ozg.pluto.grpc.command.GrpcCommandBodyField;
+
+@Mapper
+public interface CommandBodyMapper {
+
+	final Predicate<Entry<Object, Object>> IS_NOT_CLASS_VALUE = entry -> !StringUtils.equals("class", entry.getKey().toString());
+	final Predicate<Entry<Object, Object>> IS_NOT_VERSION_VALUE = entry -> !StringUtils.equals("version", entry.getKey().toString());
+
+	static final String VORGANG_ID_PROPERTY = "vorgangId";
+	static final String ITEM_NAME_PROPERTY = "itemName";
+	static final String ITEM_PROPERTY = "item";
+
+	default GrpcCommandBody mapToBody(Map<String, String> bodyMap) {
+		if (Objects.isNull(bodyMap)) {
+			return GrpcCommandBody.getDefaultInstance();
+		}
+
+		var builder = GrpcCommandBody.newBuilder();
+
+		bodyMap.entrySet().stream()
+				.filter(entry -> Objects.nonNull(entry.getValue()))
+				.map(entry -> GrpcCommandBodyField.newBuilder().setName(entry.getKey()).setValue(entry.getValue()).build())
+				.forEach(builder::addField);
+
+		return builder.build();
+	}
+
+	default List<GrpcCommandBodyField> mapToBodyFields(CommandBody body) {
+		if (Objects.isNull(body)) {
+			return Collections.emptyList();
+		}
+
+		return mapToBodyFields(new BeanMap(body));
+	}
+
+	default List<GrpcCommandBodyField> mapToBodyFields(Map<Object, Object> bodyMap) {
+		if (Objects.isNull(bodyMap)) {
+			return Collections.emptyList();
+		}
+
+		return bodyMap.entrySet().stream()
+				.filter(IS_NOT_CLASS_VALUE)
+				.map(this::buildBodyField)
+				.toList();
+	}
+
+	default GrpcCommandBodyField buildBodyField(Entry<Object, Object> entry) {
+		var bodyFieldMapper = GrpcCommandBodyField.newBuilder().setName(entry.getKey().toString());
+
+		if (Objects.nonNull(entry.getValue())) {
+			bodyFieldMapper.setValue(getNullableStringValue(entry.getValue()));
+		}
+		return bodyFieldMapper.build();
+	}
+
+	default String getNullableStringValue(Object entryValue) {
+		return Optional.ofNullable(entryValue).map(Object::toString).orElse(null);
+	}
+
+	default Map<String, String> map(GrpcCommandBody body) {
+		return body.getFieldList().stream().collect(Collectors.toMap(GrpcCommandBodyField::getName, GrpcCommandBodyField::getValue));
+	}
+
+	default Map<String, Object> mapToBodyMap(CreateCommand command, String itemName) {
+		return Map.of(
+				VORGANG_ID_PROPERTY, command.getVorgangId(),
+				ITEM_NAME_PROPERTY, itemName,
+				ITEM_PROPERTY, fromObjectToMap(command.getBody()));
+	}
+
+	default Map<String, Object> fromObjectToMap(Object object) {
+		return new BeanMap(object).entrySet().stream()
+				.filter(IS_NOT_CLASS_VALUE)
+				.filter(IS_NOT_VERSION_VALUE)
+				.collect(HashMap::new, (map, entry) -> map.put(entry.getKey().toString(), checkEnumValue(entry.getValue())), Map::putAll);
+	}
+
+	private Object checkEnumValue(Object entryValue) {
+		if (entryValue instanceof Enum<?> enumValue) {
+			return enumValue.name();
+		}
+		return entryValue;
+	}
+}
\ No newline at end of file
diff --git a/goofy-server/src/main/java/de/itvsh/goofy/common/command/CommandController.java b/goofy-server/src/main/java/de/itvsh/goofy/common/command/CommandController.java
new file mode 100644
index 0000000000000000000000000000000000000000..304e4d4bac5844a27f08ab47c8a14ce895355c9c
--- /dev/null
+++ b/goofy-server/src/main/java/de/itvsh/goofy/common/command/CommandController.java
@@ -0,0 +1,135 @@
+/*
+ * 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.itvsh.goofy.common.command;
+
+import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.*;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.hateoas.CollectionModel;
+import org.springframework.hateoas.EntityModel;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PatchMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import de.itvsh.goofy.postfach.PostfachMail;
+import de.itvsh.goofy.vorgang.VorgangController;
+import de.itvsh.goofy.vorgang.VorgangWithEingang;
+import lombok.Getter;
+
+@RestController
+@RequestMapping(CommandController.COMMANDS_PATH)
+public class CommandController {
+
+	static final String COMMANDS_PATH = "/api/commands"; // NOSONAR
+
+	static final String PARAM_PENDING = "pending";
+	static final String PARAM_VORGANG_ID = "vorgangId";
+
+	@Autowired
+	private CommandService service;
+	@Autowired
+	private CommandModelAssembler modelAssembler;
+
+	@GetMapping("{commandId}")
+	public EntityModel<Command> getById(@PathVariable String commandId) {
+		return modelAssembler.toModel(service.getById(commandId));
+	}
+
+	@PatchMapping("{commandId}")
+	public ResponseEntity<EntityModel<Command>> revoke(@PathVariable String commandId, @RequestBody StatusPatch patch) {
+		if (patch.getStatus() == CommandStatus.REVOKED) {
+			return ResponseEntity.ok(modelAssembler.toModel(service.revoke(commandId)));
+		}
+		return ResponseEntity.status(HttpStatus.FORBIDDEN).build();
+	}
+
+	@Getter
+	static class StatusPatch {
+		private CommandStatus status;
+	}
+
+	@GetMapping(params = { PARAM_PENDING, PARAM_VORGANG_ID })
+	public CollectionModel<EntityModel<Command>> getPendingCommands(@RequestParam boolean pending, @RequestParam String vorgangId) {
+		return modelAssembler.toCollectionModel(service.getPendingCommands(vorgangId));
+	}
+
+	public boolean existsPendingCommands(String relationId) {
+		return service.existsPendingCommands(relationId);
+	}
+
+	@RestController
+	@RequestMapping(CommandByRelationController.COMMAND_BY_RELATION_PATH)
+	public static class CommandByRelationController {
+
+		static final String COMMAND_BY_RELATION_PATH = "/api/vorgangs/{vorgangId}/relations/{relationId}/{relationVersion}/commands"; // NOSONAR
+		static final String PARAM_ORDER = "order";
+
+		@Autowired
+		private CommandService service;
+		@Autowired
+		private VorgangController vorgangController;
+
+		@PostMapping
+		public ResponseEntity<Void> createCommand(@PathVariable String vorgangId, @PathVariable String relationId,
+				@PathVariable long relationVersion, @RequestBody CreateCommand command) {
+			command = command.toBuilder().vorgangId(vorgangId).relationId(relationId).build();
+
+			if (isSendPostfachMailOrder(command)) {
+				command = addPostfachIdToBody(command, vorgangId);
+			}
+
+			var created = service.createCommand(command, relationVersion);
+
+			return ResponseEntity.created(linkTo(CommandController.class).slash(created.getId()).toUri()).build();
+		}
+
+		private boolean isSendPostfachMailOrder(CreateCommand command) {
+			return command.getOrder() == CommandOrder.SEND_POSTFACH_NACHRICHT;
+		}
+
+		CreateCommand addPostfachIdToBody(CreateCommand command, String vorgangId) {
+			var bodyWithPostfachId = ((PostfachMail) command.getBody()).toBuilder().postfachId(getPostfachId(vorgangId)).build();
+			return command.toBuilder().body(bodyWithPostfachId).build();
+		}
+
+		private String getPostfachId(String vorgangId) {
+			return getVorgang(vorgangId).getEingang().getAntragsteller().getPostfachId();
+		}
+
+		private VorgangWithEingang getVorgang(String vorgangId) {
+			return vorgangController.getVorgang(vorgangId);
+		}
+
+		public Command createCommand(CreateCommand command) {
+			return service.createCommand(command);
+		}
+	}
+}
\ No newline at end of file
diff --git a/goofy-server/src/main/java/de/itvsh/goofy/common/command/CommandMapper.java b/goofy-server/src/main/java/de/itvsh/goofy/common/command/CommandMapper.java
new file mode 100644
index 0000000000000000000000000000000000000000..a655d8ddcc6a68f666385f5578e29c7f940fb500
--- /dev/null
+++ b/goofy-server/src/main/java/de/itvsh/goofy/common/command/CommandMapper.java
@@ -0,0 +1,87 @@
+/*
+ * 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.itvsh.goofy.common.command;
+
+import java.util.Map;
+
+import org.apache.commons.collections.MapUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.mapstruct.Mapper;
+import org.mapstruct.Mapping;
+import org.mapstruct.MappingConstants;
+import org.mapstruct.ValueMapping;
+import org.springframework.beans.factory.annotation.Autowired;
+
+import de.itvsh.goofy.common.TimeMapper;
+import de.itvsh.goofy.common.callcontext.CallContextMapper;
+import de.itvsh.goofy.common.user.UserIdMapper;
+import de.itvsh.goofy.vorgang.forwarding.RedirectRequest;
+import de.itvsh.kop.pluto.common.grpc.GrpcObjectMapper;
+import de.itvsh.ozg.pluto.grpc.command.GrpcCommand;
+import de.itvsh.ozg.pluto.grpc.command.GrpcRedirectRequest;
+
+@Mapper(uses = { CallContextMapper.class, TimeMapper.class, CommandBodyMapper.class, GrpcObjectMapper.class, UserIdMapper.class })
+public abstract class CommandMapper {
+
+	@Autowired
+	private GrpcObjectMapper objectMapper;
+	@Autowired
+	private CommandBodyMapper bodyMapper;
+
+	@ValueMapping(source = "UNRECOGNIZED", target = MappingConstants.NULL)
+	@ValueMapping(source = "UNDEFINED", target = MappingConstants.NULL)
+	@Mapping(target = "order", expression = "java(mapOrder(grpcCommand))")
+	@Mapping(target = "body", expression = "java(mapBody(grpcCommand))")
+	abstract Command toCommand(GrpcCommand grpcCommand);
+
+	RedirectRequest mapRedirectRequest(GrpcRedirectRequest request) {
+		var builder = RedirectRequest.builder().email(request.getEmail());
+		if (StringUtils.isNotEmpty(request.getPassword())) {
+			builder.password(request.getPassword().toCharArray());
+		}
+		return builder.build();
+	}
+
+	Map<String, ?> mapBody(GrpcCommand command) {
+		var bodyObject = objectMapper.mapFromGrpc(command.getBodyObj());
+
+		if (MapUtils.isNotEmpty(bodyObject)) {
+			return bodyObject;
+		} else {
+			return bodyMapper.map(command.getBody());
+		}
+	}
+
+	CommandOrder mapOrder(GrpcCommand command) {
+		if (StringUtils.isNotBlank(command.getOrderString())) {
+			return CommandOrder.valueOf(command.getOrderString());
+		} else {
+			return CommandOrder.valueOf(command.getOrder().name());
+		}
+	}
+
+	String mapStringtoNull(String in) {
+		return StringUtils.trimToNull(in);
+	}
+}
\ No newline at end of file
diff --git a/goofy-server/src/main/java/de/itvsh/goofy/common/command/CommandModelAssembler.java b/goofy-server/src/main/java/de/itvsh/goofy/common/command/CommandModelAssembler.java
new file mode 100644
index 0000000000000000000000000000000000000000..db21be840129e5d1907caf0de93d14b9ce438fa2
--- /dev/null
+++ b/goofy-server/src/main/java/de/itvsh/goofy/common/command/CommandModelAssembler.java
@@ -0,0 +1,97 @@
+/*
+ * 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.itvsh.goofy.common.command;
+
+import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.*;
+
+import java.util.Optional;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import org.springframework.hateoas.CollectionModel;
+import org.springframework.hateoas.EntityModel;
+import org.springframework.hateoas.Link;
+import org.springframework.hateoas.LinkRelation;
+import org.springframework.hateoas.server.RepresentationModelAssembler;
+import org.springframework.hateoas.server.mvc.WebMvcLinkBuilder;
+import org.springframework.stereotype.Component;
+
+import de.itvsh.goofy.common.errorhandling.FunctionalException;
+import de.itvsh.goofy.kommentar.KommentarController;
+import de.itvsh.goofy.postfach.PostfachMailController;
+import de.itvsh.goofy.vorgang.VorgangController;
+import de.itvsh.goofy.vorgang.forwarding.ForwardingController;
+import de.itvsh.goofy.wiedervorlage.WiedervorlageController;
+
+@Component
+class CommandModelAssembler implements RepresentationModelAssembler<Command, EntityModel<Command>> {
+
+	static final LinkRelation REL_EFFECTED_RESOURCE = LinkRelation.of("effected_resource");
+	static final LinkRelation REL_REVOKE = LinkRelation.of("revoke");
+	static final LinkRelation REL_UPDATE = LinkRelation.of("update");
+
+	private static final Predicate<Command> IS_DONE = command -> command.getStatus() == CommandStatus.FINISHED
+			|| command.getStatus() == CommandStatus.REVOKED || command.getStatus() == CommandStatus.ERROR;
+
+	private static final Predicate<Command> IS_PENDING = command -> command.getStatus() == CommandStatus.PENDING
+			|| command.getStatus() == CommandStatus.REVOKE_PENDING;
+
+	@Override
+	public EntityModel<Command> toModel(Command entity) {
+		WebMvcLinkBuilder selfLinkBuilder = linkTo(CommandController.class).slash(entity.getId());
+		Link revokeLink = linkTo(methodOn(CommandController.class).revoke(entity.getId(), null)).withRel(REL_REVOKE);
+
+		var resultModel = EntityModel.of(entity, selfLinkBuilder.withSelfRel());
+		resultModel = addIf(IS_DONE, resultModel, effectedResourceLinkByOrder(entity));
+		resultModel = addIf(IS_PENDING, resultModel, selfLinkBuilder.withRel(REL_UPDATE));
+		resultModel = addIf(command -> command.getOrder().isRevokeable(), resultModel, revokeLink);
+		return resultModel;
+	}
+
+	Link effectedResourceLinkByOrder(Command entity) {
+		switch (entity.getOrder().getType()) {
+		case VORGANG:
+			return linkTo(VorgangController.class).slash(entity.getRelationId()).withRel(REL_EFFECTED_RESOURCE);
+		case WIEDERVORLAGE:
+			return linkTo(WiedervorlageController.class).slash(entity.getRelationId()).withRel(REL_EFFECTED_RESOURCE);
+		case KOMMENTAR:
+			return linkTo(KommentarController.class).slash(entity.getRelationId()).withRel(REL_EFFECTED_RESOURCE);
+		case FORWARDING:
+			return linkTo(methodOn(ForwardingController.class).findByVorgangId(entity.getVorgangId())).withRel(REL_EFFECTED_RESOURCE);
+		case POSTFACH:
+			return linkTo(methodOn(PostfachMailController.class).getAll(entity.getVorgangId())).withRel(REL_EFFECTED_RESOURCE);
+		default:
+			throw new FunctionalException(() -> "command_order_unkown");
+		}
+	}
+
+	private EntityModel<Command> addIf(Predicate<Command> condition, EntityModel<Command> model, Link link) {
+		return Optional.ofNullable(model.getContent()).filter(condition).map(command -> model.add(link)).orElse(model);
+	}
+
+	public CollectionModel<EntityModel<Command>> toCollectionModel(Stream<Command> entities) {
+		return CollectionModel.of(entities.map(this::toModel).collect(Collectors.toList()), linkTo(CommandController.class).withSelfRel());
+	}
+}
\ No newline at end of file
diff --git a/goofy-server/src/main/java/de/itvsh/goofy/common/command/CommandOrder.java b/goofy-server/src/main/java/de/itvsh/goofy/common/command/CommandOrder.java
new file mode 100644
index 0000000000000000000000000000000000000000..594a553a06a28fd02f36258d02c1a133e883517e
--- /dev/null
+++ b/goofy-server/src/main/java/de/itvsh/goofy/common/command/CommandOrder.java
@@ -0,0 +1,74 @@
+/*
+ * 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.itvsh.goofy.common.command;
+
+import lombok.AccessLevel;
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+
+@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
+@Getter
+public enum CommandOrder {
+	VORGANG_ANNEHMEN(true, Type.VORGANG),
+	VORGANG_VERWERFEN(true, Type.VORGANG),
+	VORGANG_ZURUECKHOLEN(true, Type.VORGANG),
+	VORGANG_BEARBEITEN(true, Type.VORGANG),
+	VORGANG_BESCHEIDEN(true, Type.VORGANG),
+	VORGANG_ZURUECKSTELLEN(true, Type.VORGANG),
+	VORGANG_ABSCHLIESSEN(true, Type.VORGANG),
+	VORGANG_WIEDEREROEFFNEN(true, Type.VORGANG),
+
+	ASSIGN_USER(false, Type.VORGANG),
+
+	REDIRECT_VORGANG(false, Type.FORWARDING),
+	FORWARD_SUCCESSFULL(false, Type.FORWARDING),
+	FORWARD_FAILED(false, Type.FORWARDING),
+
+	CREATE_KOMMENTAR(false, Type.KOMMENTAR),
+	EDIT_KOMMENTAR(false, Type.KOMMENTAR),
+
+	CREATE_WIEDERVORLAGE(false, Type.WIEDERVORLAGE),
+	EDIT_WIEDERVORLAGE(false, Type.WIEDERVORLAGE),
+	WIEDERVORLAGE_ERLEDIGEN(false, Type.WIEDERVORLAGE),
+	WIEDERVORLAGE_WIEDEREROEFFNEN(false, Type.WIEDERVORLAGE),
+
+	@Deprecated
+	SEND_POSTFACH_MAIL(false, Type.POSTFACH),
+	SEND_POSTFACH_NACHRICHT(false, Type.POSTFACH),
+	RESEND_POSTFACH_MAIL(false, Type.POSTFACH),
+	RECEIVE_POSTFACH_NACHRICHT(false, Type.POSTFACH),
+
+	CREATE_ATTACHED_ITEM(false, Type.VORGANG),
+	UPDATE_ATTACHED_ITEM(false, Type.VORGANG),
+	PATCH_ATTACHED_ITEM(false, Type.VORGANG);
+
+	enum Type {
+		VORGANG, WIEDERVORLAGE, KOMMENTAR, FORWARDING, POSTFACH
+	}
+
+	private final boolean revokeable;
+	@Getter(value = AccessLevel.PROTECTED)
+	private final Type type;
+
+}
\ No newline at end of file
diff --git a/goofy-server/src/main/java/de/itvsh/goofy/common/command/CommandRemoteService.java b/goofy-server/src/main/java/de/itvsh/goofy/common/command/CommandRemoteService.java
new file mode 100644
index 0000000000000000000000000000000000000000..6ed320f930829d0a198bb89a527be4af583d99f0
--- /dev/null
+++ b/goofy-server/src/main/java/de/itvsh/goofy/common/command/CommandRemoteService.java
@@ -0,0 +1,158 @@
+/*
+ * 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.itvsh.goofy.common.command;
+
+import java.util.Optional;
+import java.util.stream.Stream;
+
+import org.apache.commons.lang3.ArrayUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import de.itvsh.goofy.common.callcontext.ContextService;
+import de.itvsh.goofy.vorgang.forwarding.RedirectRequest;
+import de.itvsh.kop.pluto.common.grpc.GrpcObjectMapper;
+import de.itvsh.ozg.pluto.grpc.command.CommandServiceGrpc.CommandServiceBlockingStub;
+import de.itvsh.ozg.pluto.grpc.command.GrpcCreateCommandRequest;
+import de.itvsh.ozg.pluto.grpc.command.GrpcExistsPendingCommandsRequest;
+import de.itvsh.ozg.pluto.grpc.command.GrpcFindCommandsRequest;
+import de.itvsh.ozg.pluto.grpc.command.GrpcGetCommandRequest;
+import de.itvsh.ozg.pluto.grpc.command.GrpcGetPendingCommandsRequest;
+import de.itvsh.ozg.pluto.grpc.command.GrpcOrder;
+import de.itvsh.ozg.pluto.grpc.command.GrpcRedirectRequest;
+import de.itvsh.ozg.pluto.grpc.command.GrpcRevokeCommandRequest;
+import net.devh.boot.grpc.client.inject.GrpcClient;
+
+@Service
+public class CommandRemoteService {
+
+	@GrpcClient("pluto")
+	private CommandServiceBlockingStub commandServiceStub;
+	@Autowired
+	private ContextService contextService;
+	@Autowired
+	private CommandMapper mapper;
+	@Autowired
+	private CommandBodyMapper bodyMapper;
+	@Autowired
+	private GrpcObjectMapper objectMapper;
+
+	public Command createCommand(CreateCommand command) {
+		var response = commandServiceStub.createCommand(buildCreateCommandRequest(command));
+
+		return mapper.toCommand(response.getCommand());
+	}
+
+	GrpcCreateCommandRequest buildCreateCommandRequest(CreateCommand command) {
+		var requestBuilder = GrpcCreateCommandRequest.newBuilder()
+				.setCallContext(contextService.createCallContext())
+				.setVorgangId(command.getVorgangId())
+				.setRelationId(command.getRelationId())
+				.setRelationVersion(command.getRelationVersion())
+				.setOrderString(command.getOrder().name())
+				.addAllBody(bodyMapper.mapToBodyFields(command.getBody()))
+				.setBodyObj(objectMapper.fromMap(bodyMapper.fromObjectToMap(command.getBody())));
+
+		Optional.ofNullable(command.getRedirectRequest()).map(this::buildForwardRequest).ifPresent(requestBuilder::setRedirectRequest);
+
+		return requestBuilder.build();
+	}
+
+	GrpcRedirectRequest buildForwardRequest(RedirectRequest forwardRequest) {
+		var builder = GrpcRedirectRequest.newBuilder().setEmail(forwardRequest.getEmail());
+		if (ArrayUtils.isNotEmpty(forwardRequest.getPassword())) {
+			builder.setPassword(String.valueOf(forwardRequest.getPassword()));
+		}
+		return builder.build();
+	}
+
+	public Command revokeCommand(String commandId) {
+		var response = commandServiceStub.revokeCommand(createRevokeRequest(commandId));
+
+		return mapper.toCommand(response.getCommand());
+	}
+
+	GrpcRevokeCommandRequest createRevokeRequest(String commandId) {
+		return GrpcRevokeCommandRequest.newBuilder()
+				.setContext(contextService.createCallContext())
+				.setId(commandId)
+				.build();
+	}
+
+	public Command getCommand(String commandId) {
+		var response = commandServiceStub.getCommand(createGetCommandRequest(commandId));
+
+		return mapper.toCommand(response);
+	}
+
+	GrpcGetCommandRequest createGetCommandRequest(String commandId) {
+		return GrpcGetCommandRequest.newBuilder()
+				.setContext(contextService.createCallContext())
+				.setId(commandId)
+				.build();
+	}
+
+	public boolean existsPendingCommands(String relationId) {
+		var response = commandServiceStub.existsPendingCommands(buildGrpcExistsPendingCommandsRequest(relationId));
+
+		return response.getExistsPendingCommands();
+	}
+
+	private GrpcExistsPendingCommandsRequest buildGrpcExistsPendingCommandsRequest(String relationId) {
+		return GrpcExistsPendingCommandsRequest.newBuilder()
+				.setContext(contextService.createCallContext())
+				.setVorgangId(relationId)
+				.build();
+	}
+
+	public Stream<Command> getPendingCommands(String vorgangId) {
+		var response = commandServiceStub.getPendingCommands(buildGrpcGetPendingCommandsRequest(vorgangId));
+
+		return response.getCommandList().stream().map(mapper::toCommand);
+	}
+
+	private GrpcGetPendingCommandsRequest buildGrpcGetPendingCommandsRequest(String vorgangId) {
+		return GrpcGetPendingCommandsRequest.newBuilder()
+				.setContext(contextService.createCallContext())
+				.setVorgangId(vorgangId)
+				.build();
+	}
+
+	public Stream<Command> findCommands(String vorgangId, Optional<CommandStatus> status, Optional<CommandOrder> order) {
+		var response = commandServiceStub.findCommands(buildFindCommandsRequest(vorgangId, status, order));
+
+		return response.getCommandList().stream().map(mapper::toCommand);
+	}
+
+	private GrpcFindCommandsRequest buildFindCommandsRequest(String vorgangId, Optional<CommandStatus> status, Optional<CommandOrder> order) {
+		var builder = GrpcFindCommandsRequest.newBuilder()
+				.setContext(contextService.createCallContext())
+				.setVorgangId(vorgangId);
+
+		status.map(CommandStatus::name).ifPresent(builder::addStatus);
+		order.map(commandOrder -> GrpcOrder.valueOf(commandOrder.name())).ifPresent(builder::setOrder);
+
+		return builder.build();
+	}
+}
\ No newline at end of file
diff --git a/goofy-server/src/main/java/de/itvsh/goofy/common/command/CommandService.java b/goofy-server/src/main/java/de/itvsh/goofy/common/command/CommandService.java
new file mode 100644
index 0000000000000000000000000000000000000000..a55a1ff0b5d7d6af7907651fa535481189f8444a
--- /dev/null
+++ b/goofy-server/src/main/java/de/itvsh/goofy/common/command/CommandService.java
@@ -0,0 +1,77 @@
+/*
+ * 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.itvsh.goofy.common.command;
+
+import java.util.Optional;
+import java.util.stream.Stream;
+
+import javax.validation.Valid;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.validation.annotation.Validated;
+
+import lombok.NonNull;
+
+@Validated
+@Service
+public class CommandService {
+
+	static final long NO_RELATION_VERSION = -1;
+
+	@Autowired
+	private CommandRemoteService remoteService;
+
+	public Command createCommand(CreateCommand command) {
+		command = command.toBuilder().relationVersion(NO_RELATION_VERSION).build();
+
+		return remoteService.createCommand(command);
+	}
+
+	public Command createCommand(@Valid CreateCommand command, long relationVersion) {
+		command = command.toBuilder().relationVersion(relationVersion).build();
+
+		return remoteService.createCommand(command);
+	}
+
+	public Command getById(String commandId) {
+		return remoteService.getCommand(commandId);
+	}
+
+	public Command revoke(String commandId) {
+		return remoteService.revokeCommand(commandId);
+	}
+
+	public boolean existsPendingCommands(String relationId) {
+		return remoteService.existsPendingCommands(relationId);
+	}
+
+	public Stream<Command> getPendingCommands(String vorgangId) {
+		return remoteService.getPendingCommands(vorgangId);
+	}
+
+	public Stream<Command> findFinishedCommands(@NonNull String vorgangId) {
+		return remoteService.findCommands(vorgangId, Optional.of(CommandStatus.FINISHED), Optional.empty());
+	}
+}
\ No newline at end of file
diff --git a/goofy-server/src/main/java/de/itvsh/goofy/common/command/CommandStatus.java b/goofy-server/src/main/java/de/itvsh/goofy/common/command/CommandStatus.java
new file mode 100644
index 0000000000000000000000000000000000000000..7a8c8e0e35b4b48c07bf600a6c50f819b84b0de9
--- /dev/null
+++ b/goofy-server/src/main/java/de/itvsh/goofy/common/command/CommandStatus.java
@@ -0,0 +1,28 @@
+/*
+ * 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.itvsh.goofy.common.command;
+
+public enum CommandStatus {
+	PENDING, FINISHED, ERROR, REVOKE_PENDING, REVOKED;
+}
diff --git a/goofy-server/src/main/java/de/itvsh/goofy/common/command/CreateCommand.java b/goofy-server/src/main/java/de/itvsh/goofy/common/command/CreateCommand.java
new file mode 100644
index 0000000000000000000000000000000000000000..762515619039551e24a3a470fd6377c160beedde
--- /dev/null
+++ b/goofy-server/src/main/java/de/itvsh/goofy/common/command/CreateCommand.java
@@ -0,0 +1,63 @@
+/*
+ * 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.itvsh.goofy.common.command;
+
+import javax.validation.Valid;
+
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.fasterxml.jackson.annotation.JsonTypeInfo;
+import com.fasterxml.jackson.annotation.JsonTypeInfo.As;
+import com.fasterxml.jackson.annotation.JsonTypeInfo.Id;
+
+import de.itvsh.goofy.common.user.UserId;
+import de.itvsh.goofy.vorgang.forwarding.RedirectRequest;
+import lombok.AccessLevel;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+
+@Getter
+@Builder(toBuilder = true)
+@AllArgsConstructor(access = AccessLevel.PRIVATE)
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public class CreateCommand {
+
+	@JsonIgnore
+	private UserId createdBy;
+	@JsonIgnore
+	private CommandStatus status;
+
+	private String vorgangId;
+	private String relationId;
+	private long relationVersion;
+	private CommandOrder order;
+
+	@Valid
+	private RedirectRequest redirectRequest;
+
+	@Valid
+	@JsonTypeInfo(use = Id.NAME, include = As.EXTERNAL_PROPERTY, property = "order")
+	private CommandBody body;
+}
\ No newline at end of file
diff --git a/goofy-server/src/main/java/de/itvsh/goofy/common/downloadtoken/DownloadTokenAuthenticationFilter.java b/goofy-server/src/main/java/de/itvsh/goofy/common/downloadtoken/DownloadTokenAuthenticationFilter.java
new file mode 100644
index 0000000000000000000000000000000000000000..fda3e1a7381334201313d22ff7a4b6e68cd9d496
--- /dev/null
+++ b/goofy-server/src/main/java/de/itvsh/goofy/common/downloadtoken/DownloadTokenAuthenticationFilter.java
@@ -0,0 +1,70 @@
+/*
+ * 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.itvsh.goofy.common.downloadtoken;
+
+import java.io.IOException;
+
+import javax.servlet.FilterChain;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.http.HttpStatus;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.core.annotation.Order;
+import org.springframework.stereotype.Component;
+import org.springframework.web.filter.OncePerRequestFilter;
+
+import de.itvsh.kop.common.errorhandling.TechnicalException;
+
+@Component
+@Order(0)
+public class DownloadTokenAuthenticationFilter extends OncePerRequestFilter {
+	@Autowired
+	private DownloadTokenService downloadTokenService;
+
+	@Override
+	protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
+			throws ServletException, IOException {
+
+		try {
+			downloadTokenService.handleToken(request, getDownloadToken(request));
+		} catch (TechnicalException e) {
+			response.setStatus(HttpStatus.SC_UNAUTHORIZED);
+			return;
+		}
+
+		filterChain.doFilter(request, response);
+	}
+
+	@Override
+	protected boolean shouldNotFilter(HttpServletRequest request) throws ServletException {
+		return StringUtils.isEmpty(getDownloadToken(request));
+	}
+
+	private String getDownloadToken(HttpServletRequest request) {
+		return request.getParameter(DownloadTokenController.PARAM_TOKEN);
+	}
+}
\ No newline at end of file
diff --git a/goofy-server/src/main/java/de/itvsh/goofy/common/downloadtoken/DownloadTokenController.java b/goofy-server/src/main/java/de/itvsh/goofy/common/downloadtoken/DownloadTokenController.java
new file mode 100644
index 0000000000000000000000000000000000000000..e9d427ce4f4429783bf46d767f84eaf4f05dd9e1
--- /dev/null
+++ b/goofy-server/src/main/java/de/itvsh/goofy/common/downloadtoken/DownloadTokenController.java
@@ -0,0 +1,67 @@
+/*
+ * 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.itvsh.goofy.common.downloadtoken;
+
+import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.*;
+
+import java.net.URI;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import de.itvsh.goofy.common.binaryfile.FileId;
+
+@RestController
+@RequestMapping(DownloadTokenController.DOWNLOAD_TOKEN_PATH)
+public class DownloadTokenController {
+
+	public static final String DOWNLOAD_TOKEN_PATH = "/api/downloadtoken"; // NOSONAR
+
+	public static final String PARAM_TOKEN = "token";
+
+	@Autowired
+	private DownloadTokenService tokenService;
+
+	@PostMapping
+	public ResponseEntity<Void> downloadToken(@RequestBody DownloadTokenRequest request) {
+		return ResponseEntity.created(buildUri(request.getFileId())).build();
+	}
+
+	private URI buildUri(FileId fileId) {
+		return linkTo(DownloadTokenController.class).toUriComponentsBuilder().queryParam(PARAM_TOKEN, tokenService.createToken(fileId))
+				.build()
+				.toUri();
+	}
+
+	@GetMapping
+	public ResponseEntity<DownloadTokenResponse> getToken(@RequestParam String token) {
+		return ResponseEntity.ok(DownloadTokenResponse.builder().token(token).build());
+	}
+}
\ No newline at end of file
diff --git a/goofy-server/src/main/java/de/itvsh/goofy/common/downloadtoken/DownloadTokenProperties.java b/goofy-server/src/main/java/de/itvsh/goofy/common/downloadtoken/DownloadTokenProperties.java
new file mode 100644
index 0000000000000000000000000000000000000000..a124a43e3d8973189726c64a1bfd800510685785
--- /dev/null
+++ b/goofy-server/src/main/java/de/itvsh/goofy/common/downloadtoken/DownloadTokenProperties.java
@@ -0,0 +1,48 @@
+/*
+ * 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.itvsh.goofy.common.downloadtoken;
+
+import javax.validation.constraints.NotNull;
+
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.validation.annotation.Validated;
+
+import lombok.Getter;
+import lombok.Setter;
+
+@Getter
+@Setter
+@Validated
+@Configuration
+@ConfigurationProperties(DownloadTokenProperties.PREFIX)
+public class DownloadTokenProperties {
+
+	public static final String PREFIX = "kop.auth.token";
+
+	@NotNull
+	private String secret;
+	@NotNull
+	private int validity;
+}
\ No newline at end of file
diff --git a/goofy-server/src/main/java/de/itvsh/goofy/common/downloadtoken/DownloadTokenRequest.java b/goofy-server/src/main/java/de/itvsh/goofy/common/downloadtoken/DownloadTokenRequest.java
new file mode 100644
index 0000000000000000000000000000000000000000..40aad859635de8387ec8a1b617f2133e5c68f438
--- /dev/null
+++ b/goofy-server/src/main/java/de/itvsh/goofy/common/downloadtoken/DownloadTokenRequest.java
@@ -0,0 +1,36 @@
+/*
+ * 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.itvsh.goofy.common.downloadtoken;
+
+import de.itvsh.goofy.common.LinkedResource;
+import de.itvsh.goofy.common.binaryfile.BinaryFileController;
+import de.itvsh.goofy.common.binaryfile.FileId;
+import lombok.Getter;
+
+@Getter
+class DownloadTokenRequest {
+
+	@LinkedResource(controllerClass = BinaryFileController.class)
+	private FileId fileId;
+}
\ No newline at end of file
diff --git a/goofy-server/src/main/java/de/itvsh/goofy/common/downloadtoken/DownloadTokenResponse.java b/goofy-server/src/main/java/de/itvsh/goofy/common/downloadtoken/DownloadTokenResponse.java
new file mode 100644
index 0000000000000000000000000000000000000000..e13db1135c812f2280b541f1aa764dbc90fffc8a
--- /dev/null
+++ b/goofy-server/src/main/java/de/itvsh/goofy/common/downloadtoken/DownloadTokenResponse.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.itvsh.goofy.common.downloadtoken;
+
+import lombok.Builder;
+import lombok.Getter;
+
+@Getter
+@Builder
+class DownloadTokenResponse {
+
+	private String token;
+}
\ No newline at end of file
diff --git a/goofy-server/src/main/java/de/itvsh/goofy/common/downloadtoken/DownloadTokenService.java b/goofy-server/src/main/java/de/itvsh/goofy/common/downloadtoken/DownloadTokenService.java
new file mode 100644
index 0000000000000000000000000000000000000000..e4b234bac54a3eb29c511d494394f0f1c9eeb0c0
--- /dev/null
+++ b/goofy-server/src/main/java/de/itvsh/goofy/common/downloadtoken/DownloadTokenService.java
@@ -0,0 +1,103 @@
+/*
+ * 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.itvsh.goofy.common.downloadtoken;
+
+import static de.itvsh.goofy.JwtTokenUtil.*;
+
+import java.util.Optional;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
+import org.springframework.stereotype.Service;
+
+import com.auth0.jwt.exceptions.JWTVerificationException;
+
+import de.itvsh.goofy.JwtTokenUtil;
+import de.itvsh.goofy.common.binaryfile.FileId;
+import de.itvsh.goofy.common.binaryfile.GoofyUserWithFileId;
+import de.itvsh.goofy.common.user.CurrentUserService;
+import de.itvsh.goofy.common.user.UserProfile;
+import de.itvsh.goofy.common.user.UserId;
+import de.itvsh.kop.common.errorhandling.TechnicalException;
+import io.jsonwebtoken.Claims;
+import lombok.extern.log4j.Log4j2;
+
+@Log4j2
+@Service
+class DownloadTokenService {
+
+	@Autowired
+	private CurrentUserService userService;
+
+	@Autowired
+	private JwtTokenUtil jwtTokenUtil;
+
+	public String createToken(FileId fileId) {
+		var user = userService.getUser();
+		return jwtTokenUtil.generateToken(fileId, user);
+	}
+
+	public void handleToken(HttpServletRequest request, String token) {
+		if (StringUtils.isNotBlank(token)) {
+			try {
+				jwtTokenUtil.verifyToken(token);
+
+				SecurityContextHolder.getContext().setAuthentication(buildAuthentication(request, token));
+			} catch (JWTVerificationException e) {
+				LOG.error("JwtVerficationException in DownloadTokenService", e);
+				throw new TechnicalException("download token not valid", e);
+			}
+		}
+	}
+
+	private Authentication buildAuthentication(HttpServletRequest request, String token) {
+		var user = getUserFromToken(token);
+
+		var authenticationToken = new UsernamePasswordAuthenticationToken(user, null, user.getAuthorities());
+		authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
+
+		return authenticationToken;
+	}
+
+	GoofyUserWithFileId getUserFromToken(String token) {
+		Optional<Claims> claimsOptional = jwtTokenUtil.getAllClaimsFromToken(token);
+		var downloadUserBuilder = GoofyUserWithFileId.builder();
+		claimsOptional.ifPresent(claims -> downloadUserBuilder.user(
+				UserProfile.builder()
+						.id(UserId.from(claims.get(USERID_CLAIM, String.class)))
+						.firstName(claims.get(FIRSTNAME_CLAIM, String.class))
+						.lastName(claims.get(LASTNAME_CLAIM, String.class))
+						.authorities(jwtTokenUtil.getRolesFromToken(token))
+						.organisationseinheitIds(jwtTokenUtil.getOrganisationseinheitIdsFromToken(token)).build())
+				.fileId(FileId.from(claims.get(FILEID_CLAIM, String.class))));
+
+		return downloadUserBuilder.build();
+	}
+}
\ No newline at end of file
diff --git a/goofy-server/src/main/java/de/itvsh/goofy/common/errorhandling/ApiError.java b/goofy-server/src/main/java/de/itvsh/goofy/common/errorhandling/ApiError.java
new file mode 100644
index 0000000000000000000000000000000000000000..05ead44be65ccd8b7fb79f9d00d2d72cf705bfd7
--- /dev/null
+++ b/goofy-server/src/main/java/de/itvsh/goofy/common/errorhandling/ApiError.java
@@ -0,0 +1,42 @@
+/*
+ * 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.itvsh.goofy.common.errorhandling;
+
+import java.util.List;
+
+import lombok.AccessLevel;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Getter;
+import lombok.Singular;
+
+@Getter
+@Builder
+@AllArgsConstructor(access = AccessLevel.PRIVATE)
+class ApiError {
+
+	@Singular
+	private List<Issue> issues;
+
+}
diff --git a/goofy-server/src/main/java/de/itvsh/goofy/common/errorhandling/ExceptionController.java b/goofy-server/src/main/java/de/itvsh/goofy/common/errorhandling/ExceptionController.java
new file mode 100644
index 0000000000000000000000000000000000000000..bc6076da1206786609275e87069265e5bbef0dca
--- /dev/null
+++ b/goofy-server/src/main/java/de/itvsh/goofy/common/errorhandling/ExceptionController.java
@@ -0,0 +1,210 @@
+/*
+ * 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.itvsh.goofy.common.errorhandling;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map.Entry;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.Set;
+import java.util.UUID;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import javax.validation.ConstraintViolation;
+import javax.validation.ConstraintViolationException;
+import javax.validation.Path;
+import javax.validation.metadata.ConstraintDescriptor;
+
+import org.hibernate.validator.engine.HibernateConstraintViolation;
+import org.springframework.core.annotation.Order;
+import org.springframework.http.HttpStatus;
+import org.springframework.security.access.AccessDeniedException;
+import org.springframework.web.bind.annotation.ControllerAdvice;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.bind.annotation.ResponseBody;
+import org.springframework.web.bind.annotation.ResponseStatus;
+
+import de.itvsh.goofy.common.binaryfile.DynamicViolationParameter;
+import de.itvsh.kop.common.errorhandling.ExceptionUtil;
+import de.itvsh.kop.common.errorhandling.TechnicalException;
+import lombok.extern.log4j.Log4j2;
+
+@ControllerAdvice
+@Log4j2
+@Order(99)
+public class ExceptionController {
+
+	private static final Set<String> IGNORABLE_CONSTRAINT_VIOLATION_ATTRIBUTES = new HashSet<>(Arrays.asList("groups", "payload", "message"));
+
+	static final String RUNTIME_MESSAGE_CODE = "generale.server_error";
+	static final String RESOURCE_NOT_FOUNT_MESSAGE_CODE = "resource.not_found";
+	static final String ACCESS_DENIED_MESSAGE_CODE = "generale.access_denied";
+	static final String SERVICE_UNAVAILABLE_MESSAGE_CODE = "generale.service_unavailable";
+
+	@ExceptionHandler(FunctionalException.class)
+	@ResponseStatus(HttpStatus.BAD_REQUEST)
+	@ResponseBody
+	public ApiError handleFunctionalException(FunctionalException e) {
+		LOG.error("BadRequest", e);
+		return ApiError.builder().issue(buildIssueForFunctionalException(e)).build();
+	}
+
+	private Issue buildIssueForFunctionalException(FunctionalException e) {
+		return Issue.builder().message(e.getMessage()).messageCode(e.getErrorCode()).exceptionId(e.getExceptionId()).build();
+	}
+
+	@ExceptionHandler(AccessDeniedException.class)
+	@ResponseStatus(HttpStatus.FORBIDDEN)
+	@ResponseBody
+	public ApiError handleAccessDeniedException(AccessDeniedException e) {
+		var exceptionId = createExceptionId();
+		var messageWithExceptionId = ExceptionUtil.formatMessageWithExceptionId(e.getMessage(), exceptionId);
+		LOG.error("Access Denied Exception", messageWithExceptionId);
+
+		return ApiError.builder().issue(buildIssueForAccessDeniedException(messageWithExceptionId, exceptionId)).build();
+	}
+
+	private Issue buildIssueForAccessDeniedException(String message, String exceptionId) {
+		return Issue.builder().messageCode(ACCESS_DENIED_MESSAGE_CODE).message(message).exceptionId(exceptionId).build();
+	}
+
+	@ExceptionHandler(ResourceNotFoundException.class)
+	@ResponseStatus(HttpStatus.NOT_FOUND)
+	@ResponseBody
+	public ApiError handleResourceNotFoundException(ResourceNotFoundException e) {
+		LOG.warn("Resource not found: {}", e.getMessage());
+		return ApiError.builder().issue(buildIssueForResourceNotFoundException(e)).build();
+	}
+
+	private Issue buildIssueForResourceNotFoundException(ResourceNotFoundException e) {
+		return Issue.builder().message(e.getMessage()).messageCode(RESOURCE_NOT_FOUNT_MESSAGE_CODE).exceptionId(e.getExceptionId()).build();
+	}
+
+	@ExceptionHandler(ConstraintViolationException.class)
+	@ResponseStatus(HttpStatus.UNPROCESSABLE_ENTITY)
+	@ResponseBody
+	public ApiError handleConstraintViolationException(ConstraintViolationException e) {
+		var exceptionId = createExceptionId();
+		var messageWithExceptionId = ExceptionUtil.formatMessageWithExceptionId(e.getMessage(), exceptionId);
+		LOG.warn("Validation Exception: {}", messageWithExceptionId);
+		return ApiError.builder().issues(buildIssues(e, exceptionId)).build();
+	}
+
+	private List<Issue> buildIssues(ConstraintViolationException e, String exceptionId) {
+		return e.getConstraintViolations().stream()
+				.map(violation -> buildIssue(violation, exceptionId))
+				.collect(Collectors.toList());
+	}
+
+	private Issue buildIssue(ConstraintViolation<?> violation, String exceptionId) {
+		return Issue.builder()//
+				.field(buildFieldPath(violation.getPropertyPath()))//
+				.messageCode(violation.getMessageTemplate().replace("{", "").replace("}", ""))//
+				.message(violation.getMessage())//
+				.parameters(buildParameters(violation).collect(Collectors.toList()))
+				.exceptionId(exceptionId)
+				.build();
+	}
+
+	private String buildFieldPath(Path propertyPath) {
+		return propertyPath.toString().substring(propertyPath.toString().indexOf('.') + 1);
+	}
+
+	Stream<IssueParam> buildParameters(ConstraintViolation<?> violation) {
+		var dynamicPayload = getDynamicPayload(violation);
+		return Optional.ofNullable(violation.getConstraintDescriptor())
+				.map(ConstraintDescriptor::getAttributes)
+				.map(descr -> descr.entrySet().stream()
+						.filter(entry -> !IGNORABLE_CONSTRAINT_VIOLATION_ATTRIBUTES.contains(entry.getKey()))
+						.map(entryMap -> buildIssueParam(entryMap, dynamicPayload)))
+				.orElse(Stream.empty());
+	}
+
+	private IssueParam buildIssueParam(Entry<String, Object> entry, Optional<DynamicViolationParameter> dynamicValues) {
+		return IssueParam.builder().name(entry.getKey()).value(getValue(entry, dynamicValues)).build();
+	}
+
+	private String getValue(Entry<String, Object> entry, Optional<DynamicViolationParameter> dynamicValues) {
+		return dynamicValues
+				.map(DynamicViolationParameter::getMap)
+				.map(map -> map.get(entry.getKey()))
+				.filter(Objects::nonNull)
+				.map(String::valueOf)
+				.orElse(String.valueOf(entry.getValue()));
+	}
+
+	private Optional<DynamicViolationParameter> getDynamicPayload(ConstraintViolation<?> violation) {
+		HibernateConstraintViolation<?> hibernateViolation = violation.unwrap(HibernateConstraintViolation.class);
+		return Optional.ofNullable(hibernateViolation.getDynamicPayload(DynamicViolationParameter.class));
+	}
+
+	@ExceptionHandler(TechnicalException.class)
+	@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
+	@ResponseBody
+	public ApiError handleTechnicalException(TechnicalException e) {
+		LOG.error("TechnicalException on Request", e);
+		return buildRuntimeApiError(e.getMessage(), e.getExceptionId());
+	}
+
+	@ExceptionHandler(RuntimeException.class)
+	@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
+	@ResponseBody
+	public ApiError handleRuntimeException(RuntimeException e) {
+		var exceptionId = createExceptionId();
+		var messageWithExceptionId = ExceptionUtil.formatMessageWithExceptionId(e.getMessage(), exceptionId);
+		LOG.error("RuntimeException on Request", messageWithExceptionId);
+		LOG.error("RuntimeException on Request", e);
+		return buildRuntimeApiError(messageWithExceptionId, exceptionId);
+	}
+
+	String createExceptionId() {
+		return UUID.randomUUID().toString();
+	}
+
+	private ApiError buildRuntimeApiError(String message, String exceptionId) {
+		return ApiError.builder().issue(createIssueForRuntimeException(message, exceptionId)).build();
+	}
+
+	private Issue createIssueForRuntimeException(String message, String exceptionId) {
+		return Issue.builder().messageCode(RUNTIME_MESSAGE_CODE).message(message).exceptionId(exceptionId).build();
+	}
+
+	@ExceptionHandler(ServiceUnavailableException.class)
+	@ResponseStatus(HttpStatus.SERVICE_UNAVAILABLE)
+	@ResponseBody
+	public ApiError handleServiceUnavailableException(ServiceUnavailableException exception) {
+		return buildServiceUnavailableApiError(exception);
+	}
+
+	private ApiError buildServiceUnavailableApiError(ServiceUnavailableException exception) {
+		return ApiError.builder().issue(createIssueForServiceUnavailableException(exception)).build();
+	}
+
+	private Issue createIssueForServiceUnavailableException(ServiceUnavailableException exception) {
+		return Issue.builder().messageCode(exception.getMessageCode()).message(exception.getMessage()).build();
+	}
+}
\ No newline at end of file
diff --git a/goofy-server/src/main/java/de/itvsh/goofy/common/errorhandling/FunctionalException.java b/goofy-server/src/main/java/de/itvsh/goofy/common/errorhandling/FunctionalException.java
new file mode 100644
index 0000000000000000000000000000000000000000..49c826c0ad247cb9bb7a5c3b8103ace31a140f78
--- /dev/null
+++ b/goofy-server/src/main/java/de/itvsh/goofy/common/errorhandling/FunctionalException.java
@@ -0,0 +1,59 @@
+/*
+ * 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.itvsh.goofy.common.errorhandling;
+
+import java.util.UUID;
+
+import de.itvsh.kop.common.errorhandling.ExceptionUtil;
+import de.itvsh.kop.common.errorhandling.FunctionalErrorCode;
+import de.itvsh.kop.common.errorhandling.IdentifiableException;
+
+public class FunctionalException extends RuntimeException implements IdentifiableException {
+
+	private static final long serialVersionUID = 1L;
+
+	private final FunctionalErrorCode errorCode;
+	private final String exceptionId;
+
+	public FunctionalException(FunctionalErrorCode errorCode) {
+		super("Functional error: " + errorCode.getErrorCode());
+
+		this.errorCode = errorCode;
+		this.exceptionId = UUID.randomUUID().toString();
+	}
+
+	public String getErrorCode() {
+		return errorCode.getErrorCode();
+	}
+
+	@Override
+	public String getMessage() {
+		return ExceptionUtil.formatMessageWithExceptionId(super.getMessage(), exceptionId);
+	}
+
+	@Override
+	public String getExceptionId() {
+		return exceptionId;
+	}
+}
\ No newline at end of file
diff --git a/goofy-server/src/main/java/de/itvsh/goofy/common/errorhandling/GrpcExceptionController.java b/goofy-server/src/main/java/de/itvsh/goofy/common/errorhandling/GrpcExceptionController.java
new file mode 100644
index 0000000000000000000000000000000000000000..97d5fdd33c9a8b93c3e565094f131aa91dd3ebe2
--- /dev/null
+++ b/goofy-server/src/main/java/de/itvsh/goofy/common/errorhandling/GrpcExceptionController.java
@@ -0,0 +1,216 @@
+/*
+ * 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.itvsh.goofy.common.errorhandling;
+
+import java.util.List;
+import java.util.Objects;
+import java.util.UUID;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
+
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.core.annotation.Order;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.ControllerAdvice;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+
+import de.itvsh.kop.common.errorhandling.ExceptionUtil;
+import de.itvsh.kop.common.errorhandling.FunctionalErrorCode;
+import io.grpc.Metadata;
+import io.grpc.Metadata.Key;
+import io.grpc.Status;
+import io.grpc.StatusRuntimeException;
+import lombok.extern.log4j.Log4j2;
+
+@ControllerAdvice
+@Log4j2
+@Order(98)
+public class GrpcExceptionController {
+
+	static final FunctionalErrorCode GRPC_NOT_FOUND_ERROR_CODE_KEY = () -> ExceptionController.RESOURCE_NOT_FOUNT_MESSAGE_CODE;
+	static final FunctionalErrorCode GRPC_INTERNAL_ERROR_CODE_KEY = () -> ExceptionController.RUNTIME_MESSAGE_CODE;
+	static final FunctionalErrorCode GRPC_PERMISSION_DENIED_CODE_KEY = () -> ExceptionController.ACCESS_DENIED_MESSAGE_CODE;
+
+	static final String KEY_ERROR_CODE = "ERROR_CODE";
+	static final String KEY_EXCEPTION_ID = "EXCEPTION_ID";
+
+	private final Predicate<String> ignoreKey = key -> !isErrorCode(key) && !isExceptionId(key);
+
+	private boolean isErrorCode(String metadataKey) {
+		return isParamKeyEquals(metadataKey, KEY_ERROR_CODE);
+	}
+
+	private boolean isExceptionId(String metadataKey) {
+		return isParamKeyEquals(metadataKey, KEY_EXCEPTION_ID);
+	}
+
+	private boolean isParamKeyEquals(String metaDataKey, String key) {
+		return createMetadataKey(metaDataKey).equals(createMetadataKey(key));
+	}
+
+	@ExceptionHandler(StatusRuntimeException.class)
+	public ResponseEntity<ApiError> handleStatusRuntimeException(StatusRuntimeException e) {
+		if (isNotFoundStatusCode(e)) {
+			return buildNotFoundErrorResponse(e);
+		} else if (isInternalStatusCode(e)) {
+			return buildInternalErrorResponse(e);
+		} else if (isPermissionDeniedCode(e)) {
+			return buildAccessDeniedErrorResponse(e);
+		}
+
+		return buildInternalUnavailableErrorResponse(e);
+	}
+
+	private boolean isNotFoundStatusCode(StatusRuntimeException e) {
+		return e.getStatus().getCode() == Status.NOT_FOUND.getCode();
+	}
+
+	private ResponseEntity<ApiError> buildNotFoundErrorResponse(StatusRuntimeException e) {
+		LOG.error("Grpc not found exception: {}", e.getMessage());
+
+		Issue.IssueBuilder issueBuilder = Issue.builder().message(e.getMessage()).messageCode(GRPC_NOT_FOUND_ERROR_CODE_KEY.getErrorCode());
+		addExceptionId(issueBuilder, e);
+
+		return new ResponseEntity<>(buildApiErrorWithIssue(issueBuilder.build()), HttpStatus.NOT_FOUND);
+	}
+
+	private ResponseEntity<ApiError> buildInternalUnavailableErrorResponse(StatusRuntimeException e) {
+		if (hasExceptionId(e)) {
+			return new ResponseEntity<>(buildInternalUnavailableApiError(e, getExceptionId(e)), HttpStatus.SERVICE_UNAVAILABLE);
+		}
+
+		var exceptionId = createExceptionId();
+		var messageWithExceptionId = ExceptionUtil.formatMessageWithExceptionId(e.getMessage(), exceptionId);
+		LOG.error("Grpc service unavailable: {}", messageWithExceptionId);
+
+		return new ResponseEntity<>(buildInternalUnavailableApiError(e, exceptionId), HttpStatus.SERVICE_UNAVAILABLE);
+	}
+
+	String createExceptionId() {
+		return UUID.randomUUID().toString();
+	}
+
+	private ApiError buildInternalUnavailableApiError(StatusRuntimeException e, String exceptionId) {
+		return ApiError.builder().issue(Issue.builder().message(e.getMessage()).messageCode(getMessageCode(e)).exceptionId(exceptionId).build())
+				.build();
+	}
+
+	private ResponseEntity<ApiError> buildInternalErrorResponse(StatusRuntimeException e) {
+		LOG.error("Grpc internal server error: " + e.getMessage(), e);
+
+		return new ResponseEntity<>(buildInternalApiError(e), HttpStatus.INTERNAL_SERVER_ERROR);
+	}
+
+	private boolean isInternalStatusCode(StatusRuntimeException e) {
+		return e.getStatus().getCode() == Status.INTERNAL.getCode();
+	}
+
+	private ApiError buildInternalApiError(StatusRuntimeException e) {
+		Issue.IssueBuilder issueBuilder = Issue.builder().message(e.getMessage()).messageCode(getMessageCode(e));
+
+		addParameter(issueBuilder, e);
+		addExceptionId(issueBuilder, e);
+
+		return buildApiErrorWithIssue(issueBuilder.build());
+	}
+
+	private String getMessageCode(StatusRuntimeException e) {
+		return hasErrorCode(e) ? getErrorCode(e) : GRPC_INTERNAL_ERROR_CODE_KEY.getErrorCode();
+	}
+
+	private boolean hasErrorCode(StatusRuntimeException e) {
+		return hasParameter(e) && StringUtils.isNotEmpty(getErrorCode(e));
+	}
+
+	private ApiError buildApiErrorWithIssue(Issue issue) {
+		return ApiError.builder().issue(issue).build();
+	}
+
+	private boolean isPermissionDeniedCode(StatusRuntimeException e) {
+		return e.getStatus().getCode() == Status.PERMISSION_DENIED.getCode();
+	}
+
+	private ResponseEntity<ApiError> buildAccessDeniedErrorResponse(StatusRuntimeException e) {
+		var exceptionId = createExceptionId();
+		var messageWithExceptionId = ExceptionUtil.formatMessageWithExceptionId(e.getMessage(), exceptionId);
+		LOG.error("Grpc permission denied error: " + messageWithExceptionId);
+
+		return new ResponseEntity<>(buildAccessDeniedApiError(e, exceptionId), HttpStatus.FORBIDDEN);
+	}
+
+	private ApiError buildAccessDeniedApiError(StatusRuntimeException e, String exceptionId) {
+		return ApiError.builder()
+				.issue(Issue.builder().message(e.getMessage()).messageCode(GRPC_PERMISSION_DENIED_CODE_KEY.getErrorCode()).exceptionId(exceptionId)
+						.build())
+				.build();
+	}
+
+	void addParameter(Issue.IssueBuilder builder, StatusRuntimeException e) {
+		if (hasParameter(e)) {
+			builder.parameters(mapToIssueParams(e.getTrailers()));
+		}
+	}
+
+	List<IssueParam> mapToIssueParams(Metadata metaData) {
+		return metaData.keys().stream()
+				.filter(ignoreKey)
+				.map(key -> buildIssueParam(metaData, key))
+				.collect(Collectors.toList());
+	}
+
+	private IssueParam buildIssueParam(Metadata metaData, String key) {
+		return IssueParam.builder().name(key).value(metaData.get(createMetadataKey(key))).build();
+	}
+
+	void addExceptionId(Issue.IssueBuilder builder, StatusRuntimeException e) {
+		if (hasExceptionId(e)) {
+			builder.exceptionId(getExceptionId(e));
+		}
+	}
+
+	private boolean hasExceptionId(StatusRuntimeException e) {
+		return hasParameter(e) && StringUtils.isNotEmpty(getExceptionId(e));
+	}
+
+	private boolean hasParameter(StatusRuntimeException e) {
+		return Objects.nonNull(e.getTrailers());
+	}
+
+	private String getExceptionId(StatusRuntimeException e) {
+		return getParameter(e, KEY_EXCEPTION_ID);
+	}
+
+	private String getErrorCode(StatusRuntimeException e) {
+		return getParameter(e, KEY_ERROR_CODE);
+	}
+
+	private String getParameter(StatusRuntimeException e, String metadataKey) {
+		return e.getTrailers().get(createMetadataKey(metadataKey));
+	}
+
+	private Key<String> createMetadataKey(String key) {
+		return Key.of(key, Metadata.ASCII_STRING_MARSHALLER);
+	}
+}
\ No newline at end of file
diff --git a/goofy-server/src/main/java/de/itvsh/goofy/common/errorhandling/Issue.java b/goofy-server/src/main/java/de/itvsh/goofy/common/errorhandling/Issue.java
new file mode 100644
index 0000000000000000000000000000000000000000..867bbe07b5dc393fe4037cf3b9b52aebc883cfcf
--- /dev/null
+++ b/goofy-server/src/main/java/de/itvsh/goofy/common/errorhandling/Issue.java
@@ -0,0 +1,56 @@
+/*
+ * 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.itvsh.goofy.common.errorhandling;
+
+import java.util.List;
+
+import lombok.AccessLevel;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Singular;
+
+@Getter
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor(access = AccessLevel.PRIVATE)
+public class Issue {
+
+	private String field;
+	private String messageCode;
+	private String message;
+	private String exceptionId;
+
+	@Singular
+	private List<IssueParam> parameters;
+
+}
+
+@Builder
+@Getter
+class IssueParam {
+	private String name;
+	private String value;
+}
diff --git a/goofy-server/src/main/java/de/itvsh/goofy/common/errorhandling/MessageCode.java b/goofy-server/src/main/java/de/itvsh/goofy/common/errorhandling/MessageCode.java
new file mode 100644
index 0000000000000000000000000000000000000000..c4875de80dd5189737a8896f0e28e52ac3f98349
--- /dev/null
+++ b/goofy-server/src/main/java/de/itvsh/goofy/common/errorhandling/MessageCode.java
@@ -0,0 +1,29 @@
+/*
+ * 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.itvsh.goofy.common.errorhandling;
+
+public class MessageCode {
+
+	public final static String USER_MANAGER_SERVICE_UNAVAILABLE = "general.service_unavailable.usermanager";
+}
diff --git a/goofy-server/src/main/java/de/itvsh/goofy/common/errorhandling/ResourceNotFoundException.java b/goofy-server/src/main/java/de/itvsh/goofy/common/errorhandling/ResourceNotFoundException.java
new file mode 100644
index 0000000000000000000000000000000000000000..593c1b03132831ef8575d6042f42906fcc580d54
--- /dev/null
+++ b/goofy-server/src/main/java/de/itvsh/goofy/common/errorhandling/ResourceNotFoundException.java
@@ -0,0 +1,48 @@
+/*
+ * 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.itvsh.goofy.common.errorhandling;
+
+import de.itvsh.kop.common.errorhandling.FunctionalErrorCode;
+import lombok.Getter;
+
+@Getter
+public class ResourceNotFoundException extends FunctionalException {
+
+	private static final long serialVersionUID = 1L;
+	private static final String MESSAGE_TEMPLATE = "Resource '%s' with id '%s' not found.";
+
+	private final String resourceName;
+	private final transient Object id;
+
+	public ResourceNotFoundException(Class<?> resource, Object id) {
+		super(buildErrorCode(resource, id));
+
+		this.resourceName = resource.getSimpleName();
+		this.id = id;
+	}
+
+	private static FunctionalErrorCode buildErrorCode(Class<?> resource, Object id) {
+		return () -> String.format(MESSAGE_TEMPLATE, resource.getSimpleName(), id.toString());
+	}
+}
\ No newline at end of file
diff --git a/goofy-server/src/main/java/de/itvsh/goofy/common/errorhandling/ServiceUnavailableException.java b/goofy-server/src/main/java/de/itvsh/goofy/common/errorhandling/ServiceUnavailableException.java
new file mode 100644
index 0000000000000000000000000000000000000000..5018f491388ef3ebe2f106bcf8e37d81d2ada651
--- /dev/null
+++ b/goofy-server/src/main/java/de/itvsh/goofy/common/errorhandling/ServiceUnavailableException.java
@@ -0,0 +1,40 @@
+/*
+ * 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.itvsh.goofy.common.errorhandling;
+
+import lombok.Getter;
+
+public class ServiceUnavailableException extends RuntimeException {
+
+	private static final long serialVersionUID = 1L;
+
+	@Getter
+	private String messageCode;
+
+	public ServiceUnavailableException(String messageCode, Throwable throwable) {
+		super("Service Unavailable", throwable);
+
+		this.messageCode = messageCode;
+	}
+}
\ No newline at end of file
diff --git a/goofy-server/src/main/java/de/itvsh/goofy/common/file/OzgFile.java b/goofy-server/src/main/java/de/itvsh/goofy/common/file/OzgFile.java
new file mode 100644
index 0000000000000000000000000000000000000000..a9523ee7eb1e01f4b86829686196ec8f64190bb2
--- /dev/null
+++ b/goofy-server/src/main/java/de/itvsh/goofy/common/file/OzgFile.java
@@ -0,0 +1,47 @@
+/*
+ * 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.itvsh.goofy.common.file;
+
+import com.fasterxml.jackson.annotation.JsonIgnore;
+
+import de.itvsh.goofy.common.binaryfile.FileId;
+import lombok.AccessLevel;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+
+@Builder
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+@AllArgsConstructor(access = AccessLevel.PACKAGE)
+@Getter
+public class OzgFile {
+
+	@JsonIgnore
+	private FileId id;
+	private String name;
+	private long size;
+
+	private String contentType;
+}
\ No newline at end of file
diff --git a/goofy-server/src/main/java/de/itvsh/goofy/common/file/OzgFileMapper.java b/goofy-server/src/main/java/de/itvsh/goofy/common/file/OzgFileMapper.java
new file mode 100644
index 0000000000000000000000000000000000000000..d893991f0a0f32fc4ae08cff52f5ea5bb781a0b6
--- /dev/null
+++ b/goofy-server/src/main/java/de/itvsh/goofy/common/file/OzgFileMapper.java
@@ -0,0 +1,39 @@
+/*
+ * 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.itvsh.goofy.common.file;
+
+import org.mapstruct.Mapper;
+
+import de.itvsh.goofy.common.binaryfile.FileId;
+import de.itvsh.ozg.pluto.grpc.file.GrpcOzgFile;
+
+@Mapper
+public interface OzgFileMapper {
+
+	OzgFile toFile(GrpcOzgFile file);
+
+	default FileId toFileId(String id) {
+		return FileId.from(id);
+	}
+}
\ No newline at end of file
diff --git a/goofy-server/src/main/java/de/itvsh/goofy/common/file/OzgFileRemoteService.java b/goofy-server/src/main/java/de/itvsh/goofy/common/file/OzgFileRemoteService.java
new file mode 100644
index 0000000000000000000000000000000000000000..7459a492f0b29b7c6ae832861165b5a730fd4a23
--- /dev/null
+++ b/goofy-server/src/main/java/de/itvsh/goofy/common/file/OzgFileRemoteService.java
@@ -0,0 +1,72 @@
+/*
+ * 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.itvsh.goofy.common.file;
+
+import java.util.stream.Stream;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import de.itvsh.goofy.common.callcontext.ContextService;
+import de.itvsh.ozg.pluto.grpc.file.FileServiceGrpc.FileServiceBlockingStub;
+import de.itvsh.ozg.pluto.grpc.file.GrpcGetAttachmentsRequest;
+import de.itvsh.ozg.pluto.grpc.file.GrpcGetRepresentationsRequest;
+import net.devh.boot.grpc.client.inject.GrpcClient;
+
+@Service
+public class OzgFileRemoteService {
+
+	@GrpcClient("pluto")
+	private FileServiceBlockingStub fileServiceStub;
+	@Autowired
+	private ContextService contextService;
+	@Autowired
+	private OzgFileMapper fileMapper;
+
+	public Stream<OzgFile> getAttachmentsByEingang(String eingangId) {
+		var response = fileServiceStub.getAttachments(buildGrpcGetAttachmentsRequest(eingangId));
+
+		return response.getFileList().stream().map(fileMapper::toFile);
+	}
+
+	private GrpcGetAttachmentsRequest buildGrpcGetAttachmentsRequest(String eingangId) {
+		return GrpcGetAttachmentsRequest.newBuilder()
+				.setContext(contextService.createCallContext())
+				.setEingangId(eingangId)
+				.build();
+	}
+
+	public Stream<OzgFile> getRepresentationsByEingang(String eingangId) {
+		var response = fileServiceStub.getRepresentations(buildGrpcGetRepresentationsRequest(eingangId));
+
+		return response.getFileList().stream().map(fileMapper::toFile);
+	}
+
+	private GrpcGetRepresentationsRequest buildGrpcGetRepresentationsRequest(String eingangId) {
+		return GrpcGetRepresentationsRequest.newBuilder()
+				.setContext(contextService.createCallContext())
+				.setEingangId(eingangId)
+				.build();
+	}
+}
\ No newline at end of file
diff --git a/goofy-server/src/main/java/de/itvsh/goofy/common/file/OzgFileService.java b/goofy-server/src/main/java/de/itvsh/goofy/common/file/OzgFileService.java
new file mode 100644
index 0000000000000000000000000000000000000000..93d0e94db5d83905a27e3e50e841ab17bb75834a
--- /dev/null
+++ b/goofy-server/src/main/java/de/itvsh/goofy/common/file/OzgFileService.java
@@ -0,0 +1,44 @@
+/*
+ * 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.itvsh.goofy.common.file;
+
+import java.util.stream.Stream;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+@Service
+public class OzgFileService {
+
+	@Autowired
+	private OzgFileRemoteService remoteService;
+
+	public Stream<OzgFile> getAttachmentsByEingang(String eingangId) {
+		return remoteService.getAttachmentsByEingang(eingangId);
+	}
+
+	public Stream<OzgFile> getRepresentationsByEingang(String eingangId) {
+		return remoteService.getRepresentationsByEingang(eingangId);
+	}
+}
\ No newline at end of file
diff --git a/goofy-server/src/main/java/de/itvsh/goofy/common/user/CurrentUserHelper.java b/goofy-server/src/main/java/de/itvsh/goofy/common/user/CurrentUserHelper.java
new file mode 100644
index 0000000000000000000000000000000000000000..44732514a548bb98b5d679dce430864485604ef3
--- /dev/null
+++ b/goofy-server/src/main/java/de/itvsh/goofy/common/user/CurrentUserHelper.java
@@ -0,0 +1,106 @@
+/*
+ * 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.itvsh.goofy.common.user;
+
+import java.util.Collection;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.function.Predicate;
+
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.security.authentication.AuthenticationTrustResolver;
+import org.springframework.security.authentication.AuthenticationTrustResolverImpl;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.context.SecurityContextHolder;
+
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public class CurrentUserHelper {
+	static final String ROLE_PREFIX = "ROLE_";
+
+	public static final Predicate<String> HAS_ROLE = CurrentUserHelper::hasRole;
+
+	private static final AuthenticationTrustResolver TRUST_RESOLVER = new AuthenticationTrustResolverImpl();
+	private static final Predicate<Authentication> TRUSTED = auth -> !TRUST_RESOLVER.isAnonymous(auth);
+
+	public static Authentication getAuthentication() {
+		return findAuthentication().orElseThrow(() -> new IllegalStateException("No authenticated User found"));
+	}
+
+	public static Optional<Authentication> findAuthentication() {
+		return Optional.ofNullable(SecurityContextHolder.getContext().getAuthentication()).filter(TRUSTED);
+	}
+
+	public static boolean hasRole(String role) {
+		var auth = getAuthentication();
+
+		if ((Objects.isNull(auth)) || (Objects.isNull(auth.getPrincipal()))) {
+			return false;
+		}
+		Collection<? extends GrantedAuthority> authorities = auth.getAuthorities();
+		return containsRole(authorities, role);
+
+	}
+
+	public static boolean containsRole(Collection<? extends GrantedAuthority> authorities, String role) {
+		String roleToCheck;
+
+		if (Objects.nonNull(role) && !role.startsWith(ROLE_PREFIX)) {
+			roleToCheck = ROLE_PREFIX + role;
+		} else {
+			roleToCheck = role;
+		}
+
+		if (Objects.isNull(authorities)) {
+			return false;
+		}
+
+		return containsRole(roleToCheck, authorities);
+	}
+
+	public static String prepareRoleForCheck(String role) {
+		if ((Objects.nonNull(role)) && (!role.startsWith(ROLE_PREFIX))) {
+			return ROLE_PREFIX + role;
+		} else {
+			return role;
+		}
+	}
+
+	public static boolean containsRole(String role, Collection<? extends GrantedAuthority> authorities) {
+		return authorities.stream().anyMatch(a -> isAuthorityEquals(role, a.getAuthority()));
+	}
+
+	private static boolean isAuthorityEquals(String role, String authority) {
+		String roleToCheck = prepareRoleForCheck(role);
+		return StringUtils.equalsIgnoreCase(role, authority) || StringUtils.equalsIgnoreCase(roleToCheck, authority);
+	}
+
+	public static UserId getCurrentUserId() {
+		return UserId.from(getAuthentication().getName());
+	}
+
+}
diff --git a/goofy-server/src/main/java/de/itvsh/goofy/common/user/CurrentUserService.java b/goofy-server/src/main/java/de/itvsh/goofy/common/user/CurrentUserService.java
new file mode 100644
index 0000000000000000000000000000000000000000..d1692e4371ca85fbb6778655cf4befe325a81c86
--- /dev/null
+++ b/goofy-server/src/main/java/de/itvsh/goofy/common/user/CurrentUserService.java
@@ -0,0 +1,103 @@
+/*
+ * 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.itvsh.goofy.common.user;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.stream.Collectors;
+
+import org.keycloak.KeycloakPrincipal;
+import org.keycloak.representations.AccessToken;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.stereotype.Service;
+
+import de.itvsh.goofy.common.binaryfile.GoofyUserWithFileId;
+
+@Service
+public class CurrentUserService {
+
+	public static final String VERIFY_HAS_ROLE_VERWALTUNG_USER = "@currentUserService.hasRole(\"" + UserRole.VERWALTUNG_USER + "\")";
+
+	static final String USER_ATTRIBUTE_ORGANISATIONSEINHEIT_ID = "organisationseinheitId";
+
+	public boolean hasRole(String role) {
+		return CurrentUserHelper.hasRole(role);
+	}
+
+	public Collection<GrantedAuthority> getAuthorities() {
+		return Collections.unmodifiableCollection(new HashSet<GrantedAuthority>(CurrentUserHelper.getAuthentication().getAuthorities()));
+	}
+
+	public UserProfile getUser() {
+		var dlUser = getDownloadUser();
+		if (dlUser.isPresent()) {
+			return dlUser.get();
+		}
+
+		Optional<AccessToken> token = getCurrentSecurityToken();
+
+		var userBuilder = UserProfile.builder()
+				.id(getUserId())
+				.authorities(getAuthorities());
+
+		token.ifPresent(t -> userBuilder.userName(t.getPreferredUsername())
+				.firstName(t.getGivenName())
+				.lastName(t.getFamilyName())
+				.organisationseinheitIds(getOrganisationseinheitId(t.getOtherClaims())));
+
+		return userBuilder.build();
+	}
+
+	public UserId getUserId() {
+		return CurrentUserHelper.getCurrentUserId();
+	}
+
+	List<String> getOrganisationseinheitId(Map<String, Object> claims) {
+		return Optional.ofNullable(claims.get(USER_ATTRIBUTE_ORGANISATIONSEINHEIT_ID))
+				.map(col -> (Collection<?>) col).orElse(Collections.emptyList()) // NOSONAR - Collection.class::cast has type-safty issue
+				.stream().map(Object::toString).collect(Collectors.toList());
+	}
+
+	private Optional<UserProfile> getDownloadUser() {
+		return Optional.of(CurrentUserHelper.getAuthentication().getPrincipal())
+				.filter(GoofyUserWithFileId.class::isInstance)
+				.map(GoofyUserWithFileId.class::cast)
+				.map(GoofyUserWithFileId::getUser);
+
+	}
+
+	private Optional<AccessToken> getCurrentSecurityToken() {
+		Object principal = CurrentUserHelper.getAuthentication().getPrincipal();
+
+		if (principal instanceof KeycloakPrincipal<?> kcPrincipal) {
+			return Optional.of(kcPrincipal.getKeycloakSecurityContext().getToken());
+		}
+
+		return Optional.empty();
+	}
+}
\ No newline at end of file
diff --git a/goofy-server/src/main/java/de/itvsh/goofy/common/user/UserId.java b/goofy-server/src/main/java/de/itvsh/goofy/common/user/UserId.java
new file mode 100644
index 0000000000000000000000000000000000000000..dcdd11eec3a4239cee505bb4d077c9e99adeb284
--- /dev/null
+++ b/goofy-server/src/main/java/de/itvsh/goofy/common/user/UserId.java
@@ -0,0 +1,51 @@
+/*
+ * 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.itvsh.goofy.common.user;
+
+import java.util.UUID;
+
+import de.itvsh.kop.common.datatype.StringBasedValue;
+import lombok.AccessLevel;
+import lombok.EqualsAndHashCode;
+import lombok.NoArgsConstructor;
+
+@NoArgsConstructor(access = AccessLevel.PACKAGE)
+@EqualsAndHashCode(callSuper = true)
+public class UserId extends StringBasedValue {
+
+	private static final long serialVersionUID = 1L;
+
+	UserId(String userId) {
+		super(userId);
+	}
+
+	public static UserId from(UUID userId) {
+		return UserId.from(userId.toString());
+	}
+
+	public static UserId from(String userId) {
+		return new UserId(userId);
+	}
+
+}
diff --git a/goofy-server/src/main/java/de/itvsh/goofy/common/user/UserIdMapper.java b/goofy-server/src/main/java/de/itvsh/goofy/common/user/UserIdMapper.java
new file mode 100644
index 0000000000000000000000000000000000000000..bd529d8262b0f3f011d20ee2aa775e4a12cf6ca6
--- /dev/null
+++ b/goofy-server/src/main/java/de/itvsh/goofy/common/user/UserIdMapper.java
@@ -0,0 +1,41 @@
+/*
+ * 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.itvsh.goofy.common.user;
+
+import java.util.Optional;
+
+import org.apache.commons.lang3.StringUtils;
+import org.mapstruct.Mapper;
+
+@Mapper
+public interface UserIdMapper {
+
+	default UserId fromString(String userId) {
+		return Optional.ofNullable(StringUtils.trimToNull(userId)).map(UserId::from).orElse(null);
+	}
+
+	default String toString(UserId userId) {
+		return userId == null ? null : userId.toString();
+	}
+}
diff --git a/goofy-server/src/main/java/de/itvsh/goofy/common/user/UserManagerProperties.java b/goofy-server/src/main/java/de/itvsh/goofy/common/user/UserManagerProperties.java
new file mode 100644
index 0000000000000000000000000000000000000000..54e219971217beb07fcec61a25dfdb74c559b9d8
--- /dev/null
+++ b/goofy-server/src/main/java/de/itvsh/goofy/common/user/UserManagerProperties.java
@@ -0,0 +1,50 @@
+/*
+ * 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.itvsh.goofy.common.user;
+
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.context.annotation.Configuration;
+
+import lombok.Getter;
+import lombok.Setter;
+
+@Getter
+@Setter
+@Configuration
+@ConfigurationProperties(UserManagerProperties.PREFIX)
+public class UserManagerProperties {
+
+	static final String PREFIX = "kop.user-manager";
+
+	private static final String MIGRATION_PATH = "/migration/user/{externalUserId}"; // NOSONAR
+
+	private String url;
+	private String profileTemplate;
+	private String searchTemplate;
+	private String internalurl;
+
+	String getFullInternalUrlTemplate() {
+		return internalurl + MIGRATION_PATH;
+	}
+}
\ No newline at end of file
diff --git a/goofy-server/src/main/java/de/itvsh/goofy/common/user/UserManagerUrlProvider.java b/goofy-server/src/main/java/de/itvsh/goofy/common/user/UserManagerUrlProvider.java
new file mode 100644
index 0000000000000000000000000000000000000000..e6902afed4a132a89ab6b9cc25359002bf30c010
--- /dev/null
+++ b/goofy-server/src/main/java/de/itvsh/goofy/common/user/UserManagerUrlProvider.java
@@ -0,0 +1,49 @@
+package de.itvsh.goofy.common.user;
+
+import java.util.Objects;
+import java.util.Optional;
+import java.util.function.Predicate;
+
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import de.itvsh.goofy.postfach.PostfachMail;
+
+@Service
+public class UserManagerUrlProvider {
+
+	public static final String SYSTEM_USER_IDENTIFIER = "system";
+	public static final Predicate<PostfachMail> SENT_BY_CLIENT_USER = postfachNachricht -> Optional.ofNullable(postfachNachricht.getCreatedBy())
+			.map(createdBy -> !createdBy.toString().startsWith(SYSTEM_USER_IDENTIFIER)).orElse(false);
+
+	@Autowired
+	private UserManagerProperties userManagerProperties;
+
+	public String getUserProfileTemplate() {
+		return userManagerProperties.getUrl() + userManagerProperties.getProfileTemplate();
+	}
+
+	public String getUserProfileSearchTemplate() {
+		return userManagerProperties.getUrl() + userManagerProperties.getSearchTemplate();
+	}
+
+	public String getInternalUserIdTemplate() {
+		return userManagerProperties.getFullInternalUrlTemplate();
+	}
+
+	public boolean isConfiguredForUserProfile() {
+		return Objects.nonNull(StringUtils.trimToNull(userManagerProperties.getUrl()))
+				&& Objects.nonNull(StringUtils.trimToNull(userManagerProperties.getProfileTemplate()));
+	}
+
+	public boolean isConfiguredForSearchUserProfile() {
+		return Objects.nonNull(StringUtils.trimToNull(userManagerProperties.getUrl()))
+				&& Objects.nonNull(StringUtils.trimToNull(userManagerProperties.getSearchTemplate()));
+	}
+
+	public boolean isConfiguredForInternalUserId() {
+		return Objects.nonNull(StringUtils.trimToNull(userManagerProperties.getInternalurl()));
+	}
+
+}
diff --git a/goofy-server/src/main/java/de/itvsh/goofy/common/user/UserProfile.java b/goofy-server/src/main/java/de/itvsh/goofy/common/user/UserProfile.java
new file mode 100644
index 0000000000000000000000000000000000000000..d53b318add4bda1b9af0ec95f4a880c1cbe68f95
--- /dev/null
+++ b/goofy-server/src/main/java/de/itvsh/goofy/common/user/UserProfile.java
@@ -0,0 +1,58 @@
+/*
+ * 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.itvsh.goofy.common.user;
+
+import java.util.Collection;
+import java.util.Date;
+
+import org.springframework.security.core.GrantedAuthority;
+
+import com.fasterxml.jackson.annotation.JsonIgnore;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Getter;
+import lombok.Singular;
+
+@Builder
+@Getter
+@AllArgsConstructor
+public class UserProfile {
+
+	@JsonIgnore
+	private UserId id;
+	private String userName;
+	private String firstName;
+	private String lastName;
+	private Date createdAt;
+
+	@Singular
+	private Collection<GrantedAuthority> authorities;
+	@Singular
+	private Collection<String> organisationseinheitIds;
+
+	public String getFullName() {
+		return String.format("%s %s", firstName, lastName);
+	}
+}
diff --git a/goofy-server/src/main/java/de/itvsh/goofy/common/user/UserRemoteService.java b/goofy-server/src/main/java/de/itvsh/goofy/common/user/UserRemoteService.java
new file mode 100644
index 0000000000000000000000000000000000000000..442632535cbd649159c0ec789b53b824e80906aa
--- /dev/null
+++ b/goofy-server/src/main/java/de/itvsh/goofy/common/user/UserRemoteService.java
@@ -0,0 +1,131 @@
+/*
+ * 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.itvsh.goofy.common.user;
+
+import java.util.LinkedHashMap;
+import java.util.Optional;
+import java.util.function.Supplier;
+
+import org.apache.commons.lang3.StringUtils;
+import org.keycloak.KeycloakPrincipal;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.HttpEntity;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpMethod;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.stereotype.Component;
+import org.springframework.web.client.HttpClientErrorException;
+import org.springframework.web.client.RestClientException;
+import org.springframework.web.client.RestTemplate;
+import org.springframework.web.util.UriComponentsBuilder;
+
+import de.itvsh.goofy.common.errorhandling.MessageCode;
+import de.itvsh.goofy.common.errorhandling.ServiceUnavailableException;
+import lombok.extern.log4j.Log4j2;
+
+@Log4j2
+@Component
+public class UserRemoteService {
+
+	static final String FIRST_NAME_KEY = "firstName";
+	static final String LAST_NAME_KEY = "lastName";
+
+	@Autowired
+	private UserManagerProperties userManagerProperties;
+	@Autowired
+	private UserManagerUrlProvider userManagerUrlProvider;
+
+	private RestTemplate restTemplate = new RestTemplate();
+
+	public Optional<UserId> getUserId(UserId externalUserId) {
+		try {
+			if (userManagerUrlProvider.isConfiguredForInternalUserId()) {
+				var internalId = restTemplate.getForObject(userManagerProperties.getFullInternalUrlTemplate(), String.class,
+						externalUserId.toString());
+				return StringUtils.isNotEmpty(internalId) ? Optional.of(UserId.from(internalId)) : Optional.empty();
+			} else {
+				return Optional.empty();
+			}
+		} catch (RestClientException e) {
+			LOG.warn("Error loading internal Userid.", e);
+			return Optional.empty();
+		}
+	}
+
+	public UserProfile getUser(UserId userId) {
+		return executeHandlingException(() -> getUserById(userId));
+	}
+
+	private <T> T executeHandlingException(Supplier<T> runnable) {
+		try {
+			return runnable.get();
+		} catch (HttpClientErrorException e) {
+			if (e.getStatusCode() == HttpStatus.NOT_FOUND) {
+				return null;
+			}
+			LOG.error("HttpClientErrorException: Error getting User by id.", e);
+			throw new ServiceUnavailableException(MessageCode.USER_MANAGER_SERVICE_UNAVAILABLE, e);
+		} catch (IllegalArgumentException e) {
+			LOG.error("IllegalArgumentException: Error getting User by id.", e);
+			throw new ServiceUnavailableException(MessageCode.USER_MANAGER_SERVICE_UNAVAILABLE, e);
+		}
+	}
+
+	private UserProfile getUserById(UserId userId) {
+		return buildUser(getBodyMap(doExchange(userId)));
+	}
+
+	private ResponseEntity<Object> doExchange(UserId userId) {
+		return restTemplate.exchange(buildUserProfileUri(userId), HttpMethod.GET, buildHttpEntityWithAuthorization(), Object.class);
+	}
+
+	String buildUserProfileUri(UserId userId) {
+		return UriComponentsBuilder.fromUriString(String.format(userManagerUrlProvider.getInternalUserIdTemplate(), userId.toString())).toUriString();
+	}
+
+	private HttpEntity<Object> buildHttpEntityWithAuthorization() {
+		var headers = new HttpHeaders();
+		headers.add("Authorization", "Bearer " + getToken());
+		return new HttpEntity<>(headers);
+	}
+
+	String getToken() {
+		var principle = (KeycloakPrincipal<?>) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
+		return principle.getKeycloakSecurityContext().getTokenString();
+	}
+
+	@SuppressWarnings("unchecked")
+	<T> LinkedHashMap<String, Object> getBodyMap(ResponseEntity<T> responseEntity) {
+		return (LinkedHashMap<String, Object>) responseEntity.getBody();
+	}
+
+	UserProfile buildUser(LinkedHashMap<String, Object> bodyMap) {
+		return UserProfile.builder()
+				.firstName((String) bodyMap.getOrDefault(FIRST_NAME_KEY, StringUtils.EMPTY))
+				.lastName((String) bodyMap.getOrDefault(LAST_NAME_KEY, StringUtils.EMPTY))
+				.build();
+	}
+}
diff --git a/goofy-server/src/main/java/de/itvsh/goofy/common/user/UserRole.java b/goofy-server/src/main/java/de/itvsh/goofy/common/user/UserRole.java
new file mode 100644
index 0000000000000000000000000000000000000000..7f60273540b4aae49bc7d35ec2d0245da17fb976
--- /dev/null
+++ b/goofy-server/src/main/java/de/itvsh/goofy/common/user/UserRole.java
@@ -0,0 +1,35 @@
+/*
+ * 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.itvsh.goofy.common.user;
+
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public class UserRole {
+
+	public static final String EINHEITLICHER_ANSPRECHPARTNER = "EINHEITLICHER_ANSPRECHPARTNER";
+	public static final String VERWALTUNG_POSTSTELLE = "VERWALTUNG_POSTSTELLE";
+	public static final String VERWALTUNG_USER = "VERWALTUNG_USER";
+}
diff --git a/goofy-server/src/main/java/de/itvsh/goofy/common/user/UserService.java b/goofy-server/src/main/java/de/itvsh/goofy/common/user/UserService.java
new file mode 100644
index 0000000000000000000000000000000000000000..4c970317e1433ca620e90cb9c83971987b1bdde6
--- /dev/null
+++ b/goofy-server/src/main/java/de/itvsh/goofy/common/user/UserService.java
@@ -0,0 +1,38 @@
+/*
+ * 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.itvsh.goofy.common.user;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+@Service
+public class UserService {
+
+	@Autowired
+	private UserRemoteService remoteService;
+
+	public UserProfile getById(UserId userId) {
+		return remoteService.getUser(userId);
+	}
+}
\ No newline at end of file
diff --git a/goofy-server/src/main/java/de/itvsh/goofy/historie/HistorieCommandHandler.java b/goofy-server/src/main/java/de/itvsh/goofy/historie/HistorieCommandHandler.java
new file mode 100644
index 0000000000000000000000000000000000000000..76e1d9af249bc2a45cd7dda5cec3c294ff307031
--- /dev/null
+++ b/goofy-server/src/main/java/de/itvsh/goofy/historie/HistorieCommandHandler.java
@@ -0,0 +1,150 @@
+/*
+ * 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.itvsh.goofy.historie;
+
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+
+import org.springframework.stereotype.Component;
+
+import de.itvsh.goofy.common.command.Command;
+import de.itvsh.goofy.common.command.CommandBodyMapper;
+import de.itvsh.goofy.common.command.CommandOrder;
+import de.itvsh.goofy.kommentar.Kommentar;
+import de.itvsh.goofy.wiedervorlage.Wiedervorlage;
+
+@Component
+class HistorieCommandHandler {
+
+	static final String DIRECTION_INCOMMING = "IN";
+	static final String DIRECTION_OUTGOING = "OUT";
+	static final String DIRECTION = "direction";
+	static final String MAIL_SERVICE = "MailService";
+	static final String CLIENT = "client";
+
+	boolean isHistorieCommand(Command command) {
+		return !isOutgoingPostfachNachrichtByMailService(command);
+	}
+
+	boolean isOutgoingPostfachNachrichtByMailService(Command command) {
+		return command.getOrder().equals(CommandOrder.CREATE_ATTACHED_ITEM) && isMailService(command) && isOutgoing(command);
+	}
+
+	private boolean isMailService(Command command) {
+		var client = command.getBody().get(CLIENT);
+		return Objects.nonNull(client) && ((String) client).equals(MAIL_SERVICE);
+	}
+
+	private boolean isOutgoing(Command command) {
+		return isDirection(command, DIRECTION_OUTGOING);
+	}
+
+	private boolean isIncomming(Command command) {
+		return isDirection(command, DIRECTION_INCOMMING);
+	}
+
+	private boolean isDirection(Command command, String expectedDirection) {
+		var direction = getItemMap(command).get(DIRECTION);
+		return Objects.nonNull(direction) && ((String) direction).equals(expectedDirection);
+	}
+
+	Command translateOrder(Command command) {
+		HistorieCommandHandler translator = new HistorieCommandHandler();
+		return switch (command.getOrder()) {
+			case CREATE_ATTACHED_ITEM:
+				yield translator.mapCreateOrder(command);
+			case UPDATE_ATTACHED_ITEM:
+				yield translator.mapUpdateOrder(command);
+			case PATCH_ATTACHED_ITEM:
+				yield translator.mapPatchOrder(command);
+			case SEND_POSTFACH_MAIL:
+				yield command.toBuilder().order(CommandOrder.SEND_POSTFACH_NACHRICHT).build();
+			default:
+				yield command;
+		};
+	}
+
+	private Command mapCreateOrder(Command command) {
+		var resultBuilder = command.toBuilder();
+		var itemName = getItemName(command);
+
+		itemName.ifPresent(name -> {
+			if (name.equals(Kommentar.class.getSimpleName())) {
+				resultBuilder.order(CommandOrder.CREATE_KOMMENTAR).build();
+			} else if (name.equals(Wiedervorlage.class.getSimpleName())) {
+				resultBuilder.order(CommandOrder.CREATE_WIEDERVORLAGE).build();
+			} else if (isMailService(command) && isIncomming(command)) {
+				resultBuilder.order(CommandOrder.RECEIVE_POSTFACH_NACHRICHT).build();
+			}
+		});
+
+		return resultBuilder.build();
+	}
+
+	private Command mapUpdateOrder(Command command) {
+		var resultBuilder = command.toBuilder();
+		var itemName = getItemName(command);
+
+		itemName.ifPresent(name -> {
+			if (name.equals(Kommentar.class.getSimpleName())) {
+				resultBuilder.order(CommandOrder.EDIT_KOMMENTAR).build();
+			} else if (name.equals(Wiedervorlage.class.getSimpleName())) {
+				resultBuilder.order(CommandOrder.EDIT_WIEDERVORLAGE).build();
+			}
+		});
+
+		return resultBuilder.build();
+	}
+
+	private Command mapPatchOrder(Command command) {
+		var resultBuilder = command.toBuilder();
+		var isDone = getDoneValue(command);
+
+		isDone.ifPresent(done -> {
+			if (done.booleanValue()) {
+				resultBuilder.order(CommandOrder.WIEDERVORLAGE_ERLEDIGEN).build();
+			} else {
+				resultBuilder.order(CommandOrder.WIEDERVORLAGE_WIEDEREROEFFNEN).build();
+			}
+		});
+
+		return resultBuilder.build();
+	}
+
+	private Optional<String> getItemName(Command command) {
+		return Optional.ofNullable((String) command.getBody().get(CommandBodyMapper.ITEM_NAME_PROPERTY));
+	}
+
+	private Optional<Boolean> getDoneValue(Command command) {
+		var body = getItemMap(command);
+		return body.containsKey("done") ? Optional.of(Boolean.valueOf(String.valueOf(body.get("done")))) : Optional.empty();
+	}
+
+	@SuppressWarnings("unchecked")
+	private Map<String, Object> getItemMap(Command command) {
+		return (Map<String, Object>) command.getBody().get(CommandBodyMapper.ITEM_PROPERTY);
+	}
+
+}
diff --git a/goofy-server/src/main/java/de/itvsh/goofy/historie/HistorieController.java b/goofy-server/src/main/java/de/itvsh/goofy/historie/HistorieController.java
new file mode 100644
index 0000000000000000000000000000000000000000..af4f7cca062e09bb3eacb3b4cac42c452f9822ff
--- /dev/null
+++ b/goofy-server/src/main/java/de/itvsh/goofy/historie/HistorieController.java
@@ -0,0 +1,54 @@
+/*
+ * 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.itvsh.goofy.historie;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.hateoas.CollectionModel;
+import org.springframework.hateoas.EntityModel;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import de.itvsh.goofy.common.command.Command;
+
+@RestController
+@RequestMapping(HistorieController.PATH)
+public class HistorieController {
+	static final String PATH = "/api/histories"; // NOSONAR
+	static final String PARAM_VORGANG_ID = "vorgangId";
+
+	@Autowired
+	private HistorieService historieService;
+
+	@Autowired
+	private HistorieModelAssembler modelAssembler;
+
+	@GetMapping(params = PARAM_VORGANG_ID)
+	public CollectionModel<EntityModel<Command>> getHistorieList(@RequestParam String vorgangId) {
+		return modelAssembler
+				.toCollectionModel(historieService.findFinishedCommands(vorgangId));
+	}
+
+}
diff --git a/goofy-server/src/main/java/de/itvsh/goofy/historie/HistorieModelAssembler.java b/goofy-server/src/main/java/de/itvsh/goofy/historie/HistorieModelAssembler.java
new file mode 100644
index 0000000000000000000000000000000000000000..c224c02c4c5f2cf8303cf3ba8d8ae2c91651d9d3
--- /dev/null
+++ b/goofy-server/src/main/java/de/itvsh/goofy/historie/HistorieModelAssembler.java
@@ -0,0 +1,88 @@
+/*
+ * 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.itvsh.goofy.historie;
+
+import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.*;
+
+import java.util.Objects;
+import java.util.Optional;
+import java.util.stream.Stream;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.hateoas.CollectionModel;
+import org.springframework.hateoas.EntityModel;
+import org.springframework.hateoas.Link;
+import org.springframework.hateoas.server.RepresentationModelAssembler;
+import org.springframework.stereotype.Component;
+
+import de.itvsh.goofy.common.ModelBuilder;
+import de.itvsh.goofy.common.command.Command;
+import de.itvsh.goofy.common.user.UserManagerUrlProvider;
+
+@Component
+class HistorieModelAssembler implements RepresentationModelAssembler<Command, EntityModel<Command>> {
+	static final String SYSTEM_NOTIFICATION_MANAGER_PREFIX = "system-notification_manager";
+
+	static final String REL_ASSIGNED_TO = "assignedTo";
+
+	static final String ASSIGNED_TO_BODY_FIELD = "assignedTo";
+
+	@Autowired
+	private UserManagerUrlProvider userManagerUrlProvider;
+
+	@Override
+	public EntityModel<Command> toModel(Command entity) {
+		var cleanCommand = unsetSystemUser(entity);
+		var modelBuilder = ModelBuilder.fromEntity(cleanCommand).addLink(linkTo(HistorieController.class).slash(cleanCommand.getId()).withSelfRel());
+
+		addAssignedTo(cleanCommand, modelBuilder);
+
+		return modelBuilder.buildModel();
+	}
+
+	Command unsetSystemUser(Command entity) {
+		if (isSystemNotificationUser(entity)) {
+			return entity.toBuilder().createdBy(null).build();
+		}
+
+		return entity;
+	}
+
+	private boolean isSystemNotificationUser(Command entity) {
+		return Objects.nonNull(entity.getCreatedBy()) && entity.getCreatedBy().toString().startsWith(SYSTEM_NOTIFICATION_MANAGER_PREFIX);
+	}
+
+	private void addAssignedTo(Command entity, ModelBuilder<Command> modelBuilder) {
+		Optional.ofNullable(entity.getBody()).map(body -> body.get(ASSIGNED_TO_BODY_FIELD))
+				.ifPresent(assignedTo -> {
+					if (userManagerUrlProvider.isConfiguredForUserProfile()) {
+						modelBuilder.addLink(Link.of(String.format(userManagerUrlProvider.getUserProfileTemplate(), assignedTo), REL_ASSIGNED_TO));
+					}
+				});
+	}
+
+	public CollectionModel<EntityModel<Command>> toCollectionModel(Stream<Command> entities) {
+		return CollectionModel.of(entities.map(this::toModel).toList(), linkTo(HistorieController.class).withSelfRel());
+	}
+}
\ No newline at end of file
diff --git a/goofy-server/src/main/java/de/itvsh/goofy/historie/HistorieService.java b/goofy-server/src/main/java/de/itvsh/goofy/historie/HistorieService.java
new file mode 100644
index 0000000000000000000000000000000000000000..f3a8aef9aa3d8f4376481210f771b249a2fc4263
--- /dev/null
+++ b/goofy-server/src/main/java/de/itvsh/goofy/historie/HistorieService.java
@@ -0,0 +1,48 @@
+/*
+ * 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.itvsh.goofy.historie;
+
+import java.util.stream.Stream;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import de.itvsh.goofy.common.command.Command;
+import de.itvsh.goofy.common.command.CommandService;
+
+@Service
+class HistorieService {
+
+	@Autowired
+	private CommandService commandService;
+
+	@Autowired
+	private HistorieCommandHandler historieCommandHandler;
+
+	public Stream<Command> findFinishedCommands(String vorgangId) {
+		return commandService.findFinishedCommands(vorgangId)
+				.filter(historieCommandHandler::isHistorieCommand)
+				.map(historieCommandHandler::translateOrder);
+	}
+}
\ No newline at end of file
diff --git a/goofy-server/src/main/java/de/itvsh/goofy/kommentar/Kommentar.java b/goofy-server/src/main/java/de/itvsh/goofy/kommentar/Kommentar.java
new file mode 100644
index 0000000000000000000000000000000000000000..e92ce16ffa5d9c053971a76412a6dfdd26faf61f
--- /dev/null
+++ b/goofy-server/src/main/java/de/itvsh/goofy/kommentar/Kommentar.java
@@ -0,0 +1,70 @@
+/*
+ * 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.itvsh.goofy.kommentar;
+
+import static de.itvsh.goofy.common.ValidationMessageCodes.*;
+
+import java.time.ZonedDateTime;
+
+import javax.validation.constraints.NotNull;
+import javax.validation.constraints.Size;
+
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.annotation.JsonProperty.Access;
+
+import de.itvsh.goofy.common.LinkedUserProfileResource;
+import de.itvsh.goofy.common.command.CommandBody;
+import lombok.AccessLevel;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.ToString;
+
+@Getter
+@Builder(toBuilder = true)
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+@AllArgsConstructor(access = AccessLevel.PACKAGE)
+@ToString
+public class Kommentar implements CommandBody {
+
+	@JsonIgnore
+	private String id;
+	@JsonIgnore
+	private long version;
+
+	@JsonIgnore
+	private String vorgangId;
+
+	@JsonProperty(access = Access.READ_ONLY)
+	@LinkedUserProfileResource
+	private String createdBy;
+	@JsonProperty(access = Access.READ_ONLY)
+	private ZonedDateTime createdAt;
+
+	@NotNull(message = FIELD_IS_EMPTY)
+	@Size(min = 1, message = FIELD_MIN_SIZE)
+	private String text;
+}
diff --git a/goofy-server/src/main/java/de/itvsh/goofy/kommentar/KommentarCommand.java b/goofy-server/src/main/java/de/itvsh/goofy/kommentar/KommentarCommand.java
new file mode 100644
index 0000000000000000000000000000000000000000..273268bf150709de58c26740c95da90f1da3dee7
--- /dev/null
+++ b/goofy-server/src/main/java/de/itvsh/goofy/kommentar/KommentarCommand.java
@@ -0,0 +1,56 @@
+/*
+ * 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.itvsh.goofy.kommentar;
+
+import javax.validation.Valid;
+
+import com.fasterxml.jackson.annotation.JsonIgnore;
+
+import de.itvsh.goofy.common.command.CommandOrder;
+import de.itvsh.goofy.common.command.CommandStatus;
+import lombok.AccessLevel;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.EqualsAndHashCode;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+
+@Builder
+@Getter
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+@AllArgsConstructor(access = AccessLevel.PACKAGE)
+@EqualsAndHashCode
+class KommentarCommand {
+
+	@JsonIgnore
+	private String id;
+
+	private CommandOrder order;
+
+	@JsonIgnore
+	private CommandStatus status;
+
+	@Valid
+	private Kommentar kommentar;
+}
diff --git a/goofy-server/src/main/java/de/itvsh/goofy/kommentar/KommentarCommandController.java b/goofy-server/src/main/java/de/itvsh/goofy/kommentar/KommentarCommandController.java
new file mode 100644
index 0000000000000000000000000000000000000000..f8158517b850676d67c9f971fb139a56cb141e54
--- /dev/null
+++ b/goofy-server/src/main/java/de/itvsh/goofy/kommentar/KommentarCommandController.java
@@ -0,0 +1,76 @@
+/*
+ * 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.itvsh.goofy.kommentar;
+
+import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.*;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import de.itvsh.goofy.common.command.Command;
+import de.itvsh.goofy.common.command.CommandController;
+
+@RestController
+@RequestMapping(KommentarCommandController.KOMMENTAR_COMMANDS)
+public class KommentarCommandController {
+
+	static final String KOMMENTAR_COMMANDS = "/api/kommentars/{kommentarId}/{kommentarVersion}/commands";
+
+	@Autowired
+	private KommentarService service;
+
+	@PostMapping
+	public ResponseEntity<Void> editKommentar(@RequestBody KommentarCommand kommentarCommand, @PathVariable String kommentarId,
+			@PathVariable long kommentarVersion) {
+		var createdCommand = service.editKommentar(kommentarCommand.getKommentar(), kommentarId, kommentarVersion);
+
+		return buildResponseLink(createdCommand);
+	}
+
+	private ResponseEntity<Void> buildResponseLink(Command createdKommentarCommand) {
+		return ResponseEntity.created(linkTo(CommandController.class).slash(createdKommentarCommand.getId()).toUri()).build();
+	}
+
+	@RestController
+	@RequestMapping(KommentarCommandByVorgangController.KOMMENTAR_COMMANDS_BY_VORGANG)
+	public static class KommentarCommandByVorgangController {
+
+		static final String KOMMENTAR_COMMANDS_BY_VORGANG = "/api/vorgangs/{vorgangId}/kommentarCommands";
+
+		@Autowired
+		private KommentarService service;
+
+		@PostMapping
+		public ResponseEntity<Void> createKommentar(@RequestBody KommentarCommand command, @PathVariable String vorgangId) {
+			var createdCommand = service.createKommentar(command.getKommentar(), vorgangId);
+
+			return ResponseEntity.created(linkTo(CommandController.class).slash(createdCommand.getId()).toUri()).build();
+		}
+	}
+}
\ No newline at end of file
diff --git a/goofy-server/src/main/java/de/itvsh/goofy/kommentar/KommentarController.java b/goofy-server/src/main/java/de/itvsh/goofy/kommentar/KommentarController.java
new file mode 100644
index 0000000000000000000000000000000000000000..80bbf05f26043b46e39cf5871599111fb179c95e
--- /dev/null
+++ b/goofy-server/src/main/java/de/itvsh/goofy/kommentar/KommentarController.java
@@ -0,0 +1,75 @@
+/*
+ * 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.itvsh.goofy.kommentar;
+
+import java.util.Comparator;
+import java.util.stream.Stream;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.hateoas.CollectionModel;
+import org.springframework.hateoas.EntityModel;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+@RestController
+@RequestMapping(KommentarController.KOMMENTAR_PATH)
+public class KommentarController {
+
+	static final String KOMMENTAR_PATH = "/api/kommentars"; // NOSONAR
+
+	@Autowired
+	private KommentarService service;
+
+	@Autowired
+	private KommentarModelAssembler modelAssembler;
+
+	@GetMapping("/{kommentarId}")
+	public EntityModel<Kommentar> getById(@PathVariable String kommentarId) {
+		return modelAssembler.toModel(service.getById(kommentarId));
+	}
+
+	@RestController
+	@RequestMapping(KommentarByVorgangController.KOMMENTAR_BY_VORGANG_PATH)
+	public static class KommentarByVorgangController {
+
+		static final String KOMMENTAR_BY_VORGANG_PATH = "/api/vorgangs"; // NOSONAR
+
+		@Autowired
+		private KommentarService service;
+
+		@Autowired
+		private KommentarModelAssembler modelAssembler;
+
+		@GetMapping("/{vorgangId}/kommentars")
+		public CollectionModel<EntityModel<Kommentar>> getAll(@PathVariable String vorgangId) {
+			return modelAssembler.toCollectionModel(sortByCreatedAt(service.findByVorgangId(vorgangId)), vorgangId);
+		}
+
+		Stream<Kommentar> sortByCreatedAt(Stream<Kommentar> kommentare) {
+			return kommentare.sorted(Comparator.comparing(Kommentar::getCreatedAt).reversed());
+		}
+	}
+}
\ No newline at end of file
diff --git a/goofy-server/src/main/java/de/itvsh/goofy/kommentar/KommentarMapper.java b/goofy-server/src/main/java/de/itvsh/goofy/kommentar/KommentarMapper.java
new file mode 100644
index 0000000000000000000000000000000000000000..2c4b648c6e4fc37656afa54861413a5c897016eb
--- /dev/null
+++ b/goofy-server/src/main/java/de/itvsh/goofy/kommentar/KommentarMapper.java
@@ -0,0 +1,70 @@
+/*
+ * 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.itvsh.goofy.kommentar;
+
+import java.time.ZonedDateTime;
+import java.util.Map;
+
+import org.mapstruct.Mapper;
+import org.mapstruct.Mapping;
+import org.mapstruct.MappingConstants;
+import org.mapstruct.NullValueCheckStrategy;
+import org.mapstruct.ReportingPolicy;
+import org.mapstruct.ValueMapping;
+import org.springframework.beans.factory.annotation.Autowired;
+
+import de.itvsh.kop.pluto.common.grpc.GrpcObjectMapper;
+import de.itvsh.ozg.pluto.grpc.command.GrpcCommand;
+import de.itvsh.ozg.pluto.vorgangAttachedItem.GrpcVorgangAttachedItem;
+
+@Mapper(unmappedTargetPolicy = ReportingPolicy.ERROR, nullValueCheckStrategy = NullValueCheckStrategy.ALWAYS)
+abstract class KommentarMapper {
+
+	static final String ID = "id";
+	static final String TEXT = "text";
+	static final String CREATED_BY = "createdBy";
+	static final String CREATED_AT = "createdAt";
+
+	@Autowired
+	private GrpcObjectMapper grpcObjectMapper;
+
+	@Mapping(target = "kommentar", ignore = true)
+	@ValueMapping(source = "UNRECOGNIZED", target = MappingConstants.NULL)
+	@ValueMapping(source = "UNDEFINED", target = MappingConstants.NULL)
+	abstract KommentarCommand toKommentarCommand(GrpcCommand command);
+
+	public Kommentar fromItem(GrpcVorgangAttachedItem vorgangAttachedItem) {
+		return fromItemMap(grpcObjectMapper.mapFromGrpc(vorgangAttachedItem.getItem()), vorgangAttachedItem.getVersion());
+	}
+
+	Kommentar fromItemMap(Map<String, Object> map, long version) {
+		return Kommentar.builder()
+				.id((String) map.get(ID))
+				.version(version)
+				.createdAt(ZonedDateTime.parse((String) map.get(CREATED_AT)))
+				.createdBy((String) map.get(CREATED_BY))
+				.text((String) map.get(TEXT))
+				.build();
+	}
+}
diff --git a/goofy-server/src/main/java/de/itvsh/goofy/kommentar/KommentarModelAssembler.java b/goofy-server/src/main/java/de/itvsh/goofy/kommentar/KommentarModelAssembler.java
new file mode 100644
index 0000000000000000000000000000000000000000..2f8a985debd3fabc4abb8a39acbba11f9f6b114d
--- /dev/null
+++ b/goofy-server/src/main/java/de/itvsh/goofy/kommentar/KommentarModelAssembler.java
@@ -0,0 +1,60 @@
+/*
+ * 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.itvsh.goofy.kommentar;
+
+import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.*;
+
+import java.util.stream.Stream;
+
+import org.springframework.hateoas.CollectionModel;
+import org.springframework.hateoas.EntityModel;
+import org.springframework.hateoas.server.RepresentationModelAssembler;
+import org.springframework.stereotype.Component;
+
+import de.itvsh.goofy.common.ModelBuilder;
+import de.itvsh.goofy.kommentar.KommentarCommandController.KommentarCommandByVorgangController;
+
+@Component
+class KommentarModelAssembler implements RepresentationModelAssembler<Kommentar, EntityModel<Kommentar>> {
+
+	static final String REL_CREATE = "create-kommentar";
+	static final String REL_EDIT = "edit";
+
+	@Override
+	public EntityModel<Kommentar> toModel(Kommentar kommentar) {
+		var selfLink = linkTo(KommentarController.class).slash(kommentar.getId());
+		var commandLink = linkTo(methodOn(KommentarCommandController.class).editKommentar(null, kommentar.getId(), kommentar.getVersion()));
+
+		return ModelBuilder.fromEntity(kommentar).addLink(selfLink.withSelfRel())
+				.addLink(commandLink.withRel(REL_EDIT))
+				.buildModel();
+	}
+
+	public CollectionModel<EntityModel<Kommentar>> toCollectionModel(Stream<Kommentar> entities, String vorgangId) {
+		return CollectionModel.of(entities.map(this::toModel).toList(),
+				linkTo(KommentarController.class).withSelfRel(),
+				linkTo(methodOn(KommentarCommandByVorgangController.class).createKommentar(null, vorgangId))
+						.withRel(REL_CREATE));
+	}
+}
\ No newline at end of file
diff --git a/goofy-server/src/main/java/de/itvsh/goofy/kommentar/KommentarRemoteService.java b/goofy-server/src/main/java/de/itvsh/goofy/kommentar/KommentarRemoteService.java
new file mode 100644
index 0000000000000000000000000000000000000000..d7823ca6a5cbaa5131031bd1289759b41c9db0ce
--- /dev/null
+++ b/goofy-server/src/main/java/de/itvsh/goofy/kommentar/KommentarRemoteService.java
@@ -0,0 +1,69 @@
+/*
+ * 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.itvsh.goofy.kommentar;
+
+import java.util.stream.Stream;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import de.itvsh.goofy.GoofyServerApplication;
+import de.itvsh.ozg.pluto.vorgangAttachedItem.GrpcFindVorgangAttachedItemRequest;
+import de.itvsh.ozg.pluto.vorgangAttachedItem.GrpcVorgangAttachedItemRequest;
+import de.itvsh.ozg.pluto.vorgangAttachedItem.VorgangAttachedItemServiceGrpc.VorgangAttachedItemServiceBlockingStub;
+import net.devh.boot.grpc.client.inject.GrpcClient;
+
+@Service
+class KommentarRemoteService {
+
+	static final String ITEM_NAME = "Kommentar";
+
+	@GrpcClient(GoofyServerApplication.GRPC_CLIENT)
+	private VorgangAttachedItemServiceBlockingStub vorgangAttachedItemServiceStub;
+	@Autowired
+	private KommentarMapper mapper;
+
+	public Stream<Kommentar> findByVorgangId(String vorgangId) {
+		var response = vorgangAttachedItemServiceStub.find(buildFindRequest(vorgangId));
+
+		return response.getVorgangAttachedItemsList().stream().map(mapper::fromItem);
+	}
+
+	GrpcFindVorgangAttachedItemRequest buildFindRequest(String vorgangId) {
+		return GrpcFindVorgangAttachedItemRequest.newBuilder()
+				.setVorgangId(vorgangId)
+				.setItemName(ITEM_NAME)
+				.build();
+	}
+
+	public Kommentar getById(String kommentarId) {
+		var response = vorgangAttachedItemServiceStub.getById(buildRequest(kommentarId));
+
+		return mapper.fromItem(response.getVorgangAttachedItem()).toBuilder().vorgangId(response.getVorgangAttachedItem().getVorgangId()).build();
+	}
+
+	private GrpcVorgangAttachedItemRequest buildRequest(String kommentarId) {
+		return GrpcVorgangAttachedItemRequest.newBuilder().setId(kommentarId).build();
+	}
+}
diff --git a/goofy-server/src/main/java/de/itvsh/goofy/kommentar/KommentarService.java b/goofy-server/src/main/java/de/itvsh/goofy/kommentar/KommentarService.java
new file mode 100644
index 0000000000000000000000000000000000000000..175908fecd85ef601c0e8a5ef7e4f077273fa6a0
--- /dev/null
+++ b/goofy-server/src/main/java/de/itvsh/goofy/kommentar/KommentarService.java
@@ -0,0 +1,75 @@
+/*
+ * 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.itvsh.goofy.kommentar;
+
+import java.time.ZonedDateTime;
+import java.util.stream.Stream;
+
+import javax.validation.Valid;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.validation.annotation.Validated;
+
+import de.itvsh.goofy.common.attacheditem.VorgangAttachedItemService;
+import de.itvsh.goofy.common.command.Command;
+import de.itvsh.goofy.common.user.CurrentUserService;
+
+@Validated
+@Service
+class KommentarService {
+
+	@Autowired
+	private KommentarRemoteService remoteService;
+	@Autowired
+	private CurrentUserService currentUserService;
+	@Autowired
+	private VorgangAttachedItemService vorgangAttachedItemService;
+
+	public Command createKommentar(@Valid Kommentar kommentar, String vorgangId) {
+		return vorgangAttachedItemService.createNewKommentar(addCreated(kommentar), vorgangId);
+	}
+
+	Kommentar addCreated(Kommentar kommentar) {
+		return kommentar.toBuilder()
+				.createdAt(ZonedDateTime.now().withNano(0))
+				.createdBy(currentUserService.getUserId().toString())
+				.build();
+	}
+
+	public Command editKommentar(@Valid Kommentar kommentar, String kommentarId, long kommentarVersion) {
+		var loadedKommentar = getById(kommentarId);
+		var preparedKommentar = loadedKommentar.toBuilder().text(kommentar.getText()).build();
+
+		return vorgangAttachedItemService.editKommentar(preparedKommentar, kommentarId, kommentarVersion);
+	}
+
+	public Stream<Kommentar> findByVorgangId(String vorgangId) {
+		return remoteService.findByVorgangId(vorgangId);
+	}
+
+	public Kommentar getById(String kommentarId) {
+		return remoteService.getById(kommentarId);
+	}
+}
\ No newline at end of file
diff --git a/goofy-server/src/main/java/de/itvsh/goofy/postfach/PostfachMail.java b/goofy-server/src/main/java/de/itvsh/goofy/postfach/PostfachMail.java
new file mode 100644
index 0000000000000000000000000000000000000000..fe0443d976fb79e6d667d27c99e1d95a436cf6b9
--- /dev/null
+++ b/goofy-server/src/main/java/de/itvsh/goofy/postfach/PostfachMail.java
@@ -0,0 +1,88 @@
+/*
+ * 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.itvsh.goofy.postfach;
+
+import static de.itvsh.goofy.common.ValidationMessageCodes.*;
+
+import java.time.ZonedDateTime;
+import java.util.List;
+
+import javax.validation.constraints.NotEmpty;
+import javax.validation.constraints.Size;
+
+import com.fasterxml.jackson.annotation.JsonIgnore;
+
+import de.itvsh.goofy.common.LinkedResource;
+import de.itvsh.goofy.common.binaryfile.BinaryFileController;
+import de.itvsh.goofy.common.binaryfile.FileId;
+import de.itvsh.goofy.common.command.CommandBody;
+import de.itvsh.goofy.common.user.UserId;
+import lombok.AccessLevel;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Singular;
+import lombok.ToString;
+
+@Builder(toBuilder = true)
+@Getter
+@ToString
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+@AllArgsConstructor(access = AccessLevel.PUBLIC)
+public class PostfachMail implements CommandBody {
+
+	enum Direction {
+		IN, OUT
+	}
+
+	@JsonIgnore
+	private String id;
+	@JsonIgnore
+	private String postfachId;
+	@JsonIgnore
+	private String vorgangId;
+
+	private ZonedDateTime createdAt;
+	@JsonIgnore
+	private UserId createdBy;
+
+	private ZonedDateTime sentAt;
+	private Boolean sentSuccessful;
+	private String messageCode;
+
+	private Direction direction;
+	private ReplyOption replyOption;
+
+	@NotEmpty(message = FIELD_IS_EMPTY)
+	@Size(max = 70, message = FIELD_MAX_SIZE)
+	private String subject;
+
+	@NotEmpty(message = FIELD_IS_EMPTY)
+	private String mailBody;
+
+	@LinkedResource(controllerClass = BinaryFileController.class)
+	@Singular
+	private List<FileId> attachments;
+}
\ No newline at end of file
diff --git a/goofy-server/src/main/java/de/itvsh/goofy/postfach/PostfachMailController.java b/goofy-server/src/main/java/de/itvsh/goofy/postfach/PostfachMailController.java
new file mode 100644
index 0000000000000000000000000000000000000000..4ffd89afaa0590440f0a7439f7a33ed513612192
--- /dev/null
+++ b/goofy-server/src/main/java/de/itvsh/goofy/postfach/PostfachMailController.java
@@ -0,0 +1,156 @@
+/*
+ * 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.itvsh.goofy.postfach;
+
+import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.*;
+
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Optional;
+import java.util.stream.Stream;
+
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.hateoas.CollectionModel;
+import org.springframework.hateoas.EntityModel;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.MediaType;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
+
+import de.itvsh.goofy.common.binaryfile.BinaryFileController;
+import de.itvsh.goofy.common.command.CommandController;
+import de.itvsh.goofy.common.command.CommandController.CommandByRelationController;
+import de.itvsh.goofy.common.command.CreateCommand;
+import de.itvsh.goofy.common.file.OzgFile;
+import de.itvsh.goofy.vorgang.Antragsteller;
+import de.itvsh.goofy.vorgang.VorgangController;
+import de.itvsh.goofy.vorgang.VorgangWithEingang;
+
+@RestController
+@RequestMapping(PostfachMailController.PATH)
+public class PostfachMailController {
+
+	public static final String PATH = "/api/postfachMails"; // NOSONAR
+
+	public static final String PARAM_VORGANG_ID = "vorgangId";
+
+	static final String PDF_NAME_TEMPLATE = "%s_%s_Nachrichten.pdf";
+	static final SimpleDateFormat PDF_NAME_DATE_FORMATTER = new SimpleDateFormat("YYYYMMDD");
+
+	@Autowired
+	private PostfachMailService service;
+	@Autowired
+	private PostfachMailModelAssembler assembler;
+
+	@Autowired
+	private VorgangController vorgangController;
+	@Autowired
+	private BinaryFileController binaryFileController;
+
+	@GetMapping(params = PARAM_VORGANG_ID)
+	public CollectionModel<EntityModel<PostfachMail>> getAll(@RequestParam String vorgangId) {
+		var vorgang = getVorgang(vorgangId);
+
+		return assembler.toCollectionModel(sort(service.getAll(vorgangId)), vorgang, getPostfachId(vorgang));
+	}
+
+	@GetMapping(params = PARAM_VORGANG_ID, produces = MediaType.APPLICATION_PDF_VALUE)
+	public ResponseEntity<StreamingResponseBody> getAllAsPdf(@RequestParam String vorgangId) {
+		var vorgang = getVorgang(vorgangId);
+
+		return buildResponseEntity(vorgang, createDownloadStreamingBody(vorgang));
+	}
+
+	StreamingResponseBody createDownloadStreamingBody(VorgangWithEingang vorgang) {
+		return out -> service.getAllAsPdf(vorgang, out);
+	}
+
+	ResponseEntity<StreamingResponseBody> buildResponseEntity(VorgangWithEingang vorgang, StreamingResponseBody responseBody) {
+		return ResponseEntity.ok()
+				.header(HttpHeaders.CONTENT_DISPOSITION, buildPdfName(vorgang))
+				.contentType(MediaType.APPLICATION_PDF)
+				.body(responseBody);
+	}
+
+	private String buildPdfName(VorgangWithEingang vorgang) {
+		return String.format(PDF_NAME_TEMPLATE, vorgang.getNummer(), PDF_NAME_DATE_FORMATTER.format(new Date()));
+	}
+
+	private VorgangWithEingang getVorgang(String vorgangId) {
+		return vorgangController.getVorgang(vorgangId);
+	}
+
+	Optional<String> getPostfachId(VorgangWithEingang vorgang) {
+		return Optional.ofNullable(vorgang.getEingang().getAntragsteller()).map(Antragsteller::getPostfachId).map(StringUtils::trimToNull);
+	}
+
+	private Stream<PostfachMail> sort(Stream<PostfachMail> nachrichten) {
+		return nachrichten.sorted(new PostfachNachrichtComparator().reversed());
+	}
+
+	public boolean isPostfachConfigured() {
+		return service.isPostfachConfigured();
+	}
+
+	@GetMapping("{nachrichtId}/attachments")
+	public CollectionModel<EntityModel<OzgFile>> findAttachments(@PathVariable PostfachNachrichtId nachrichtId) {
+		var postfachNachricht = service.findById(nachrichtId);
+
+		return binaryFileController.getFiles(postfachNachricht.getAttachments());
+	}
+
+	@RestController
+	@RequestMapping(PostfachMailCommandController.POSTFACH_MAIL_COMMAND_PATH)
+	public static class PostfachMailCommandController {
+
+		static final String POSTFACH_MAIL_COMMAND_PATH = "/api/vorgangs/{vorgangId}/postfachMails/{postfachMailId}/commands"; // NOSONAR
+
+		@Autowired
+		private PostfachMailService service;
+		@Autowired
+		private CommandByRelationController commandByRelationController;
+
+		@PostMapping
+		public ResponseEntity<Void> createCommand(@PathVariable String vorgangId, @PathVariable String postfachMailId,
+				@RequestBody CreateCommand command) {
+			command = command.toBuilder()
+					.vorgangId(vorgangId)
+					.relationId(postfachMailId)
+					.build();
+			var created = commandByRelationController.createCommand(command);
+
+			service.resendPostfachMail(created.getId(), postfachMailId);
+
+			return ResponseEntity.created(linkTo(CommandController.class).slash(created.getId()).toUri()).build();
+		}
+	}
+}
\ No newline at end of file
diff --git a/goofy-server/src/main/java/de/itvsh/goofy/postfach/PostfachMailMapper.java b/goofy-server/src/main/java/de/itvsh/goofy/postfach/PostfachMailMapper.java
new file mode 100644
index 0000000000000000000000000000000000000000..910fc2ae3ce6ed68f03e97fa834d1ddaf7aa7f1c
--- /dev/null
+++ b/goofy-server/src/main/java/de/itvsh/goofy/postfach/PostfachMailMapper.java
@@ -0,0 +1,54 @@
+/*
+ * 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.itvsh.goofy.postfach;
+
+import java.util.List;
+
+import org.mapstruct.Mapper;
+import org.mapstruct.Mapping;
+import org.mapstruct.MappingConstants;
+import org.mapstruct.NullValueCheckStrategy;
+import org.mapstruct.ValueMapping;
+
+import com.google.protobuf.ProtocolStringList;
+
+import de.itvsh.goofy.common.TimeMapper;
+import de.itvsh.goofy.common.binaryfile.FileId;
+import de.itvsh.goofy.common.user.UserIdMapper;
+import de.itvsh.ozg.mail.postfach.GrpcPostfachMail;
+
+@Mapper(uses = { TimeMapper.class, UserIdMapper.class }, //
+		nullValueCheckStrategy = NullValueCheckStrategy.ALWAYS)
+interface PostfachMailMapper {
+
+	@Mapping(target = "attachment", ignore = true)
+	@ValueMapping(source = "UNRECOGNIZED", target = MappingConstants.NULL)
+	@ValueMapping(source = "UNDEFINED", target = MappingConstants.NULL)
+	@Mapping(target = "attachments", source = "attachmentList")
+	PostfachMail toPostfachMail(GrpcPostfachMail grpcPostfachMail);
+
+	default List<FileId> fromStringList(ProtocolStringList stringList) {
+		return stringList.stream().map(FileId::from).toList();
+	}
+}
\ No newline at end of file
diff --git a/goofy-server/src/main/java/de/itvsh/goofy/postfach/PostfachMailModelAssembler.java b/goofy-server/src/main/java/de/itvsh/goofy/postfach/PostfachMailModelAssembler.java
new file mode 100644
index 0000000000000000000000000000000000000000..6914d78d08db17897bea3bdb3b92ae7567718d42
--- /dev/null
+++ b/goofy-server/src/main/java/de/itvsh/goofy/postfach/PostfachMailModelAssembler.java
@@ -0,0 +1,107 @@
+/*
+ * 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.itvsh.goofy.postfach;
+
+import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.*;
+
+import java.util.Optional;
+import java.util.function.Predicate;
+import java.util.stream.Stream;
+
+import org.apache.commons.collections.CollectionUtils;
+import org.apache.commons.lang3.BooleanUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.hateoas.CollectionModel;
+import org.springframework.hateoas.EntityModel;
+import org.springframework.hateoas.Link;
+import org.springframework.hateoas.server.RepresentationModelAssembler;
+import org.springframework.stereotype.Component;
+
+import de.itvsh.goofy.common.ModelBuilder;
+import de.itvsh.goofy.common.binaryfile.BinaryFileController;
+import de.itvsh.goofy.common.command.CommandController.CommandByRelationController;
+import de.itvsh.goofy.common.user.UserManagerUrlProvider;
+import de.itvsh.goofy.postfach.PostfachMailController.PostfachMailCommandController;
+import de.itvsh.goofy.vorgang.VorgangController;
+import de.itvsh.goofy.vorgang.VorgangWithEingang;
+
+@Component
+class PostfachMailModelAssembler implements RepresentationModelAssembler<PostfachMail, EntityModel<PostfachMail>> {
+
+	public static final String REL_SEND_POSTFACH_MAIL = "sendPostfachMail";
+	public static final String REL_RESEND_POSTFACH_MAIL = "resendPostfachMail";
+	public static final String REL_RESET_NEW_POSTFACH_MAIL = "resetHasNewPostfachNachricht";
+	public static final String REL_ATTACHMENTS = "attachments";
+	static final String REL_UPLOAD_ATTACHMENT = "uploadAttachment";
+	static final String REL_CREATED_BY = "createdBy";
+
+	private static final Predicate<PostfachMail> SENT_FAILED = postfachMail -> BooleanUtils.isFalse(postfachMail.getSentSuccessful());
+	private static final Predicate<PostfachMail> HAS_ATTACHMENTS = nachricht -> CollectionUtils.isNotEmpty(nachricht.getAttachments());
+
+	static final String SYSTEM_USER_IDENTIFIER = "system";
+	private static final Predicate<PostfachMail> SENT_BY_CLIENT_USER = postfachNachricht -> Optional.ofNullable(postfachNachricht.getCreatedBy())
+			.map(createdBy -> !createdBy.toString().startsWith(SYSTEM_USER_IDENTIFIER)).orElse(false);
+
+	@Autowired
+	private UserManagerUrlProvider userManagerUrlProvider;
+
+	public CollectionModel<EntityModel<PostfachMail>> toCollectionModel(Stream<PostfachMail> entities, VorgangWithEingang vorgang,
+			Optional<String> postfachId) {
+		CollectionModel<EntityModel<PostfachMail>> model = CollectionModel.of(entities.map(this::toModel).toList(),
+				linkTo(PostfachMailController.class).withSelfRel());
+
+		postfachId.ifPresent(id -> addPostfachNachrichtLinks(model, vorgang));
+
+		return model;
+	}
+
+	@Override
+	public EntityModel<PostfachMail> toModel(PostfachMail postfachMail) {
+		var selfLink = linkTo(PostfachMailController.class).slash(postfachMail.getId());
+
+		return ModelBuilder.fromEntity(postfachMail).addLink(selfLink.withSelfRel())
+				.ifMatch(SENT_FAILED)
+				.addLink(linkTo(methodOn(PostfachMailCommandController.class).createCommand(postfachMail.getVorgangId(), postfachMail.getId(), null))
+						.withRel(REL_RESEND_POSTFACH_MAIL))
+				.ifMatch(HAS_ATTACHMENTS)
+				.addLink(linkTo(methodOn(PostfachMailController.class).findAttachments(PostfachNachrichtId.from(postfachMail.getId())))
+						.withRel(REL_ATTACHMENTS))
+				.ifMatch(() -> userManagerUrlProvider.isConfiguredForUserProfile() && SENT_BY_CLIENT_USER.test(postfachMail))
+				.addLink(() -> Link.of(String.format(userManagerUrlProvider.getUserProfileTemplate(), postfachMail.getCreatedBy()), REL_CREATED_BY))
+				.buildModel();
+	}
+
+	private void addPostfachNachrichtLinks(CollectionModel<EntityModel<PostfachMail>> model, VorgangWithEingang vorgang) {
+		var vorgangId = vorgang.getId();
+		model.add(linkTo(methodOn(CommandByRelationController.class).createCommand(vorgangId, vorgangId, vorgang.getVersion(), null))
+				.withRel(REL_SEND_POSTFACH_MAIL));
+		model.add(linkTo(BinaryFileController.class).slash(vorgangId).slash("postfachNachrichtAttachment").slash("file")
+				.withRel(REL_UPLOAD_ATTACHMENT));
+
+		if (vorgang.isHasNewPostfachNachricht()) {
+			model.add(linkTo(VorgangController.class).slash(vorgangId).slash("hasNewPostfachNachricht").withRel(REL_RESET_NEW_POSTFACH_MAIL));
+		}
+	}
+
+}
\ No newline at end of file
diff --git a/goofy-server/src/main/java/de/itvsh/goofy/postfach/PostfachMailRemoteService.java b/goofy-server/src/main/java/de/itvsh/goofy/postfach/PostfachMailRemoteService.java
new file mode 100644
index 0000000000000000000000000000000000000000..041696c0498784907938a8421045a513d1346ff5
--- /dev/null
+++ b/goofy-server/src/main/java/de/itvsh/goofy/postfach/PostfachMailRemoteService.java
@@ -0,0 +1,98 @@
+/*
+ * 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.itvsh.goofy.postfach;
+
+import java.util.Optional;
+import java.util.stream.Stream;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import de.itvsh.goofy.common.callcontext.ContextService;
+import de.itvsh.ozg.mail.postfach.GrpcFindPostfachMailRequest;
+import de.itvsh.ozg.mail.postfach.GrpcFindPostfachMailsRequest;
+import de.itvsh.ozg.mail.postfach.GrpcFindPostfachMailsResponse;
+import de.itvsh.ozg.mail.postfach.GrpcIsPostfachConfiguredRequest;
+import de.itvsh.ozg.mail.postfach.GrpcResendPostfachMailRequest;
+import de.itvsh.ozg.mail.postfach.PostfachServiceGrpc.PostfachServiceBlockingStub;
+import net.devh.boot.grpc.client.inject.GrpcClient;
+
+@Service
+class PostfachMailRemoteService {
+
+	@GrpcClient("pluto")
+	private PostfachServiceBlockingStub serviceStub;
+	@Autowired
+	private ContextService contextService;
+	@Autowired
+	private PostfachMailMapper postfachNachrichtMapper;
+
+	public Stream<PostfachMail> findPostfachMails(String vorgangId) {
+		GrpcFindPostfachMailsResponse response = serviceStub.findPostfachMails(buildFindPostfachMailsRequest(vorgangId));
+
+		return response.getMailsList().stream().map(postfachNachrichtMapper::toPostfachMail);
+	}
+
+	private GrpcFindPostfachMailsRequest buildFindPostfachMailsRequest(String vorgangId) {
+		return GrpcFindPostfachMailsRequest.newBuilder()
+				.setVorgangId(vorgangId)
+				.setContext(contextService.createCallContext())
+				.build();
+	}
+
+	public Optional<PostfachMail> findById(PostfachNachrichtId nachrichtId) {
+		var response = serviceStub.findPostfachMail(buildFindPostfachMailRequest(nachrichtId));
+
+		return Optional.ofNullable(response.getNachricht()).map(postfachNachrichtMapper::toPostfachMail);
+	}
+
+	private GrpcFindPostfachMailRequest buildFindPostfachMailRequest(PostfachNachrichtId nachrichtId) {
+		return GrpcFindPostfachMailRequest.newBuilder()
+				.setContext(contextService.createCallContext())
+				.setNachrichtId(nachrichtId.toString())
+				.build();
+	}
+
+	public void resendPostfachMail(String commandId, String postfachMailId) {
+		serviceStub.resendPostfachMail(buildResendPostfachMailRequest(commandId, postfachMailId));
+	}
+
+	GrpcResendPostfachMailRequest buildResendPostfachMailRequest(String commandId, String postfachMailId) {
+		return GrpcResendPostfachMailRequest.newBuilder()
+				.setContext(contextService.createCallContext())
+				.setPostfachMailId(postfachMailId)
+				.setCommandId(commandId)
+				.build();
+	}
+
+	public boolean isPostfachConfigured() {
+		return serviceStub.isPostfachConfigured(buildIsPostfachConfiguredRequest()).getIsConfigured();
+	}
+
+	private GrpcIsPostfachConfiguredRequest buildIsPostfachConfiguredRequest() {
+		return GrpcIsPostfachConfiguredRequest.newBuilder()
+				.setContext(contextService.createCallContext())
+				.build();
+	}
+}
\ No newline at end of file
diff --git a/goofy-server/src/main/java/de/itvsh/goofy/postfach/PostfachMailService.java b/goofy-server/src/main/java/de/itvsh/goofy/postfach/PostfachMailService.java
new file mode 100644
index 0000000000000000000000000000000000000000..16fea2fbb687b990248eada729fb11adecc7ffd0
--- /dev/null
+++ b/goofy-server/src/main/java/de/itvsh/goofy/postfach/PostfachMailService.java
@@ -0,0 +1,138 @@
+/*
+ * 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.itvsh.goofy.postfach;
+
+import java.io.OutputStream;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import de.itvsh.goofy.common.binaryfile.BinaryFileService;
+import de.itvsh.goofy.common.binaryfile.FileId;
+import de.itvsh.goofy.common.errorhandling.ResourceNotFoundException;
+import de.itvsh.goofy.common.file.OzgFile;
+import de.itvsh.goofy.common.user.UserManagerUrlProvider;
+import de.itvsh.goofy.common.user.UserProfile;
+import de.itvsh.goofy.common.user.UserService;
+import de.itvsh.goofy.vorgang.VorgangWithEingang;
+import lombok.extern.log4j.Log4j2;
+
+@Log4j2
+@Service
+class PostfachMailService {
+
+	private Boolean isPostfachConfigured = null;
+
+	@Autowired
+	private PostfachNachrichtPdfService pdfService;
+
+	@Autowired
+	private PostfachMailRemoteService remoteService;
+
+	@Autowired
+	private BinaryFileService fileService;
+
+	@Autowired
+	private UserService userService;
+
+	public PostfachMail findById(PostfachNachrichtId nachrichtId) {
+		return remoteService.findById(nachrichtId)
+				.orElseThrow(() -> new ResourceNotFoundException(PostfachMail.class, nachrichtId));
+	}
+
+	// @Async FIXME Er scheint den CallContext zu verlieren
+	public void resendPostfachMail(String commandId, String postfachMailId) {
+		try {
+			remoteService.resendPostfachMail(commandId, postfachMailId);
+		} catch (RuntimeException e) {
+			LOG.error("Error sending PostfachNachricht: " + e.getMessage(), e);
+//			FIXME mark command als error
+		}
+	}
+
+	public boolean isPostfachConfigured() {
+		if (Objects.isNull(isPostfachConfigured)) {
+			LOG.info("isPostfachConfigured is null");
+			isPostfachConfigured = remoteService.isPostfachConfigured();
+			LOG.info("isPostfachConfigured set to " + isPostfachConfigured);
+		}
+		return isPostfachConfigured;
+	}
+
+	public OutputStream getAllAsPdf(VorgangWithEingang vorgang, OutputStream out) {
+		var postfachNachrichtPdfDataList = buildPostfachNachrichtPdfDataList(vorgang.getId());
+
+		return pdfService.getAllAsPdf(vorgang, postfachNachrichtPdfDataList, out);
+	}
+
+	Stream<PostfachNachrichtPdfData> buildPostfachNachrichtPdfDataList(String vorgangId) {
+		var postfachNachrichten = getAll(vorgangId).toList();
+		var ozgFileIdOzgFileMap = getFiles(postfachNachrichten.stream()).collect(Collectors.toMap(OzgFile::getId, OzgFile::getName));
+
+		return postfachNachrichten.stream().map(postfachNachricht -> buildPostfachNachrichtPdfData(postfachNachricht, ozgFileIdOzgFileMap));
+	}
+
+	public Stream<PostfachMail> getAll(String vorgangId) {
+		return remoteService.findPostfachMails(vorgangId);
+	}
+
+	private Stream<OzgFile> getFiles(Stream<PostfachMail> postfachMails) {
+		return fileService.getFiles(getFileIdsFromAllAttachments(postfachMails));
+	}
+
+	List<FileId> getFileIdsFromAllAttachments(Stream<PostfachMail> postfachMails) {
+		return postfachMails.map(PostfachMail::getAttachments)
+				.flatMap(Collection::stream)
+				.toList();
+	}
+
+	PostfachNachrichtPdfData buildPostfachNachrichtPdfData(PostfachMail postfachNachricht,
+			Map<FileId, String> ozgFileIdOzgFileMap) {
+		return PostfachNachrichtPdfData.builder()
+				.createdAt(postfachNachricht.getCreatedAt())
+				.user(getUser(postfachNachricht))
+				.direction(postfachNachricht.getDirection())
+				.mailBody(postfachNachricht.getMailBody())
+				.subject(postfachNachricht.getSubject())
+				.attachmentNames(postfachNachricht.getAttachments().stream().map(ozgFileIdOzgFileMap::get).toList())
+				.build();
+	}
+
+	UserProfile getUser(PostfachMail postfachNachricht) {
+		var createdBy = postfachNachricht.getCreatedBy();
+		if (UserManagerUrlProvider.SENT_BY_CLIENT_USER.test(postfachNachricht)) {
+			return Optional.ofNullable(userService.getById(createdBy)).orElseGet(() -> null);
+		}
+		return Optional.ofNullable(createdBy)
+				.map(created -> UserProfile.builder().id(created).build())
+				.orElseGet(() -> null);
+	}
+}
\ No newline at end of file
diff --git a/goofy-server/src/main/java/de/itvsh/goofy/postfach/PostfachNachrichtComparator.java b/goofy-server/src/main/java/de/itvsh/goofy/postfach/PostfachNachrichtComparator.java
new file mode 100644
index 0000000000000000000000000000000000000000..a8155bf3ecce866bd01a92fdbaef858f3849c7b4
--- /dev/null
+++ b/goofy-server/src/main/java/de/itvsh/goofy/postfach/PostfachNachrichtComparator.java
@@ -0,0 +1,37 @@
+/*
+ * 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.itvsh.goofy.postfach;
+
+import java.util.Comparator;
+import java.util.Objects;
+
+class PostfachNachrichtComparator implements Comparator<PostfachMail> {
+
+	@Override
+	public int compare(PostfachMail o1, PostfachMail o2) {
+		var comp1 = Objects.isNull(o1.getSentAt()) ? o1.getCreatedAt() : o1.getSentAt();
+		var comp2 = Objects.isNull(o2.getSentAt()) ? o2.getCreatedAt() : o2.getSentAt();
+		return comp1.compareTo(comp2);
+	}
+}
\ No newline at end of file
diff --git a/goofy-server/src/main/java/de/itvsh/goofy/postfach/PostfachNachrichtId.java b/goofy-server/src/main/java/de/itvsh/goofy/postfach/PostfachNachrichtId.java
new file mode 100644
index 0000000000000000000000000000000000000000..1ea92c5191e68d82770a596e12fb34c4c8e95972
--- /dev/null
+++ b/goofy-server/src/main/java/de/itvsh/goofy/postfach/PostfachNachrichtId.java
@@ -0,0 +1,42 @@
+/*
+ * 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.itvsh.goofy.postfach;
+
+import de.itvsh.kop.common.datatype.StringBasedValue;
+import lombok.EqualsAndHashCode;
+
+@EqualsAndHashCode(callSuper = true)
+public class PostfachNachrichtId extends StringBasedValue {
+
+	private static final long serialVersionUID = 1L;
+
+	public PostfachNachrichtId(String nachrichtId) {
+		super(nachrichtId);
+	}
+
+	public static PostfachNachrichtId from(String nachrichtId) {
+		return new PostfachNachrichtId(nachrichtId);
+	}
+
+}
diff --git a/goofy-server/src/main/java/de/itvsh/goofy/postfach/PostfachNachrichtPdfData.java b/goofy-server/src/main/java/de/itvsh/goofy/postfach/PostfachNachrichtPdfData.java
new file mode 100644
index 0000000000000000000000000000000000000000..fe208292b21a27a2f3c94c9e41ac8d9f3e6e883a
--- /dev/null
+++ b/goofy-server/src/main/java/de/itvsh/goofy/postfach/PostfachNachrichtPdfData.java
@@ -0,0 +1,49 @@
+/*
+ * 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.itvsh.goofy.postfach;
+
+import java.time.ZonedDateTime;
+import java.util.List;
+
+import de.itvsh.goofy.common.user.UserProfile;
+import de.itvsh.goofy.postfach.PostfachMail.Direction;
+import lombok.Builder;
+import lombok.Getter;
+import lombok.Setter;
+
+@Getter
+@Setter
+@Builder
+class PostfachNachrichtPdfData {
+
+	private ZonedDateTime createdAt;
+	private UserProfile user;
+
+	private String subject;
+	private String mailBody;
+	private Direction direction;
+
+	private List<String> attachmentNames;
+
+}
\ No newline at end of file
diff --git a/goofy-server/src/main/java/de/itvsh/goofy/postfach/PostfachNachrichtPdfModel.java b/goofy-server/src/main/java/de/itvsh/goofy/postfach/PostfachNachrichtPdfModel.java
new file mode 100644
index 0000000000000000000000000000000000000000..402876136b533c8665d75abfd84740ae5a3e467b
--- /dev/null
+++ b/goofy-server/src/main/java/de/itvsh/goofy/postfach/PostfachNachrichtPdfModel.java
@@ -0,0 +1,99 @@
+/*
+ * 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.itvsh.goofy.postfach;
+
+import java.util.List;
+
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlElementWrapper;
+import javax.xml.bind.annotation.XmlRootElement;
+
+import lombok.AccessLevel;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+
+@Getter
+@XmlRootElement
+@Builder
+@AllArgsConstructor(access = AccessLevel.PRIVATE)
+@NoArgsConstructor(access = AccessLevel.PACKAGE)
+class PostfachNachrichtPdfModel {
+
+	@XmlElement
+	private String vorgangName;
+
+	@XmlElement
+	private String vorgangNummer;
+
+	@XmlElement
+	private String antragstellerAnrede;
+
+	@XmlElement
+	private String antragstellerVorname;
+
+	@XmlElement
+	private String antragstellerNachname;
+
+	@XmlElement
+	private String antragstellerStrasse;
+
+	@XmlElement
+	private String antragstellerHausnummer;
+
+	@XmlElement
+	private String antragstellerPlz;
+
+	@XmlElement
+	private String antragstellerOrt;
+
+	@XmlElementWrapper
+	@XmlElement(name = "nachricht")
+	private List<Nachricht> nachrichten;
+
+	@Getter
+	@Builder
+	static class Nachricht {
+		
+		@XmlElement
+		private boolean isFirst;
+
+		@XmlElement
+		private String subject;
+
+		@XmlElement
+		private String mailBody;
+
+		@XmlElement
+		private String createdBy;
+
+		@XmlElement
+		private String createdAt;
+
+		@XmlElementWrapper
+		@XmlElement(name = "attachment")
+		private List<String> attachments;
+	}
+}
\ No newline at end of file
diff --git a/goofy-server/src/main/java/de/itvsh/goofy/postfach/PostfachNachrichtPdfService.java b/goofy-server/src/main/java/de/itvsh/goofy/postfach/PostfachNachrichtPdfService.java
new file mode 100644
index 0000000000000000000000000000000000000000..c4117625a214ec046f0a3d4b2ac38bddbb24d0dd
--- /dev/null
+++ b/goofy-server/src/main/java/de/itvsh/goofy/postfach/PostfachNachrichtPdfService.java
@@ -0,0 +1,165 @@
+/*
+ * 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.itvsh.goofy.postfach;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.time.format.DateTimeFormatter;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.stream.Stream;
+
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.core.io.Resource;
+import org.springframework.stereotype.Service;
+
+import de.itvsh.goofy.common.user.UserManagerUrlProvider;
+import de.itvsh.goofy.common.user.UserProfile;
+import de.itvsh.goofy.postfach.PostfachMail.Direction;
+import de.itvsh.goofy.vorgang.Antragsteller;
+import de.itvsh.goofy.vorgang.VorgangWithEingang;
+import de.itvsh.kop.common.errorhandling.TechnicalException;
+import de.itvsh.kop.common.pdf.PdfService;
+
+@Service
+class PostfachNachrichtPdfService {
+
+	static final String PDF_TEMPLATE_PATH = "classpath:fop/postfach-nachrichten.xsl";
+
+	static final String SYSTEM_NACHRICHT_NAME = StringUtils.EMPTY;
+	static final String FALLBACK_USER_NAME = "Unbekannter Benutzer";
+
+	static final String FALLBACK_ANTRAGSTELLER_NAME = "Antragsteller";
+
+	// TODO Auf Konstante mit Locale umstellen
+	private static final DateTimeFormatter CREATED_AT_FORMATTER = DateTimeFormatter.ofPattern("dd.MM.yyyy hh:mm:ss");
+
+	@Autowired
+	private PdfService pdfService;
+
+	private boolean isFirstNachricht;
+
+	@Value(PostfachNachrichtPdfService.PDF_TEMPLATE_PATH)
+	private Resource pdfTemplate;
+
+	public OutputStream getAllAsPdf(VorgangWithEingang vorgang, Stream<PostfachNachrichtPdfData> postfachNachrichten, OutputStream out) {
+		return pdfService.createPdf(getTemplate(), out, buildModel(vorgang, postfachNachrichten));
+	}
+
+	InputStream getTemplate() {
+		try {
+			return pdfTemplate.getInputStream();
+		} catch (IOException e) {
+			throw new TechnicalException("Pdf Template for postfach nachrichten could not be loaded", e);
+		}
+	}
+
+	PostfachNachrichtPdfModel buildModel(VorgangWithEingang vorgang, Stream<PostfachNachrichtPdfData> postfachNachrichten) {
+		var pdfModelBuilder = PostfachNachrichtPdfModel.builder().vorgangNummer(vorgang.getNummer()).vorgangName(vorgang.getName());
+
+		Optional.ofNullable(vorgang.getEingang().getAntragsteller()).ifPresent(antragsteller -> mapAntragsteller(pdfModelBuilder, antragsteller));
+
+		mapNachrichten(pdfModelBuilder, postfachNachrichten, vorgang.getEingang().getAntragsteller());
+
+		return pdfModelBuilder.build();
+	}
+
+	void mapAntragsteller(PostfachNachrichtPdfModel.PostfachNachrichtPdfModelBuilder modelBuilder, Antragsteller antragsteller) {
+		modelBuilder.antragstellerAnrede(antragsteller.getAnrede())
+				.antragstellerVorname(antragsteller.getVorname())
+				.antragstellerNachname(antragsteller.getNachname())
+				.antragstellerStrasse(antragsteller.getStrasse())
+				.antragstellerHausnummer(antragsteller.getHausnummer())
+				.antragstellerPlz(antragsteller.getPlz())
+				.antragstellerOrt(antragsteller.getOrt());
+	}
+
+	private void mapNachrichten(PostfachNachrichtPdfModel.PostfachNachrichtPdfModelBuilder pdfModelBuilder,
+			Stream<PostfachNachrichtPdfData> postfachNachrichten, Antragsteller antragsteller) {
+		isFirstNachricht = true;
+
+		pdfModelBuilder.nachrichten(postfachNachrichten.map(nachricht -> mapPostfachNachricht(nachricht, antragsteller)).toList());
+
+	}
+
+	PostfachNachrichtPdfModel.Nachricht mapPostfachNachricht(PostfachNachrichtPdfData postfachMail, Antragsteller antragsteller) {
+		return PostfachNachrichtPdfModel.Nachricht.builder()
+				.isFirst(isFirstNachricht())
+				.subject(postfachMail.getSubject())
+				.mailBody(postfachMail.getMailBody())
+				.createdAt(CREATED_AT_FORMATTER.format(postfachMail.getCreatedAt()))
+				.createdBy(buildAbsenderName(postfachMail, antragsteller))
+				.attachments(postfachMail.getAttachmentNames())
+				.build();
+	}
+
+	private boolean isFirstNachricht() {
+		if (isFirstNachricht) {
+			isFirstNachricht = false;
+			return true;
+		}
+		return false;
+	}
+
+	String buildAbsenderName(PostfachNachrichtPdfData postfachNachrichtPdfData, Antragsteller antragsteller) {
+		if (postfachNachrichtPdfData.getDirection() == Direction.IN) {
+			return buildAbsenderForOutgoingNachricht(antragsteller);
+		}
+		return buildAbsenderForIncomingNachricht(postfachNachrichtPdfData);
+	}
+
+	String buildAbsenderForOutgoingNachricht(Antragsteller antragsteller) {
+		return Optional.ofNullable(antragsteller)
+				.map(this::formatAntragstellerName)
+				.orElseGet(() -> FALLBACK_ANTRAGSTELLER_NAME);
+	}
+
+	String buildAbsenderForIncomingNachricht(PostfachNachrichtPdfData postfachNachrichtPdfData) {
+		return Optional.ofNullable(postfachNachrichtPdfData.getUser()).map(user -> {
+			if (Objects.nonNull(user.getId()) && user.toString().startsWith(UserManagerUrlProvider.SYSTEM_USER_IDENTIFIER)) {
+				return SYSTEM_NACHRICHT_NAME;
+			}
+			return formatUserName(postfachNachrichtPdfData.getUser());
+		}).orElseGet(() -> FALLBACK_USER_NAME);
+	}
+
+	private String formatUserName(UserProfile user) {
+		return formatForPdf(user.getFirstName(), user.getLastName());
+	}
+
+	private String formatAntragstellerName(Antragsteller antragsteller) {
+		return formatForPdf(antragsteller.getVorname(), antragsteller.getNachname());
+	}
+
+	private String formatForPdf(String firstName, String lastName) {
+		return String.format("%s %s", getPdfStringValue(firstName), getPdfStringValue(lastName)).trim();
+	}
+
+	private String getPdfStringValue(String value) {
+		return Optional.ofNullable(StringUtils.trimToNull(value)).orElseGet(() -> StringUtils.EMPTY);
+	}
+}
\ No newline at end of file
diff --git a/goofy-server/src/main/java/de/itvsh/goofy/postfach/ReplyOption.java b/goofy-server/src/main/java/de/itvsh/goofy/postfach/ReplyOption.java
new file mode 100644
index 0000000000000000000000000000000000000000..aa447471baa157605aea5795ef7e9749c89a2cb4
--- /dev/null
+++ b/goofy-server/src/main/java/de/itvsh/goofy/postfach/ReplyOption.java
@@ -0,0 +1,28 @@
+/*
+ * 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.itvsh.goofy.postfach;
+
+enum ReplyOption {
+	POSSIBLE, MANDATORY, FORBIDDEN;
+}
\ No newline at end of file
diff --git a/goofy-server/src/main/java/de/itvsh/goofy/representation/RepresentationController.java b/goofy-server/src/main/java/de/itvsh/goofy/representation/RepresentationController.java
new file mode 100644
index 0000000000000000000000000000000000000000..81d1af2bc141391fb3620cdf902bf31b10d921d7
--- /dev/null
+++ b/goofy-server/src/main/java/de/itvsh/goofy/representation/RepresentationController.java
@@ -0,0 +1,55 @@
+/*
+ * 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.itvsh.goofy.representation;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.hateoas.CollectionModel;
+import org.springframework.hateoas.EntityModel;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import de.itvsh.goofy.common.binaryfile.BinaryFileModelAssembler;
+import de.itvsh.goofy.common.file.OzgFile;
+import de.itvsh.goofy.common.file.OzgFileService;
+
+@RestController
+@RequestMapping(RepresentationController.REPRESENTATIONS_PATH)
+public class RepresentationController {
+
+	static final String REPRESENTATIONS_PATH = "/api/representations"; // NOSONAR
+
+	static final String PARAM_EINGANG_ID = "eingangId";
+
+	@Autowired
+	private OzgFileService fileService;
+	@Autowired
+	private BinaryFileModelAssembler modelAssembler;
+
+	@GetMapping(params = PARAM_EINGANG_ID)
+	public CollectionModel<EntityModel<OzgFile>> getAllByEingang(@RequestParam String eingangId) {
+		return modelAssembler.toCollectionModel(fileService.getRepresentationsByEingang(eingangId));
+	}
+}
diff --git a/goofy-server/src/main/java/de/itvsh/goofy/system/PlutoSystemStatus.java b/goofy-server/src/main/java/de/itvsh/goofy/system/PlutoSystemStatus.java
new file mode 100644
index 0000000000000000000000000000000000000000..0bc1b50bf510e22678c6771d017aed78fb508356
--- /dev/null
+++ b/goofy-server/src/main/java/de/itvsh/goofy/system/PlutoSystemStatus.java
@@ -0,0 +1,30 @@
+/*
+ * 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.itvsh.goofy.system;
+
+import lombok.Builder;
+
+@Builder
+public record PlutoSystemStatus(boolean isSearchServerAvailable, String javaVersion, String buildVersion) {
+}
diff --git a/goofy-server/src/main/java/de/itvsh/goofy/system/RemoteSystemStatusMapper.java b/goofy-server/src/main/java/de/itvsh/goofy/system/RemoteSystemStatusMapper.java
new file mode 100644
index 0000000000000000000000000000000000000000..b5eea4fa81f4421272a25a4f48a4a7d2e045733c
--- /dev/null
+++ b/goofy-server/src/main/java/de/itvsh/goofy/system/RemoteSystemStatusMapper.java
@@ -0,0 +1,33 @@
+/*
+ * 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.itvsh.goofy.system;
+
+import de.itvsh.ozg.pluto.system.GrpcGetSystemStatusResponse;
+import org.mapstruct.Mapper;
+
+@Mapper
+interface RemoteSystemStatusMapper {
+
+	PlutoSystemStatus fromGrpc(GrpcGetSystemStatusResponse systemStatusResponse);
+}
diff --git a/goofy-server/src/main/java/de/itvsh/goofy/system/SystemStatusRemoteService.java b/goofy-server/src/main/java/de/itvsh/goofy/system/SystemStatusRemoteService.java
new file mode 100644
index 0000000000000000000000000000000000000000..178ecbd1128a8350ded564e3cc0ffef1fd337a18
--- /dev/null
+++ b/goofy-server/src/main/java/de/itvsh/goofy/system/SystemStatusRemoteService.java
@@ -0,0 +1,44 @@
+/*
+ * 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.itvsh.goofy.system;
+
+import de.itvsh.ozg.pluto.system.GrpcGetSystemStatusRequest;
+import de.itvsh.ozg.pluto.system.SystemStatusServiceGrpc.SystemStatusServiceBlockingStub;
+import net.devh.boot.grpc.client.inject.GrpcClient;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+@Service
+class SystemStatusRemoteService {
+
+	@GrpcClient("pluto")
+	private SystemStatusServiceBlockingStub systemStatusBlockingStub;
+	@Autowired
+	private RemoteSystemStatusMapper remoteSystemStatusMapper;
+
+	public PlutoSystemStatus getSystemStatus() {
+		var systemStatusResponse = systemStatusBlockingStub.getSystemStatus(GrpcGetSystemStatusRequest.newBuilder().build());
+		return remoteSystemStatusMapper.fromGrpc(systemStatusResponse);
+	}
+}
diff --git a/goofy-server/src/main/java/de/itvsh/goofy/system/SystemStatusService.java b/goofy-server/src/main/java/de/itvsh/goofy/system/SystemStatusService.java
new file mode 100644
index 0000000000000000000000000000000000000000..c2e24f5f9e101a699fa6a80bc37afa94275967f7
--- /dev/null
+++ b/goofy-server/src/main/java/de/itvsh/goofy/system/SystemStatusService.java
@@ -0,0 +1,38 @@
+/*
+ * 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.itvsh.goofy.system;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+@Service
+public class SystemStatusService {
+
+	@Autowired
+	private SystemStatusRemoteService systemStatusRemoteService;
+
+	public boolean isSearchServerAvailable() {
+		return systemStatusRemoteService.getSystemStatus().isSearchServerAvailable();
+	}
+}
diff --git a/goofy-server/src/main/java/de/itvsh/goofy/vorgang/Antragsteller.java b/goofy-server/src/main/java/de/itvsh/goofy/vorgang/Antragsteller.java
new file mode 100644
index 0000000000000000000000000000000000000000..bbb0b3b6383b5fd503d8b05a10bcc32df05e621e
--- /dev/null
+++ b/goofy-server/src/main/java/de/itvsh/goofy/vorgang/Antragsteller.java
@@ -0,0 +1,50 @@
+/*
+ * 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.itvsh.goofy.vorgang;
+
+import java.util.Map;
+
+import lombok.Builder;
+import lombok.Getter;
+
+@Getter
+@Builder
+public class Antragsteller {
+
+	private String anrede;
+	private String nachname;
+	private String vorname;
+	private String geburtsdatum;
+	private String geburtsort;
+	private String geburtsname;
+	private String email;
+	private String telefon;
+	private String strasse;
+	private String hausnummer;
+	private String plz;
+	private String ort;
+	private String postfachId;
+
+	private Map<String, Object> otherData;
+}
\ No newline at end of file
diff --git a/goofy-server/src/main/java/de/itvsh/goofy/vorgang/AntragstellerMapper.java b/goofy-server/src/main/java/de/itvsh/goofy/vorgang/AntragstellerMapper.java
new file mode 100644
index 0000000000000000000000000000000000000000..0f6d454c5c35d5438ea4ba12f63f3cea6b919aeb
--- /dev/null
+++ b/goofy-server/src/main/java/de/itvsh/goofy/vorgang/AntragstellerMapper.java
@@ -0,0 +1,39 @@
+/*
+ * 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.itvsh.goofy.vorgang;
+
+import org.mapstruct.Mapper;
+import org.mapstruct.NullValueCheckStrategy;
+import org.mapstruct.NullValuePropertyMappingStrategy;
+
+import de.itvsh.kop.pluto.common.grpc.GrpcFormDataMapper;
+import de.itvsh.ozg.pluto.vorgang.GrpcAntragsteller;
+
+@Mapper(uses = { GrpcFormDataMapper.class }, //
+		nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE, //
+		nullValueCheckStrategy = NullValueCheckStrategy.ALWAYS)
+interface AntragstellerMapper {
+
+	Antragsteller toAntragsteller(GrpcAntragsteller antragsteller);
+}
\ No newline at end of file
diff --git a/goofy-server/src/main/java/de/itvsh/goofy/vorgang/AssignUserCommandBody.java b/goofy-server/src/main/java/de/itvsh/goofy/vorgang/AssignUserCommandBody.java
new file mode 100644
index 0000000000000000000000000000000000000000..f1c7fce660b022810ab0d4768245df99c8f2f7f3
--- /dev/null
+++ b/goofy-server/src/main/java/de/itvsh/goofy/vorgang/AssignUserCommandBody.java
@@ -0,0 +1,36 @@
+/*
+ * 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.itvsh.goofy.vorgang;
+
+import de.itvsh.goofy.common.LinkedUserProfileResource;
+import de.itvsh.goofy.common.command.CommandBody;
+import de.itvsh.goofy.common.user.UserId;
+import lombok.Getter;
+
+@Getter
+public class AssignUserCommandBody implements CommandBody {
+
+	@LinkedUserProfileResource
+	private UserId assignedTo;
+}
diff --git a/goofy-server/src/main/java/de/itvsh/goofy/vorgang/ClientAttributeUtils.java b/goofy-server/src/main/java/de/itvsh/goofy/vorgang/ClientAttributeUtils.java
new file mode 100644
index 0000000000000000000000000000000000000000..79cee65868070efc3e04ae220dee80e1b2fb2187
--- /dev/null
+++ b/goofy-server/src/main/java/de/itvsh/goofy/vorgang/ClientAttributeUtils.java
@@ -0,0 +1,43 @@
+/*
+ * 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.itvsh.goofy.vorgang;
+
+import java.util.List;
+import java.util.stream.Stream;
+
+import org.apache.commons.lang3.StringUtils;
+
+import de.itvsh.ozg.pluto.grpc.clientAttribute.GrpcClientAttribute;
+import de.itvsh.ozg.pluto.grpc.clientAttribute.GrpcClientAttributeValue;
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+final class ClientAttributeUtils {
+	public static Stream<GrpcClientAttributeValue> findByName(String attributeName, List<GrpcClientAttribute> clientAttributes) {
+		return clientAttributes.stream()
+				.filter(attribute -> StringUtils.equals(attribute.getAttributeName(), attributeName))
+				.map(GrpcClientAttribute::getValue);
+	}
+}
diff --git a/goofy-server/src/main/java/de/itvsh/goofy/vorgang/Eingang.java b/goofy-server/src/main/java/de/itvsh/goofy/vorgang/Eingang.java
new file mode 100644
index 0000000000000000000000000000000000000000..52ee93023a3dc7573b0995319fbabb932d82e187
--- /dev/null
+++ b/goofy-server/src/main/java/de/itvsh/goofy/vorgang/Eingang.java
@@ -0,0 +1,53 @@
+/*
+ * 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.itvsh.goofy.vorgang;
+
+import java.util.Map;
+
+import lombok.AccessLevel;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.ToString;
+
+@Getter
+@Builder
+@ToString
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+@AllArgsConstructor(access = AccessLevel.PACKAGE)
+public class Eingang {
+
+	private String id;
+
+	private int numberOfAttachments;
+	private int numberOfRepresentations;
+
+	private ZustaendigeStelle zustaendigeStelle;
+	private Antragsteller antragsteller;
+	private EingangHeader header;
+
+	@ToString.Exclude
+	private Map<String, Object> formData;
+}
diff --git a/goofy-server/src/main/java/de/itvsh/goofy/vorgang/EingangHeader.java b/goofy-server/src/main/java/de/itvsh/goofy/vorgang/EingangHeader.java
new file mode 100644
index 0000000000000000000000000000000000000000..8e80c0296fb404127b93cdfa5c52edd002ca363f
--- /dev/null
+++ b/goofy-server/src/main/java/de/itvsh/goofy/vorgang/EingangHeader.java
@@ -0,0 +1,44 @@
+/*
+ * 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.itvsh.goofy.vorgang;
+
+import java.time.ZonedDateTime;
+
+import lombok.Builder;
+import lombok.Getter;
+
+@Getter
+@Builder
+public class EingangHeader {
+
+	private String requestId;
+	private ZonedDateTime createdAt;
+	private String formId;
+	private String formName;
+	private String sender;
+	private String customer;
+	private String customerId;
+	private String client;
+	private String clientId;
+}
diff --git a/goofy-server/src/main/java/de/itvsh/goofy/vorgang/EingangHeaderMapper.java b/goofy-server/src/main/java/de/itvsh/goofy/vorgang/EingangHeaderMapper.java
new file mode 100644
index 0000000000000000000000000000000000000000..49d096ffbf611ff78aef9217e8683a80c5ed3d61
--- /dev/null
+++ b/goofy-server/src/main/java/de/itvsh/goofy/vorgang/EingangHeaderMapper.java
@@ -0,0 +1,40 @@
+/*
+ * 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.itvsh.goofy.vorgang;
+
+import java.time.ZonedDateTime;
+
+import org.mapstruct.Mapper;
+
+import de.itvsh.ozg.pluto.vorgang.GrpcEingangHeader;
+
+@Mapper
+interface EingangHeaderMapper {
+
+	EingangHeader toEingangHeader(GrpcEingangHeader header);
+
+	public static ZonedDateTime mapDateStrToZonedDateTime(String zdtStr) {
+		return ZonedDateTime.parse(zdtStr);
+	}
+}
\ No newline at end of file
diff --git a/goofy-server/src/main/java/de/itvsh/goofy/vorgang/EingangMapper.java b/goofy-server/src/main/java/de/itvsh/goofy/vorgang/EingangMapper.java
new file mode 100644
index 0000000000000000000000000000000000000000..91764d4ead04d2fd8c89efb55fceb3f52c2c8a73
--- /dev/null
+++ b/goofy-server/src/main/java/de/itvsh/goofy/vorgang/EingangMapper.java
@@ -0,0 +1,41 @@
+/*
+ * 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.itvsh.goofy.vorgang;
+
+import org.mapstruct.CollectionMappingStrategy;
+import org.mapstruct.Mapper;
+import org.mapstruct.Mapping;
+
+import de.itvsh.kop.pluto.common.grpc.GrpcFormDataMapper;
+import de.itvsh.ozg.pluto.vorgang.GrpcEingang;
+
+@Mapper(uses = { AntragstellerMapper.class,
+		EingangHeaderMapper.class, ZustaendigeStelleMapper.class,
+		GrpcFormDataMapper.class }, collectionMappingStrategy = CollectionMappingStrategy.ADDER_PREFERRED)
+interface EingangMapper {
+
+	@Mapping(source = "formData", target = "formData")
+	Eingang fromGrpc(GrpcEingang eingang);
+
+}
diff --git a/goofy-server/src/main/java/de/itvsh/goofy/vorgang/FindVorgaengeHeaderRequestCriteria.java b/goofy-server/src/main/java/de/itvsh/goofy/vorgang/FindVorgaengeHeaderRequestCriteria.java
new file mode 100644
index 0000000000000000000000000000000000000000..91e7d6490e166aca78a5aae55364680236767fcf
--- /dev/null
+++ b/goofy-server/src/main/java/de/itvsh/goofy/vorgang/FindVorgaengeHeaderRequestCriteria.java
@@ -0,0 +1,42 @@
+/*
+ * 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.itvsh.goofy.vorgang;
+
+import java.util.Optional;
+
+import de.itvsh.goofy.common.user.UserId;
+import lombok.Builder;
+import lombok.Getter;
+
+@Builder(toBuilder = true)
+@Getter
+class FindVorgaengeHeaderRequestCriteria {
+	private int page;
+	private Optional<Integer> requestLimit;
+	private int limit;
+	private int offset;
+	private Optional<String> searchBy;
+	private OrderBy orderBy;
+	private Optional<UserId> assignedTo;
+}
diff --git a/goofy-server/src/main/java/de/itvsh/goofy/vorgang/OrderBy.java b/goofy-server/src/main/java/de/itvsh/goofy/vorgang/OrderBy.java
new file mode 100644
index 0000000000000000000000000000000000000000..9f5058c8b0a6437b767dd479ec72d3f4772ea17c
--- /dev/null
+++ b/goofy-server/src/main/java/de/itvsh/goofy/vorgang/OrderBy.java
@@ -0,0 +1,29 @@
+/*
+ * 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.itvsh.goofy.vorgang;
+
+enum OrderBy {
+	PRIORITY,
+	EA_PRIORITY
+}
diff --git a/goofy-server/src/main/java/de/itvsh/goofy/vorgang/ResetNewPostfachNachrichtBody.java b/goofy-server/src/main/java/de/itvsh/goofy/vorgang/ResetNewPostfachNachrichtBody.java
new file mode 100644
index 0000000000000000000000000000000000000000..0297ed07eed3f71889ded8c408710a90fefabd5a
--- /dev/null
+++ b/goofy-server/src/main/java/de/itvsh/goofy/vorgang/ResetNewPostfachNachrichtBody.java
@@ -0,0 +1,33 @@
+/*
+ * 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.itvsh.goofy.vorgang;
+
+import lombok.Getter;
+import lombok.Setter;
+
+@Getter
+@Setter
+class ResetNewPostfachNachrichtBody {
+	private boolean hasNewPostfachNachricht;
+}
diff --git a/goofy-server/src/main/java/de/itvsh/goofy/vorgang/VorgaengeHeaderResponse.java b/goofy-server/src/main/java/de/itvsh/goofy/vorgang/VorgaengeHeaderResponse.java
new file mode 100644
index 0000000000000000000000000000000000000000..070b530907b5a4bb64d2042567731ce59b193c1b
--- /dev/null
+++ b/goofy-server/src/main/java/de/itvsh/goofy/vorgang/VorgaengeHeaderResponse.java
@@ -0,0 +1,38 @@
+/*
+ * 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.itvsh.goofy.vorgang;
+
+import java.util.List;
+
+import lombok.Builder;
+import lombok.Getter;
+
+@Getter
+@Builder
+class VorgaengeHeaderResponse {
+
+	private List<VorgangHeader> vorgaengeHeader;
+	private long total;
+
+}
\ No newline at end of file
diff --git a/goofy-server/src/main/java/de/itvsh/goofy/vorgang/Vorgang.java b/goofy-server/src/main/java/de/itvsh/goofy/vorgang/Vorgang.java
new file mode 100644
index 0000000000000000000000000000000000000000..50e976cb17b89f2ecd795c35e778ffb90bb3b679
--- /dev/null
+++ b/goofy-server/src/main/java/de/itvsh/goofy/vorgang/Vorgang.java
@@ -0,0 +1,68 @@
+/*
+ * 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.itvsh.goofy.vorgang;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+
+import de.itvsh.goofy.common.user.UserRole;
+
+public interface Vorgang {
+
+	enum VorgangStatus {
+		NEU, ANGENOMMEN, VERWORFEN, IN_BEARBEITUNG, BESCHIEDEN, ABGESCHLOSSEN, WEITERGELEITET;
+
+		private Map<String, Set<VorgangStatus>> allowedFollowStatusByRole = new HashMap<>();
+
+		static {
+			NEU.allowedFollowStatusByRole.put(UserRole.EINHEITLICHER_ANSPRECHPARTNER, Set.of(WEITERGELEITET, VERWORFEN));
+			IN_BEARBEITUNG.allowedFollowStatusByRole.put(UserRole.EINHEITLICHER_ANSPRECHPARTNER, Set.of(ABGESCHLOSSEN, WEITERGELEITET));
+			VERWORFEN.allowedFollowStatusByRole.put(UserRole.EINHEITLICHER_ANSPRECHPARTNER, Set.of(NEU));
+			ABGESCHLOSSEN.allowedFollowStatusByRole.put(UserRole.EINHEITLICHER_ANSPRECHPARTNER, Set.of(IN_BEARBEITUNG));
+
+			NEU.allowedFollowStatusByRole.put(UserRole.VERWALTUNG_USER, Set.of(ANGENOMMEN, VERWORFEN));
+			VERWORFEN.allowedFollowStatusByRole.put(UserRole.VERWALTUNG_USER, Set.of(NEU));
+			ANGENOMMEN.allowedFollowStatusByRole.put(UserRole.VERWALTUNG_USER, Set.of(IN_BEARBEITUNG));
+			IN_BEARBEITUNG.allowedFollowStatusByRole.put(UserRole.VERWALTUNG_USER, Set.of(BESCHIEDEN, ANGENOMMEN));
+			BESCHIEDEN.allowedFollowStatusByRole.put(UserRole.VERWALTUNG_USER, Set.of(ABGESCHLOSSEN, IN_BEARBEITUNG));
+			ABGESCHLOSSEN.allowedFollowStatusByRole.put(UserRole.VERWALTUNG_USER, Set.of(IN_BEARBEITUNG));
+		}
+
+		public boolean isTransactionAllowed(String userRole, VorgangStatus newStatus) {
+			if (Objects.isNull(allowedFollowStatusByRole.get(userRole))) {
+				return false;
+			}
+			return allowedFollowStatusByRole.get(userRole).contains(newStatus);
+		}
+	}
+
+	String getId();
+
+	long getVersion();
+
+	VorgangStatus getStatus();
+
+}
\ No newline at end of file
diff --git a/goofy-server/src/main/java/de/itvsh/goofy/vorgang/VorgangAuthorizationService.java b/goofy-server/src/main/java/de/itvsh/goofy/vorgang/VorgangAuthorizationService.java
new file mode 100644
index 0000000000000000000000000000000000000000..8a42994afb9501211d2ce12d91d44f1ace1f0493
--- /dev/null
+++ b/goofy-server/src/main/java/de/itvsh/goofy/vorgang/VorgangAuthorizationService.java
@@ -0,0 +1,47 @@
+/*
+ * 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.itvsh.goofy.vorgang;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import de.itvsh.goofy.common.user.CurrentUserService;
+import de.itvsh.goofy.common.user.UserRole;
+import de.itvsh.goofy.vorgang.Vorgang.VorgangStatus;
+
+@Service
+class VorgangAuthorizationService {
+
+	static final String VERIFY_BY_USER_ROLE = "@vorgangAuthorizationService.verifyByUserRole(returnObject.getContent())";
+
+	@Autowired
+	private CurrentUserService userService;
+
+	public boolean verifyByUserRole(VorgangWithEingang vorgang) {
+		if (userService.hasRole(UserRole.VERWALTUNG_POSTSTELLE)) {
+			return vorgang.getStatus() == VorgangStatus.NEU;
+		}
+		return true;
+	}
+}
\ No newline at end of file
diff --git a/goofy-server/src/main/java/de/itvsh/goofy/vorgang/VorgangController.java b/goofy-server/src/main/java/de/itvsh/goofy/vorgang/VorgangController.java
new file mode 100644
index 0000000000000000000000000000000000000000..a62fa05a4d234b2893240a0ee9f862ebbbf583f6
--- /dev/null
+++ b/goofy-server/src/main/java/de/itvsh/goofy/vorgang/VorgangController.java
@@ -0,0 +1,117 @@
+/*
+ * 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.itvsh.goofy.vorgang;
+
+import java.util.Optional;
+
+import javax.servlet.http.HttpServletResponse;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.hateoas.CollectionModel;
+import org.springframework.hateoas.EntityModel;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
+import org.springframework.security.access.prepost.PostAuthorize;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PutMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import de.itvsh.goofy.common.clientattribute.ClientAttributeService;
+import de.itvsh.goofy.common.user.UserId;
+
+@RestController
+@RequestMapping(VorgangController.PATH)
+public class VorgangController {
+
+	static final String PATH = "/api/vorgangs"; // NOSONAR
+	static final String PARAM_SEARCH = "searchBy";
+	static final String PARAM_PAGE = "page";
+	static final String PARAM_LIMIT = "limit";
+	static final String PARAM_ASSIGNED_TO = "assignedTo";
+
+	static final int VORGANG_PAGE_SIZE = 100;
+
+	@Autowired
+	private ClientAttributeService clientAttributeService;
+	@Autowired
+	private VorgangService vorgangService;
+	@Autowired
+	private VorgangModelAssembler modelAssembler;
+
+	@GetMapping
+	public CollectionModel<EntityModel<Vorgang>> getVorgangListByPage(@RequestParam(defaultValue = "0") int page,
+			@RequestParam Optional<String> searchBy, @RequestParam Optional<Integer> limit, @RequestParam Optional<UserId> assignedTo) {
+		var requestCriteria = buildFindVorgaengeRequestCriteria(page, searchBy, limit, assignedTo);
+
+		var vorgaengeHeaderResponse = vorgangService.findVorgaengeHeader(requestCriteria);
+
+		return modelAssembler.toCollectionModel(vorgaengeHeaderResponse.getVorgaengeHeader().stream(), vorgaengeHeaderResponse, requestCriteria);
+	}
+
+	FindVorgaengeHeaderRequestCriteria buildFindVorgaengeRequestCriteria(int page, Optional<String> searchBy, Optional<Integer> limit,
+			Optional<UserId> assignedTo) {
+		return FindVorgaengeHeaderRequestCriteria.builder()
+				.page(page)
+				.requestLimit(limit)
+				.limit(getLimitValue(limit))
+				.offset(calculateOffset(page, limit))
+				.searchBy(searchBy)
+				.assignedTo(assignedTo)
+				.build();
+	}
+
+	private int calculateOffset(final int page, final Optional<Integer> limit) {
+		return page * getLimitValue(limit);
+	}
+
+	private int getLimitValue(Optional<Integer> limit) {
+		return limit.orElse(VORGANG_PAGE_SIZE);
+	}
+
+	@PostAuthorize(VorgangAuthorizationService.VERIFY_BY_USER_ROLE)
+	@GetMapping("/{vorgangId}")
+	public EntityModel<Vorgang> getVorgangWithEingang(@PathVariable String vorgangId) {
+		return modelAssembler.toModel(getVorgang(vorgangId));
+	}
+
+	public VorgangWithEingang getVorgang(String vorgangId) {
+		return vorgangService.findVorgangWithEingang(vorgangId);
+	}
+
+	@PutMapping(path = "/{vorgangId}/hasNewPostfachNachricht", consumes = MediaType.APPLICATION_JSON_VALUE)
+	public void resetNewPostfachNachricht(@PathVariable String vorgangId, @RequestBody ResetNewPostfachNachrichtBody body,
+			HttpServletResponse response) {
+		if (body.isHasNewPostfachNachricht()) {
+			response.setStatus(HttpStatus.UNPROCESSABLE_ENTITY.value());
+			return;
+		}
+
+		clientAttributeService.resetPostfachNachricht(vorgangId);
+		response.setStatus(HttpStatus.NO_CONTENT.value());
+	}
+}
\ No newline at end of file
diff --git a/goofy-server/src/main/java/de/itvsh/goofy/vorgang/VorgangHeader.java b/goofy-server/src/main/java/de/itvsh/goofy/vorgang/VorgangHeader.java
new file mode 100644
index 0000000000000000000000000000000000000000..0aee2c0b3d84e66a771dddb632a6b22728f8ad38
--- /dev/null
+++ b/goofy-server/src/main/java/de/itvsh/goofy/vorgang/VorgangHeader.java
@@ -0,0 +1,64 @@
+/*
+ * 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.itvsh.goofy.vorgang;
+
+import java.time.LocalDate;
+import java.time.ZonedDateTime;
+
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.annotation.JsonProperty.Access;
+
+import de.itvsh.goofy.common.LinkedUserProfileResource;
+import de.itvsh.goofy.common.user.UserId;
+import lombok.Builder;
+import lombok.Getter;
+import lombok.With;
+
+@Getter
+@Builder
+class VorgangHeader implements Vorgang {
+
+	@JsonIgnore
+	private String id;
+	@JsonIgnore
+	private long version;
+
+	@LinkedUserProfileResource
+	private UserId assignedTo;
+
+	private String name;
+	private VorgangStatus status;
+	private ZonedDateTime createdAt;
+	private String aktenzeichen;
+	private String nummer;
+
+	@With
+	@JsonProperty(access = Access.READ_ONLY)
+	private LocalDate nextFrist;
+
+	private boolean hasPostfachNachricht;
+
+	private boolean hasNewPostfachNachricht;
+}
diff --git a/goofy-server/src/main/java/de/itvsh/goofy/vorgang/VorgangHeaderMapper.java b/goofy-server/src/main/java/de/itvsh/goofy/vorgang/VorgangHeaderMapper.java
new file mode 100644
index 0000000000000000000000000000000000000000..b87f10b0283402e95d98b4979fe5c881a346dcab
--- /dev/null
+++ b/goofy-server/src/main/java/de/itvsh/goofy/vorgang/VorgangHeaderMapper.java
@@ -0,0 +1,63 @@
+/*
+ * 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.itvsh.goofy.vorgang;
+
+import java.time.LocalDate;
+import java.util.List;
+
+import org.mapstruct.CollectionMappingStrategy;
+import org.mapstruct.Mapper;
+import org.mapstruct.Mapping;
+import org.mapstruct.ReportingPolicy;
+
+import de.itvsh.goofy.common.clientattribute.ClientAttributeService;
+import de.itvsh.goofy.common.user.UserIdMapper;
+import de.itvsh.ozg.pluto.grpc.clientAttribute.GrpcClientAttribute;
+import de.itvsh.ozg.pluto.grpc.clientAttribute.GrpcClientAttributeValue;
+import de.itvsh.ozg.pluto.vorgang.GrpcVorgangHeader;
+
+@Mapper(uses = UserIdMapper.class, unmappedTargetPolicy = ReportingPolicy.WARN, collectionMappingStrategy = CollectionMappingStrategy.ADDER_PREFERRED)
+interface VorgangHeaderMapper {
+
+	static final String WIEDERVORLAGE_NEXT_FRIST_ATTRIBUTE_NAME = "nextWiedervorlageFrist";
+	static final String HAS_POSTFACH_NACHRICHT_ATTRIBUTE_NAME = "hasPostfachNachricht";
+	static final String HAS_NEW_POSTFACH_NACHRICHT_ATTRIBUTE_NAME = ClientAttributeService.HAS_NEW_POSTFACH_NACHRICHT_ATTRIBUTE_NAME;
+
+	@Mapping(target = "nextFrist", source = "clientAttributesList")
+	@Mapping(target = "hasPostfachNachricht", expression = "java(mapBoolClientAttributet(vorgangHeader.getClientAttributesList(), HAS_POSTFACH_NACHRICHT_ATTRIBUTE_NAME))")
+	@Mapping(target = "hasNewPostfachNachricht", expression = "java(mapBoolClientAttributet(vorgangHeader.getClientAttributesList(), HAS_NEW_POSTFACH_NACHRICHT_ATTRIBUTE_NAME))")
+	VorgangHeader toVorgangHeader(GrpcVorgangHeader vorgangHeader);
+
+	default LocalDate mapNextFrist(List<GrpcClientAttribute> clientAttributes) {
+		return ClientAttributeUtils.findByName(WIEDERVORLAGE_NEXT_FRIST_ATTRIBUTE_NAME, clientAttributes)
+				.map(GrpcClientAttributeValue::getStringValue)
+				.map(LocalDate::parse).findFirst().orElse(null);
+	}
+
+	default boolean mapBoolClientAttributet(List<GrpcClientAttribute> clientAttributes, String attributeName) {
+		return ClientAttributeUtils.findByName(attributeName, clientAttributes)
+				.map(GrpcClientAttributeValue::getBoolValue)
+				.findFirst().orElse(false);
+	}
+}
diff --git a/goofy-server/src/main/java/de/itvsh/goofy/vorgang/VorgangModelAssembler.java b/goofy-server/src/main/java/de/itvsh/goofy/vorgang/VorgangModelAssembler.java
new file mode 100644
index 0000000000000000000000000000000000000000..3b29f1cfbc65d7908a03148bc2740aabb68c4182
--- /dev/null
+++ b/goofy-server/src/main/java/de/itvsh/goofy/vorgang/VorgangModelAssembler.java
@@ -0,0 +1,142 @@
+/*
+ * 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.itvsh.goofy.vorgang;
+
+import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.*;
+
+import java.util.Optional;
+import java.util.stream.Stream;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.hateoas.CollectionModel;
+import org.springframework.hateoas.EntityModel;
+import org.springframework.hateoas.Link;
+import org.springframework.hateoas.server.RepresentationModelAssembler;
+import org.springframework.stereotype.Component;
+import org.springframework.web.util.UriComponentsBuilder;
+
+import de.itvsh.goofy.common.ModelBuilder;
+import de.itvsh.goofy.common.command.CommandController.CommandByRelationController;
+import de.itvsh.goofy.common.user.CurrentUserService;
+import de.itvsh.goofy.common.user.UserRole;
+import de.itvsh.goofy.wiedervorlage.WiedervorlageController;
+
+@Component
+class VorgangModelAssembler implements RepresentationModelAssembler<Vorgang, EntityModel<Vorgang>> {
+
+	static final String REL_NEXT = "next";
+	static final String REL_PREVIOUS = "previous";
+	static final String REL_VORGANG_MIT_EINGANG = "vorgang-mit-eingang";
+	static final String REL_WIEDERVORLAGEN = "wiedervorlagen";
+	static final String REL_VORGANG_ASSIGN = "assign";
+
+	@Autowired
+	private CurrentUserService userService;
+
+	public CollectionModel<EntityModel<Vorgang>> toCollectionModel(Stream<? extends VorgangHeader> entities, VorgaengeHeaderResponse response,
+			FindVorgaengeHeaderRequestCriteria requestCriteria) {
+		// FIXME selfLink
+		var model = CollectionModel.of(entities.map(this::toModel).toList(), linkTo(VorgangController.class).withSelfRel());
+
+		var currentPage = requestCriteria.getPage();
+		var lastEntryIndex = getLastEntryIndex(currentPage, response.getVorgaengeHeader().size(), requestCriteria.getLimit());
+
+		getPreviousPage(currentPage).ifPresent(page -> model.add(buildPageLink(page, REL_PREVIOUS, requestCriteria)));
+		getNextPage(response.getTotal(), lastEntryIndex, currentPage).ifPresent(page -> model.add(buildPageLink(page, REL_NEXT, requestCriteria)));
+
+		return model;
+	}
+
+	int getLastEntryIndex(int currentPage, int currentPageSize, int pageSize) {
+		if (isFirstPage(currentPage)) {
+			return currentPageSize;
+		}
+		return currentPageSize + (currentPage * pageSize);
+	}
+
+	Optional<Integer> getPreviousPage(int currentPage) {
+		if (isFirstPage(currentPage)) {
+			return Optional.empty();
+		}
+		return Optional.of(currentPage - 1);
+	}
+
+	private boolean isFirstPage(int currentPage) {
+		return currentPage == 0;
+	}
+
+	Optional<Integer> getNextPage(long total, int lastEntryIndex, int currentPage) {
+		if (total > lastEntryIndex) {
+			return Optional.of(currentPage + 1);
+		}
+		return Optional.empty();
+	}
+
+	private Link buildPageLink(int page, String linkRel, FindVorgaengeHeaderRequestCriteria requestCriteria) {
+		return createLink(page, requestCriteria).withRel(linkRel);
+	}
+
+	private Link createLink(int page, FindVorgaengeHeaderRequestCriteria requestCriteria) {
+		var uriBuilder = createUriBuilder(page);
+		addSearchParameter(requestCriteria, uriBuilder);
+		addLimitParameter(requestCriteria, uriBuilder);
+		addAssignedToParameter(requestCriteria, uriBuilder);
+		return Link.of(uriBuilder.build().toUri().toASCIIString());
+	}
+
+	UriComponentsBuilder createUriBuilder(int page) {
+		return linkTo(VorgangController.class).toUriComponentsBuilder().queryParam(VorgangController.PARAM_PAGE, page);
+	}
+
+	void addLimitParameter(FindVorgaengeHeaderRequestCriteria requestCriteria, UriComponentsBuilder uriBuilder) {
+		requestCriteria.getRequestLimit().ifPresent(limit -> uriBuilder.queryParam(VorgangController.PARAM_LIMIT, limit));
+	}
+
+	void addSearchParameter(FindVorgaengeHeaderRequestCriteria requestCriteria, UriComponentsBuilder uriBuilder) {
+		requestCriteria.getSearchBy().ifPresent(searchBy -> uriBuilder.queryParam(VorgangController.PARAM_SEARCH, searchBy));
+	}
+
+	void addAssignedToParameter(FindVorgaengeHeaderRequestCriteria requestCriteria, UriComponentsBuilder uriBuilder) {
+		requestCriteria.getAssignedTo().ifPresent(assignedTo -> uriBuilder.queryParam(VorgangController.PARAM_ASSIGNED_TO, assignedTo));
+	}
+
+	@Override
+	public EntityModel<Vorgang> toModel(Vorgang vorgang) {
+		var selfLinkBuilder = linkTo(VorgangController.class).slash(vorgang.getId());
+		var assignLink = linkTo(methodOn(CommandByRelationController.class).createCommand(vorgang.getId(), vorgang.getId(),
+				vorgang.getVersion(), null)).withRel(REL_VORGANG_ASSIGN);
+		var wiedervorlagenLink = linkTo(methodOn(WiedervorlageController.class).findByVorgang(vorgang.getId())).withRel(REL_WIEDERVORLAGEN);
+
+		return ModelBuilder.fromEntity(vorgang)
+				.addLink(selfLinkBuilder.withSelfRel())
+				.addLink(selfLinkBuilder.withRel(REL_VORGANG_MIT_EINGANG))
+				.ifMatch(this::isVerwaltung).addLink(assignLink)
+				.ifMatch(() -> userService.hasRole(UserRole.VERWALTUNG_USER)).addLink(wiedervorlagenLink)
+				.buildModel();
+	}
+
+	private boolean isVerwaltung() {
+		return userService.hasRole(UserRole.VERWALTUNG_USER) || userService.hasRole(UserRole.VERWALTUNG_POSTSTELLE);
+	}
+}
\ No newline at end of file
diff --git a/goofy-server/src/main/java/de/itvsh/goofy/vorgang/VorgangRemoteService.java b/goofy-server/src/main/java/de/itvsh/goofy/vorgang/VorgangRemoteService.java
new file mode 100644
index 0000000000000000000000000000000000000000..0e0f3d1baf9e77999c732148fe2951d238a55d70
--- /dev/null
+++ b/goofy-server/src/main/java/de/itvsh/goofy/vorgang/VorgangRemoteService.java
@@ -0,0 +1,152 @@
+/*
+ * 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.itvsh.goofy.vorgang;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Optional;
+import java.util.stream.Collectors;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import de.itvsh.goofy.common.user.CurrentUserService;
+import de.itvsh.goofy.common.user.UserId;
+import de.itvsh.goofy.common.user.UserRole;
+import de.itvsh.goofy.vorgang.Vorgang.VorgangStatus;
+import de.itvsh.ozg.pluto.vorgang.GrpcFilterBy;
+import de.itvsh.ozg.pluto.vorgang.GrpcFindVorgangRequest;
+import de.itvsh.ozg.pluto.vorgang.GrpcFindVorgangRequest.GrpcOrderBy;
+import de.itvsh.ozg.pluto.vorgang.GrpcFindVorgangResponse;
+import de.itvsh.ozg.pluto.vorgang.GrpcFindVorgangWithEingangRequest;
+import de.itvsh.ozg.pluto.vorgang.GrpcFindVorgangWithEingangResponse;
+import de.itvsh.ozg.pluto.vorgang.VorgangServiceGrpc.VorgangServiceBlockingStub;
+import net.devh.boot.grpc.client.inject.GrpcClient;
+
+@Service
+class VorgangRemoteService {
+
+	@GrpcClient("pluto")
+	private VorgangServiceBlockingStub vorgangServiceStub;
+	@Autowired
+	private VorgangHeaderMapper vorgangHeaderMapper;
+	@Autowired
+	private VorgangWithEingangMapper vorgangWithEingangMapper;
+	@Autowired
+	private CurrentUserService userService;
+
+	public VorgaengeHeaderResponse findVorgaengeHeader(FindVorgaengeHeaderRequestCriteria requestCriteria) {
+		var request = buildFindVorgangRequest(requestCriteria);
+
+		var response = vorgangServiceStub.findVorgang(request);
+
+		return buildVorgaengeHeaderResponse(response);
+	}
+
+	GrpcFindVorgangRequest buildFindVorgangRequest(FindVorgaengeHeaderRequestCriteria requestCriteria) {
+		var requestBuilder = createFindVorgangRequestBuilder(requestCriteria);
+
+		requestCriteria.getSearchBy().ifPresent(requestBuilder::setSearchBy);
+
+		return requestBuilder.build();
+	}
+
+	private GrpcFindVorgangRequest.Builder createFindVorgangRequestBuilder(FindVorgaengeHeaderRequestCriteria requestCriteria) {
+		return GrpcFindVorgangRequest.newBuilder()
+				.setOrderBy(GrpcOrderBy.valueOf(requestCriteria.getOrderBy().name()))
+				.setLimit(requestCriteria.getLimit())
+				.setOffset(requestCriteria.getOffset())
+				.setFilterBy(createFilterBy(requestCriteria.getAssignedTo()));
+	}
+
+	VorgaengeHeaderResponse buildVorgaengeHeaderResponse(GrpcFindVorgangResponse response) {
+		return VorgaengeHeaderResponse.builder()
+				.total(response.getTotal())
+				.vorgaengeHeader(getVorgaengeHeaderFromResponse(response))
+				.build();
+	}
+
+	private List<VorgangHeader> getVorgaengeHeaderFromResponse(GrpcFindVorgangResponse response) {
+		return response.getVorgangList().stream()
+				.map(vorgangHeaderMapper::toVorgangHeader)
+				.collect(Collectors.toList());
+	}
+
+	public VorgangWithEingang findVorgangWithEingang(String vorgangId) {
+		var request = buildFindVorgangWithEingangRequest(vorgangId);
+
+		var response = vorgangServiceStub.findVorgangWithEingang(request);
+
+		return buildVorgangWithEingangResponse(response);
+	}
+
+	GrpcFindVorgangWithEingangRequest buildFindVorgangWithEingangRequest(String vorgangId) {
+		return GrpcFindVorgangWithEingangRequest.newBuilder()
+				.setId(vorgangId)
+				.setFilterBy(createFilterBy(Optional.empty()))
+				.build();
+	}
+
+	GrpcFilterBy createFilterBy(Optional<UserId> assignedTo) {
+		var builder = GrpcFilterBy.newBuilder();
+		assignedTo.ifPresent(assignToId -> builder.setAssignedTo(assignToId.toString()));
+
+		if (userService.hasRole(UserRole.VERWALTUNG_POSTSTELLE)) {
+			return buildFilterByForPoststelle(builder);
+		}
+		if (userService.hasRole(UserRole.EINHEITLICHER_ANSPRECHPARTNER)) {
+			return buildFilterByForEa(builder);
+		}
+
+		return buildFilterByForOtherRoles(builder);
+	}
+
+	GrpcFilterBy buildFilterByForPoststelle(GrpcFilterBy.Builder builder) {
+		return builder
+				.setFilterByOrganisationseinheitenId(false)
+				.addStatus(VorgangStatus.NEU.name())
+				.build();
+	}
+
+	private GrpcFilterBy buildFilterByForEa(GrpcFilterBy.Builder builder) {
+		return builder
+				.setFilterByOrganisationseinheitenId(false)
+				.build();
+	}
+
+	private GrpcFilterBy buildFilterByForOtherRoles(GrpcFilterBy.Builder builder) {
+		return builder
+				.setFilterByOrganisationseinheitenId(true)
+				.addAllOrganisationseinheitId(getOrganisationseinheitenIdsFromUser())
+				.build();
+	}
+
+	Collection<String> getOrganisationseinheitenIdsFromUser() {
+		return userService.getUser().getOrganisationseinheitIds();
+	}
+
+	private VorgangWithEingang buildVorgangWithEingangResponse(GrpcFindVorgangWithEingangResponse response) {
+		return vorgangWithEingangMapper.toVorgangWithEingang(response.getVorgangWithEingang());
+	}
+}
\ No newline at end of file
diff --git a/goofy-server/src/main/java/de/itvsh/goofy/vorgang/VorgangService.java b/goofy-server/src/main/java/de/itvsh/goofy/vorgang/VorgangService.java
new file mode 100644
index 0000000000000000000000000000000000000000..8cb7e2648e4ae80da5ccc0da17b5aa0bf32cc3ff
--- /dev/null
+++ b/goofy-server/src/main/java/de/itvsh/goofy/vorgang/VorgangService.java
@@ -0,0 +1,57 @@
+/*
+ * 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.itvsh.goofy.vorgang;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import de.itvsh.goofy.common.user.CurrentUserService;
+import de.itvsh.goofy.common.user.UserRole;
+
+@Service
+class VorgangService {
+
+	@Autowired
+	private VorgangRemoteService remoteService;
+	@Autowired
+	private CurrentUserService userService;
+
+	public VorgaengeHeaderResponse findVorgaengeHeader(FindVorgaengeHeaderRequestCriteria requestCriteria) {
+		requestCriteria = setOrderBy(requestCriteria);
+
+		return remoteService.findVorgaengeHeader(requestCriteria);
+	}
+
+	FindVorgaengeHeaderRequestCriteria setOrderBy(FindVorgaengeHeaderRequestCriteria requestCriteria) {
+		if (userService.hasRole(UserRole.EINHEITLICHER_ANSPRECHPARTNER)) {
+			return requestCriteria.toBuilder().orderBy(OrderBy.EA_PRIORITY).build();
+		} else {
+			return requestCriteria.toBuilder().orderBy(OrderBy.PRIORITY).build();
+		}
+	}
+
+	public VorgangWithEingang findVorgangWithEingang(String vorgangId) {
+		return remoteService.findVorgangWithEingang(vorgangId);
+	}
+}
\ No newline at end of file
diff --git a/goofy-server/src/main/java/de/itvsh/goofy/vorgang/VorgangWithEingang.java b/goofy-server/src/main/java/de/itvsh/goofy/vorgang/VorgangWithEingang.java
new file mode 100644
index 0000000000000000000000000000000000000000..cd4d4598ab09d4882251fb9f71f9de3f3387f3bb
--- /dev/null
+++ b/goofy-server/src/main/java/de/itvsh/goofy/vorgang/VorgangWithEingang.java
@@ -0,0 +1,60 @@
+/*
+ * 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.itvsh.goofy.vorgang;
+
+import java.time.ZonedDateTime;
+
+import com.fasterxml.jackson.annotation.JsonIgnore;
+
+import de.itvsh.goofy.common.LinkedUserProfileResource;
+import de.itvsh.goofy.common.user.UserId;
+import lombok.AccessLevel;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+
+@Getter
+@Builder
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+@AllArgsConstructor(access = AccessLevel.PACKAGE)
+public class VorgangWithEingang implements Vorgang {
+
+	@JsonIgnore
+	private String id;
+	@JsonIgnore
+	private long version;
+
+	@LinkedUserProfileResource
+	private UserId assignedTo;
+
+	private String name;
+	private VorgangStatus status;
+	private ZonedDateTime createdAt;
+	private String aktenzeichen;
+	private String nummer;
+	private boolean hasNewPostfachNachricht;
+
+	private Eingang eingang;
+}
\ No newline at end of file
diff --git a/goofy-server/src/main/java/de/itvsh/goofy/vorgang/VorgangWithEingangMapper.java b/goofy-server/src/main/java/de/itvsh/goofy/vorgang/VorgangWithEingangMapper.java
new file mode 100644
index 0000000000000000000000000000000000000000..9a68c842f0e371bb75e0c3a34e482b36d22633e1
--- /dev/null
+++ b/goofy-server/src/main/java/de/itvsh/goofy/vorgang/VorgangWithEingangMapper.java
@@ -0,0 +1,50 @@
+/*
+ * 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.itvsh.goofy.vorgang;
+
+import java.util.List;
+
+import org.mapstruct.Mapper;
+import org.mapstruct.Mapping;
+
+import de.itvsh.goofy.common.BaseTypesMapper;
+import de.itvsh.goofy.common.clientattribute.ClientAttributeService;
+import de.itvsh.goofy.common.user.UserIdMapper;
+import de.itvsh.ozg.pluto.grpc.clientAttribute.GrpcClientAttribute;
+import de.itvsh.ozg.pluto.grpc.clientAttribute.GrpcClientAttributeValue;
+import de.itvsh.ozg.pluto.vorgang.GrpcVorgangWithEingang;
+
+@Mapper(uses = { EingangMapper.class, UserIdMapper.class, BaseTypesMapper.class })
+interface VorgangWithEingangMapper {
+
+	@Mapping(target = "hasNewPostfachNachricht", source = "clientAttributesList")
+	VorgangWithEingang toVorgangWithEingang(GrpcVorgangWithEingang vorgangMitEingang);
+
+	default boolean mapHasNewPostfachNachricht(List<GrpcClientAttribute> clientAttributes) {
+		return ClientAttributeUtils.findByName(ClientAttributeService.HAS_NEW_POSTFACH_NACHRICHT_ATTRIBUTE_NAME, clientAttributes)
+				.map(GrpcClientAttributeValue::getBoolValue)
+				.findFirst().orElse(false);
+	}
+
+}
\ No newline at end of file
diff --git a/goofy-server/src/main/java/de/itvsh/goofy/vorgang/VorgangWithEingangProzessor.java b/goofy-server/src/main/java/de/itvsh/goofy/vorgang/VorgangWithEingangProzessor.java
new file mode 100644
index 0000000000000000000000000000000000000000..fa08d2876f97c02484df6fbf5399ed356ab001c8
--- /dev/null
+++ b/goofy-server/src/main/java/de/itvsh/goofy/vorgang/VorgangWithEingangProzessor.java
@@ -0,0 +1,98 @@
+/*
+ * 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.itvsh.goofy.vorgang;
+
+import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.*;
+
+import java.util.Objects;
+import java.util.function.Predicate;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.hateoas.EntityModel;
+import org.springframework.hateoas.LinkRelation;
+import org.springframework.hateoas.server.RepresentationModelProcessor;
+import org.springframework.stereotype.Component;
+
+import de.itvsh.goofy.attachment.AttachmentController;
+import de.itvsh.goofy.common.ModelBuilder;
+import de.itvsh.goofy.common.user.CurrentUserService;
+import de.itvsh.goofy.common.user.UserRole;
+import de.itvsh.goofy.historie.HistorieController;
+import de.itvsh.goofy.kommentar.KommentarController.KommentarByVorgangController;
+import de.itvsh.goofy.postfach.PostfachMailController;
+import de.itvsh.goofy.representation.RepresentationController;
+import de.itvsh.goofy.vorgang.forwarding.ForwardingController;
+
+@Component
+class VorgangWithEingangProzessor implements RepresentationModelProcessor<EntityModel<VorgangWithEingang>> {
+
+	static final LinkRelation REL_KOMMENTARE = LinkRelation.of("kommentare");
+	static final LinkRelation REL_ATTACHMENTS = LinkRelation.of("attachments");
+	static final LinkRelation REL_REPRESENTATIONS = LinkRelation.of("representations");
+	static final LinkRelation REL_POSTFACH_MAILS = LinkRelation.of("postfachMails");
+	static final LinkRelation REL_VORGANG_FORWARDING = LinkRelation.of("forwarding");
+	static final LinkRelation REL_HISTORIE = LinkRelation.of("historie");
+
+	@Autowired
+	private PostfachMailController postfachMailController;
+	@Autowired
+	private CurrentUserService userService;
+
+	private static final Predicate<VorgangWithEingang> HAS_ATTACHMENTS = vorgangWithEingang -> vorgangWithEingang.getEingang()
+			.getNumberOfAttachments() > 0;
+
+	private static final Predicate<VorgangWithEingang> HAS_REPRESENTATIONS = vorgangWithEingang -> vorgangWithEingang.getEingang()
+			.getNumberOfRepresentations() > 0;
+
+	@Override
+	public EntityModel<VorgangWithEingang> process(EntityModel<VorgangWithEingang> model) {
+		var vorgang = model.getContent();
+		if (Objects.isNull(vorgang)) {
+			return model;
+		}
+		return ModelBuilder.fromModel(model)
+				.addLink(linkTo(KommentarByVorgangController.class).slash(vorgang.getId()).slash("kommentars").withRel(REL_KOMMENTARE))
+				.ifMatch(HAS_ATTACHMENTS)
+				.addLink(vorgangWithEingang -> linkTo(methodOn(AttachmentController.class).getAllByEingang(vorgangWithEingang.getEingang().getId()))
+						.withRel(REL_ATTACHMENTS))
+				.ifMatch(HAS_REPRESENTATIONS)
+				.addLink(vorgangWithEingang -> linkTo(
+						methodOn(RepresentationController.class).getAllByEingang(vorgangWithEingang.getEingang().getId()))
+								.withRel(REL_REPRESENTATIONS))
+				.ifMatch(this::isPostfachConfigured)
+				.addLink(linkTo(methodOn(PostfachMailController.class).getAll(vorgang.getId())).withRel(REL_POSTFACH_MAILS))
+				.ifMatch(this::isEinheitlicherAnsprechpartner)
+				.addLink(linkTo(methodOn(ForwardingController.class).findByVorgangId(vorgang.getId())).withRel(REL_VORGANG_FORWARDING))
+				.addLink(linkTo(methodOn(HistorieController.class).getHistorieList(vorgang.getId())).withRel(REL_HISTORIE))
+				.buildModel();
+	}
+
+	private boolean isPostfachConfigured(VorgangWithEingang vorgang) {
+		return postfachMailController.isPostfachConfigured();
+	}
+
+	private boolean isEinheitlicherAnsprechpartner(VorgangWithEingang vorgang) {
+		return userService.hasRole(UserRole.EINHEITLICHER_ANSPRECHPARTNER);
+	}
+}
\ No newline at end of file
diff --git a/goofy-server/src/main/java/de/itvsh/goofy/vorgang/VorgangWithEingangResponse.java b/goofy-server/src/main/java/de/itvsh/goofy/vorgang/VorgangWithEingangResponse.java
new file mode 100644
index 0000000000000000000000000000000000000000..957b6ab30a1f731aa42f4372c8d865157a661643
--- /dev/null
+++ b/goofy-server/src/main/java/de/itvsh/goofy/vorgang/VorgangWithEingangResponse.java
@@ -0,0 +1,33 @@
+/*
+ * 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.itvsh.goofy.vorgang;
+
+import lombok.Builder;
+import lombok.Getter;
+
+@Getter
+@Builder
+public class VorgangWithEingangResponse {
+	private VorgangWithEingang vorgangWithEingang;
+}
diff --git a/goofy-server/src/main/java/de/itvsh/goofy/vorgang/ZustaendigeStelle.java b/goofy-server/src/main/java/de/itvsh/goofy/vorgang/ZustaendigeStelle.java
new file mode 100644
index 0000000000000000000000000000000000000000..3c46aff605d6f27aa0f4709e712f45d85149729a
--- /dev/null
+++ b/goofy-server/src/main/java/de/itvsh/goofy/vorgang/ZustaendigeStelle.java
@@ -0,0 +1,35 @@
+/*
+ * 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.itvsh.goofy.vorgang;
+
+import lombok.Builder;
+import lombok.Getter;
+
+@Getter
+@Builder
+public class ZustaendigeStelle {
+
+	private String organisationseinheitenId;
+	private String email;
+}
\ No newline at end of file
diff --git a/goofy-server/src/main/java/de/itvsh/goofy/vorgang/ZustaendigeStelleMapper.java b/goofy-server/src/main/java/de/itvsh/goofy/vorgang/ZustaendigeStelleMapper.java
new file mode 100644
index 0000000000000000000000000000000000000000..a83f60b09c5a31235c1f3e3c6221dc69280af583
--- /dev/null
+++ b/goofy-server/src/main/java/de/itvsh/goofy/vorgang/ZustaendigeStelleMapper.java
@@ -0,0 +1,33 @@
+/*
+ * 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.itvsh.goofy.vorgang;
+
+import de.itvsh.ozg.pluto.vorgang.GrpcZustaendigeStelle;
+import org.mapstruct.Mapper;
+
+@Mapper
+interface ZustaendigeStelleMapper {
+
+    ZustaendigeStelle mapToZustaendigeStelle(GrpcZustaendigeStelle grpcZustaendigeStelle);
+}
diff --git a/goofy-server/src/main/java/de/itvsh/goofy/vorgang/command/VorgangCommandProzessor.java b/goofy-server/src/main/java/de/itvsh/goofy/vorgang/command/VorgangCommandProzessor.java
new file mode 100644
index 0000000000000000000000000000000000000000..868f2bd8cabd7b11557dbcc0c73cb61098e970b2
--- /dev/null
+++ b/goofy-server/src/main/java/de/itvsh/goofy/vorgang/command/VorgangCommandProzessor.java
@@ -0,0 +1,116 @@
+/*
+ * 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.itvsh.goofy.vorgang.command;
+
+import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.*;
+
+import java.util.Objects;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.hateoas.EntityModel;
+import org.springframework.hateoas.LinkRelation;
+import org.springframework.hateoas.server.RepresentationModelProcessor;
+import org.springframework.stereotype.Component;
+
+import de.itvsh.goofy.common.command.CommandController.CommandByRelationController;
+import de.itvsh.goofy.common.user.CurrentUserService;
+import de.itvsh.goofy.common.user.UserRole;
+import de.itvsh.goofy.vorgang.Vorgang;
+import de.itvsh.goofy.vorgang.Vorgang.VorgangStatus;
+
+@Component
+class VorgangCommandProzessor implements RepresentationModelProcessor<EntityModel<? extends Vorgang>> {
+
+	static final LinkRelation REL_VORGANG_ANNEHMEN = LinkRelation.of("annehmen");
+	static final LinkRelation REL_VORGANG_VERWERFEN = LinkRelation.of("verwerfen");
+	static final LinkRelation REL_VORGANG_ZURUECKHOLEN = LinkRelation.of("zurueckholen");
+	static final LinkRelation REL_VORGANG_BEARBEITEN = LinkRelation.of("bearbeiten");
+	static final LinkRelation REL_VORGANG_BESCHEIDEN = LinkRelation.of("bescheiden");
+	static final LinkRelation REL_VORGANG_ZURUECKSTELLEN = LinkRelation.of("zurueckstellen");
+	static final LinkRelation REL_VORGANG_ABSCHLIESSEN = LinkRelation.of("abschliessen");
+	static final LinkRelation REL_VORGANG_WIEDEREROEFFNEN = LinkRelation.of("wiedereroeffnen");
+
+	@Autowired
+	private CurrentUserService userService;
+
+	@Override
+	public EntityModel<? extends Vorgang> process(EntityModel<? extends Vorgang> model) {
+		var vorgang = model.getContent();
+
+		if (Objects.isNull(vorgang)) {
+			return model;
+		}
+
+		addLinkIfTransactionAllowed(VorgangStatus.ANGENOMMEN, getRelationForAngenommenByStatus(vorgang), vorgang, model);
+		addLinkIfTransactionAllowed(VorgangStatus.VERWORFEN, REL_VORGANG_VERWERFEN, vorgang, model);
+		addLinkIfTransactionAllowed(VorgangStatus.NEU, REL_VORGANG_ZURUECKHOLEN, vorgang, model);
+		addLinkIfTransactionAllowed(VorgangStatus.IN_BEARBEITUNG, getRelationForInBearbeitungByStatus(vorgang), vorgang, model);
+		addLinkIfTransactionAllowed(VorgangStatus.BESCHIEDEN, REL_VORGANG_BESCHEIDEN, vorgang, model);
+		addLinkIfTransactionAllowed(VorgangStatus.ABGESCHLOSSEN, REL_VORGANG_ABSCHLIESSEN, vorgang, model);
+
+		return model;
+	}
+
+	private LinkRelation getRelationForAngenommenByStatus(Vorgang vorgang) {
+		if (vorgang.getStatus() == VorgangStatus.NEU) {
+			return REL_VORGANG_ANNEHMEN;
+		} else {
+			return REL_VORGANG_ZURUECKSTELLEN;
+		}
+	}
+
+	private LinkRelation getRelationForInBearbeitungByStatus(Vorgang vorgang) {
+		if (vorgang.getStatus() == VorgangStatus.ANGENOMMEN) {
+			return REL_VORGANG_BEARBEITEN;
+		} else {
+			return REL_VORGANG_WIEDEREROEFFNEN;
+		}
+	}
+
+	private void addLinkIfTransactionAllowed(VorgangStatus newStatus, LinkRelation relation, Vorgang vorgang,
+			EntityModel<? extends Vorgang> model) {
+		if (isTransactonAllowed(vorgang, newStatus)) {
+			model.add(linkTo(
+					methodOn(CommandByRelationController.class).createCommand(vorgang.getId(), vorgang.getId(), vorgang.getVersion(), null))
+							.withRel(relation));
+		}
+	}
+
+	private boolean isTransactonAllowed(Vorgang vorgang, VorgangStatus newStatus) {
+		return vorgang.getStatus().isTransactionAllowed(getUserRole(), newStatus);
+	}
+
+	String getUserRole() {
+		String role;
+		if (userService.hasRole(UserRole.EINHEITLICHER_ANSPRECHPARTNER)) {
+			role = UserRole.EINHEITLICHER_ANSPRECHPARTNER;
+		} else if (userService.hasRole(UserRole.VERWALTUNG_POSTSTELLE)) {
+			role = UserRole.VERWALTUNG_POSTSTELLE;
+		} else {
+			role = UserRole.VERWALTUNG_USER;
+		}
+
+		return role;
+	}
+}
\ No newline at end of file
diff --git a/goofy-server/src/main/java/de/itvsh/goofy/vorgang/command/VorgangWithEingangCommandProzessor.java b/goofy-server/src/main/java/de/itvsh/goofy/vorgang/command/VorgangWithEingangCommandProzessor.java
new file mode 100644
index 0000000000000000000000000000000000000000..7f39316ea957fa2ceef9a815b34581a88d15173c
--- /dev/null
+++ b/goofy-server/src/main/java/de/itvsh/goofy/vorgang/command/VorgangWithEingangCommandProzessor.java
@@ -0,0 +1,88 @@
+/*
+ * 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.itvsh.goofy.vorgang.command;
+
+import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.*;
+
+import java.util.Objects;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.hateoas.EntityModel;
+import org.springframework.hateoas.LinkRelation;
+import org.springframework.hateoas.server.RepresentationModelProcessor;
+import org.springframework.stereotype.Component;
+
+import de.itvsh.goofy.common.ModelBuilder;
+import de.itvsh.goofy.common.command.CommandController;
+import de.itvsh.goofy.common.command.CommandController.CommandByRelationController;
+import de.itvsh.goofy.common.user.CurrentUserService;
+import de.itvsh.goofy.common.user.UserRole;
+import de.itvsh.goofy.vorgang.Vorgang.VorgangStatus;
+import de.itvsh.goofy.vorgang.VorgangWithEingang;
+import de.itvsh.goofy.vorgang.forwarding.ForwardingController;
+
+@Component
+public class VorgangWithEingangCommandProzessor implements RepresentationModelProcessor<EntityModel<VorgangWithEingang>> {
+
+	static final LinkRelation REL_VORGANG_FORWARD = LinkRelation.of("forward");
+	static final LinkRelation REL_PENDING_COMMANDS = LinkRelation.of("pending-commands");
+
+	@Autowired
+	private CurrentUserService userService;
+	@Autowired
+	private ForwardingController forwardingController;
+	@Autowired
+	private CommandController commandController;
+
+	@Override
+	public EntityModel<VorgangWithEingang> process(EntityModel<VorgangWithEingang> model) {
+		var vorgang = model.getContent();
+		if (Objects.isNull(vorgang)) {
+			return model;
+		}
+		return ModelBuilder.fromModel(model)
+				.ifMatch(this::isForwardingAllowed)
+				.addLink(linkTo(
+						methodOn(CommandByRelationController.class).createCommand(vorgang.getId(), vorgang.getId(), vorgang.getVersion(),
+								null)).withRel(REL_VORGANG_FORWARD))
+				.ifMatch(this::existsPendingCommands)
+				.addLink(linkTo(methodOn(CommandController.class).getPendingCommands(true, vorgang.getId())).withRel(REL_PENDING_COMMANDS))
+				.buildModel();
+	}
+
+	boolean isForwardingAllowed(VorgangWithEingang vorgang) {
+		return userService.hasRole(UserRole.EINHEITLICHER_ANSPRECHPARTNER)
+				&& (vorgang.getStatus() == VorgangStatus.NEU
+						|| (vorgang.getStatus() == VorgangStatus.IN_BEARBEITUNG
+								&& hasForwardingFailed(vorgang)));
+	}
+
+	private boolean hasForwardingFailed(VorgangWithEingang vorgang) {
+		return !forwardingController.findFailedForwardings(vorgang.getId()).isEmpty();
+	}
+
+	private boolean existsPendingCommands(VorgangWithEingang vorgang) {
+		return commandController.existsPendingCommands(vorgang.getId());
+	}
+}
\ No newline at end of file
diff --git a/goofy-server/src/main/java/de/itvsh/goofy/vorgang/forwarding/Forwarding.java b/goofy-server/src/main/java/de/itvsh/goofy/vorgang/forwarding/Forwarding.java
new file mode 100644
index 0000000000000000000000000000000000000000..74b6ba1f25ea904c27cabd197c97cfe33c549f1e
--- /dev/null
+++ b/goofy-server/src/main/java/de/itvsh/goofy/vorgang/forwarding/Forwarding.java
@@ -0,0 +1,57 @@
+/*
+ * 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.itvsh.goofy.vorgang.forwarding;
+
+import java.time.ZonedDateTime;
+
+import com.fasterxml.jackson.annotation.JsonIgnore;
+
+import lombok.Builder;
+import lombok.Getter;
+
+@Getter
+@Builder
+public class Forwarding {
+
+	public enum Status {
+		NEW, SENT, SUCCESSFULL, SEND_ERROR, FAILED
+	}
+
+	@JsonIgnore
+	private String id;
+	@JsonIgnore
+	private String vorgangId;
+	@JsonIgnore
+	private Status status;
+
+	private String zustaendigeStelle;
+
+	private ZonedDateTime createdAt;
+	private String createdBy;
+	private String createdByName;
+
+	private ZonedDateTime sentAt;
+
+	private String errorMessage;
+}
diff --git a/goofy-server/src/main/java/de/itvsh/goofy/vorgang/forwarding/ForwardingController.java b/goofy-server/src/main/java/de/itvsh/goofy/vorgang/forwarding/ForwardingController.java
new file mode 100644
index 0000000000000000000000000000000000000000..298f36ed3084fd14959354b2e345e2d3fa8f5d07
--- /dev/null
+++ b/goofy-server/src/main/java/de/itvsh/goofy/vorgang/forwarding/ForwardingController.java
@@ -0,0 +1,67 @@
+/*
+ * 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.itvsh.goofy.vorgang.forwarding;
+
+import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.hateoas.CollectionModel;
+import org.springframework.hateoas.EntityModel;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import de.itvsh.goofy.vorgang.forwarding.Forwarding.Status;
+
+@RestController
+@RequestMapping(ForwardingController.LIST_PATH)
+public class ForwardingController {
+
+	static final String LIST_PATH = "/api/forwardings"; // NOSONAR
+
+	static final String PARAM_VORGANG_ID = "vorgangId";
+
+	@Autowired
+	private ForwardingService service;
+	@Autowired
+	private ForwardingModelAssembler modelAssembler;
+
+	@GetMapping(params = { PARAM_VORGANG_ID })
+	public CollectionModel<EntityModel<Forwarding>> findByVorgangId(@RequestParam String vorgangId) {
+		return modelAssembler.toCollectionModel(findByVorgang(vorgangId));
+	}
+
+	public List<Forwarding> findFailedForwardings(String vorgangId) {
+		return findByVorgang(vorgangId)
+				.filter(forwarding -> forwarding.getStatus() == Status.FAILED || forwarding.getStatus() == Status.SEND_ERROR)
+				.collect(Collectors.toList());
+	}
+
+	public Stream<Forwarding> findByVorgang(String vorgangId) {
+		return service.findByVorgang(vorgangId);
+	}
+}
\ No newline at end of file
diff --git a/goofy-server/src/main/java/de/itvsh/goofy/vorgang/forwarding/ForwardingLandesnetzInfoService.java b/goofy-server/src/main/java/de/itvsh/goofy/vorgang/forwarding/ForwardingLandesnetzInfoService.java
new file mode 100644
index 0000000000000000000000000000000000000000..1b0e313e8d8bbac81919a1b3642e5353d0dbe178
--- /dev/null
+++ b/goofy-server/src/main/java/de/itvsh/goofy/vorgang/forwarding/ForwardingLandesnetzInfoService.java
@@ -0,0 +1,77 @@
+/*
+ * 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.itvsh.goofy.vorgang.forwarding;
+
+import java.util.Collections;
+import java.util.Objects;
+import java.util.Set;
+
+import javax.annotation.PostConstruct;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import lombok.extern.log4j.Log4j2;
+
+@Log4j2
+@Service
+class ForwardingLandesnetzInfoService {
+
+	@Autowired(required = false)
+	private LandesnetzInfoReadService readService;
+
+	private Set<String> landesnetzInfo = Collections.emptySet();
+
+	@PostConstruct
+	private void initLandesnetzList() {
+		if (Objects.isNull(readService)) {
+			LOG.info("Landesnetz-Info not configured.");
+			return;
+		}
+
+		readService.readLandesnetzInfo().thenAccept(this::assingInfos);
+	}
+
+	private void assingInfos(Set<String> lnInfos) {
+		this.landesnetzInfo = lnInfos;
+		LOG.info("{} Landesnetz-Infos entries read.", landesnetzInfo.size());
+	}
+
+	public boolean isEmailInLandesnetz(String email) {
+		return isLandesnetzIncludingDomain(getDomain(email));
+	}
+
+	private String getDomain(String email) {
+		return email.substring(email.lastIndexOf('@') + 1, email.length());
+	}
+
+	private boolean isLandesnetzIncludingDomain(String emailDomain) {
+		return landesnetzInfo.contains(emailDomain) ||
+				landesnetzInfo.stream().anyMatch(lnInfo -> isDomainMatching(lnInfo, emailDomain));
+	}
+
+	private boolean isDomainMatching(String netzDomain, String emailDomain) {
+		return netzDomain.startsWith(".") && emailDomain.endsWith(netzDomain);
+	}
+}
\ No newline at end of file
diff --git a/goofy-server/src/main/java/de/itvsh/goofy/vorgang/forwarding/ForwardingMapper.java b/goofy-server/src/main/java/de/itvsh/goofy/vorgang/forwarding/ForwardingMapper.java
new file mode 100644
index 0000000000000000000000000000000000000000..7ede4790ca7a692b76af85738c54a5d17417556d
--- /dev/null
+++ b/goofy-server/src/main/java/de/itvsh/goofy/vorgang/forwarding/ForwardingMapper.java
@@ -0,0 +1,35 @@
+/*
+ * 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.itvsh.goofy.vorgang.forwarding;
+
+import org.mapstruct.Mapper;
+
+import de.itvsh.goofy.common.TimeMapper;
+import de.itvsh.ozg.pluto.forwarding.GrpcForwarding;
+
+@Mapper(uses = TimeMapper.class)
+public interface ForwardingMapper {
+
+	Forwarding fromGrpc(GrpcForwarding forwarding);
+}
diff --git a/goofy-server/src/main/java/de/itvsh/goofy/vorgang/forwarding/ForwardingModelAssembler.java b/goofy-server/src/main/java/de/itvsh/goofy/vorgang/forwarding/ForwardingModelAssembler.java
new file mode 100644
index 0000000000000000000000000000000000000000..fc031e6da9c601d8eddf68cc8151f09f0f2e600f
--- /dev/null
+++ b/goofy-server/src/main/java/de/itvsh/goofy/vorgang/forwarding/ForwardingModelAssembler.java
@@ -0,0 +1,89 @@
+/*
+ * 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.itvsh.goofy.vorgang.forwarding;
+
+import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.*;
+
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import org.springframework.hateoas.CollectionModel;
+import org.springframework.hateoas.EntityModel;
+import org.springframework.hateoas.Link;
+import org.springframework.hateoas.LinkRelation;
+import org.springframework.hateoas.server.RepresentationModelAssembler;
+import org.springframework.stereotype.Component;
+
+import de.itvsh.goofy.common.ModelBuilder;
+import de.itvsh.goofy.common.command.CommandController.CommandByRelationController;
+import de.itvsh.goofy.vorgang.forwarding.Forwarding.Status;
+
+@Component
+class ForwardingModelAssembler implements RepresentationModelAssembler<Forwarding, EntityModel<Forwarding>> {
+
+	static final LinkRelation REL_MARK_AS_SUCCESS = LinkRelation.of("mark-as-success");
+	static final LinkRelation REL_MARK_AS_FAIL = LinkRelation.of("mark-as-fail");
+
+	static final LinkRelation REL_SUCCESSFULL = LinkRelation.of("successfull");
+	static final LinkRelation REL_FAILED = LinkRelation.of("failed");
+
+	static final LinkRelation REL_ERROR = LinkRelation.of("error");
+
+	@Override
+	public EntityModel<Forwarding> toModel(Forwarding entity) {
+		var selfLink = linkTo(ForwardingController.class).slash(entity.getId());
+
+		return ModelBuilder.fromEntity(entity)
+				.addLink(linkTo(ForwardingController.class).slash(entity.getId()).withSelfRel())
+				.ifMatch(forwarding -> forwarding.getStatus() == Status.SENT)
+				.addLinks(buildMarkAsSuccessLink(entity))
+				.ifMatch(forwarding -> forwarding.getStatus() == Status.SENT || forwarding.getStatus() == Status.SUCCESSFULL)
+				.addLinks(buildMarkAsFailLink(entity))
+				.ifMatch(forwarding -> forwarding.getStatus() == Status.FAILED)
+				.addLink(selfLink.withRel(REL_FAILED))
+				.ifMatch(forwarding -> forwarding.getStatus() == Status.SUCCESSFULL)
+				.addLink(selfLink.withRel(REL_SUCCESSFULL))
+				.ifMatch(forwarding -> forwarding.getStatus() == Status.SEND_ERROR)
+				.addLink(selfLink.withRel(REL_ERROR))
+				.buildModel();
+	}
+
+	public CollectionModel<EntityModel<Forwarding>> toCollectionModel(Stream<Forwarding> entities) {
+		return CollectionModel.of(entities.map(this::toModel).collect(Collectors.toList()),
+				linkTo(ForwardingController.class).withSelfRel());
+	}
+
+	public Link buildMarkAsSuccessLink(Forwarding forwarding) {
+		return buildLinkWithLinkRel(forwarding, REL_MARK_AS_SUCCESS);
+	}
+
+	public Link buildMarkAsFailLink(Forwarding forwarding) {
+		return buildLinkWithLinkRel(forwarding, REL_MARK_AS_FAIL);
+	}
+
+	private Link buildLinkWithLinkRel(Forwarding forwarding, LinkRelation linkRel) {
+		return linkTo(methodOn(CommandByRelationController.class).createCommand(forwarding.getVorgangId(), forwarding.getId(), -1, null))
+				.withRel(linkRel);
+	}
+}
\ No newline at end of file
diff --git a/goofy-server/src/main/java/de/itvsh/goofy/vorgang/forwarding/ForwardingPasswordSizeConstraint.java b/goofy-server/src/main/java/de/itvsh/goofy/vorgang/forwarding/ForwardingPasswordSizeConstraint.java
new file mode 100644
index 0000000000000000000000000000000000000000..d02f76a21571755587b7c53d96d1bf2141c9fb9a
--- /dev/null
+++ b/goofy-server/src/main/java/de/itvsh/goofy/vorgang/forwarding/ForwardingPasswordSizeConstraint.java
@@ -0,0 +1,52 @@
+/*
+ * 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.itvsh.goofy.vorgang.forwarding;
+
+import static java.lang.annotation.ElementType.*;
+import static java.lang.annotation.RetentionPolicy.*;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import javax.validation.Constraint;
+import javax.validation.Payload;
+
+import de.itvsh.goofy.common.ValidationMessageCodes;
+
+@Retention(RUNTIME)
+@Target({ FIELD, METHOD, PARAMETER, ANNOTATION_TYPE, TYPE_USE })
+@Constraint(validatedBy = { ForwardingPasswordValidator.class })
+@Documented
+public @interface ForwardingPasswordSizeConstraint {
+	String message() default ValidationMessageCodes.FIELD_SIZE;
+
+	Class<?>[] groups() default {};
+
+	Class<? extends Payload>[] payload() default {};
+
+	int max() default 40;
+
+	int min() default 8;
+}
\ No newline at end of file
diff --git a/goofy-server/src/main/java/de/itvsh/goofy/vorgang/forwarding/ForwardingPasswordValidator.java b/goofy-server/src/main/java/de/itvsh/goofy/vorgang/forwarding/ForwardingPasswordValidator.java
new file mode 100644
index 0000000000000000000000000000000000000000..fb27a9c3d1a11ed674bd4db53196d485cd4cc4f1
--- /dev/null
+++ b/goofy-server/src/main/java/de/itvsh/goofy/vorgang/forwarding/ForwardingPasswordValidator.java
@@ -0,0 +1,66 @@
+/*
+ * 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.itvsh.goofy.vorgang.forwarding;
+
+import javax.validation.ConstraintValidator;
+import javax.validation.ConstraintValidatorContext;
+
+import org.apache.commons.lang3.ArrayUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+
+import de.itvsh.goofy.common.ValidationMessageCodes;
+
+class ForwardingPasswordValidator implements ConstraintValidator<ForwardingPasswordSizeConstraint, RedirectRequest> {
+
+	@Autowired
+	private ForwardingLandesnetzInfoService landesnetzInfoService;
+
+	@Override
+	public boolean isValid(RedirectRequest request, ConstraintValidatorContext context) {
+		if (StringUtils.isNotBlank(request.getEmail()) && isNotInLandesnetz(request.getEmail())) {
+			prepareConstraint(context);
+
+			return isValidPassword(request);
+		}
+		return true;
+	}
+
+	private boolean isNotInLandesnetz(String email) {
+		return !landesnetzInfoService.isEmailInLandesnetz(email);
+	}
+
+	boolean isValidPassword(RedirectRequest request) {
+		var password = request.getPassword();
+		var constraint = request.getClass().getAnnotation(ForwardingPasswordSizeConstraint.class);
+
+		return ArrayUtils.isNotEmpty(password) && password.length >= constraint.min() && password.length <= constraint.max();
+	}
+
+	void prepareConstraint(ConstraintValidatorContext context) {
+		context.disableDefaultConstraintViolation();
+		context.buildConstraintViolationWithTemplate(ValidationMessageCodes.FIELD_SIZE)
+				.addPropertyNode("password").addConstraintViolation();
+	}
+}
\ No newline at end of file
diff --git a/goofy-server/src/main/java/de/itvsh/goofy/vorgang/forwarding/ForwardingRemoteService.java b/goofy-server/src/main/java/de/itvsh/goofy/vorgang/forwarding/ForwardingRemoteService.java
new file mode 100644
index 0000000000000000000000000000000000000000..00267c59331749e1b5d4e4e77f0aad9ce37df399
--- /dev/null
+++ b/goofy-server/src/main/java/de/itvsh/goofy/vorgang/forwarding/ForwardingRemoteService.java
@@ -0,0 +1,60 @@
+/*
+ * 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.itvsh.goofy.vorgang.forwarding;
+
+import java.util.stream.Stream;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import de.itvsh.goofy.common.callcontext.ContextService;
+import de.itvsh.ozg.pluto.forwarding.ForwardingServiceGrpc.ForwardingServiceBlockingStub;
+import de.itvsh.ozg.pluto.forwarding.GrpcFindForwardingsRequest;
+import net.devh.boot.grpc.client.inject.GrpcClient;
+
+@Service
+public class ForwardingRemoteService {
+
+	@GrpcClient("pluto")
+	private ForwardingServiceBlockingStub serviceStub;
+
+	@Autowired
+	private ForwardingMapper mapper;
+
+	@Autowired
+	private ContextService contextService;
+
+	public Stream<Forwarding> findForwardings(String vorgangId) {
+		var response = serviceStub.findForwardings(buildFindForwardingsRequest(vorgangId));
+
+		return response.getForwardingsList().stream().map(mapper::fromGrpc);
+	}
+
+	private GrpcFindForwardingsRequest buildFindForwardingsRequest(String vorgangId) {
+		return GrpcFindForwardingsRequest.newBuilder()
+				.setContext(contextService.createCallContext())
+				.setVorgangId(vorgangId)
+				.build();
+	}
+}
diff --git a/goofy-server/src/main/java/de/itvsh/goofy/vorgang/forwarding/ForwardingService.java b/goofy-server/src/main/java/de/itvsh/goofy/vorgang/forwarding/ForwardingService.java
new file mode 100644
index 0000000000000000000000000000000000000000..80b6708d4efeace04bba90b12cd14925de2f4777
--- /dev/null
+++ b/goofy-server/src/main/java/de/itvsh/goofy/vorgang/forwarding/ForwardingService.java
@@ -0,0 +1,40 @@
+/*
+ * 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.itvsh.goofy.vorgang.forwarding;
+
+import java.util.stream.Stream;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+@Service
+class ForwardingService {
+
+	@Autowired
+	private ForwardingRemoteService remoteService;
+
+	public Stream<Forwarding> findByVorgang(String vorgangId) {
+		return remoteService.findForwardings(vorgangId);
+	}
+}
diff --git a/goofy-server/src/main/java/de/itvsh/goofy/vorgang/forwarding/LandesnetzInfoProperties.java b/goofy-server/src/main/java/de/itvsh/goofy/vorgang/forwarding/LandesnetzInfoProperties.java
new file mode 100644
index 0000000000000000000000000000000000000000..fa3546749ef3226b48310f8f1d576b50c638805d
--- /dev/null
+++ b/goofy-server/src/main/java/de/itvsh/goofy/vorgang/forwarding/LandesnetzInfoProperties.java
@@ -0,0 +1,42 @@
+/*
+ * 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.itvsh.goofy.vorgang.forwarding;
+
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.core.io.Resource;
+
+import lombok.Getter;
+import lombok.Setter;
+
+@Getter
+@Setter
+@Configuration
+@ConfigurationProperties(prefix = LandesnetzInfoProperties.LNINFO_CONFIG_PREFIX)
+public class LandesnetzInfoProperties {
+
+	static final String LNINFO_CONFIG_PREFIX = "kop.forwarding.lninfo";
+
+	private Resource url;
+}
diff --git a/goofy-server/src/main/java/de/itvsh/goofy/vorgang/forwarding/LandesnetzInfoReadService.java b/goofy-server/src/main/java/de/itvsh/goofy/vorgang/forwarding/LandesnetzInfoReadService.java
new file mode 100644
index 0000000000000000000000000000000000000000..5cd49f758089dbb21ec299a0fc77da45e5ecb82f
--- /dev/null
+++ b/goofy-server/src/main/java/de/itvsh/goofy/vorgang/forwarding/LandesnetzInfoReadService.java
@@ -0,0 +1,152 @@
+/*
+ * 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.itvsh.goofy.vorgang.forwarding;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.Set;
+import java.util.concurrent.CompletableFuture;
+import java.util.stream.Collectors;
+
+import org.apache.commons.lang3.StringUtils;
+import org.jsoup.Jsoup;
+import org.jsoup.nodes.Document;
+import org.jsoup.nodes.Element;
+import org.jsoup.nodes.Node;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.scheduling.annotation.Async;
+import org.springframework.stereotype.Service;
+import org.springframework.util.StreamUtils;
+
+import de.itvsh.kop.common.errorhandling.TechnicalException;
+import lombok.extern.log4j.Log4j2;
+
+@Log4j2
+@Service
+@ConditionalOnProperty(prefix = LandesnetzInfoProperties.LNINFO_CONFIG_PREFIX, name = { "url" })
+class LandesnetzInfoReadService {
+
+	private static final String LANDESNETZ_TABLE_IDENTIFIER = "tbl_domains";
+	private static final String LNSH = "LNSH";
+	private static final String LNSH_ALTUMGEBUNG = "LNSH Altumgebung";
+
+	@Autowired
+	private LandesnetzInfoProperties properties;
+
+	@Async
+	public CompletableFuture<Set<String>> readLandesnetzInfo() {
+		try {
+			return getResourceAsString()
+					.map(this::parseToDocument)
+					.map(this::parseTableEntries)
+					.filter(Objects::nonNull)
+					.map(CompletableFuture::completedFuture)
+					.orElseGet(() -> {
+						LOG.warn("No Landesnetz-Information found");
+						return CompletableFuture.completedFuture(Collections.emptySet());
+					});
+		} catch (RuntimeException e) {
+			LOG.error("Error on reading Landesnetz-Infos.", e);
+		}
+		return CompletableFuture.completedFuture(Collections.emptySet());
+	}
+
+	private Optional<String> getResourceAsString() {
+		return getInputStream().map(this::copyToString);
+	}
+
+	private Optional<InputStream> getInputStream() {
+		try {
+			return Optional.ofNullable(properties.getUrl().getInputStream());
+		} catch (IOException e) {
+			throw new TechnicalException("Error on reading Landesnetz-Infos", e);
+		}
+	}
+
+	private String copyToString(InputStream stream) {
+		try {
+			return StreamUtils.copyToString(stream, StandardCharsets.UTF_8);
+		} catch (IOException e) {
+			throw new TechnicalException("Error on reading Landesnetz-Infos", e);
+		}
+	}
+
+	private Document parseToDocument(String source) {
+		return Jsoup.parse(source);
+	}
+
+	public Set<String> parseTableEntries(Document resourceDoc) {
+		return parseTable(resourceDoc);
+	}
+
+	private Set<String> parseTable(Document doc) {
+		return getTableColumnValues(doc).stream()
+				.filter(this::isTableEntry)
+				.filter(column -> isValid(getSecondColumn(column)))
+				.map(this::getFirstColumn)
+				.collect(Collectors.toUnmodifiableSet());
+	}
+
+	private List<Element> getTableColumnValues(Document doc) {
+		return doc.select("#" + LANDESNETZ_TABLE_IDENTIFIER + " tr");
+	}
+
+	private boolean isValid(String value) {
+		return isLnsh(value) || isLnshAltumgebung(value);
+	}
+
+	private boolean isLnsh(String value) {
+		return StringUtils.equals(value, LNSH);
+	}
+
+	private boolean isLnshAltumgebung(String value) {
+		return StringUtils.equals(value, LNSH_ALTUMGEBUNG);
+	}
+
+	private boolean isTableEntry(Element element) {
+		return !isHeaderEntry(element) && !element.childNodes().isEmpty();
+	}
+
+	private boolean isHeaderEntry(Node element) {
+		return StringUtils.equals(element.attr("class"), "header");
+	}
+
+	private String getSecondColumn(Element element) {
+		return selectTextFromTdElement(element, 1);
+	}
+
+	private String getFirstColumn(Element element) {
+		return selectTextFromTdElement(element, 0);
+	}
+
+	private String selectTextFromTdElement(Element element, int index) {
+		return element.select("td").get(index).text();
+	}
+}
diff --git a/goofy-server/src/main/java/de/itvsh/goofy/vorgang/forwarding/RedirectRequest.java b/goofy-server/src/main/java/de/itvsh/goofy/vorgang/forwarding/RedirectRequest.java
new file mode 100644
index 0000000000000000000000000000000000000000..e39fa3627da588ab9672b590f51644e74382a90d
--- /dev/null
+++ b/goofy-server/src/main/java/de/itvsh/goofy/vorgang/forwarding/RedirectRequest.java
@@ -0,0 +1,58 @@
+/*
+ * 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.itvsh.goofy.vorgang.forwarding;
+
+import static de.itvsh.goofy.common.ValidationMessageCodes.*;
+
+import javax.validation.constraints.Email;
+import javax.validation.constraints.NotEmpty;
+
+import org.springframework.validation.annotation.Validated;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.annotation.JsonProperty.Access;
+
+import lombok.AccessLevel;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.ToString;
+
+@Builder
+@Getter
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+@AllArgsConstructor(access = AccessLevel.PUBLIC)
+@ToString
+@ForwardingPasswordSizeConstraint
+@Validated
+public class RedirectRequest {
+
+	@NotEmpty(message = FIELD_IS_EMPTY)
+	@Email(message = FIELD_INVALID)
+	private String email;
+
+	@JsonProperty(access = Access.WRITE_ONLY)
+	private char[] password;
+}
\ No newline at end of file
diff --git a/goofy-server/src/main/java/de/itvsh/goofy/wiedervorlage/Wiedervorlage.java b/goofy-server/src/main/java/de/itvsh/goofy/wiedervorlage/Wiedervorlage.java
new file mode 100644
index 0000000000000000000000000000000000000000..e9ca6501ad8849614300964ff7e790dfff82beb6
--- /dev/null
+++ b/goofy-server/src/main/java/de/itvsh/goofy/wiedervorlage/Wiedervorlage.java
@@ -0,0 +1,89 @@
+/*
+ * 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.itvsh.goofy.wiedervorlage;
+
+import static de.itvsh.goofy.common.ValidationMessageCodes.*;
+
+import java.time.LocalDate;
+import java.time.ZonedDateTime;
+import java.util.List;
+
+import javax.validation.constraints.FutureOrPresent;
+import javax.validation.constraints.NotNull;
+import javax.validation.constraints.Size;
+
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.annotation.JsonProperty.Access;
+
+import de.itvsh.goofy.common.LinkedResource;
+import de.itvsh.goofy.common.LinkedUserProfileResource;
+import de.itvsh.goofy.common.binaryfile.BinaryFileController;
+import de.itvsh.goofy.common.binaryfile.FileId;
+import de.itvsh.goofy.common.command.CommandBody;
+import lombok.AccessLevel;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Singular;
+import lombok.ToString;
+
+@Getter
+@Builder(toBuilder = true)
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+@AllArgsConstructor(access = AccessLevel.PACKAGE)
+@ToString
+public class Wiedervorlage implements CommandBody {
+
+	@JsonIgnore
+	private String id;
+
+	@JsonIgnore
+	private String vorgangId;
+	@JsonIgnore
+	private long version;
+
+	@JsonProperty(access = Access.READ_ONLY)
+	private boolean done;
+
+	@JsonIgnore
+	@LinkedUserProfileResource
+	private String createdBy;
+	@JsonProperty(access = Access.READ_ONLY)
+	private ZonedDateTime createdAt;
+
+	@NotNull(message = FIELD_IS_EMPTY)
+	@Size(min = 3, max = 40, message = FIELD_SIZE)
+	private String betreff;
+	private String beschreibung;
+
+	@NotNull(message = FIELD_IS_EMPTY)
+	@FutureOrPresent(message = FIELD_DATE_PAST)
+	private LocalDate frist;
+
+	@LinkedResource(controllerClass = BinaryFileController.class)
+	@Singular
+	private List<FileId> attachments;
+}
\ No newline at end of file
diff --git a/goofy-server/src/main/java/de/itvsh/goofy/wiedervorlage/WiedervorlageCommand.java b/goofy-server/src/main/java/de/itvsh/goofy/wiedervorlage/WiedervorlageCommand.java
new file mode 100644
index 0000000000000000000000000000000000000000..1ebea270fefe937aca49bf4e94257b6e802a285d
--- /dev/null
+++ b/goofy-server/src/main/java/de/itvsh/goofy/wiedervorlage/WiedervorlageCommand.java
@@ -0,0 +1,56 @@
+/*
+ * 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.itvsh.goofy.wiedervorlage;
+
+import javax.validation.Valid;
+
+import com.fasterxml.jackson.annotation.JsonIgnore;
+
+import de.itvsh.goofy.common.command.CommandOrder;
+import de.itvsh.goofy.common.command.CommandStatus;
+import lombok.AccessLevel;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.EqualsAndHashCode;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+
+@Builder(toBuilder = true)
+@Getter
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+@AllArgsConstructor(access = AccessLevel.PACKAGE)
+@EqualsAndHashCode
+class WiedervorlageCommand {
+
+	@JsonIgnore
+	private String id;
+
+	private CommandOrder order;
+
+	@JsonIgnore
+	private CommandStatus status;
+
+	@Valid
+	private Wiedervorlage wiedervorlage;
+}
\ No newline at end of file
diff --git a/goofy-server/src/main/java/de/itvsh/goofy/wiedervorlage/WiedervorlageCommandController.java b/goofy-server/src/main/java/de/itvsh/goofy/wiedervorlage/WiedervorlageCommandController.java
new file mode 100644
index 0000000000000000000000000000000000000000..2709e4c45f359473678f61769f879b5b5c3e251b
--- /dev/null
+++ b/goofy-server/src/main/java/de/itvsh/goofy/wiedervorlage/WiedervorlageCommandController.java
@@ -0,0 +1,102 @@
+/*
+ * 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.itvsh.goofy.wiedervorlage;
+
+import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.*;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import de.itvsh.goofy.common.command.Command;
+import de.itvsh.goofy.common.command.CommandController;
+import de.itvsh.kop.common.errorhandling.TechnicalException;
+
+@RestController
+@RequestMapping(WiedervorlageCommandController.WIEDERVORLAGE_COMMANDS)
+public class WiedervorlageCommandController {
+
+	static final String WIEDERVORLAGE_COMMANDS = "/api/wiedervorlages/{wiedervorlageId}/{wiedervorlageVersion}/commands";
+
+	@Autowired
+	private WiedervorlageService service;
+
+	@PostMapping
+	public ResponseEntity<Void> updateWiedervorlage(@RequestBody WiedervorlageCommand command, @PathVariable String wiedervorlageId,
+			@PathVariable long wiedervorlageVersion) {
+		var wiedervorlage = service.getById(wiedervorlageId);
+		var createdCommand = createCommand(wiedervorlage, command);
+
+		service.updateNextFrist(wiedervorlage.getVorgangId());
+
+		return ResponseEntity.created(linkTo(CommandController.class).slash(createdCommand.getId()).toUri()).build();
+	}
+
+	Command createCommand(Wiedervorlage wiedervorlage, WiedervorlageCommand command) {
+		switch (command.getOrder()) {
+		case WIEDERVORLAGE_ERLEDIGEN: {
+			return service.erledigen(wiedervorlage);
+		}
+		case WIEDERVORLAGE_WIEDEREROEFFNEN: {
+			return service.wiedereroeffnen(wiedervorlage);
+		}
+		case EDIT_WIEDERVORLAGE: {
+			return service.editWiedervorlage(updateWiedervorlageByCommand(wiedervorlage, command), wiedervorlage.getId(), wiedervorlage.getVersion());
+		}
+		default:
+			throw new TechnicalException("Unsupported order " + command.getOrder());
+		}
+	}
+
+	Wiedervorlage updateWiedervorlageByCommand(Wiedervorlage wiedervorlage, WiedervorlageCommand command) {
+		return wiedervorlage.toBuilder()
+				.clearAttachments().attachments(command.getWiedervorlage().getAttachments())
+				.betreff(command.getWiedervorlage().getBetreff())
+				.beschreibung(command.getWiedervorlage().getBeschreibung())
+				.frist(command.getWiedervorlage().getFrist()).build();
+	}
+
+	@RestController
+	@RequestMapping(WiedervorlageCommandByVorgangController.WIEDERVORLAGE_COMMANDS_BY_VORGANG)
+	public static class WiedervorlageCommandByVorgangController {
+
+		static final String WIEDERVORLAGE_COMMANDS_BY_VORGANG = "/api/vorgangs/{vorgangId}/wiedervorlageCommands";
+
+		@Autowired
+		private WiedervorlageService service;
+
+		@PostMapping
+		public ResponseEntity<Void> createWiedervorlage(@RequestBody WiedervorlageCommand command, @PathVariable String vorgangId) {
+			var createdCommand = service.createWiedervorlage(command.getWiedervorlage(), vorgangId);
+
+			service.updateNextFrist(vorgangId);
+
+			return ResponseEntity.created(linkTo(CommandController.class).slash(createdCommand.getId()).toUri()).build();
+		}
+	}
+}
\ No newline at end of file
diff --git a/goofy-server/src/main/java/de/itvsh/goofy/wiedervorlage/WiedervorlageController.java b/goofy-server/src/main/java/de/itvsh/goofy/wiedervorlage/WiedervorlageController.java
new file mode 100644
index 0000000000000000000000000000000000000000..83a093727433ec17e6a062c7129be60ba2f14677
--- /dev/null
+++ b/goofy-server/src/main/java/de/itvsh/goofy/wiedervorlage/WiedervorlageController.java
@@ -0,0 +1,81 @@
+/*
+ * 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.itvsh.goofy.wiedervorlage;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.hateoas.CollectionModel;
+import org.springframework.hateoas.EntityModel;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import de.itvsh.goofy.common.binaryfile.BinaryFileController;
+import de.itvsh.goofy.common.file.OzgFile;
+import de.itvsh.goofy.common.user.CurrentUserService;
+
+@RestController
+@RequestMapping(WiedervorlageController.WIEDERVORLAGE_PATH)
+public class WiedervorlageController {
+
+	static final String WIEDERVORLAGE_PATH = "/api/wiedervorlages"; // NOSONAR
+	static final String PARAM_VORGANG_ID = "vorgangId";
+
+	@Autowired
+	private WiedervorlageService service;
+	@Autowired
+	private WiedervorlageModelAssembler modelAssembler;
+
+	@PreAuthorize(CurrentUserService.VERIFY_HAS_ROLE_VERWALTUNG_USER)
+	@GetMapping("/{wiedervorlageId}")
+	public EntityModel<Wiedervorlage> getById(@PathVariable String wiedervorlageId) {
+		return modelAssembler.toModel(service.getById(wiedervorlageId));
+	}
+
+	@GetMapping(params = PARAM_VORGANG_ID)
+	public CollectionModel<EntityModel<Wiedervorlage>> findByVorgang(@RequestParam String vorgangId) {
+		return modelAssembler.toCollectionModel(service.findByVorgangId(vorgangId), vorgangId);
+	}
+
+	@RestController
+	@RequestMapping(AttachmentsByWiedervorlageController.PATH)
+	static class AttachmentsByWiedervorlageController {
+
+		static final String PATH = "/api/wiedervorlages/{wiedervorlageId}"; // NOSONAR
+
+		@Autowired
+		private BinaryFileController binaryFileController;
+		@Autowired
+		private WiedervorlageService service;
+
+		@GetMapping("/attachments")
+		public CollectionModel<EntityModel<OzgFile>> getAttachments(@PathVariable String wiedervorlageId) {
+			var wiedervorlage = service.getById(wiedervorlageId);
+
+			return binaryFileController.getFiles(wiedervorlage.getAttachments());
+		}
+	}
+}
\ No newline at end of file
diff --git a/goofy-server/src/main/java/de/itvsh/goofy/wiedervorlage/WiedervorlageMapper.java b/goofy-server/src/main/java/de/itvsh/goofy/wiedervorlage/WiedervorlageMapper.java
new file mode 100644
index 0000000000000000000000000000000000000000..6d708991d5d5fff71928c4576525b2edc6e802f2
--- /dev/null
+++ b/goofy-server/src/main/java/de/itvsh/goofy/wiedervorlage/WiedervorlageMapper.java
@@ -0,0 +1,90 @@
+/*
+ * 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.itvsh.goofy.wiedervorlage;
+
+import java.time.LocalDate;
+import java.time.ZonedDateTime;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+
+import org.mapstruct.CollectionMappingStrategy;
+import org.mapstruct.Mapper;
+import org.mapstruct.NullValueCheckStrategy;
+import org.mapstruct.ReportingPolicy;
+import org.springframework.beans.factory.annotation.Autowired;
+
+import de.itvsh.goofy.common.binaryfile.FileId;
+import de.itvsh.goofy.common.binaryfile.FileIdMapper;
+import de.itvsh.kop.pluto.common.grpc.GrpcObjectMapper;
+import de.itvsh.ozg.pluto.vorgangAttachedItem.GrpcVorgangAttachedItem;
+
+@Mapper(uses = { FileIdMapper.class }, unmappedTargetPolicy = ReportingPolicy.WARN, //
+		unmappedSourcePolicy = ReportingPolicy.WARN, //
+		nullValueCheckStrategy = NullValueCheckStrategy.ALWAYS, //
+		collectionMappingStrategy = CollectionMappingStrategy.ADDER_PREFERRED)
+abstract class WiedervorlageMapper {
+
+	@Autowired
+	private GrpcObjectMapper grpcObjectMapper;
+
+	static final String ID = "id";
+	static final String DONE = "done";
+	static final String CREATED_BY = "createdBy";
+	static final String CREATED_BY_NAME = "createdByName";
+	static final String CREATED_AT = "createdAt";
+	static final String BETREFF = "betreff";
+	static final String BESCHREIBUNG = "beschreibung";
+	static final String FRIST = "frist";
+	static final String ATTACHMENTS = "attachments";
+
+	public Wiedervorlage fromItem(GrpcVorgangAttachedItem item) {
+		return fromItemMap(grpcObjectMapper.mapFromGrpc(item.getItem()), item.getVersion());
+	}
+
+	Wiedervorlage fromItemMap(Map<String, Object> itemMap, long version) {
+		return Wiedervorlage.builder()
+				.id((String) itemMap.get(ID))
+				.version(version)
+				.done(Boolean.valueOf((String) itemMap.get(DONE)))
+				.createdBy((String) itemMap.get(CREATED_BY))
+				.createdAt(ZonedDateTime.parse((String) itemMap.get(CREATED_AT)))
+				.betreff((String) itemMap.get(BETREFF))
+				.beschreibung((String) itemMap.get(BESCHREIBUNG))
+				.frist(LocalDate.parse((String) itemMap.get(FRIST)))
+				.attachments(mapAttachments(itemMap.get(ATTACHMENTS)))
+				.build();
+	}
+
+	private List<FileId> mapAttachments(Object attachments) {
+		if (Objects.isNull(attachments)) {
+			return Collections.emptyList();
+		}
+		if (attachments instanceof List<?> attachmentList) {
+			return attachmentList.stream().map(String::valueOf).map(FileId::from).toList();
+		}
+		return Collections.singletonList(FileId.from((String) attachments));
+	}
+}
\ No newline at end of file
diff --git a/goofy-server/src/main/java/de/itvsh/goofy/wiedervorlage/WiedervorlageModelAssembler.java b/goofy-server/src/main/java/de/itvsh/goofy/wiedervorlage/WiedervorlageModelAssembler.java
new file mode 100644
index 0000000000000000000000000000000000000000..42a15247eca1283cb58bf2e8b8dbe467ab044f0a
--- /dev/null
+++ b/goofy-server/src/main/java/de/itvsh/goofy/wiedervorlage/WiedervorlageModelAssembler.java
@@ -0,0 +1,80 @@
+/*
+ * 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.itvsh.goofy.wiedervorlage;
+
+import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.*;
+
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import org.springframework.hateoas.CollectionModel;
+import org.springframework.hateoas.EntityModel;
+import org.springframework.hateoas.server.RepresentationModelAssembler;
+import org.springframework.stereotype.Component;
+
+import de.itvsh.goofy.common.ModelBuilder;
+import de.itvsh.goofy.common.binaryfile.BinaryFileController;
+import de.itvsh.goofy.wiedervorlage.WiedervorlageCommandController.WiedervorlageCommandByVorgangController;
+import de.itvsh.goofy.wiedervorlage.WiedervorlageController.AttachmentsByWiedervorlageController;
+
+@Component
+class WiedervorlageModelAssembler implements RepresentationModelAssembler<Wiedervorlage, EntityModel<Wiedervorlage>> {
+
+	static final String REL_EDIT = "edit";
+	static final String REL_ERLEDIGEN = "erledigen";
+	static final String REL_WIEDEREROEFFNEN = "wiedereroeffnen";
+	static final String REL_ATTACHMENTS = "attachments";
+
+	static final String REL_CREATE = "create-wiedervorlage";
+	static final String REL_UPLOAD_FILE = "uploadFile";
+
+	private static final Predicate<Wiedervorlage> ALLOW_ERLEDIGEN = wiedervorlage -> !wiedervorlage.isDone();
+	private static final Predicate<Wiedervorlage> ALLOW_WIEDEREROEFFNEN = Wiedervorlage::isDone;
+	private static final Predicate<Wiedervorlage> HAS_ATTACHMENTS = wiedervorlage -> !wiedervorlage.getAttachments().isEmpty();
+
+	@Override
+	public EntityModel<Wiedervorlage> toModel(Wiedervorlage wiedervorlage) {
+		var selfLink = linkTo(WiedervorlageController.class).slash(wiedervorlage.getId());
+		var commandLink = linkTo(
+				methodOn(WiedervorlageCommandController.class).updateWiedervorlage(null, wiedervorlage.getId(), wiedervorlage.getVersion()));
+
+		return ModelBuilder.fromEntity(wiedervorlage).addLink(selfLink.withSelfRel())
+				.addLink(commandLink.withRel(REL_EDIT))
+				.ifMatch(ALLOW_ERLEDIGEN).addLink(commandLink.withRel(REL_ERLEDIGEN))
+				.ifMatch(ALLOW_WIEDEREROEFFNEN).addLink(commandLink.withRel(REL_WIEDEREROEFFNEN))
+				.ifMatch(HAS_ATTACHMENTS)
+				.addLink(linkTo(methodOn(AttachmentsByWiedervorlageController.class).getAttachments(wiedervorlage.getId())).withRel(REL_ATTACHMENTS))
+				.buildModel();
+	}
+
+	public CollectionModel<EntityModel<Wiedervorlage>> toCollectionModel(Stream<Wiedervorlage> entities, String vorgangId) {
+		return CollectionModel.of(entities.map(this::toModel).collect(Collectors.toList()),
+				linkTo(WiedervorlageController.class).withSelfRel(),
+				linkTo(methodOn(WiedervorlageCommandByVorgangController.class).createWiedervorlage(null, vorgangId))
+						.withRel(REL_CREATE),
+				linkTo(BinaryFileController.class).slash(vorgangId).slash("wiedervorlageAttachment").slash("file")
+						.withRel(REL_UPLOAD_FILE));
+	}
+}
\ No newline at end of file
diff --git a/goofy-server/src/main/java/de/itvsh/goofy/wiedervorlage/WiedervorlageRemoteService.java b/goofy-server/src/main/java/de/itvsh/goofy/wiedervorlage/WiedervorlageRemoteService.java
new file mode 100644
index 0000000000000000000000000000000000000000..09cf218b8b31763d0c7d7f2d1246dd2c1da4961f
--- /dev/null
+++ b/goofy-server/src/main/java/de/itvsh/goofy/wiedervorlage/WiedervorlageRemoteService.java
@@ -0,0 +1,109 @@
+/*
+ * 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.itvsh.goofy.wiedervorlage;
+
+import java.time.LocalDate;
+import java.time.format.DateTimeFormatter;
+import java.util.Optional;
+import java.util.stream.Stream;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.ApplicationContext;
+import org.springframework.stereotype.Service;
+
+import de.itvsh.goofy.GoofyServerApplication;
+import de.itvsh.ozg.pluto.grpc.clientAttribute.ClientAttributeServiceGrpc.ClientAttributeServiceBlockingStub;
+import de.itvsh.ozg.pluto.grpc.clientAttribute.GrpcAccessPermission;
+import de.itvsh.ozg.pluto.grpc.clientAttribute.GrpcClientAttribute;
+import de.itvsh.ozg.pluto.grpc.clientAttribute.GrpcClientAttributeValue;
+import de.itvsh.ozg.pluto.grpc.clientAttribute.GrpcDeleteClientAttributeRequest;
+import de.itvsh.ozg.pluto.grpc.clientAttribute.GrpcSetClientAttributeRequest;
+import de.itvsh.ozg.pluto.vorgangAttachedItem.GrpcFindVorgangAttachedItemRequest;
+import de.itvsh.ozg.pluto.vorgangAttachedItem.GrpcVorgangAttachedItemRequest;
+import de.itvsh.ozg.pluto.vorgangAttachedItem.VorgangAttachedItemServiceGrpc.VorgangAttachedItemServiceBlockingStub;
+import net.devh.boot.grpc.client.inject.GrpcClient;
+
+@Service
+public class WiedervorlageRemoteService {
+
+	static final String CLIENT_ATTRIBUTE_NEXT_WIEDERVORLAGE_FRIST = "nextWiedervorlageFrist";
+	static final String ITEM_NAME = "Wiedervorlage";
+
+	@GrpcClient(GoofyServerApplication.GRPC_CLIENT)
+	private VorgangAttachedItemServiceBlockingStub vorgangAttachedItemServiceStub;
+
+	@GrpcClient(GoofyServerApplication.GRPC_CLIENT)
+	private ClientAttributeServiceBlockingStub clientAttributeServiceStub;
+
+	@Autowired
+	private WiedervorlageMapper mapper;
+	@Autowired
+	private ApplicationContext context;
+
+	public Stream<Wiedervorlage> findByVorgangId(String vorgangId) {
+		var response = vorgangAttachedItemServiceStub.find(buildFindRequest(vorgangId));
+
+		return response.getVorgangAttachedItemsList().stream().map(mapper::fromItem);
+	}
+
+	GrpcFindVorgangAttachedItemRequest buildFindRequest(String vorgangId) {
+		return GrpcFindVorgangAttachedItemRequest.newBuilder()
+				.setVorgangId(vorgangId)
+				.setItemName(ITEM_NAME)
+				.build();
+	}
+
+	public Wiedervorlage getById(String wiedervorlageId) {
+		var response = vorgangAttachedItemServiceStub.getById(GrpcVorgangAttachedItemRequest.newBuilder().setId(wiedervorlageId).build());
+
+		return mapper.fromItem(response.getVorgangAttachedItem()).toBuilder().vorgangId(response.getVorgangAttachedItem().getVorgangId()).build();
+	}
+
+	public void updateNextFrist(String vorgangId, Optional<LocalDate> nextFrist) {
+		nextFrist.ifPresentOrElse(frist -> setNextFrist(vorgangId, frist), () -> deleteNextFrist(vorgangId));
+	}
+
+	void deleteNextFrist(String vorgangId) {
+		clientAttributeServiceStub.delete(GrpcDeleteClientAttributeRequest.newBuilder()
+				.setVorgangId(vorgangId)
+				.setClientName(context.getId())
+				.setAttributeName(CLIENT_ATTRIBUTE_NEXT_WIEDERVORLAGE_FRIST)
+				.build());
+	}
+
+	void setNextFrist(String vorgangId, LocalDate nextFrist) {
+		clientAttributeServiceStub.set(buildUpdateNextFristRequest(vorgangId, nextFrist));
+	}
+
+	private GrpcSetClientAttributeRequest buildUpdateNextFristRequest(String vorgangId, LocalDate nextFrist) {
+		return GrpcSetClientAttributeRequest.newBuilder()
+				.setVorgangId(vorgangId)
+				.setAttribute(GrpcClientAttribute.newBuilder()
+						.setAccess(GrpcAccessPermission.READ_ONLY)
+						.setAttributeName(CLIENT_ATTRIBUTE_NEXT_WIEDERVORLAGE_FRIST)
+						.setValue(GrpcClientAttributeValue.newBuilder().setStringValue(nextFrist.format(DateTimeFormatter.ISO_DATE)))
+						.build())
+				.build();
+	}
+}
\ No newline at end of file
diff --git a/goofy-server/src/main/java/de/itvsh/goofy/wiedervorlage/WiedervorlageService.java b/goofy-server/src/main/java/de/itvsh/goofy/wiedervorlage/WiedervorlageService.java
new file mode 100644
index 0000000000000000000000000000000000000000..e30ff0903ad141582f34128bdfd1dd01b01d8894
--- /dev/null
+++ b/goofy-server/src/main/java/de/itvsh/goofy/wiedervorlage/WiedervorlageService.java
@@ -0,0 +1,105 @@
+/*
+ * 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.itvsh.goofy.wiedervorlage;
+
+import java.time.LocalDate;
+import java.time.ZonedDateTime;
+import java.util.Comparator;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.function.Predicate;
+import java.util.stream.Stream;
+
+import javax.validation.Valid;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.scheduling.annotation.Async;
+import org.springframework.stereotype.Service;
+import org.springframework.validation.annotation.Validated;
+
+import de.itvsh.goofy.common.attacheditem.VorgangAttachedItemService;
+import de.itvsh.goofy.common.command.Command;
+import de.itvsh.goofy.common.user.CurrentUserService;
+
+@Validated
+@Service
+class WiedervorlageService {
+
+	private static final Predicate<Wiedervorlage> IS_NOT_DONE = wiedervorlage -> !wiedervorlage.isDone();
+
+	@Autowired
+	private WiedervorlageRemoteService remoteService;
+	@Autowired
+	private VorgangAttachedItemService vorgangAttachedItemService;
+	@Autowired
+	private CurrentUserService currentUserService;
+
+	public Command createWiedervorlage(@Valid Wiedervorlage wiedervorlage, String vorgangId) {
+		return vorgangAttachedItemService.createNewWiedervorlage(addCreated(wiedervorlage), vorgangId);
+	}
+
+	Wiedervorlage addCreated(Wiedervorlage wiedervorlage) {
+		return wiedervorlage.toBuilder()
+				.createdAt(ZonedDateTime.now().withNano(0))
+				.createdBy(currentUserService.getUserId().toString())
+				.build();
+	}
+
+	public Command editWiedervorlage(@Valid Wiedervorlage wiedervorlage, String wiedervorlageId, long version) {
+		return vorgangAttachedItemService.editWiedervorlage(wiedervorlage, wiedervorlageId, version);
+	}
+
+	public Wiedervorlage getById(String wiedervorlageId) {
+		return remoteService.getById(wiedervorlageId);
+	}
+
+	@Async
+	public void updateNextFrist(String vorgangId) {
+		var allWiedervorlagen = findByVorgangId(vorgangId);
+
+		remoteService.updateNextFrist(vorgangId, calculateNextFrist(allWiedervorlagen));
+	}
+
+	Optional<LocalDate> calculateNextFrist(Stream<Wiedervorlage> wiedervorlagen) {
+		return wiedervorlagen
+				.filter(Objects::nonNull)
+				.filter(IS_NOT_DONE)
+				.sorted(Comparator.comparing(Wiedervorlage::getFrist))
+				.map(Wiedervorlage::getFrist)
+				.findFirst();
+	}
+
+	public Stream<Wiedervorlage> findByVorgangId(String vorgangId) {
+		return remoteService.findByVorgangId(vorgangId);
+	}
+
+	Command erledigen(Wiedervorlage wiedervorlage) {
+		return vorgangAttachedItemService.setWiedervorlageDone(wiedervorlage, true);
+	}
+
+	Command wiedereroeffnen(Wiedervorlage wiedervorlage) {
+		return vorgangAttachedItemService.setWiedervorlageDone(wiedervorlage, false);
+	}
+
+}
\ No newline at end of file
diff --git a/goofy-server/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/goofy-server/src/main/resources/META-INF/additional-spring-configuration-metadata.json
new file mode 100644
index 0000000000000000000000000000000000000000..8b6ef837da5a5d749a02ec15755c3edb2aabf436
--- /dev/null
+++ b/goofy-server/src/main/resources/META-INF/additional-spring-configuration-metadata.json
@@ -0,0 +1,9 @@
+{
+	"properties": [
+		{
+			"name": "goofy.production",
+			"type": "java.lang.Boolean",
+			"description": "Enables/Disables the production mode. (default: true)"
+		}
+	]
+}
\ No newline at end of file
diff --git a/goofy-server/src/main/resources/application-dev.yml b/goofy-server/src/main/resources/application-dev.yml
new file mode 100644
index 0000000000000000000000000000000000000000..2e7f1d297f8df38f021e38fc122988f2fb8745f5
--- /dev/null
+++ b/goofy-server/src/main/resources/application-dev.yml
@@ -0,0 +1,11 @@
+goofy:
+  production: false
+
+keycloak:
+  auth-server-url: https://sso.dev.ozg-sh.de
+  realm: sh-kiel-dev
+  resource: sh-kiel-dev-goofy
+
+server:
+  error:
+    include-stacktrace: always
\ No newline at end of file
diff --git a/goofy-server/src/main/resources/application-e2e.yml b/goofy-server/src/main/resources/application-e2e.yml
new file mode 100644
index 0000000000000000000000000000000000000000..ae50cc668ec4497e586270ac708e3a23dbd60720
--- /dev/null
+++ b/goofy-server/src/main/resources/application-e2e.yml
@@ -0,0 +1,11 @@
+keycloak:
+  realm: sh-e2e-dev
+  resource: sh-e2e-dev-goofy
+  
+kop:
+  forwarding:
+    lninfo:
+      url: classpath:files/LandesnetzInfo.html
+  user-manager:
+    url: http://localhost:9092
+    internalurl: http://ozg-usermanager:8080
\ No newline at end of file
diff --git a/goofy-server/src/main/resources/application-ea.yml b/goofy-server/src/main/resources/application-ea.yml
new file mode 100644
index 0000000000000000000000000000000000000000..58740a745daf75b078572400c0d3ce0b1b6e6104
--- /dev/null
+++ b/goofy-server/src/main/resources/application-ea.yml
@@ -0,0 +1,28 @@
+kop:
+  forwarding:
+    lninfo:
+      url: http://lninfo.ozg-tools.svc.cluster.local/
+      
+---
+spring:
+  config:
+    activate:
+      on-profile:
+      - dev
+      
+kop:
+  forwarding:
+    lninfo:
+      url: classpath:files/LandesnetzInfo.html
+      
+---
+spring:
+  config:
+    activate:
+      on-profile:
+      - test
+      
+kop:
+  forwarding:
+    lninfo:
+      url: classpath:files/LandesnetzInfo.html
\ No newline at end of file
diff --git a/goofy-server/src/main/resources/application-local.yml b/goofy-server/src/main/resources/application-local.yml
new file mode 100644
index 0000000000000000000000000000000000000000..336c6f9170989e553e7e5d33999774709f502a03
--- /dev/null
+++ b/goofy-server/src/main/resources/application-local.yml
@@ -0,0 +1,23 @@
+logging:
+  level:
+    '[de.itvsh]': INFO
+  config: classpath:log4j2-local.xml
+
+goofy:
+  production: false
+    
+keycloak:
+  auth-server-url: http://localhost:8088
+  realm: sh-kiel-dev
+  resource: sh-kiel-dev-goofy
+
+server:
+  error:
+    include-stacktrace: always
+
+kop:
+  user-manager:
+    url: http://localhost:9092
+    internalurl: http://localhost:9092
+    profile-template: /api/userProfiles/%s
+    search-template: /api/userProfiles/?searchBy={searchBy}
\ No newline at end of file
diff --git a/goofy-server/src/main/resources/application-oc.yml b/goofy-server/src/main/resources/application-oc.yml
new file mode 100644
index 0000000000000000000000000000000000000000..2f098ef2b675bca901ca675c70d5dc71915fff86
--- /dev/null
+++ b/goofy-server/src/main/resources/application-oc.yml
@@ -0,0 +1,14 @@
+spring:
+  boot:
+    admin:
+      client:
+        instance:
+          prefer-ip: true
+          
+grpc:
+  client:
+    pluto:
+      address: static://172.30.125.177:9090
+      
+logging:
+  config: classpath:log4j2.xml
\ No newline at end of file
diff --git a/goofy-server/src/main/resources/application-remotekc.yml b/goofy-server/src/main/resources/application-remotekc.yml
new file mode 100644
index 0000000000000000000000000000000000000000..a9c24daa3d8cf653c70c5e9700fae348d1cc5882
--- /dev/null
+++ b/goofy-server/src/main/resources/application-remotekc.yml
@@ -0,0 +1,6 @@
+keycloak: 
+  realm: sh-kiel-dev
+  resource: sh-kiel-dev-goofy
+  public-client: true
+  use-resource-role-mappings: true
+  auth-server-url: https://sso.dev.ozg-sh.de
diff --git a/goofy-server/src/main/resources/application.yml b/goofy-server/src/main/resources/application.yml
new file mode 100644
index 0000000000000000000000000000000000000000..4d9c4b01b56ad1f9b4a16693cb9cecd7d107a2b6
--- /dev/null
+++ b/goofy-server/src/main/resources/application.yml
@@ -0,0 +1,77 @@
+logging:
+  level:
+    ROOT: WARN
+    '[de.itvsh]': INFO,
+    '[org.springframework.security]': WARN
+    '[org.keycloak.adapters]': WARN
+
+spring:
+  mvc:
+    pathmatch:
+      matching-strategy: ant-path-matcher
+  application:
+    name: Goofy
+  jackson:
+    deserialization: 
+      'ADJUST_DATES_TO_CONTEXT_TIME_ZONE': false
+  servlet:
+    multipart:
+      max-file-size: 2GB
+      max-request-size: 2GB
+
+server:
+  http2:
+    enabled: true
+  error:
+    include-stacktrace: never
+    
+management:
+  server:
+    port: 8081
+  health:
+    livenessState:
+      enabled: true
+    readinessState:
+      enabled: true
+  endpoint:
+    health:
+      group:
+        exploratory:
+          include: livenessState,readinessState,ping
+          show-details: always
+      probes:
+        enabled: true
+    prometheus:
+      enabled: true
+  endpoints:
+    web:
+      exposure:
+        include: health,prometheus
+
+goofy:
+  production: true
+      
+keycloak:
+  auth-server-url: http://localhost:8088
+  realm: sh-kiel-dev
+  resource: sh-kiel-dev-goofy
+  public-client: true
+  use-resource-role-mappings: true
+  
+grpc:
+  client:
+    pluto:
+      address: static://127.0.0.1:9090
+      negotiationType: PLAINTEXT
+
+kop:
+  auth:
+    token:
+      validity: 60000
+  upload:
+    maxFileSize:
+      postfachNachrichtAttachment: 3MB
+      wiedervorlageAttachment: 40MB
+  user-manager:
+    profile-template: /api/userProfiles/%s
+    search-template: /api/userProfiles/?searchBy={searchBy}
\ No newline at end of file
diff --git a/goofy-server/src/main/resources/banner.txt b/goofy-server/src/main/resources/banner.txt
new file mode 100644
index 0000000000000000000000000000000000000000..13e3273b90490767f580107cfd3fec70dcb2ba0d
--- /dev/null
+++ b/goofy-server/src/main/resources/banner.txt
@@ -0,0 +1,6 @@
+  ___   ___    ___   ___ __   __
+ / __| / _ \  / _ \ | __|\ \ / /
+| (_ || (_) || (_) || _|  \   / 
+ \___| \___/  \___/ |_|    |_|  
+================================
+${spring-boot.version}		${application.version}
diff --git a/goofy-server/src/main/resources/fop/postfach-nachrichten.xsl b/goofy-server/src/main/resources/fop/postfach-nachrichten.xsl
new file mode 100644
index 0000000000000000000000000000000000000000..b030aacd5e9d4f8127ce23fe7a65b7cf5402da04
--- /dev/null
+++ b/goofy-server/src/main/resources/fop/postfach-nachrichten.xsl
@@ -0,0 +1,140 @@
+<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:fo="http://www.w3.org/1999/XSL/Format" version="1.1">
+
+	<xsl:template name="functional-template" match="/testModel">
+		<fo:flow flow-name="xsl-region-body">
+
+			<fo:block font-size="14pt">
+				<fo:table>
+					<fo:table-column column-width="50mm" />
+					<fo:table-column column-width="125mm"/>
+					<fo:table-body>
+						<fo:table-row>
+							<fo:table-cell>
+								<fo:block>Vorgangsname</fo:block>
+							</fo:table-cell>
+							<fo:table-cell>
+								<fo:block><xsl:value-of select="postfachNachrichtPdfModel/vorgangName" /></fo:block>
+							</fo:table-cell>
+						</fo:table-row>
+
+						<fo:table-row>
+							<fo:table-cell>
+								<fo:block>Vorgangsnummer</fo:block>
+							</fo:table-cell>
+							<fo:table-cell>
+								<fo:block><xsl:value-of select="postfachNachrichtPdfModel/vorgangNummer" /></fo:block>
+							</fo:table-cell>
+						</fo:table-row>
+					</fo:table-body>
+				</fo:table>
+			</fo:block>
+			
+			<fo:block font-size="11pt" margin-top="30px">
+				<fo:table>
+					<fo:table-column column-width="50mm" />
+					<fo:table-column column-width="125mm"/>
+					<fo:table-body>
+						<fo:table-row>
+							<fo:table-cell>
+								<fo:block>Antragsteller</fo:block>
+							</fo:table-cell>
+							<fo:table-cell>
+								<fo:block>
+								    <xsl:value-of select="postfachNachrichtPdfModel/antragstellerAnrede" />
+							        <xsl:text> </xsl:text>
+								    <xsl:value-of select="postfachNachrichtPdfModel/antragstellerVorname" />
+								    <xsl:text> </xsl:text>
+								    <xsl:value-of select="postfachNachrichtPdfModel/antragstellerNachname" />
+								</fo:block>
+								<fo:block>
+								    <xsl:value-of select="postfachNachrichtPdfModel/antragstellerStrasse" />
+								    <xsl:text> </xsl:text>
+								    <xsl:value-of select="postfachNachrichtPdfModel/antragstellerHausnummer" />
+								</fo:block>
+								<fo:block>
+								    <xsl:value-of select="postfachNachrichtPdfModel/antragstellerPlz" />
+								    <xsl:text> </xsl:text>
+								    <xsl:value-of select="postfachNachrichtPdfModel/antragstellerOrt" />
+								</fo:block>
+							</fo:table-cell>
+						</fo:table-row>
+
+					</fo:table-body>
+				</fo:table>
+			</fo:block>
+			
+			<fo:block-container font-size="11pt" margin-top="1cm">
+				<fo:block font-size="14pt" margin-bottom="3mm">Nachrichten</fo:block>
+				
+				<xsl:for-each select="postfachNachrichtPdfModel/nachrichten/nachricht">
+			        <xsl:call-template name="nachricht"/>
+				</xsl:for-each>
+			</fo:block-container>
+		</fo:flow>
+	</xsl:template>
+	
+	<xsl:template name="nachricht">
+		
+			<fo:block font-size="11pt"  margin-bottom="2mm">
+				<xsl:if test="isFirst='false'">
+					<fo:leader leader-pattern="rule" leader-length="175mm" rule-style="solid" rule-thickness="1pt"/>
+				</xsl:if>
+				<fo:table>
+					<fo:table-column column-width="30mm" />
+					<fo:table-column column-width="145mm"/>
+					<fo:table-body>
+						<fo:table-row>
+							<fo:table-cell padding="3px">
+								<fo:block>Absender</fo:block>
+							</fo:table-cell>
+							<fo:table-cell padding="3px">
+								<fo:block><xsl:value-of select="createdBy" /></fo:block>
+							</fo:table-cell>
+						</fo:table-row>
+
+						<fo:table-row>
+							<fo:table-cell padding="3px">
+								<fo:block>Datum</fo:block>
+							</fo:table-cell>
+							<fo:table-cell padding="3px">
+								<fo:block><xsl:value-of select="createdAt" /></fo:block>
+							</fo:table-cell>
+						</fo:table-row>
+						
+						<fo:table-row>
+							<fo:table-cell padding="3px">
+								<fo:block>Betreff</fo:block>
+							</fo:table-cell>
+							<fo:table-cell padding="3px">
+								<fo:block><xsl:value-of select="subject" /></fo:block>
+							</fo:table-cell>
+						</fo:table-row>
+						
+						<fo:table-row>
+							<fo:table-cell padding="3px">
+								<fo:block>Text</fo:block>
+							</fo:table-cell>
+							<fo:table-cell padding="3px">
+								<fo:block><xsl:value-of select="mailBody" /></fo:block>
+							</fo:table-cell>
+						</fo:table-row>
+						
+						<fo:table-row>
+							<fo:table-cell padding="3px">
+								<fo:block>Anhänge</fo:block>
+							</fo:table-cell>
+							<xsl:if test="attachments/attachment">
+								<fo:table-cell padding="3px">
+									<xsl:for-each select="attachments/attachment">
+										<fo:block><xsl:value-of select="."/></fo:block>
+									</xsl:for-each>
+								</fo:table-cell>
+							</xsl:if>
+						</fo:table-row>
+					</fo:table-body>
+				</fo:table>             
+			</fo:block>
+			
+	</xsl:template>
+			
+</xsl:stylesheet>
\ No newline at end of file
diff --git a/goofy-server/src/test/java/de/itvsh/goofy/ApplicationTestFactory.java b/goofy-server/src/test/java/de/itvsh/goofy/ApplicationTestFactory.java
new file mode 100644
index 0000000000000000000000000000000000000000..cc1f9697fc00765c1dd19f38c481e9da99b4f6f9
--- /dev/null
+++ b/goofy-server/src/test/java/de/itvsh/goofy/ApplicationTestFactory.java
@@ -0,0 +1,31 @@
+/*
+ * 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.itvsh.goofy;
+
+import com.thedeanda.lorem.LoremIpsum;
+
+public class ApplicationTestFactory {
+
+	public static final String CONTEXT_ID = LoremIpsum.getInstance().getWords(1);
+}
diff --git a/goofy-server/src/test/java/de/itvsh/goofy/EnvironmentControllerTest.java b/goofy-server/src/test/java/de/itvsh/goofy/EnvironmentControllerTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..1cf9aefddc08317f7b66661ad848caad645e2652
--- /dev/null
+++ b/goofy-server/src/test/java/de/itvsh/goofy/EnvironmentControllerTest.java
@@ -0,0 +1,73 @@
+/*
+ * 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.itvsh.goofy;
+
+import static org.mockito.Mockito.*;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.keycloak.adapters.springboot.KeycloakSpringBootProperties;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.springframework.test.web.servlet.MockMvc;
+import org.springframework.test.web.servlet.setup.MockMvcBuilders;
+
+class EnvironmentControllerTest {
+
+	private final String PATH = "/api/environment";
+
+	@InjectMocks
+	private EnvironmentController controller;
+	@Mock
+	private KeycloakSpringBootProperties kcProperties;
+
+	private MockMvc mockMvc;
+
+	@BeforeEach
+	void initTest() {
+		mockMvc = MockMvcBuilders.standaloneSetup(controller).build();
+	}
+
+	@Test
+	void loadEnvironment() throws Exception {
+		mockMvc.perform(get(PATH)).andExpect(status().isOk());
+	}
+
+	@Test
+	void shouldHaveProductionTrueAsDefault() throws Exception {
+		mockMvc.perform(get(PATH)).andExpect(status().is2xxSuccessful())//
+				.andExpect(jsonPath("$.production").value(true));
+	}
+
+	@Test
+	void shouldHaveClientId() throws Exception {
+		var client = "goofy";
+
+		when(kcProperties.getResource()).thenReturn(client);
+
+		mockMvc.perform(get(PATH)).andExpect(jsonPath("$.clientId").value(client));
+	}
+}
\ No newline at end of file
diff --git a/goofy-server/src/test/java/de/itvsh/goofy/GoofyServerApplicationTest.java b/goofy-server/src/test/java/de/itvsh/goofy/GoofyServerApplicationTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..83a13fde60745e5f8be87535b78a946acf097d92
--- /dev/null
+++ b/goofy-server/src/test/java/de/itvsh/goofy/GoofyServerApplicationTest.java
@@ -0,0 +1,35 @@
+/*
+ * 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.itvsh.goofy;
+
+import org.junit.jupiter.api.Test;
+import org.springframework.boot.test.context.SpringBootTest;
+
+@SpringBootTest
+class GoofyServerApplicationTest {
+
+	@Test
+	void contextLoads() { // NOSONAR
+	}
+}
\ No newline at end of file
diff --git a/goofy-server/src/test/java/de/itvsh/goofy/JwtTokenUtilTest.java b/goofy-server/src/test/java/de/itvsh/goofy/JwtTokenUtilTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..92a75d932300994bec622d8b8c23c0a774b6d0cd
--- /dev/null
+++ b/goofy-server/src/test/java/de/itvsh/goofy/JwtTokenUtilTest.java
@@ -0,0 +1,155 @@
+/*
+ * 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.itvsh.goofy;
+
+import static org.assertj.core.api.Assertions.*;
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.Mockito.*;
+
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.Spy;
+
+import com.auth0.jwt.exceptions.JWTVerificationException;
+
+import de.itvsh.goofy.common.binaryfile.FileId;
+import de.itvsh.goofy.common.downloadtoken.DownloadTokenProperties;
+import de.itvsh.goofy.common.user.UserProfileTestFactory;
+import de.itvsh.goofy.vorgang.ZustaendigeStelleTestFactory;
+import io.jsonwebtoken.Claims;
+import io.jsonwebtoken.Jwts;
+import io.jsonwebtoken.SignatureAlgorithm;
+
+class JwtTokenUtilTest {
+
+	@Spy
+	@InjectMocks
+	private JwtTokenUtil jwtTokenUtil;
+	@Mock
+	private DownloadTokenProperties downloadTokenProperties;
+
+	private static final String TOKEN_SECRET = "t0pS3cr3t";
+	private static final int TOKEN_VALIDITY = 60000;
+
+	@BeforeEach
+	public void initTest() throws Exception {
+		when(downloadTokenProperties.getSecret()).thenReturn(TOKEN_SECRET);
+	}
+
+	@Nested
+	@DisplayName("Verify token generation")
+	class TestGenerateToken {
+
+		private String generatedToken;
+
+		@BeforeEach
+		void initTest() {
+			when(downloadTokenProperties.getSecret()).thenReturn(TOKEN_SECRET);
+			when(downloadTokenProperties.getValidity()).thenReturn(TOKEN_VALIDITY);
+
+			generatedToken = jwtTokenUtil.generateToken(FileId.createNew(), UserProfileTestFactory.create());
+		}
+
+		@Test
+		void userId() {
+			var userId = getParsedBody().getSubject();
+
+			assertThat(userId).isEqualTo(SecurityTestFactory.SUBJECT.toString());
+		}
+
+		@Test
+		void expirationDate() {
+			var before = new Date();
+			var expirationDate = getParsedBody().getExpiration();
+			var after = new Date(System.currentTimeMillis() + 900000);
+
+			assertThat(expirationDate).isAfter(before).isBefore(after);
+		}
+
+		@Test
+		void organisationseinheitIds() {
+			var organisationseinheitIds = jwtTokenUtil.getOrganisationseinheitIdsFromToken(generatedToken);
+
+			assertThat(organisationseinheitIds).isEqualTo(List.of(ZustaendigeStelleTestFactory.ORGANISATIONSEINHEITEN_ID));
+		}
+
+		private Claims getParsedBody() {
+			return Jwts.parser().setSigningKey(TOKEN_SECRET.getBytes()).parseClaimsJws(generatedToken).getBody();
+		}
+	}
+
+	@Nested
+	class TestVerifyToken {
+
+		@Test
+		void shouldDoNotThrowExcepionOnValidToken() {
+			var token = buildToken(UUID.randomUUID().toString(), TOKEN_SECRET, TOKEN_VALIDITY);
+
+			jwtTokenUtil.verifyToken(token);
+		}
+
+		@Test
+		void shouldThrowExceptionOnInvalidToken() {
+			var token = buildToken(UUID.randomUUID().toString(), "invalid_token", TOKEN_VALIDITY);
+
+			assertThrows(JWTVerificationException.class, () -> jwtTokenUtil.verifyToken(token));
+		}
+
+		@Test
+		void shouldThrowExceptionOnTimeExpired() {
+			var token = buildToken(UUID.randomUUID().toString(), TOKEN_SECRET, -1000);
+
+			assertThrows(JWTVerificationException.class, () -> jwtTokenUtil.verifyToken(token));
+		}
+
+		private String buildToken(String subject, String token, int expiredTime) {
+			Map<String, Object> claims = new HashMap<>();
+			claims.put(JwtTokenUtil.FIRSTNAME_CLAIM, SecurityTestFactory.USER_FIRSTNAME);
+			claims.put(JwtTokenUtil.LASTNAME_CLAIM, SecurityTestFactory.USER_LASTNAME);
+			claims.put(JwtTokenUtil.ROLE_CLAIM, SecurityTestFactory.AUTHORITIES);
+			claims.put(JwtTokenUtil.FILEID_CLAIM, FileId.createNew());
+			claims.put(JwtTokenUtil.ORGANSIATIONSEINHEIT_IDS_CLAIM, SecurityTestFactory.ORGANSIATIONSEINHEIT_IDS);
+
+			return Jwts.builder()
+					.setClaims(claims)
+					.setSubject(subject)
+					.setHeaderParam(JwtTokenUtil.TOKEN_TYP_KEY, JwtTokenUtil.TOKEN_TYPE)
+					.setIssuer(JwtTokenUtil.TOKEN_ISSUER).setIssuedAt(new Date(System.currentTimeMillis()))
+					.setExpiration(new Date(System.currentTimeMillis() + expiredTime))
+					.setAudience(JwtTokenUtil.TOKEN_AUDIENCE)
+					.signWith(SignatureAlgorithm.HS512, token.getBytes())
+					.compact();
+		}
+	}
+}
\ No newline at end of file
diff --git a/goofy-server/src/test/java/de/itvsh/goofy/RequestAttributesTestFactory.java b/goofy-server/src/test/java/de/itvsh/goofy/RequestAttributesTestFactory.java
new file mode 100644
index 0000000000000000000000000000000000000000..8109e2ead0ddf83e4ae767c48e54fed305319b2d
--- /dev/null
+++ b/goofy-server/src/test/java/de/itvsh/goofy/RequestAttributesTestFactory.java
@@ -0,0 +1,40 @@
+/*
+ * 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.itvsh.goofy;
+
+import java.util.UUID;
+
+public class RequestAttributesTestFactory {
+
+	public static final String REQUEST_ID = UUID.randomUUID().toString();
+
+	public static RequestAttributes create() {
+		return createBuilder().build();
+	}
+
+	public static RequestAttributes.RequestAttributesBuilder createBuilder() {
+		return RequestAttributes.builder()
+				.requestId(REQUEST_ID);
+	}
+}
diff --git a/goofy-server/src/test/java/de/itvsh/goofy/RequestIdFilterITCase.java b/goofy-server/src/test/java/de/itvsh/goofy/RequestIdFilterITCase.java
new file mode 100644
index 0000000000000000000000000000000000000000..66b7a4de750a5bb61aa9e8154712f39432edc493
--- /dev/null
+++ b/goofy-server/src/test/java/de/itvsh/goofy/RequestIdFilterITCase.java
@@ -0,0 +1,56 @@
+/*
+ * 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.itvsh.goofy;
+
+import static org.mockito.ArgumentMatchers.*;
+import static org.mockito.Mockito.*;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
+
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.boot.test.mock.mockito.SpyBean;
+import org.springframework.security.test.context.support.WithMockUser;
+import org.springframework.test.web.servlet.MockMvc;
+
+@SpringBootTest
+@AutoConfigureMockMvc
+@WithMockUser
+class RequestIdFilterITCase {
+
+	@SpyBean
+	private RequestIdFilter filter;
+
+	@Autowired
+	private MockMvc mockMvc;
+
+	@Test
+	void shouldCallFilterMethod() throws Exception {
+		mockMvc.perform(get("/"));
+
+		verify(filter).doFilterInternal(any(), any(), any());
+	}
+
+}
diff --git a/goofy-server/src/test/java/de/itvsh/goofy/RequestIdFilterTest.java b/goofy-server/src/test/java/de/itvsh/goofy/RequestIdFilterTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..0c3e90c1641cbc6489f0ba36ee35ac37c3328fdc
--- /dev/null
+++ b/goofy-server/src/test/java/de/itvsh/goofy/RequestIdFilterTest.java
@@ -0,0 +1,88 @@
+/*
+ * 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.itvsh.goofy;
+
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.ArgumentMatchers.*;
+import static org.mockito.Mockito.*;
+
+import java.io.IOException;
+
+import javax.servlet.FilterChain;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.junit.jupiter.api.Test;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.Spy;
+
+class RequestIdFilterTest {
+
+	@InjectMocks
+	private RequestIdFilter filter;
+	@Mock
+	private FilterChain filterChain;
+	@Mock
+	private HttpServletRequest request;
+	@Mock
+	private HttpServletResponse response;
+	@Mock
+	private CallScope callScope;
+
+	@Spy
+	private RequestAttributes attributes;
+
+	@Test
+	void shouldContinueFilterChain() throws IOException, ServletException {
+		callDoFilterInternal();
+
+		verify(filterChain).doFilter(request, response);
+	}
+
+	@Test
+	void shouldStartCallScope() {
+		callDoFilterInternal();
+
+		verify(callScope).startScope();
+	}
+
+	@Test
+	void shouldEndCallScopeAfterException() throws IOException, ServletException {
+		doThrow(RuntimeException.class).when(filterChain).doFilter(any(), any());
+
+		assertThrows(RuntimeException.class, this::callDoFilterInternal);
+
+		verify(callScope).endScope();
+	}
+
+	private void callDoFilterInternal() {
+		try {
+			filter.doFilterInternal(request, response, filterChain);
+		} catch (ServletException | IOException e) {
+			throw new RuntimeException(e);
+		}
+	}
+}
diff --git a/goofy-server/src/test/java/de/itvsh/goofy/RootControllerTest.java b/goofy-server/src/test/java/de/itvsh/goofy/RootControllerTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..079520b7a8c70287892fbd67f7ce88fc029f8a79
--- /dev/null
+++ b/goofy-server/src/test/java/de/itvsh/goofy/RootControllerTest.java
@@ -0,0 +1,349 @@
+/*
+ * 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.itvsh.goofy;
+
+import static org.assertj.core.api.Assertions.*;
+import static org.mockito.ArgumentMatchers.*;
+import static org.mockito.Mockito.*;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
+
+import java.time.LocalDateTime;
+import java.time.ZoneOffset;
+import java.util.Optional;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.Spy;
+import org.springframework.boot.info.BuildProperties;
+import org.springframework.hateoas.IanaLinkRelations;
+import org.springframework.hateoas.Link;
+import org.springframework.test.web.servlet.MockMvc;
+import org.springframework.test.web.servlet.ResultActions;
+import org.springframework.test.web.servlet.setup.MockMvcBuilders;
+
+import de.itvsh.goofy.common.user.CurrentUserService;
+import de.itvsh.goofy.common.user.UserId;
+import de.itvsh.goofy.common.user.UserManagerUrlProvider;
+import de.itvsh.goofy.common.user.UserProfileTestFactory;
+import de.itvsh.goofy.common.user.UserRemoteService;
+import de.itvsh.goofy.common.user.UserRole;
+import de.itvsh.goofy.system.SystemStatusService;
+
+class RootControllerTest {
+
+	@Spy
+	@InjectMocks // NOSONAR
+	private RootController controller;
+	@Mock
+	private BuildProperties properties;
+	@Mock
+	private CurrentUserService currentUserService;
+	@Mock
+	private SystemStatusService systemStatusService;
+	@Mock
+	private UserRemoteService internalUserIdService;
+	@Mock
+	private UserManagerUrlProvider userManagerUrlProvider;
+
+	private MockMvc mockMvc;
+
+	@BeforeEach
+	void initTest() {
+		mockMvc = MockMvcBuilders.standaloneSetup(controller).build();
+
+		when(currentUserService.getUserId()).thenReturn(UserId.from("42"));
+		when(internalUserIdService.getUserId(any())).thenReturn(Optional.empty());
+		when(userManagerUrlProvider.getUserProfileSearchTemplate()).thenReturn("UserProfileSearchTemplateDummy/$");
+	}
+
+	@DisplayName("Links for user")
+	@Nested
+	class TestLinks {
+
+		@DisplayName("with role " + UserRole.VERWALTUNG_USER)
+		@Nested
+		class TestWithVerwaltungRole {
+
+			@BeforeEach
+			void mockCurrentUserService() {
+				doReturn(true).when(controller).hasVerwaltungRole();
+				when(systemStatusService.isSearchServerAvailable()).thenReturn(true);
+			}
+
+			@Test
+			void shouldHaveSelfLink() {
+				var model = controller.getRootResource();
+
+				assertThat(model.getLink(IanaLinkRelations.SELF)).isPresent();
+			}
+
+			@Test
+			void shouldHaveLinkToVorgangList() {
+				var model = controller.getRootResource();
+
+				assertThat(model.getLink(RootController.REL_VORGAENGE)).isPresent().get()
+						.extracting(Link::getHref).isEqualTo("/api/vorgangs");
+			}
+
+			@Test
+			void shouldHaveSearchLinkWithLimit() {
+				var model = controller.getRootResource();
+
+				assertThat(model.getLink(RootController.REL_SEARCH)).isPresent().get()
+						.extracting(Link::getHref).isEqualTo("/api/vorgangs?page=0{&searchBy,limit,assignedTo}");
+			}
+
+			@Test
+			void shouldHaveMyVorgaengeLink() {
+				when(internalUserIdService.getUserId(any())).thenReturn(Optional.of(UserProfileTestFactory.ID));
+
+				var model = controller.getRootResource();
+
+				assertThat(model.getLink(RootController.REL_MY_VORGAENGE)).isPresent().get().extracting(Link::getHref)
+						.isEqualTo("/api/vorgangs?page=0&assignedTo=" + UserProfileTestFactory.ID.toString() + "{&searchBy,limit}");
+			}
+
+			@Test
+			void shouldHaveSearchMyVorgaengeLink() {
+				when(internalUserIdService.getUserId(any())).thenReturn(Optional.of(UserProfileTestFactory.ID));
+
+				var model = controller.getRootResource();
+
+				assertThat(model.getLink(RootController.REL_SEARCH_MY_VORGAENGE)).isPresent().get().extracting(Link::getHref)
+						.isEqualTo("/api/vorgangs?page=0&assignedTo=" + UserProfileTestFactory.ID.toString() + "{&searchBy,limit}");
+			}
+
+			@Test
+			void shouldHaveDownloadTokenLink() {
+				var model = controller.getRootResource();
+
+				assertThat(model.getLink(RootController.REL_DOWNLOAD_TOKEN)).isPresent().get().extracting(Link::getHref)
+						.isEqualTo("/api/downloadtoken");
+			}
+
+			@DisplayName("search service not available")
+			@Nested
+			class TestWithoutSearchService {
+
+				@BeforeEach
+				void init() {
+					when(systemStatusService.isSearchServerAvailable()).thenReturn(false);
+				}
+
+				@Test
+				void shouldNotHaveSearchLink() {
+					var model = controller.getRootResource();
+
+					assertThat(model.getLink(RootController.REL_SEARCH)).isNotPresent();
+				}
+
+				@Test
+				void shouldHaveMyVorgaengeLink() {
+					when(internalUserIdService.getUserId(any())).thenReturn(Optional.of(UserProfileTestFactory.ID));
+
+					var model = controller.getRootResource();
+
+					assertThat(model.getLink(RootController.REL_MY_VORGAENGE)).isPresent().get().extracting(Link::getHref)
+							.isEqualTo("/api/vorgangs?page=0&assignedTo=" + UserProfileTestFactory.ID.toString() + "{&searchBy,limit}");
+				}
+
+				@Test
+				void shouldNotHaveSearchMyVorgaengeLink() {
+					var model = controller.getRootResource();
+
+					assertThat(model.getLink(RootController.REL_SEARCH_MY_VORGAENGE)).isNotPresent();
+				}
+			}
+		}
+
+		@DisplayName("with role " + UserRole.EINHEITLICHER_ANSPRECHPARTNER)
+		@Nested
+		class TestWithEinheitlicherAnsprechpartnerRole {
+
+			@BeforeEach
+			void mock() {
+				when(currentUserService.hasRole(UserRole.EINHEITLICHER_ANSPRECHPARTNER)).thenReturn(true);
+
+				doReturn(false).when(controller).hasVerwaltungRole();
+			}
+
+			@Test
+			void shoulNotHaveMyVorgaengeLink() {
+				var model = controller.getRootResource();
+
+				assertThat(model.getLink(RootController.REL_MY_VORGAENGE)).isNotPresent();
+			}
+
+			@Test
+			void shoulNotHaveSearchMyVorgaengeLink() {
+				var model = controller.getRootResource();
+
+				assertThat(model.getLink(RootController.REL_SEARCH_MY_VORGAENGE)).isNotPresent();
+			}
+
+			@DisplayName("search service not available")
+			@Nested
+			class TestWithoutSearchService {
+
+				@Test
+				void shouldNotHaveSearchLink() {
+					when(systemStatusService.isSearchServerAvailable()).thenReturn(false);
+
+					var model = controller.getRootResource();
+
+					assertThat(model.getLink(RootController.REL_SEARCH)).isNotPresent();
+				}
+			}
+		}
+
+		@DisplayName("with no role")
+		@Nested
+		class TestWithNoRole {
+
+			@BeforeEach
+			void mockCurrentUserService() {
+				when(currentUserService.hasRole(anyString())).thenReturn(false);
+			}
+
+			@Test
+			void shouldHaveNoLinkAtAll() {
+				var model = controller.getRootResource();
+
+				assertThat(model.getLinks()).isEmpty();
+			}
+		}
+
+		@DisplayName("current user")
+		@Nested
+		class CurrentUser {
+
+			@DisplayName("when usermanager url is configured")
+			@Nested
+			class UsermanagerUrlConfigured {
+
+				private String userProfileTemplate = "UserProfileTemplate/%s";
+
+				@BeforeEach
+				void mock() {
+					when(currentUserService.getUserId()).thenReturn(UserProfileTestFactory.ID);
+
+					when(userManagerUrlProvider.isConfiguredForUserProfile()).thenReturn(true);
+					when(userManagerUrlProvider.getUserProfileTemplate()).thenReturn(userProfileTemplate);
+				}
+
+				@Test
+				void shouldHaveCurrentUserLink() {
+					var model = controller.getRootResource();
+
+					assertThat(model.getLink(RootController.REL_CURRENT_USER)).isPresent().get().extracting(Link::getHref)
+							.isEqualTo("UserProfileTemplate/" + UserProfileTestFactory.ID.toString());
+				}
+			}
+
+			@DisplayName("when usermanager url is not configured")
+			@Nested
+			class UsermanagerUrlNotConfigured {
+
+				@BeforeEach
+				void mock() {
+					when(userManagerUrlProvider.isConfiguredForUserProfile()).thenReturn(false);
+				}
+
+				@Test
+				void shouldNotHaveCurrentUserLink() {
+					var model = controller.getRootResource();
+
+					assertThat(model.getLink(RootController.REL_CURRENT_USER)).isNotPresent();
+				}
+			}
+
+			@DisplayName("when external Id cannot resolved to internal id")
+			@Nested
+			class NoInternalUserId {
+
+				@BeforeEach
+				void init() {
+					doReturn(true).when(controller).hasVerwaltungRole();
+
+					when(internalUserIdService.getUserId(any())).thenReturn(Optional.empty());
+					when(userManagerUrlProvider.isConfiguredForUserProfile()).thenReturn(false);
+				}
+
+				@Test
+				void shouldNotHaveMyVorgaengeLink() {
+					var model = controller.getRootResource();
+
+					assertThat(model.getLink(RootController.REL_MY_VORGAENGE)).isNotPresent();
+				}
+
+				@Test
+				void shouldNotHaveSearchMyVorgaengeLink() {
+					var model = controller.getRootResource();
+
+					assertThat(model.getLink(RootController.REL_SEARCH_MY_VORGAENGE)).isNotPresent();
+				}
+			}
+		}
+	}
+
+	@DisplayName("Root resource")
+	@Nested
+	class TestRootResource {
+
+		@BeforeEach
+		void initTest() {
+			when(currentUserService.getUserId()).thenReturn(UserId.from("42"));
+			when(internalUserIdService.getUserId(any())).thenReturn(Optional.empty());
+			when(userManagerUrlProvider.getUserProfileSearchTemplate()).thenReturn("UserProfileSearchTemplateDummy/$");
+		}
+
+		@Test
+		void shouldHaveJavaVersion() throws Exception {
+			callEndpoint().andExpect(jsonPath("$.javaVersion").exists());
+		}
+
+		@Test
+		void shouldHaveversion() throws Exception {
+			when(properties.getVersion()).thenReturn("42");
+
+			callEndpoint().andExpect(jsonPath("$.version").value("42"));
+		}
+
+		@Test
+		void shouldHavebuildTime() throws Exception {
+			when(properties.getTime()).thenReturn(LocalDateTime.parse("2021-04-01T10:30").toInstant(ZoneOffset.UTC));
+
+			callEndpoint().andExpect(jsonPath("$.buildTime").exists());
+		}
+
+		private ResultActions callEndpoint() throws Exception {
+			return mockMvc.perform(get(RootController.PATH)).andExpect(status().isOk());
+		}
+	}
+}
\ No newline at end of file
diff --git a/goofy-server/src/test/java/de/itvsh/goofy/SecurityTestFactory.java b/goofy-server/src/test/java/de/itvsh/goofy/SecurityTestFactory.java
new file mode 100644
index 0000000000000000000000000000000000000000..dabbacc63d6161fd0e559a3071d9882d62e88069
--- /dev/null
+++ b/goofy-server/src/test/java/de/itvsh/goofy/SecurityTestFactory.java
@@ -0,0 +1,42 @@
+/*
+ * 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.itvsh.goofy;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+
+import org.springframework.security.core.authority.SimpleGrantedAuthority;
+
+import de.itvsh.goofy.common.user.UserProfileTestFactory;
+
+public class SecurityTestFactory {
+
+	static final String SUBJECT = UserProfileTestFactory.ID.toString();
+	static final String USER_FIRSTNAME = "Tim";
+	static final String USER_LASTNAME = "Tester";
+	static final String ROLE = "Testrolle";
+	static final List<SimpleGrantedAuthority> AUTHORITIES = Arrays.asList(new SimpleGrantedAuthority(ROLE));
+	static final Collection<String> ORGANSIATIONSEINHEIT_IDS = List.of("812546");
+}
diff --git a/goofy-server/src/test/java/de/itvsh/goofy/TestSecurityConfiguration.java b/goofy-server/src/test/java/de/itvsh/goofy/TestSecurityConfiguration.java
new file mode 100644
index 0000000000000000000000000000000000000000..cc5791828256855e858c7485f445808f2b1c3fd4
--- /dev/null
+++ b/goofy-server/src/test/java/de/itvsh/goofy/TestSecurityConfiguration.java
@@ -0,0 +1,43 @@
+/*
+ * 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.itvsh.goofy;
+
+import org.springframework.context.annotation.Configuration;
+import org.springframework.core.annotation.Order;
+import org.springframework.security.config.annotation.web.WebSecurityConfigurer;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.config.annotation.web.builders.WebSecurity;
+import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
+
+@Configuration
+@Order(50)
+public class TestSecurityConfiguration extends WebSecurityConfigurerAdapter implements WebSecurityConfigurer<WebSecurity> {
+
+	@Override
+	protected void configure(HttpSecurity http) throws Exception {
+		super.configure(http);
+
+		http.csrf().disable();
+	}
+}
diff --git a/goofy-server/src/test/java/de/itvsh/goofy/attachment/AttachmentControllerTest.java b/goofy-server/src/test/java/de/itvsh/goofy/attachment/AttachmentControllerTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..8c76728c9c5519e5888c5f34d8a50f0feb75e715
--- /dev/null
+++ b/goofy-server/src/test/java/de/itvsh/goofy/attachment/AttachmentControllerTest.java
@@ -0,0 +1,96 @@
+/*
+ * 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.itvsh.goofy.attachment;
+
+import static org.mockito.ArgumentMatchers.*;
+import static org.mockito.Mockito.*;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
+
+import java.util.stream.Stream;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.mockito.ArgumentMatchers;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.springframework.test.web.servlet.MockMvc;
+import org.springframework.test.web.servlet.setup.MockMvcBuilders;
+
+import de.itvsh.goofy.common.binaryfile.BinaryFileModelAssembler;
+import de.itvsh.goofy.common.file.OzgFile;
+import de.itvsh.goofy.common.file.OzgFileService;
+import de.itvsh.goofy.common.file.OzgFileTestFactory;
+
+class AttachmentControllerTest {
+
+	private final String PATH = AttachmentController.ATTACHMENT_PATH;
+
+	@InjectMocks
+	private AttachmentController controller;
+	@Mock
+	private BinaryFileModelAssembler modelAssembler;
+	@Mock
+	private OzgFileService fileService;
+
+	private MockMvc mockMvc;
+
+	@BeforeEach
+	void initTest() {
+		mockMvc = MockMvcBuilders.standaloneSetup(controller).build();
+	}
+
+	@Nested
+	class TestGetAttachmentsByEingang {
+
+		@BeforeEach
+		void mockFileService() {
+			when(fileService.getAttachmentsByEingang(anyString())).thenReturn(Stream.of(OzgFileTestFactory.create()));
+		}
+
+		@Test
+		void shouldCallEndpoint() throws Exception {
+			callEndpoint();
+		}
+
+		@Test
+		void shoudlCallFileService() throws Exception {
+			callEndpoint();
+
+			verify(fileService).getAttachmentsByEingang(any());
+		}
+
+		@Test
+		void shouldCallModelAssembler() throws Exception {
+			callEndpoint();
+
+			verify(modelAssembler).toCollectionModel(ArgumentMatchers.<Stream<OzgFile>>any());
+		}
+
+		private void callEndpoint() throws Exception {
+			mockMvc.perform(get(PATH).param(AttachmentController.PARAM_EINGANG_ID, "1")).andExpect(status().isOk());
+		}
+	}
+}
\ No newline at end of file
diff --git a/goofy-server/src/test/java/de/itvsh/goofy/attachment/GrpcGetAttachmentsResponseTestFactory.java b/goofy-server/src/test/java/de/itvsh/goofy/attachment/GrpcGetAttachmentsResponseTestFactory.java
new file mode 100644
index 0000000000000000000000000000000000000000..07ecb0d2c00ce0209ff6a70c20d07104f9a44564
--- /dev/null
+++ b/goofy-server/src/test/java/de/itvsh/goofy/attachment/GrpcGetAttachmentsResponseTestFactory.java
@@ -0,0 +1,44 @@
+/*
+ * 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.itvsh.goofy.attachment;
+
+import java.util.Collections;
+
+import de.itvsh.goofy.common.file.GrpcOzgFileTestFactory;
+import de.itvsh.ozg.pluto.grpc.file.GrpcGetAttachmentsResponse;
+import de.itvsh.ozg.pluto.grpc.file.GrpcOzgFile;
+
+public class GrpcGetAttachmentsResponseTestFactory {
+
+	private static GrpcOzgFile GRPC_FILE = GrpcOzgFileTestFactory.create();
+
+	public static GrpcGetAttachmentsResponse create() {
+		return createBuilder().build();
+	}
+
+	public static GrpcGetAttachmentsResponse.Builder createBuilder() {
+		return GrpcGetAttachmentsResponse.newBuilder()
+				.addAllFile(Collections.singleton(GRPC_FILE));
+	}
+}
\ No newline at end of file
diff --git a/goofy-server/src/test/java/de/itvsh/goofy/common/GrpcCallContextTestFactory.java b/goofy-server/src/test/java/de/itvsh/goofy/common/GrpcCallContextTestFactory.java
new file mode 100644
index 0000000000000000000000000000000000000000..7422743e7270b5240c0e81b5bf529a59dc55e277
--- /dev/null
+++ b/goofy-server/src/test/java/de/itvsh/goofy/common/GrpcCallContextTestFactory.java
@@ -0,0 +1,44 @@
+/*
+ * 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.itvsh.goofy.common;
+
+import de.itvsh.goofy.common.user.GrpcUserTestFactory;
+import de.itvsh.ozg.pluto.grpc.command.GrpcCallContext;
+import de.itvsh.ozg.pluto.grpc.command.GrpcUser;
+
+public class GrpcCallContextTestFactory {
+
+	private static final GrpcUser USER = GrpcUserTestFactory.create();
+	public static final String CLIENT = "testGoofyClient";
+
+	public static GrpcCallContext create() {
+		return createBuilder().build();
+	}
+
+	public static GrpcCallContext.Builder createBuilder() {
+		return GrpcCallContext.newBuilder()
+				.setUser(USER)
+				.setClient(CLIENT);
+	}
+}
diff --git a/goofy-server/src/test/java/de/itvsh/goofy/common/IdBuilderTest.java b/goofy-server/src/test/java/de/itvsh/goofy/common/IdBuilderTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..51d7374cf49a29660b9b7b0a1990cca1977fda29
--- /dev/null
+++ b/goofy-server/src/test/java/de/itvsh/goofy/common/IdBuilderTest.java
@@ -0,0 +1,59 @@
+/*
+ * 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.itvsh.goofy.common;
+
+import static org.assertj.core.api.Assertions.*;
+import static org.mockito.Mockito.*;
+
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+
+import com.fasterxml.jackson.databind.BeanProperty;
+
+class IdBuilderTest {
+
+	@DisplayName("Test building ID when deserializing linked resources")
+	@Nested
+	class TestBuilingId {
+		private static final String ID = "id";
+
+		@Test
+		void shouldBuildId() {
+			IdBuilder idBuilder = new IdBuilder();
+
+			var idObject = idBuilder.build(ID);
+
+			assertThat(idObject).isInstanceOf(Object.class).asString().isEqualTo(ID);
+		}
+
+		@Test
+		void shouldCreateObjectBuilder() {
+			BeanProperty property = mock(BeanProperty.class);
+			ObjectBuilder<Object> idBuilder = new IdBuilder().constructContextAware(property);
+
+			assertThat(idBuilder).isNotNull();
+		}
+	}
+}
diff --git a/goofy-server/src/test/java/de/itvsh/goofy/common/LinkedResourceDeserializerTest.java b/goofy-server/src/test/java/de/itvsh/goofy/common/LinkedResourceDeserializerTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..4fbc567d388ed16852209ab7d298d26a31482aa1
--- /dev/null
+++ b/goofy-server/src/test/java/de/itvsh/goofy/common/LinkedResourceDeserializerTest.java
@@ -0,0 +1,55 @@
+/*
+ * 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.itvsh.goofy.common;
+
+import static org.assertj.core.api.Assertions.*;
+
+import java.io.IOException;
+
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+
+import com.fasterxml.jackson.core.exc.StreamReadException;
+import com.fasterxml.jackson.databind.DatabindException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+import de.itvsh.goofy.common.user.UserProfileTestFactory;
+
+class LinkedResourceDeserializerTest {
+	private static final String TEST_JSON = "{\"id\":\"/api/vorgangs/" + UserProfileTestFactory.ID.toString() + "\"}";
+
+	@DisplayName("Test the deserilization of linked resource json")
+	@Nested
+	class TestDeserialization {
+		@Test
+		void shouldDeserialize() throws StreamReadException, DatabindException, IOException {
+			LinkedResourceTestObject res = new ObjectMapper().readValue(TEST_JSON.getBytes(), LinkedResourceTestObject.class);
+
+			assertThat(res).hasFieldOrPropertyWithValue("id", UserProfileTestFactory.ID);
+		}
+
+	}
+
+}
diff --git a/goofy-server/src/test/java/de/itvsh/goofy/common/LinkedResourceSerializerTest.java b/goofy-server/src/test/java/de/itvsh/goofy/common/LinkedResourceSerializerTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..82d1e5939f290dd9af69860dd594d925a7110fa7
--- /dev/null
+++ b/goofy-server/src/test/java/de/itvsh/goofy/common/LinkedResourceSerializerTest.java
@@ -0,0 +1,52 @@
+/*
+ * 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.itvsh.goofy.common;
+
+import static org.assertj.core.api.Assertions.*;
+
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+import de.itvsh.goofy.common.user.UserProfileTestFactory;
+
+class LinkedResourceSerializerTest {
+
+	@DisplayName("Test the json serilization of linked resource annotations")
+	@Nested
+	class TestSerialization {
+		@Test
+		void shouldSerialize() throws JsonProcessingException {
+			var testObj = new LinkedResourceTestObject(UserProfileTestFactory.ID);
+
+			String serialized = new ObjectMapper().writeValueAsString(testObj);
+
+			assertThat(serialized).isEqualTo("{\"id\":\"/api/vorgangs/" + UserProfileTestFactory.ID.toString() + "\"}");
+		}
+
+	}
+}
diff --git a/goofy-server/src/test/java/de/itvsh/goofy/common/LinkedResourceTestObject.java b/goofy-server/src/test/java/de/itvsh/goofy/common/LinkedResourceTestObject.java
new file mode 100644
index 0000000000000000000000000000000000000000..9d35983568649937ebec6347946b4c7115995ab3
--- /dev/null
+++ b/goofy-server/src/test/java/de/itvsh/goofy/common/LinkedResourceTestObject.java
@@ -0,0 +1,38 @@
+/*
+ * 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.itvsh.goofy.common;
+
+import de.itvsh.goofy.common.user.UserId;
+import de.itvsh.goofy.vorgang.VorgangController;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+
+@Getter
+@AllArgsConstructor
+@NoArgsConstructor
+class LinkedResourceTestObject {
+	@LinkedResource(controllerClass = VorgangController.class)
+	private UserId id;
+}
diff --git a/goofy-server/src/test/java/de/itvsh/goofy/common/LinkedUserProfileResourceDeserializerTest.java b/goofy-server/src/test/java/de/itvsh/goofy/common/LinkedUserProfileResourceDeserializerTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..2ac3952751a60947a4ecc4eb44595934d4a87a7e
--- /dev/null
+++ b/goofy-server/src/test/java/de/itvsh/goofy/common/LinkedUserProfileResourceDeserializerTest.java
@@ -0,0 +1,53 @@
+/*
+ * 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.itvsh.goofy.common;
+
+import static org.assertj.core.api.Assertions.*;
+
+import java.io.IOException;
+
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+
+import com.fasterxml.jackson.core.exc.StreamReadException;
+import com.fasterxml.jackson.databind.DatabindException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+import de.itvsh.goofy.common.user.UserProfileTestFactory;
+
+class LinkedUserProfileResourceDeserializerTest {
+	private static final String TEST_JSON = "{\"id\":\"http://localhost/api/profile/" + UserProfileTestFactory.ID.toString() + "\"}";
+
+	@DisplayName("Test the deserilization of linked resource json")
+	@Nested
+	class TestDeserialization {
+		@Test
+		void shouldDeserialize() throws StreamReadException, DatabindException, IOException {
+			LinkedUserProfileResourceTestObject res = new ObjectMapper().readValue(TEST_JSON.getBytes(), LinkedUserProfileResourceTestObject.class);
+
+			assertThat(res).hasFieldOrPropertyWithValue("id", UserProfileTestFactory.ID);
+		}
+	}
+}
diff --git a/goofy-server/src/test/java/de/itvsh/goofy/common/LinkedUserProfileResourceSerializerTest.java b/goofy-server/src/test/java/de/itvsh/goofy/common/LinkedUserProfileResourceSerializerTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..ec0f071f36690e3a38dde89d2825df311319ad6b
--- /dev/null
+++ b/goofy-server/src/test/java/de/itvsh/goofy/common/LinkedUserProfileResourceSerializerTest.java
@@ -0,0 +1,71 @@
+/*
+ * 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.itvsh.goofy.common;
+
+import static org.assertj.core.api.Assertions.*;
+import static org.mockito.Mockito.*;
+
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.mockito.Mock;
+import org.springframework.context.ApplicationContext;
+import org.springframework.core.env.Environment;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+import de.itvsh.goofy.common.user.UserProfileTestFactory;
+
+class LinkedUserProfileResourceSerializerTest {
+	@DisplayName("Test the json serilization of linked user profile resource annotations")
+	@Nested
+	class TestSerialization {
+		private static final String HTTP_LOCALHOST = "http://localhost/";
+		private static final String API_TEMPLATE = "api/profile/%s";
+		private static final String API_PATH = "api/profile/";
+
+		private UserProfileUrlProvider provider = new UserProfileUrlProvider();
+
+		@Mock
+		private ApplicationContext context;
+		@Mock
+		private Environment env;
+
+		@Test
+		void shouldSerialize() throws JsonProcessingException {
+			when(env.getProperty(UserProfileUrlProvider.URL_ROOT_KEY)).thenReturn(HTTP_LOCALHOST);
+			when(env.getProperty(UserProfileUrlProvider.USER_PROFILES_TEMPLATE_KEY)).thenReturn(API_TEMPLATE);
+			when(context.getEnvironment()).thenReturn(env);
+			provider.setApplicationContext(context);
+
+			var testObj = new LinkedUserProfileResourceTestObject(UserProfileTestFactory.ID);
+
+			String serialized = new ObjectMapper().writeValueAsString(testObj);
+
+			assertThat(serialized).isEqualTo("{\"id\":\"" + HTTP_LOCALHOST + API_PATH + UserProfileTestFactory.ID.toString() + "\"}");
+		}
+
+	}
+}
diff --git a/goofy-server/src/test/java/de/itvsh/goofy/common/LinkedUserProfileResourceTestObject.java b/goofy-server/src/test/java/de/itvsh/goofy/common/LinkedUserProfileResourceTestObject.java
new file mode 100644
index 0000000000000000000000000000000000000000..f97355ad439c33e27de8a3056742130ba1c8e4a6
--- /dev/null
+++ b/goofy-server/src/test/java/de/itvsh/goofy/common/LinkedUserProfileResourceTestObject.java
@@ -0,0 +1,37 @@
+/*
+ * 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.itvsh.goofy.common;
+
+import de.itvsh.goofy.common.user.UserId;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+
+@Getter
+@AllArgsConstructor
+@NoArgsConstructor
+class LinkedUserProfileResourceTestObject {
+	@LinkedUserProfileResource
+	private UserId id;
+}
diff --git a/goofy-server/src/test/java/de/itvsh/goofy/common/ModelBuilderTest.java b/goofy-server/src/test/java/de/itvsh/goofy/common/ModelBuilderTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..97a15a5e6898d2c1d2358a058db56aadfc0f8392
--- /dev/null
+++ b/goofy-server/src/test/java/de/itvsh/goofy/common/ModelBuilderTest.java
@@ -0,0 +1,153 @@
+/*
+ * 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.itvsh.goofy.common;
+
+import static org.assertj.core.api.Assertions.*;
+import static org.mockito.Mockito.*;
+
+import java.util.UUID;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.mockito.Mock;
+import org.springframework.context.ApplicationContext;
+import org.springframework.core.env.Environment;
+import org.springframework.web.bind.annotation.RequestMapping;
+
+import lombok.Builder;
+
+class ModelBuilderTest {
+
+	@DisplayName("Add link by annotation if missing")
+	@Nested
+	class TestAddLinkByAnnotationIfMissing {
+
+		private static final String USER_MANAGER_URL = "http://localhost";
+		private static final String USER_MANAGER_PROFILE_TEMPLATE = "/api/profile/%s";
+
+		private UserProfileUrlProvider provider = new UserProfileUrlProvider();
+
+		@Mock
+		private ApplicationContext context;
+		@Mock
+		private Environment env;
+
+		private TestEntity entity = TestEntityTestFactory.create();
+
+		@BeforeEach
+		void mockEnvironment() {
+			when(env.getProperty(UserProfileUrlProvider.URL_ROOT_KEY)).thenReturn(USER_MANAGER_URL);
+			when(env.getProperty(UserProfileUrlProvider.USER_PROFILES_TEMPLATE_KEY)).thenReturn(USER_MANAGER_PROFILE_TEMPLATE);
+			when(context.getEnvironment()).thenReturn(env);
+
+			provider.setApplicationContext(context);
+		}
+
+		@Test
+		void shouldHaveAddLinkByLinkedResource() {
+			var model = ModelBuilder.fromEntity(entity).buildModel();
+
+			assertThat(model.getLink(TestController.FILE_REL).get().getHref()).isEqualTo(TestController.PATH + "/" + TestEntityTestFactory.FILE);
+		}
+
+		@Test
+		void shouldHaveAddLinkByLinkedUserProfileResource() {
+			var model = ModelBuilder.fromEntity(entity).buildModel();
+
+			assertThat(model.getLink(TestController.USER_REL).get().getHref())
+					.isEqualTo(String.format(USER_MANAGER_URL + USER_MANAGER_PROFILE_TEMPLATE, TestEntityTestFactory.USER));
+		}
+	}
+
+	@DisplayName("if usermanager is not configured")
+	@Nested
+	class TestNotAddLinkByAnnotationIfNotConfigured {
+
+		private UserProfileUrlProvider provider = new UserProfileUrlProvider();
+
+		@Mock
+		private ApplicationContext context;
+		@Mock
+		private Environment env;
+
+		private TestEntity entity = TestEntityTestFactory.create();
+
+		@BeforeEach
+		void mockEnvironment() {
+			when(env.getProperty(UserProfileUrlProvider.URL_ROOT_KEY)).thenReturn(null);
+			when(context.getEnvironment()).thenReturn(env);
+
+			provider.setApplicationContext(context);
+		}
+
+		@Test
+		void shouldHaveAddLinkByLinkedResource() {
+			var model = ModelBuilder.fromEntity(entity).buildModel();
+
+			assertThat(model.getLink(TestController.FILE_REL).get().getHref()).isEqualTo(TestController.PATH + "/" + TestEntityTestFactory.FILE);
+		}
+
+		@Test
+		void shouldNotHaveLinkAddByLinkedUserProfileAnnotation() {
+			var model = ModelBuilder.fromEntity(entity).buildModel();
+
+			assertThat(model.getLink(TestController.USER_REL)).isNotPresent();
+		}
+	}
+}
+
+@Builder
+class TestEntity {
+
+	@LinkedResource(controllerClass = TestController.class)
+	private String file;
+
+	@LinkedUserProfileResource
+	private String user;
+}
+
+@RequestMapping(TestController.PATH)
+class TestController {
+
+	static final String PATH = "/api/test";
+
+	static final String USER_REL = "user";
+	static final String FILE_REL = "file";
+
+}
+
+class TestEntityTestFactory {
+
+	static final String USER = UUID.randomUUID().toString();
+	static final String FILE = UUID.randomUUID().toString();
+
+	public static TestEntity create() {
+		return TestEntity.builder()
+				.file(FILE)
+				.user(USER)
+				.build();
+	}
+}
diff --git a/goofy-server/src/test/java/de/itvsh/goofy/common/RegexUtilTest.java b/goofy-server/src/test/java/de/itvsh/goofy/common/RegexUtilTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..265f9e200cc7eaa376ccb34b19fac14ac395348c
--- /dev/null
+++ b/goofy-server/src/test/java/de/itvsh/goofy/common/RegexUtilTest.java
@@ -0,0 +1,149 @@
+/*
+ * 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.itvsh.goofy.common;
+
+import static org.assertj.core.api.Assertions.*;
+
+import java.util.regex.Pattern;
+
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+
+public class RegexUtilTest {
+
+	@Nested
+	class TestValidationEMail {
+
+		private final String REGEX = RegexUtil.VALIDATION_EMAIL;
+
+		@Nested
+		class TestEmailAt {
+
+			@Test
+			void shouldMatchOnOneAtChar() {
+				shouldMatch("localpart@domain.com");
+			}
+
+			@Test
+			void shouldNotMatchOnNoneAtChar() {
+				shouldNotMatch("localpartdomain.com");
+			}
+
+			@Test
+			void shouldNotMatchOnMultipleAtChar() {
+				shouldNotMatch("localpart@@domain.com");
+			}
+		}
+
+		@Nested
+		class TestDomain {
+
+			@Nested
+			class TestDomainDot {
+
+				@Test
+				void shouldMatchOnOneDot() {
+					shouldMatch("localpart@domain.com");
+				}
+
+				@Test
+				void shouldNotMatchOnNoneDot() {
+					shouldNotMatch("localpart@domaincom");
+				}
+
+				@Test
+				void shouldMatchOnMultipleDots() {
+					shouldNotMatch("localpart@domain...com");
+				}
+			}
+
+			@Nested
+			class TestDomainCharCaseSensitive {
+
+				@Test
+				void shouldMatchOnLowerCaseChar() {
+					shouldMatch("localpart@domain.com");
+				}
+
+				@Test
+				void shouldMatchOnUpperCaseChar() {
+					shouldMatch("localpart@DOMAIN.COM");
+				}
+			}
+		}
+
+		@Nested
+		class TestLocalPart {
+
+			@Nested
+			class TestLocalPartChar {
+
+				@Test
+				void shouldMatchOnAtLeastOneChar() {
+					shouldMatch("l@domain.com");
+				}
+
+				@Test
+				void shouldMatchOnNoneChar() {
+					shouldNotMatch("@domain.com");
+				}
+			}
+
+			@Nested
+			class TestLocalPartCharCaseSensitive {
+
+				@Test
+				void shouldAllowUpperCaseChar() {
+					shouldMatch("UPPER_CHAR@domain.com");
+				}
+
+				@Test
+				void shouldAllowLowerCaseChar() {
+					shouldMatch("lower_char@domain.com");
+				}
+			}
+		}
+
+		@Test
+		void shouldAcceptCharInLocalPart() {
+			shouldMatch("local-part-1234567890!#$%&'*+/=?^_`{|}~-.'/&%*@domain.com");
+		}
+
+		private void shouldMatch(String eMail) {
+			boolean match = matchPattern(eMail);
+
+			assertThat(match).isTrue();
+		}
+
+		private void shouldNotMatch(String eMail) {
+			boolean match = matchPattern(eMail);
+
+			assertThat(match).isFalse();
+		}
+
+		private boolean matchPattern(String eMail) {
+			return Pattern.matches(REGEX, eMail);
+		}
+	}
+}
\ No newline at end of file
diff --git a/goofy-server/src/test/java/de/itvsh/goofy/common/UserProfileUrlProviderTestFactory.java b/goofy-server/src/test/java/de/itvsh/goofy/common/UserProfileUrlProviderTestFactory.java
new file mode 100644
index 0000000000000000000000000000000000000000..22c24b24229f915b64b1720a5cf64f7337572133
--- /dev/null
+++ b/goofy-server/src/test/java/de/itvsh/goofy/common/UserProfileUrlProviderTestFactory.java
@@ -0,0 +1,45 @@
+/*
+ * 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.itvsh.goofy.common;
+
+import static org.mockito.Mockito.*;
+
+import org.springframework.context.ApplicationContext;
+import org.springframework.core.env.Environment;
+
+public class UserProfileUrlProviderTestFactory {
+	public static final String ROOT_URL = "https://localhost";
+	public static final String USER_PROFILES_API_PATH = "/api/userProfiles/";
+	public static final String KOP_USER_MANAGER_PROFILE_TEMPLATE = "kop.user-manager.profile-template";
+	public static final String KOP_USER_MANAGER_URL = "kop.user-manager.url";
+
+	public static void initUserProfileUrlProvider(UserProfileUrlProvider urlProvider) {
+		ApplicationContext context = mock(ApplicationContext.class);
+		Environment environment = mock(Environment.class);
+		when(environment.getProperty(KOP_USER_MANAGER_URL)).thenReturn(ROOT_URL);
+		when(environment.getProperty(KOP_USER_MANAGER_PROFILE_TEMPLATE)).thenReturn(USER_PROFILES_API_PATH + "%s");
+		when(context.getEnvironment()).thenReturn(environment);
+		urlProvider.setApplicationContext(context);
+	}
+}
diff --git a/goofy-server/src/test/java/de/itvsh/goofy/common/attacheditem/VorgangAttachedItemServiceTest.java b/goofy-server/src/test/java/de/itvsh/goofy/common/attacheditem/VorgangAttachedItemServiceTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..29653aded651c4b6e20d585c0d07a781c9d47fd0
--- /dev/null
+++ b/goofy-server/src/test/java/de/itvsh/goofy/common/attacheditem/VorgangAttachedItemServiceTest.java
@@ -0,0 +1,332 @@
+/*
+ * 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.itvsh.goofy.common.attacheditem;
+
+import static org.assertj.core.api.Assertions.*;
+import static org.mockito.ArgumentMatchers.*;
+import static org.mockito.Mockito.*;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.Spy;
+
+import de.itvsh.goofy.common.command.Command;
+import de.itvsh.goofy.common.command.CommandBodyMapper;
+import de.itvsh.goofy.common.command.CommandOrder;
+import de.itvsh.goofy.common.command.CommandService;
+import de.itvsh.goofy.common.command.CommandTestFactory;
+import de.itvsh.goofy.common.command.CreateCommand;
+import de.itvsh.goofy.kommentar.Kommentar;
+import de.itvsh.goofy.kommentar.KommentarTestFactory;
+import de.itvsh.goofy.vorgang.VorgangHeaderTestFactory;
+import de.itvsh.goofy.wiedervorlage.Wiedervorlage;
+import de.itvsh.goofy.wiedervorlage.WiedervorlageTestFactory;
+
+class VorgangAttachedItemServiceTest {
+
+	@Spy
+	@InjectMocks
+	private VorgangAttachedItemService service;
+	@Mock
+	private CommandBodyMapper commandBodyMapper;
+	@Mock
+	private CommandService commandService;
+
+	@DisplayName("Create new wiedervorlage")
+	@Nested
+	class TestCreateNewWiedervorlage {
+
+		private Wiedervorlage wiedervorlage = WiedervorlageTestFactory.create();
+
+		@BeforeEach
+		void mockServices() {
+			doReturn(VorgangAttachedItemTestFactory.create()).when(service).buildVorgangAttachedItem(any(), any(), any());
+			doReturn(CommandTestFactory.createCreateCommand()).when(service).buildCreateCommand(any(), any());
+		}
+
+		@Test
+		void shouldBuildVorgangAttachedItem() {
+			callCreateNewWiedervorlage();
+
+			verify(service).buildVorgangAttachedItem(wiedervorlage, VorgangHeaderTestFactory.ID, VorgangAttachedItemService.WIEDERVORLAGE_ITEM_NAME);
+		}
+
+		@Test
+		void shouldBuildCreateCommand() {
+			callCreateNewWiedervorlage();
+
+			verify(service).buildCreateCommand(eq(VorgangHeaderTestFactory.ID), any(VorgangAttachedItem.class));
+		}
+
+		@Test
+		void shouldCallCommandService() {
+			callCreateNewWiedervorlage();
+
+			verify(commandService).createCommand(any(CreateCommand.class));
+		}
+
+		private Command callCreateNewWiedervorlage() {
+			return service.createNewWiedervorlage(wiedervorlage, VorgangHeaderTestFactory.ID);
+		}
+	}
+
+	@DisplayName("Edit wiedervorlage")
+	@Nested
+	class TestEditWiedervorlage {
+
+		private Wiedervorlage wiedervorlage = WiedervorlageTestFactory.create();
+
+		@BeforeEach
+		void mockService() {
+			doReturn(VorgangAttachedItemTestFactory.create()).when(service).buildVorgangAttachedItem(any(), any(), any());
+			doReturn(CommandTestFactory.createCreateCommand()).when(service).buildUpdateCommand(any(), any(), any());
+		}
+
+		@Test
+		void shouldBuildVorgangAttachedItem() {
+			callEditWiedervorlage();
+
+			verify(service).buildVorgangAttachedItem(wiedervorlage, VorgangHeaderTestFactory.ID, VorgangAttachedItemService.WIEDERVORLAGE_ITEM_NAME);
+		}
+
+		@Test
+		void shouldBuildCreateCommand() {
+			callEditWiedervorlage();
+
+			verify(service).buildUpdateCommand(eq(VorgangHeaderTestFactory.ID), eq(WiedervorlageTestFactory.ID), any(VorgangAttachedItem.class));
+		}
+
+		@Test
+		void shouldCallCommandService() {
+			callEditWiedervorlage();
+
+			verify(commandService).createCommand(any(CreateCommand.class), eq(WiedervorlageTestFactory.VERSION));
+		}
+
+		private Command callEditWiedervorlage() {
+			return service.editWiedervorlage(wiedervorlage, wiedervorlage.getId(), wiedervorlage.getVersion());
+		}
+	}
+
+	@Nested
+	class TestWiedervorlageDone {
+		private Wiedervorlage wiedervorlage = WiedervorlageTestFactory.create();
+
+		@Test
+		void shouldCallCommandService() {
+			callSetWiedervorlageDone(true);
+
+			verify(commandService).createCommand(any(CreateCommand.class), eq(WiedervorlageTestFactory.VERSION));
+		}
+
+		@Test
+		void shouldSetItemName() {
+			CreateCommand command = service.createPatchCommand(WiedervorlageTestFactory.create(), false);
+
+			assertThat(((VorgangAttachedItem) command.getBody()).getItemName()).isEqualTo(Wiedervorlage.class.getSimpleName());
+		}
+
+		@Test
+		void shouldSetItem() {
+			CreateCommand command = service.createPatchCommand(WiedervorlageTestFactory.create(), false);
+
+			assertThat(((VorgangAttachedItem) command.getBody()).getItem()).isNotEmpty();
+		}
+
+		@Test
+		void shouldSetItemDoneValue() {
+			CreateCommand command = service.createPatchCommand(WiedervorlageTestFactory.create(), true);
+
+			assertThat(((VorgangAttachedItem) command.getBody()).getItem()).containsEntry("done", true);
+		}
+
+		private Command callSetWiedervorlageDone(boolean done) {
+			return service.setWiedervorlageDone(wiedervorlage, done);
+		}
+
+	}
+
+	@DisplayName("Create new kommentar")
+	@Nested
+	class TestCreateNewKommentar {
+
+		private Kommentar kommentar = KommentarTestFactory.create();
+
+		@BeforeEach
+		void mockServices() {
+			doReturn(VorgangAttachedItemTestFactory.create()).when(service).buildVorgangAttachedItem(any(), any(), any());
+			doReturn(CommandTestFactory.createCreateCommand()).when(service).buildCreateCommand(any(), any());
+		}
+
+		@Test
+		void shouldBuildVorgangAttachedItem() {
+			callCreateNewKommentar();
+
+			verify(service).buildVorgangAttachedItem(kommentar, VorgangHeaderTestFactory.ID, VorgangAttachedItemService.KOMMENTAR_ITEM_NAME);
+		}
+
+		@Test
+		void shouldBuildCreateCommand() {
+			callCreateNewKommentar();
+
+			verify(service).buildCreateCommand(eq(VorgangHeaderTestFactory.ID), any(VorgangAttachedItem.class));
+		}
+
+		@Test
+		void shouldCallCommandService() {
+			callCreateNewKommentar();
+
+			verify(commandService).createCommand(any(CreateCommand.class));
+		}
+
+		private Command callCreateNewKommentar() {
+			return service.createNewKommentar(kommentar, VorgangHeaderTestFactory.ID);
+		}
+	}
+
+	@DisplayName("Edit kommentar")
+	@Nested
+	class TestEditKommentar {
+
+		private Kommentar kommentar = KommentarTestFactory.create();
+
+		@BeforeEach
+		void mockService() {
+			doReturn(VorgangAttachedItemTestFactory.create()).when(service).buildVorgangAttachedItem(any(), any(), any());
+			doReturn(CommandTestFactory.createCreateCommand()).when(service).buildUpdateCommand(any(), any(), any());
+		}
+
+		@Test
+		void shouldBuildVorgangAttachedItem() {
+			callEditKommentar();
+
+			verify(service).buildVorgangAttachedItem(kommentar, VorgangHeaderTestFactory.ID, VorgangAttachedItemService.KOMMENTAR_ITEM_NAME);
+		}
+
+		@Test
+		void shouldBuildCreateCommand() {
+			callEditKommentar();
+
+			verify(service).buildUpdateCommand(eq(VorgangHeaderTestFactory.ID), eq(KommentarTestFactory.ID), any(VorgangAttachedItem.class));
+		}
+
+		@Test
+		void shouldCallCommandService() {
+			callEditKommentar();
+
+			verify(commandService).createCommand(any(CreateCommand.class), eq(KommentarTestFactory.VERSION));
+		}
+
+		private Command callEditKommentar() {
+			return service.editKommentar(kommentar, KommentarTestFactory.ID, KommentarTestFactory.VERSION);
+		}
+	}
+
+	@DisplayName("build vorgang attached item")
+	@Nested
+	class TestBuildVorgangAttachedItem {
+
+		private static final Wiedervorlage BODY = WiedervorlageTestFactory.create();
+
+		@BeforeEach
+		void mockServices() {
+			when(commandBodyMapper.fromObjectToMap(any())).thenReturn(VorgangAttachedItemTestFactory.ITEM);
+		}
+
+		@Test
+		void shouldCallCommandBodyMapper() {
+			buildVorgangAttachedItem();
+
+			verify(commandBodyMapper).fromObjectToMap(BODY);
+		}
+
+		@Test
+		void shouldContainsVorgangId() {
+			var vorgangAttachedItem = buildVorgangAttachedItem();
+
+			assertThat(vorgangAttachedItem.getVorgangId()).isEqualTo(VorgangHeaderTestFactory.ID);
+		}
+
+		@Test
+		void shouldContainsItemName() {
+			var vorgangAttachedItem = buildVorgangAttachedItem();
+
+			assertThat(vorgangAttachedItem.getItemName()).isEqualTo(VorgangAttachedItemService.WIEDERVORLAGE_ITEM_NAME);
+		}
+
+		@Test
+		void shouldContainsItem() {
+			var vorgangAttachedItem = buildVorgangAttachedItem();
+
+			assertThat(vorgangAttachedItem.getItem()).isEqualTo(VorgangAttachedItemTestFactory.ITEM);
+		}
+
+		private VorgangAttachedItem buildVorgangAttachedItem() {
+			return service.buildVorgangAttachedItem(BODY, VorgangHeaderTestFactory.ID,
+					VorgangAttachedItemService.WIEDERVORLAGE_ITEM_NAME);
+		}
+	}
+
+	@DisplayName("build createCommand")
+	@Nested
+	class TestBuildCreateCommand {
+
+		@Test
+		void shouldContainsVorgangId() throws Exception {
+			var createCommand = buildCommand();
+
+			assertThat(createCommand.getVorgangId()).isEqualTo(VorgangHeaderTestFactory.ID);
+		}
+
+		@Test
+		void shouldContainsRelationId() throws Exception {
+			var createCommand = buildCommand();
+
+			assertThat(createCommand.getRelationId()).isEqualTo(VorgangHeaderTestFactory.ID);
+		}
+
+		@Test
+		void shouldContainsOrder() throws Exception {
+			var createCommand = buildCommand();
+
+			assertThat(createCommand.getOrder()).isEqualTo(CommandOrder.CREATE_ATTACHED_ITEM);
+		}
+
+		@Test
+		void shouldContainsBody() throws Exception {
+			var createCommand = buildCommand();
+
+			assertThat(((VorgangAttachedItem) createCommand.getBody())).usingRecursiveComparison()
+					.isEqualTo(VorgangAttachedItemTestFactory.create());
+		}
+
+		private CreateCommand buildCommand() {
+			return service.buildCreateCommand(VorgangHeaderTestFactory.ID, VorgangAttachedItemTestFactory.create());
+		}
+	}
+}
\ No newline at end of file
diff --git a/goofy-server/src/test/java/de/itvsh/goofy/common/attacheditem/VorgangAttachedItemTestFactory.java b/goofy-server/src/test/java/de/itvsh/goofy/common/attacheditem/VorgangAttachedItemTestFactory.java
new file mode 100644
index 0000000000000000000000000000000000000000..f689e4ac5cf2c26c53a1c8d03812203b3ea43679
--- /dev/null
+++ b/goofy-server/src/test/java/de/itvsh/goofy/common/attacheditem/VorgangAttachedItemTestFactory.java
@@ -0,0 +1,51 @@
+/*
+ * 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.itvsh.goofy.common.attacheditem;
+
+import java.util.Map;
+import java.util.UUID;
+
+import de.itvsh.goofy.common.attacheditem.VorgangAttachedItem;
+
+public class VorgangAttachedItemTestFactory {
+
+	public static final String ID = UUID.randomUUID().toString();
+	public static final long VERSION = 42;
+	public static final String CLIENT = "Goofy";
+	public static final String ITEM_NAME = "ItemName";
+	public static final Map<String, Object> ITEM = Map.of("oneKey", "oneValue");
+
+	public static VorgangAttachedItem create() {
+		return createBuilder().build();
+	}
+
+	public static VorgangAttachedItem.VorgangAttachedItemBuilder createBuilder() {
+		return VorgangAttachedItem.builder()
+				.id(ID)
+				.version(VERSION)
+				.client(CLIENT)
+				.itemName(ITEM_NAME)
+				.item(ITEM);
+	}
+}
\ No newline at end of file
diff --git a/goofy-server/src/test/java/de/itvsh/goofy/common/binaryfile/BinaryFileControllerITCase.java b/goofy-server/src/test/java/de/itvsh/goofy/common/binaryfile/BinaryFileControllerITCase.java
new file mode 100644
index 0000000000000000000000000000000000000000..3db4f520879715d0c6b8cb40bd84863f7223e17a
--- /dev/null
+++ b/goofy-server/src/test/java/de/itvsh/goofy/common/binaryfile/BinaryFileControllerITCase.java
@@ -0,0 +1,151 @@
+/*
+ * 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.itvsh.goofy.common.binaryfile;
+
+import static de.itvsh.goofy.JwtTokenUtil.*;
+import static org.mockito.ArgumentMatchers.*;
+import static org.mockito.Mockito.*;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.mockito.Mock;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.boot.test.mock.mockito.MockBean;
+import org.springframework.boot.test.mock.mockito.SpyBean;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.security.test.context.support.WithMockUser;
+import org.springframework.test.web.servlet.MockMvc;
+import org.springframework.test.web.servlet.ResultActions;
+
+import de.itvsh.goofy.common.downloadtoken.DownloadTokenController;
+import de.itvsh.goofy.common.downloadtoken.DownloadTokenProperties;
+import de.itvsh.goofy.common.downloadtoken.DownloadTokenTestFactory;
+import de.itvsh.goofy.common.file.OzgFileTestFactory;
+import de.itvsh.goofy.common.user.UserProfileTestFactory;
+import io.jsonwebtoken.JwtBuilder;
+
+@AutoConfigureMockMvc
+@SpringBootTest
+@WithMockUser
+class BinaryFileControllerITCase {
+
+	@SpyBean
+	private BinaryFileController controller;
+	@MockBean
+	private BinaryFileService service;
+	@Autowired
+	private MockMvc mockMvc;
+	@Autowired
+	private DownloadTokenProperties downloadTokenProperties;
+
+	private FileId fileId = FileId.createNew();
+
+	@Nested
+	class TestDragAndDropFlow {
+
+		private JwtBuilder tokenBuilder = DownloadTokenTestFactory.createTokenBuilder(downloadTokenProperties.getSecret(),
+				downloadTokenProperties.getValidity());
+
+		@BeforeEach
+		void init() {
+			when(service.getFile(any())).thenReturn(OzgFileTestFactory.create());
+		}
+
+		@Test
+		void shouldGet403WhenNoFileId() throws Exception {
+			setTokenToSecuriyContext(tokenBuilder.compact());
+
+			performRequest().andExpect(status().isForbidden());
+		}
+
+		@Test
+		void shouldGet403WhenWrongFileId() throws Exception {
+			setTokenToSecuriyContext(tokenBuilder.addClaims(createClaims(FileId.createNew())).compact());
+
+			performRequest().andExpect(status().isForbidden());
+		}
+
+		@Test
+		void shouldGetFile() throws Exception {
+			setTokenToSecuriyContext(tokenBuilder.addClaims(createClaims(fileId)).compact());
+
+			performRequest().andExpect(status().isOk());
+		}
+
+		private Map<String, Object> createClaims(FileId fileId) {
+			return new HashMap<>(Map.of(
+					FIRSTNAME_CLAIM, UserProfileTestFactory.FIRSTNAME,
+					LASTNAME_CLAIM, UserProfileTestFactory.LASTNAME,
+					ROLE_CLAIM, List.of(),
+					FILEID_CLAIM, fileId.toString()));
+		}
+
+		void setTokenToSecuriyContext(String token) throws Exception {
+			mockMvc.perform(get(DownloadTokenController.DOWNLOAD_TOKEN_PATH + "?" + DownloadTokenController.PARAM_TOKEN + "=" + token))
+					.andExpect(status().isOk());
+		}
+	}
+
+	@Nested
+	class TestDefaultFlow {
+
+		@Mock
+		private Authentication authentication;
+
+		@BeforeEach
+		void init() {
+			when(service.getFile(any())).thenReturn(OzgFileTestFactory.create());
+			SecurityContextHolder.getContext().setAuthentication(authentication);
+		}
+
+		@Test
+		void shouldGet401WhenUnautorised() throws Exception {
+			when(authentication.isAuthenticated()).thenReturn(Boolean.FALSE);
+
+			performRequest().andExpect(status().isUnauthorized());
+		}
+
+		@Test
+		void shouldGetFile() throws Exception {
+			when(authentication.isAuthenticated()).thenReturn(Boolean.TRUE);
+
+			performRequest().andExpect(status().isOk());
+		}
+	}
+
+	ResultActions performRequest() throws Exception {
+		return mockMvc.perform(get(BinaryFileController.PATH + "/" + fileId.toString()));
+	}
+
+}
diff --git a/goofy-server/src/test/java/de/itvsh/goofy/common/binaryfile/BinaryFileControllerTest.java b/goofy-server/src/test/java/de/itvsh/goofy/common/binaryfile/BinaryFileControllerTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..6583a3af5970b952b238ac765a4c0bc9b55126f8
--- /dev/null
+++ b/goofy-server/src/test/java/de/itvsh/goofy/common/binaryfile/BinaryFileControllerTest.java
@@ -0,0 +1,308 @@
+/*
+ * 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.itvsh.goofy.common.binaryfile;
+
+import static org.assertj.core.api.Assertions.*;
+import static org.hamcrest.CoreMatchers.*;
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.*;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.request;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.Spy;
+import org.springframework.hateoas.CollectionModel;
+import org.springframework.hateoas.EntityModel;
+import org.springframework.http.MediaType;
+import org.springframework.test.web.servlet.MockMvc;
+import org.springframework.test.web.servlet.MvcResult;
+import org.springframework.test.web.servlet.ResultActions;
+import org.springframework.test.web.servlet.setup.MockMvcBuilders;
+import org.springframework.web.multipart.MultipartFile;
+import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
+
+import de.itvsh.goofy.common.file.OzgFile;
+import de.itvsh.goofy.common.file.OzgFileTestFactory;
+import de.itvsh.goofy.vorgang.VorgangHeaderTestFactory;
+import de.itvsh.kop.common.errorhandling.TechnicalException;
+
+class BinaryFileControllerTest {
+
+	private final String PATH = BinaryFileController.PATH;
+
+	@Spy
+	@InjectMocks
+	private BinaryFileController controller;
+	@Mock
+	private BinaryFileService service;
+	@Mock
+	private BinaryFileModelAssembler modelAssembler;
+	@Mock
+	private FileIdMapper fileIdMapper;
+
+	private MockMvc mockMvc;
+
+	@BeforeEach
+	void initTest() {
+		mockMvc = MockMvcBuilders.standaloneSetup(controller).build();
+	}
+
+	@Nested
+	class TestUploadFile {
+
+		private CompletableFuture<FileId> fileIdFuture = CompletableFuture.completedFuture(BinaryFileTestFactory.FILE_ID);
+
+		@BeforeEach
+		void mockServiec() {
+			when(service.uploadFile(any())).thenReturn(fileIdFuture);
+		}
+
+		@Test
+		void shouldCallService() throws Exception {
+			var uploadBinaryFile = UploadBinaryFileTestFactory.create();
+			doReturn(uploadBinaryFile).when(controller).buildBinaryFileUploadRequest(any(), any(), any());
+
+			callEndpoint();
+
+			verify(service).uploadFile(uploadBinaryFile);
+		}
+
+		@Test
+		void shouldReturnCreatedStatus() throws Exception {
+			var result = callEndpoint();
+
+			mockMvc.perform(asyncDispatch(result)).andExpect(status().isCreated());
+		}
+
+		@Test
+		void shouldReturnLocation() throws Exception {
+			var result = callEndpoint();
+
+			mockMvc.perform(asyncDispatch(result))
+					.andExpect(header().string("Location", containsString("/api/binaryFiles/" + BinaryFileTestFactory.FILE_ID)));
+		}
+
+		private MvcResult callEndpoint() throws Exception {
+			return mockMvc.perform(multipart(PATH + "/" + VorgangHeaderTestFactory.ID + "/" + BinaryFileTestFactory.FIELD + "/file")
+					.file(BinaryFileTestFactory.TEST_FILE))
+					.andExpect(request().asyncStarted())
+					.andReturn();
+		}
+	}
+
+	@Nested
+	class TestBuildBinaryFileUploadRequest {
+
+		@Test
+		void shouldContainsVorgangId() {
+			var result = buildBinaryFileUploadRequest();
+
+			assertThat(result.getVorgangId()).isEqualTo(VorgangHeaderTestFactory.ID);
+		}
+
+		@Test
+		void shouldContainsField() {
+			var result = buildBinaryFileUploadRequest();
+
+			assertThat(result.getField()).isEqualTo(BinaryFileTestFactory.FIELD);
+		}
+
+		@Test
+		void shouldContainsSize() {
+			var result = buildBinaryFileUploadRequest();
+
+			assertThat(result.getSize()).isEqualTo(OzgFileTestFactory.SIZE);
+		}
+
+		@Test
+		void shouldContainsName() {
+			var result = buildBinaryFileUploadRequest();
+
+			assertThat(result.getName()).isEqualTo(OzgFileTestFactory.NAME);
+		}
+
+		@Test
+		void shouldContainsContentType() {
+			var result = buildBinaryFileUploadRequest();
+
+			assertThat(result.getContentType()).isEqualTo(OzgFileTestFactory.CONTENT_TYPE);
+		}
+
+		@Test
+		void shouldGetInputStreamFromFile() {
+			buildBinaryFileUploadRequest();
+
+			verify(controller).getInputStream(BinaryFileTestFactory.TEST_FILE);
+		}
+
+		private UploadBinaryFileRequest buildBinaryFileUploadRequest() {
+			return controller.buildBinaryFileUploadRequest(VorgangHeaderTestFactory.ID, BinaryFileTestFactory.FIELD, BinaryFileTestFactory.TEST_FILE);
+		}
+	}
+
+	@Nested
+	class TestGetInputStreamOfFile {
+
+		@Mock
+		private MultipartFile file;
+
+		@Test
+		void shouldReturnStream() throws IOException {
+			var uploadStream = file.getInputStream();
+
+			when(file.getInputStream()).thenReturn(uploadStream);
+
+			assertThat(controller.getInputStream(file)).isEqualTo(uploadStream);
+		}
+
+		@Test
+		void shouldThrowException() throws IOException {
+			when(file.getInputStream()).thenThrow(IOException.class);
+
+			assertThrows(TechnicalException.class, () -> controller.getInputStream(file));
+		}
+	}
+
+	@Nested
+	class TestGetFileData {
+
+		@Mock
+		private StreamingResponseBody body;
+		private OzgFile ozgFile = OzgFileTestFactory.create();
+
+		@BeforeEach
+		void mockService() {
+			when(service.getFile(any())).thenReturn(ozgFile);
+		}
+
+		@Test
+		void shouldCallServiceGetFile() throws Exception {
+			callEndpoint();
+
+			verify(service).getFile(OzgFileTestFactory.ID);
+		}
+
+		@Test
+		void shouldBuildResponseEntity() throws Exception {
+			doReturn(body).when(controller).createDownloadStreamingBody(OzgFileTestFactory.ID);
+
+			callEndpoint();
+
+			verify(controller).buildResponseEntity(ozgFile, body);
+		}
+
+		private ResultActions callEndpoint() throws Exception {
+			return mockMvc.perform(get(PATH + "/" + OzgFileTestFactory.ID).contentType(MediaType.APPLICATION_OCTET_STREAM_VALUE))
+					.andExpect(status().isOk());
+		}
+	}
+
+	@Nested
+	class TestCreateDownloadStreamingBody {
+
+		@Mock
+		private OutputStream out;
+
+		@Test
+		void shouldCallServiceWriteFileContent() throws IOException {
+			controller.createDownloadStreamingBody(OzgFileTestFactory.ID).writeTo(out);
+
+			verify(service).writeFileContent(any(), any());
+		}
+	}
+
+	@Nested
+	class TestGetFile {
+
+		private OzgFile ozgFile = OzgFileTestFactory.create();
+
+		@BeforeEach
+		void mockServiec() {
+			when(service.getFile(any())).thenReturn(ozgFile);
+		}
+
+		@Test
+		void shouldCallService() throws Exception {
+			callEndpoint();
+
+			verify(service).getFile(BinaryFileTestFactory.FILE_ID);
+		}
+
+		@Test
+		void shouldCallAssembler() throws Exception {
+			callEndpoint();
+
+			verify(modelAssembler).toModel(ozgFile);
+		}
+
+		private ResultActions callEndpoint() throws Exception {
+			return mockMvc.perform(get(BinaryFileController.PATH + '/' + BinaryFileTestFactory.FILE_ID)
+					.contentType(MediaType.APPLICATION_JSON_VALUE))
+					.andExpect(status().isOk());
+		}
+	}
+
+	@Nested
+	class TestGetFiles {
+
+		private OzgFile file = OzgFileTestFactory.create();
+
+		@BeforeEach
+		void mockServiec() {
+			when(service.getFiles(any())).thenReturn(Stream.of(file));
+			when(fileIdMapper.toString(any())).then(fileId -> fileId.toString());
+		}
+
+		@Test
+		void shouldCallService() throws Exception {
+			callEndpoint();
+
+			verify(service).getFiles(any());
+		}
+
+		@Test
+		void shouldCallAssembler() throws Exception {
+			callEndpoint();
+
+			verify(modelAssembler).toCollectionModel(Stream.of(file).collect(Collectors.toList()));
+		}
+
+		private CollectionModel<EntityModel<OzgFile>> callEndpoint() throws Exception {
+			return controller.getFiles(List.of(BinaryFileTestFactory.FILE_ID));
+		}
+	}
+}
\ No newline at end of file
diff --git a/goofy-server/src/test/java/de/itvsh/goofy/common/binaryfile/BinaryFileDownloadStreamObserverTest.java b/goofy-server/src/test/java/de/itvsh/goofy/common/binaryfile/BinaryFileDownloadStreamObserverTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..a8eda9af859bf3138a05502970747e3de4d66070
--- /dev/null
+++ b/goofy-server/src/test/java/de/itvsh/goofy/common/binaryfile/BinaryFileDownloadStreamObserverTest.java
@@ -0,0 +1,92 @@
+/*
+ * 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.itvsh.goofy.common.binaryfile;
+
+import static org.mockito.Mockito.*;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.concurrent.CompletableFuture;
+
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+
+import com.google.protobuf.ByteString;
+
+import de.itvsh.ozg.pluto.grpc.binaryFile.GrpcGetBinaryFileDataResponse;
+
+class BinaryFileDownloadStreamObserverTest {
+
+	@InjectMocks
+	private BinaryFileDownloadStreamObserver downloadStreamObserver;
+	@Mock
+	private CompletableFuture<Boolean> streamFuture;
+	@Mock
+	private OutputStream out;
+
+	@Nested
+	class TestOnNext {
+
+		@Mock
+		private GrpcGetBinaryFileDataResponse response;
+
+		@Test
+		void shouldWriteToStreamOnReceivingContentPart() throws IOException {
+			downloadStreamObserver.onNext(createFileConentResponse());
+
+			verify(out).write(BinaryFileTestFactory.DATA);
+		}
+
+		private GrpcGetBinaryFileDataResponse createFileConentResponse() {
+			return GrpcGetBinaryFileDataResponse.newBuilder().setFileContent(ByteString.copyFrom(BinaryFileTestFactory.DATA)).build();
+		}
+	}
+
+	@Nested
+	class TestOnError {
+
+		@Mock
+		private Throwable throwable;
+
+		@Test
+		void shouldCompleteFutureExceptionally() {
+			downloadStreamObserver.onError(throwable);
+
+			verify(streamFuture).completeExceptionally(throwable);
+		}
+	}
+
+	@Nested
+	class TestOnCompleted {
+
+		@Test
+		void shouldCompleteFuture() {
+			downloadStreamObserver.onCompleted();
+
+			verify(streamFuture).complete(true);
+		}
+	}
+}
\ No newline at end of file
diff --git a/goofy-server/src/test/java/de/itvsh/goofy/common/binaryfile/BinaryFileITCase.java b/goofy-server/src/test/java/de/itvsh/goofy/common/binaryfile/BinaryFileITCase.java
new file mode 100644
index 0000000000000000000000000000000000000000..e9566fb44e5ca833244f4b64d11bd4bf29328a24
--- /dev/null
+++ b/goofy-server/src/test/java/de/itvsh/goofy/common/binaryfile/BinaryFileITCase.java
@@ -0,0 +1,122 @@
+/*
+ * 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.itvsh.goofy.common.binaryfile;
+
+import static org.mockito.ArgumentMatchers.*;
+import static org.mockito.Mockito.*;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
+
+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.servlet.AutoConfigureMockMvc;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.boot.test.mock.mockito.MockBean;
+import org.springframework.mock.web.MockMultipartFile;
+import org.springframework.security.test.context.support.WithMockUser;
+import org.springframework.test.web.servlet.MockMvc;
+import org.springframework.test.web.servlet.ResultActions;
+import org.springframework.util.unit.DataSize;
+
+import de.itvsh.goofy.vorgang.VorgangHeaderTestFactory;
+
+@AutoConfigureMockMvc
+@SpringBootTest
+@WithMockUser
+class BinaryFileITCase {
+
+	@MockBean
+	private BinaryFileRemoteService remoteService;
+
+	@Autowired
+	private MockMvc mockMvc;
+
+	@WithMockUser
+	@Nested
+	class TestUploadFile {
+
+		private final String SINGLE_PATH = BinaryFileController.PATH + "/{vorgangId}/{field}/file";
+		private final CompletableFuture<FileId> future = CompletableFuture.completedFuture(BinaryFileTestFactory.FILE_ID);
+
+		public static final byte[] DATA_OVER_3_MB = BinaryFileTestFactory.createByteOfSize(DataSize.ofMegabytes(41));
+		public static final MockMultipartFile TEST_FILE = new MockMultipartFile("file", BinaryFileTestFactory.NAME,
+				BinaryFileTestFactory.CONTENT_TYPE, DATA_OVER_3_MB);
+
+		@BeforeEach
+		void mockStub() {
+			when(remoteService.uploadFile(any())).thenReturn(future);
+		}
+
+		@Nested
+		class TestDefault {
+			@Test
+			void shouldReturnConstraintViolationException() throws Exception {
+				var result = callEndpoint(TEST_FILE, BinaryFileTestFactory.FIELD);
+
+				result.andExpect(status().isUnprocessableEntity());
+			}
+
+			@Test
+			void shouldReturnConstraintViolationExceptionData() throws Exception {
+				var result = callEndpoint(TEST_FILE, BinaryFileTestFactory.FIELD);
+
+				result.andExpect(jsonPath("issues[0].field").value("uploadBinaryFileRequest"))
+						.andExpect(jsonPath("issues[0].messageCode").value("validation_field_file_size_exceeded"))
+						.andExpect(jsonPath("issues[0].message").value("validation_field_file_size_exceeded"))
+						.andExpect(jsonPath("issues[0].parameters[0].name").value("unit"))
+						.andExpect(jsonPath("issues[0].parameters[0].value").value(UploadBinaryFileSizeValidator.DEFAULT_UNIT_STRING));
+			}
+
+			@Test
+			void shouldContainsExceptionDataOnDefaultMaxSize() throws Exception {
+				var result = callEndpoint(TEST_FILE, BinaryFileTestFactory.FIELD);
+
+				result.andExpect(jsonPath("issues[0].parameters[1].name").value("max"))
+						.andExpect(jsonPath("issues[0].parameters[1].value").value("40"));
+			}
+		}
+
+		@Nested
+		class TestForPostfachNachricht {
+
+			private final static String FIELD = "postfachNachrichtAttachment";
+
+			@Test
+			void shouldContainsExceptionDataOnPostfachNachrichtMaxSize() throws Exception {
+				var result = callEndpoint(TEST_FILE, FIELD);
+
+				result.andExpect(jsonPath("issues[0].parameters[1].name").value("max"))
+						.andExpect(jsonPath("issues[0].parameters[1].value").value("3"));
+			}
+		}
+
+		private ResultActions callEndpoint(MockMultipartFile file, String field) throws Exception {
+			return mockMvc.perform(multipart(SINGLE_PATH, VorgangHeaderTestFactory.ID, field).file(file));
+		}
+	}
+}
\ No newline at end of file
diff --git a/goofy-server/src/test/java/de/itvsh/goofy/common/binaryfile/BinaryFileModelAssemblerTest.java b/goofy-server/src/test/java/de/itvsh/goofy/common/binaryfile/BinaryFileModelAssemblerTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..16fc548badf4ac12f9dad6d9af970f9cb723d67b
--- /dev/null
+++ b/goofy-server/src/test/java/de/itvsh/goofy/common/binaryfile/BinaryFileModelAssemblerTest.java
@@ -0,0 +1,73 @@
+/*
+ * 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.itvsh.goofy.common.binaryfile;
+
+import static org.assertj.core.api.Assertions.*;
+
+import java.util.stream.Stream;
+
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.mockito.InjectMocks;
+import org.springframework.hateoas.IanaLinkRelations;
+
+import de.itvsh.goofy.common.file.OzgFileTestFactory;
+
+class BinaryFileModelAssemblerTest {
+
+	@InjectMocks // NOSONAR
+	private BinaryFileModelAssembler modelAssembler;
+
+	@Nested
+	class TestLinksOnModel {
+
+		@Test
+		void shouldHaveSelfLink() {
+			var link = modelAssembler.toModel(OzgFileTestFactory.create()).getLink(IanaLinkRelations.SELF_VALUE);
+
+			assertThat(link).isPresent();
+			assertThat(link.get().getHref()).isEqualTo(BinaryFileController.PATH + "/" + OzgFileTestFactory.ID);
+		}
+
+		@Test
+		void shouldHaveDownloadLink() {
+			var link = modelAssembler.toModel(OzgFileTestFactory.create()).getLink(BinaryFileModelAssembler.REL_DOWNLOAD);
+
+			assertThat(link).isPresent();
+			assertThat(link.get().getHref()).isEqualTo(BinaryFileController.PATH + "/" + OzgFileTestFactory.ID);
+		}
+	}
+
+	@Nested
+	class TestLinksOnCollectionModel {
+
+		@Test
+		void shouldHaveSelfLink() {
+			var link = modelAssembler.toCollectionModel(Stream.of(OzgFileTestFactory.create())).getLink(IanaLinkRelations.SELF_VALUE);
+
+			assertThat(link).isPresent();
+			assertThat(link.get().getHref()).isEqualTo(BinaryFileController.PATH);
+		}
+	}
+}
\ No newline at end of file
diff --git a/goofy-server/src/test/java/de/itvsh/goofy/common/binaryfile/BinaryFileRemoteServiceTest.java b/goofy-server/src/test/java/de/itvsh/goofy/common/binaryfile/BinaryFileRemoteServiceTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..468c85eddc6f11738f005f195f8c84026fbf34e5
--- /dev/null
+++ b/goofy-server/src/test/java/de/itvsh/goofy/common/binaryfile/BinaryFileRemoteServiceTest.java
@@ -0,0 +1,341 @@
+/*
+ * 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.itvsh.goofy.common.binaryfile;
+
+import static org.assertj.core.api.Assertions.*;
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.ArgumentMatchers.*;
+import static org.mockito.Mockito.*;
+
+import java.io.OutputStream;
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
+import org.mockito.ArgumentMatchers;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.Spy;
+
+import com.google.protobuf.ByteString;
+
+import de.itvsh.goofy.common.GrpcCallContextTestFactory;
+import de.itvsh.goofy.common.callcontext.ContextService;
+import de.itvsh.goofy.common.file.GrpcOzgFileTestFactory;
+import de.itvsh.goofy.common.file.OzgFile;
+import de.itvsh.goofy.common.file.OzgFileMapper;
+import de.itvsh.goofy.common.file.OzgFileTestFactory;
+import de.itvsh.kop.common.errorhandling.TechnicalException;
+import de.itvsh.ozg.pluto.grpc.binaryFile.BinaryFileServiceGrpc.BinaryFileServiceBlockingStub;
+import de.itvsh.ozg.pluto.grpc.binaryFile.BinaryFileServiceGrpc.BinaryFileServiceStub;
+import de.itvsh.ozg.pluto.grpc.binaryFile.GrpcFindFilesResponse;
+import de.itvsh.ozg.pluto.grpc.binaryFile.GrpcGetBinaryFileDataRequest;
+import de.itvsh.ozg.pluto.grpc.binaryFile.GrpcUploadBinaryFileMetaData;
+import de.itvsh.ozg.pluto.grpc.binaryFile.GrpcUploadBinaryFileRequest;
+import de.itvsh.ozg.pluto.grpc.command.GrpcCallContext;
+import de.itvsh.ozg.pluto.grpc.file.GrpcOzgFile;
+import io.grpc.stub.CallStreamObserver;
+
+class BinaryFileRemoteServiceTest {
+
+	@Spy
+	@InjectMocks
+	private BinaryFileRemoteService remoteService;
+	@Mock
+	private BinaryFileServiceBlockingStub serviceStub;
+	@Mock
+	private BinaryFileServiceStub serviceAsyncStub;
+	@Mock
+	private ContextService contextService;
+	@Mock
+	private OzgFileMapper ozgFileMapper;
+	@Mock
+	private FileIdMapper fileIdMapper;
+
+	private GrpcCallContext callContext = GrpcCallContextTestFactory.create();
+
+	@BeforeEach
+	void mockContextService() {
+		lenient().when(contextService.createCallContext()).thenReturn(callContext);
+	}
+
+	@Nested
+	class TestUploadBinaryFile {
+
+		@Mock
+		private CallStreamObserver<GrpcUploadBinaryFileRequest> requestObserver;
+		@Mock
+		private BinaryFileUploadStreamObserver responseObserver;
+		private UploadBinaryFileRequest uploadBinaryFile = UploadBinaryFileTestFactory.create();
+
+		@BeforeEach
+		void initMocks() {
+			when(serviceAsyncStub.uploadBinaryFileAsStream(any())).thenReturn(requestObserver);
+		}
+
+		@Test
+		void shouldCreateStreamObserver() {
+			callService();
+
+			verify(remoteService).createUploadBinaryFileObserver(ArgumentMatchers.<CompletableFuture<FileId>>any(), any());
+			verify(remoteService).buildMetaDataRequest(uploadBinaryFile);
+		}
+
+		@Test
+		void shouldCallUploadBinaryFileOnServiceAsyncStub() {
+			doReturn(responseObserver).when(remoteService).createUploadBinaryFileObserver(any(), any());
+
+			callService();
+
+			verify(serviceAsyncStub).uploadBinaryFileAsStream(responseObserver);
+		}
+
+		private void callService() {
+			remoteService.uploadFile(uploadBinaryFile);
+		}
+	}
+
+	@Nested
+	class TestBuildMetaDataRequest {
+
+		@Test
+		void shouldCallContextService() {
+			callService();
+
+			verify(contextService).createCallContext();
+		}
+
+		@Test
+		void shouldContainsVorgangId() {
+			var metaData = callService();
+
+			assertThat(metaData.getVorgangId()).isEqualTo(UploadBinaryFileTestFactory.VORGANG_ID);
+		}
+
+		@Test
+		void shouldContainsFieldName() {
+			var metaData = callService();
+
+			assertThat(metaData.getField()).isEqualTo(UploadBinaryFileTestFactory.FIELD);
+		}
+
+		@Test
+		void shouldContainsContentType() {
+			var metaData = callService();
+
+			assertThat(metaData.getContentType()).isEqualTo(OzgFileTestFactory.CONTENT_TYPE);
+
+		}
+
+		@Test
+		void shouldContainsSize() {
+			var metaData = callService();
+
+			assertThat(metaData.getSize()).isEqualTo(OzgFileTestFactory.SIZE);
+		}
+
+		@Test
+		void shouldContainsFileName() {
+			var metaData = callService();
+
+			assertThat(metaData.getFileName()).isEqualTo(OzgFileTestFactory.NAME);
+		}
+
+		private GrpcUploadBinaryFileMetaData callService() {
+			return remoteService.buildMetaDataRequest(UploadBinaryFileTestFactory.create()).getMetadata();
+		}
+	}
+
+	@Nested
+	class TestBuildChunkRequest {
+
+		@Test
+		void shouldContainContent() {
+			var chunkRequest = remoteService.buildChunkRequest(BinaryFileTestFactory.DATA);
+
+			assertThat(chunkRequest.getFileContent()).isEqualTo(ByteString.copyFrom(BinaryFileTestFactory.DATA));
+		}
+	}
+
+	@Nested
+	class TestGetFile {
+
+		private final GrpcFindFilesResponse response = BinaryFileTestFactory.createFindBinaryFilesResponse();
+
+		@BeforeEach
+		void mock() {
+			when(serviceStub.findBinaryFilesMetaData(any())).thenReturn(response);
+		}
+
+		@Test
+		void shouldCallServiceStub() {
+			doServiceCall();
+
+			verify(serviceStub).findBinaryFilesMetaData(any());
+		}
+
+		@Test
+		void shouldCallContextService() {
+			doServiceCall();
+
+			verify(contextService).createCallContext();
+		}
+
+		@Test
+		void shouldCallMapper() {
+			doServiceCall();
+
+			verify(ozgFileMapper).toFile(GrpcOzgFileTestFactory.create());
+		}
+
+		private OzgFile doServiceCall() {
+			return remoteService.getFile(BinaryFileTestFactory.FILE_ID);
+		}
+	}
+
+	@Nested
+	class TestGetFileData {
+
+		@Mock
+		private OutputStream out;
+		@Mock
+		private BinaryFileDownloadStreamObserver responseObserver;
+		private String fileId = OzgFileTestFactory.ID.toString();
+
+		@BeforeEach
+		void mockStreamObserver() {
+			doReturn(responseObserver).when(remoteService).createDownloadBinaryFileObserver(any(), any());
+			when(fileIdMapper.toString(any())).thenReturn(fileId);
+			doNothing().when(remoteService).waitUntilFutureToComplete(any());
+		}
+
+		@Test
+		void shouldCreateDownloadBinaryFileObserver() {
+			callService();
+
+			verify(remoteService).createDownloadBinaryFileObserver(ArgumentMatchers.<CompletableFuture<Boolean>>any(), eq(out));
+		}
+
+		@Test
+		void shouldCallContextService() {
+			callService();
+
+			verify(contextService).createCallContext();
+		}
+
+		@Test
+		void shouldCallFileIdMapper() {
+			callService();
+
+			verify(fileIdMapper).toString(OzgFileTestFactory.ID);
+		}
+
+		@Test
+		void shouldCallGetFileDataOnAsyncServiceStub() {
+			callService();
+
+			verify(serviceAsyncStub).getBinaryFileContent(any(GrpcGetBinaryFileDataRequest.class), eq(responseObserver));
+		}
+
+		@Test
+		void shouldWaitUntilFutureToComplete() {
+			callService();
+
+			verify(remoteService).waitUntilFutureToComplete(any());
+		}
+
+		private void callService() {
+			remoteService.writeFileContent(OzgFileTestFactory.ID, out);
+		}
+	}
+
+	@Nested
+	class TestWaitUntilFutureToComplete {
+
+		@Mock
+		private CompletableFuture<Boolean> streamFuture;
+
+		@Test
+		void shouldNotThrowException() {
+			assertDoesNotThrow(() -> remoteService.waitUntilFutureToComplete(streamFuture));
+		}
+
+		@ParameterizedTest
+		@ValueSource(classes = { InterruptedException.class, ExecutionException.class, TimeoutException.class })
+		void shouldRethrowAsTechnicalException(Class<Exception> exception)
+				throws InterruptedException, ExecutionException, TimeoutException {
+			doThrow(exception).when(streamFuture).get(anyLong(), any(TimeUnit.class));
+
+			assertThrows(TechnicalException.class, () -> remoteService.waitUntilFutureToComplete(streamFuture));
+		}
+	}
+
+	@Nested
+	class TestGetFiles {
+
+		private final GrpcFindFilesResponse response = BinaryFileTestFactory.createFindBinaryFilesResponse();
+
+		@BeforeEach
+		void mock() {
+			when(serviceStub.findBinaryFilesMetaData(any())).thenReturn(response);
+			when(fileIdMapper.toString(any())).thenReturn(OzgFileTestFactory.ID.toString());
+		}
+
+		@Test
+		void shouldCallContextService() {
+			doServiceCall();
+
+			verify(contextService).createCallContext();
+		}
+
+		@Test
+		void shouldCallFileIdMapper() {
+			doServiceCall();
+
+			verify(fileIdMapper).toString(any());
+		}
+
+		@Test
+		void shouldCallOzgFileMapper() {
+			var result = doServiceCall();
+
+			result = result.collect(Collectors.toList()).stream();
+
+			verify(ozgFileMapper).toFile(any(GrpcOzgFile.class));
+		}
+
+		private Stream<OzgFile> doServiceCall() {
+			return remoteService.getFiles(List.of(OzgFileTestFactory.ID));
+		}
+	}
+}
\ No newline at end of file
diff --git a/goofy-server/src/test/java/de/itvsh/goofy/common/binaryfile/BinaryFileServiceTest.java b/goofy-server/src/test/java/de/itvsh/goofy/common/binaryfile/BinaryFileServiceTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..7f86bff09c0b554a807bc0c936ae7b5ae9d46e9c
--- /dev/null
+++ b/goofy-server/src/test/java/de/itvsh/goofy/common/binaryfile/BinaryFileServiceTest.java
@@ -0,0 +1,97 @@
+/*
+ * 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.itvsh.goofy.common.binaryfile;
+
+import static org.mockito.Mockito.*;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.List;
+
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+
+import de.itvsh.goofy.common.file.OzgFileTestFactory;
+
+class BinaryFileServiceTest {
+
+	@InjectMocks
+	private BinaryFileService service;
+
+	@Mock
+	private BinaryFileRemoteService remoteService;
+
+	@Nested
+	class TestUploadFile {
+
+		@Test
+		void shouldCallRemoteService() throws IOException {
+			var uploadBinaryFile = UploadBinaryFileTestFactory.create();
+
+			service.uploadFile(uploadBinaryFile);
+
+			verify(remoteService).uploadFile(uploadBinaryFile);
+		}
+	}
+
+	@Nested
+	class TestWriteFileContent {
+
+		@Mock
+		private OutputStream out;
+
+		@Test
+		void shouldCallRemotService() {
+			service.writeFileContent(OzgFileTestFactory.ID, out);
+
+			verify(remoteService).writeFileContent(OzgFileTestFactory.ID, out);
+		}
+	}
+
+	@Nested
+	class TestGetFile {
+
+		@Test
+		void shouldCallRemoteService() {
+			service.getFile(BinaryFileTestFactory.FILE_ID);
+
+			verify(remoteService).getFile(BinaryFileTestFactory.FILE_ID);
+		}
+	}
+
+	@Nested
+	class TestGetFiles {
+
+		@Test
+		void shouldCallRemotService() {
+			var fileIds = List.of(BinaryFileTestFactory.FILE_ID);
+
+			service.getFiles(fileIds);
+
+			verify(remoteService).getFiles(fileIds);
+		}
+	}
+}
\ No newline at end of file
diff --git a/goofy-server/src/test/java/de/itvsh/goofy/common/binaryfile/BinaryFileTestFactory.java b/goofy-server/src/test/java/de/itvsh/goofy/common/binaryfile/BinaryFileTestFactory.java
new file mode 100644
index 0000000000000000000000000000000000000000..e010d83395fab88df4d8e62d5dbe38f8b66bd2cf
--- /dev/null
+++ b/goofy-server/src/test/java/de/itvsh/goofy/common/binaryfile/BinaryFileTestFactory.java
@@ -0,0 +1,66 @@
+/*
+ * 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.itvsh.goofy.common.binaryfile;
+
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+import java.util.UUID;
+
+import org.springframework.mock.web.MockMultipartFile;
+import org.springframework.util.unit.DataSize;
+
+import com.thedeanda.lorem.LoremIpsum;
+
+import de.itvsh.goofy.common.file.GrpcOzgFileTestFactory;
+import de.itvsh.goofy.vorgang.VorgangHeaderTestFactory;
+import de.itvsh.ozg.pluto.grpc.binaryFile.GrpcFindFilesResponse;
+
+public class BinaryFileTestFactory {
+
+	public static final String ID = UUID.randomUUID().toString();
+	public static final FileId FILE_ID = FileId.from(ID);
+	public static final String NAME = "TestFile";
+	public static final String CONTENT_TYPE = "application/image";
+	public static final byte[] DATA = "juhu, ein bild".getBytes();
+	public static final MockMultipartFile TEST_FILE = new MockMultipartFile("file", NAME, CONTENT_TYPE, DATA);
+	public static final long SIZE = TEST_FILE.getSize();
+
+	public static final String FIELD = "attachment";
+	public static final String VORGANG_ID = VorgangHeaderTestFactory.ID;
+	static final InputStream STREAM = new ByteArrayInputStream(DATA);
+
+	public static GrpcFindFilesResponse createFindBinaryFilesResponse() {
+		return GrpcFindFilesResponse.newBuilder().addFile(GrpcOzgFileTestFactory.create()).build();
+	}
+
+	public static byte[] createByteOfSize(DataSize size) {
+		var testStr = LoremIpsum.getInstance().getWords(1);
+		var contentSize = (int) size.toBytes();
+		var result = new byte[contentSize];
+
+		System.arraycopy(testStr.getBytes(), 0, result, contentSize - testStr.length(), testStr.length());
+
+		return result;
+	}
+}
\ No newline at end of file
diff --git a/goofy-server/src/test/java/de/itvsh/goofy/common/binaryfile/BinaryFileUploadStreamObserverTest.java b/goofy-server/src/test/java/de/itvsh/goofy/common/binaryfile/BinaryFileUploadStreamObserverTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..fcfa98ab907667328230d38ce53ec91b5cad8ad1
--- /dev/null
+++ b/goofy-server/src/test/java/de/itvsh/goofy/common/binaryfile/BinaryFileUploadStreamObserverTest.java
@@ -0,0 +1,91 @@
+/*
+ * 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.itvsh.goofy.common.binaryfile;
+
+import static org.assertj.core.api.Assertions.*;
+import static org.mockito.Mockito.*;
+
+import java.util.concurrent.CompletableFuture;
+
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.springframework.test.util.ReflectionTestUtils;
+
+import de.itvsh.goofy.common.file.OzgFileTestFactory;
+import de.itvsh.ozg.pluto.grpc.binaryFile.GrpcUploadBinaryFileResponse;
+
+class BinaryFileUploadStreamObserverTest {
+
+	@InjectMocks
+	private BinaryFileUploadStreamObserver uploadStreamObserver;
+	@Mock
+	private CompletableFuture<FileId> fileIdFuture;
+
+	private final String FIELD_FILE_ID = "fileId";
+
+	@Nested
+	class TestOnNext {
+
+		private GrpcUploadBinaryFileResponse uploadResponse = GrpcUploadBinaryFileResponse.newBuilder()
+				.setFileId(OzgFileTestFactory.ID.toString())
+				.build();
+
+		@Test
+		void shouldSetFileId() {
+			uploadStreamObserver.onNext(uploadResponse);
+
+			assertThat(uploadStreamObserver.getFileId()).isEqualTo(OzgFileTestFactory.ID.toString());
+		}
+	}
+
+	@Nested
+	class TestOnError {
+
+		@Mock
+		private Throwable throwable;
+
+		@Test
+		void shouldCompleteFutureExceptionally() {
+			uploadStreamObserver.onError(throwable);
+
+			verify(fileIdFuture).completeExceptionally(throwable);
+		}
+	}
+
+	@Nested
+	class TestOnCompleted {
+
+		@Test
+		void shouldCompleteWithFileId() {
+			ReflectionTestUtils.setField(uploadStreamObserver, FIELD_FILE_ID, BinaryFileTestFactory.ID);
+
+			uploadStreamObserver.onCompleted();
+
+			verify(fileIdFuture).complete(FileId.from(BinaryFileTestFactory.ID));
+		}
+
+	}
+}
diff --git a/goofy-server/src/test/java/de/itvsh/goofy/common/binaryfile/ChunkedFileSenderTest.java b/goofy-server/src/test/java/de/itvsh/goofy/common/binaryfile/ChunkedFileSenderTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..8a1a3fff1fe4c19b7635e3693198c55005d2eba4
--- /dev/null
+++ b/goofy-server/src/test/java/de/itvsh/goofy/common/binaryfile/ChunkedFileSenderTest.java
@@ -0,0 +1,124 @@
+/*
+ * 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.itvsh.goofy.common.binaryfile;
+
+import de.itvsh.kop.common.errorhandling.TechnicalException;
+import de.itvsh.ozg.pluto.grpc.binaryFile.GrpcUploadBinaryFileRequest;
+import io.grpc.stub.CallStreamObserver;
+import lombok.SneakyThrows;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.springframework.test.util.ReflectionTestUtils;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.function.Function;
+
+import static org.junit.Assert.*;
+import static org.mockito.Mockito.*;
+
+class ChunkedFileSenderTest {
+
+	private CallStreamObserver<GrpcUploadBinaryFileRequest> requestObserver = mock(CallStreamObserver.class);
+	private InputStream uploadStream = mock(InputStream.class);
+
+	private int chunkSize = BinaryFileTestFactory.DATA.length;
+	private GrpcUploadBinaryFileRequest uploadBinaryFileRequest = GrpcUploadBinaryFileRequestTestFactory.createDataRequest();
+	private Function<byte[], GrpcUploadBinaryFileRequest> buildUploadBinaryFileRequest = when(mock(Function.class).apply(any())).thenReturn(uploadBinaryFileRequest).getMock();
+	private GrpcUploadBinaryFileRequest metadataRequest = GrpcUploadBinaryFileRequestTestFactory.createMetadataRequest();
+
+	private ChunkedFileSender<GrpcUploadBinaryFileRequest> streamer = new ChunkedFileSender<>(uploadStream, chunkSize, buildUploadBinaryFileRequest, metadataRequest);
+
+	@Nested
+	class TestSendBinaryFile {
+
+		@Test
+		void shouldNotSendWhenDone() {
+			ReflectionTestUtils.setField(streamer, "hasUploadFile", new AtomicBoolean(false));
+
+			streamer.sendChunkTo(requestObserver);
+
+			verify(requestObserver, never()).onNext(any());
+		}
+
+		@SneakyThrows
+		@Test
+		void shouldCloseUploadStream() {
+			when(uploadStream.readNBytes(anyInt())).thenReturn(new byte[]{});
+
+			streamer.sendChunkTo(requestObserver);
+
+			verify(uploadStream).close();
+		}
+
+		@SneakyThrows
+		@Test
+		void shouldCallOnCompleted() {
+			when(uploadStream.readNBytes(anyInt())).thenReturn(new byte[]{});
+
+			streamer.sendChunkTo(requestObserver);
+
+			verify(requestObserver).onCompleted();
+		}
+
+		@SneakyThrows
+		@Test
+		void shouldCallOnNext(){
+			when(uploadStream.readNBytes(anyInt())).thenReturn(BinaryFileTestFactory.DATA);
+
+			streamer.sendChunkTo(requestObserver);
+
+			verify(requestObserver).onNext(uploadBinaryFileRequest);
+		}
+
+		@SneakyThrows
+		@Test
+		void shouldThrowException() {
+			doThrow(IOException.class).when(uploadStream).readNBytes(anyInt());
+
+			assertThrows(TechnicalException.class, () -> streamer.sendChunkTo(requestObserver));
+		}
+
+		@SneakyThrows
+		@Test
+		void shouldBuildRequest() {
+			when(uploadStream.readNBytes(anyInt())).thenReturn(BinaryFileTestFactory.DATA);
+
+			streamer.sendChunkTo(requestObserver);
+
+			verify(buildUploadBinaryFileRequest).apply(BinaryFileTestFactory.DATA);
+		}
+
+		@SneakyThrows
+		@Test
+		void shouldSendMetadata() {
+			when(uploadStream.readNBytes(anyInt())).thenReturn(BinaryFileTestFactory.DATA);
+
+			streamer.sendChunkTo(requestObserver);
+
+			verify(requestObserver).onNext(metadataRequest);
+		}
+	}
+}
\ No newline at end of file
diff --git a/goofy-server/src/test/java/de/itvsh/goofy/common/binaryfile/DownloadAuthenticationHandlerTest.java b/goofy-server/src/test/java/de/itvsh/goofy/common/binaryfile/DownloadAuthenticationHandlerTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..ee359807deaedacde56a394118c8dd3d312b7e36
--- /dev/null
+++ b/goofy-server/src/test/java/de/itvsh/goofy/common/binaryfile/DownloadAuthenticationHandlerTest.java
@@ -0,0 +1,91 @@
+/*
+ * 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.itvsh.goofy.common.binaryfile;
+
+import static org.assertj.core.api.Assertions.*;
+import static org.mockito.Mockito.*;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.keycloak.adapters.springsecurity.token.KeycloakAuthenticationToken;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.Authentication;
+
+class DownloadAuthenticationHandlerTest {
+
+	private DownloadAuthenticationHandler downloadAuthorizationHandler = new DownloadAuthenticationHandler();
+
+	@Nested
+	class TestAuthorizationKeycloak {
+
+		private Authentication authentication = mock(Authentication.class);
+
+		@BeforeEach
+		void init() {
+			when(authentication.isAuthenticated()).thenReturn(Boolean.TRUE);
+			KeycloakAuthenticationToken keycloakToken = mock(KeycloakAuthenticationToken.class);
+			when(authentication.getPrincipal()).thenReturn(keycloakToken);
+		}
+
+		@Test
+		void shouldAuthenticate() {
+			assertThat(downloadAuthorizationHandler.check(FileId.createNew(), authentication)).isTrue();
+		}
+	}
+
+	@Nested
+	class TestAuthorizationUsernamePassword {
+		private FileId fileId = FileId.createNew();
+
+		private Authentication authentication = mock(UsernamePasswordAuthenticationToken.class);
+
+		@BeforeEach
+		void init() {
+			when(authentication.isAuthenticated()).thenReturn(Boolean.TRUE);
+			when(authentication.getPrincipal()).thenReturn(DownloadGoofyUserTestFactory.createBuilder().fileId(fileId).build());
+		}
+
+		@Test
+		void shouldAuthenticate() {
+			assertThat(downloadAuthorizationHandler.check(fileId, authentication)).isTrue();
+		}
+
+		@Test
+		void shouldNotAuthenticateWrongFileId() {
+			assertThat(downloadAuthorizationHandler.check(FileId.createNew(), authentication)).isFalse();
+		}
+
+		@Test
+		void shouldNotAuthenticateNoFileId() {
+			assertThat(downloadAuthorizationHandler.check(null, authentication)).isFalse();
+		}
+
+		@Test
+		void shouldNotAuthenticate() {
+			when(authentication.isAuthenticated()).thenReturn(Boolean.FALSE);
+			assertThat(downloadAuthorizationHandler.check(fileId, authentication)).isFalse();
+		}
+	}
+}
diff --git a/goofy-server/src/test/java/de/itvsh/goofy/common/binaryfile/DownloadGoofyUserTestFactory.java b/goofy-server/src/test/java/de/itvsh/goofy/common/binaryfile/DownloadGoofyUserTestFactory.java
new file mode 100644
index 0000000000000000000000000000000000000000..5b13b74510dbd84f70686b683133b99af7105f5d
--- /dev/null
+++ b/goofy-server/src/test/java/de/itvsh/goofy/common/binaryfile/DownloadGoofyUserTestFactory.java
@@ -0,0 +1,37 @@
+/*
+ * 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.itvsh.goofy.common.binaryfile;
+
+import de.itvsh.goofy.common.user.UserProfileTestFactory;
+
+public class DownloadGoofyUserTestFactory {
+
+	static GoofyUserWithFileId create() {
+		return createBuilder().build();
+	}
+
+	static GoofyUserWithFileId.GoofyUserWithFileIdBuilder createBuilder() {
+		return GoofyUserWithFileId.builder().user(UserProfileTestFactory.create()).fileId(BinaryFileTestFactory.FILE_ID);
+	}
+}
diff --git a/goofy-server/src/test/java/de/itvsh/goofy/common/binaryfile/GrpcUploadBinaryFileMetaDataTestFactory.java b/goofy-server/src/test/java/de/itvsh/goofy/common/binaryfile/GrpcUploadBinaryFileMetaDataTestFactory.java
new file mode 100644
index 0000000000000000000000000000000000000000..3b275ecb36ca747698727d2d7a5baa8c75b8262b
--- /dev/null
+++ b/goofy-server/src/test/java/de/itvsh/goofy/common/binaryfile/GrpcUploadBinaryFileMetaDataTestFactory.java
@@ -0,0 +1,43 @@
+/*
+ * 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.itvsh.goofy.common.binaryfile;
+
+import de.itvsh.goofy.common.file.OzgFileTestFactory;
+import de.itvsh.ozg.pluto.grpc.binaryFile.GrpcUploadBinaryFileMetaData;
+
+public class GrpcUploadBinaryFileMetaDataTestFactory {
+
+	public static GrpcUploadBinaryFileMetaData create() {
+		return createBuilder().build();
+	}
+
+	private static GrpcUploadBinaryFileMetaData.Builder createBuilder() {
+		return GrpcUploadBinaryFileMetaData.newBuilder()
+						.setVorgangId(UploadBinaryFileTestFactory.VORGANG_ID)
+						.setField(UploadBinaryFileTestFactory.FIELD)
+						.setContentType(OzgFileTestFactory.CONTENT_TYPE)
+						.setSize(OzgFileTestFactory.SIZE)
+						.setFileName(OzgFileTestFactory.NAME);
+	}
+}
diff --git a/goofy-server/src/test/java/de/itvsh/goofy/common/binaryfile/GrpcUploadBinaryFileRequestTestFactory.java b/goofy-server/src/test/java/de/itvsh/goofy/common/binaryfile/GrpcUploadBinaryFileRequestTestFactory.java
new file mode 100644
index 0000000000000000000000000000000000000000..fc694520bfb47563cb4e459e9106c450f936556e
--- /dev/null
+++ b/goofy-server/src/test/java/de/itvsh/goofy/common/binaryfile/GrpcUploadBinaryFileRequestTestFactory.java
@@ -0,0 +1,43 @@
+/*
+ * 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.itvsh.goofy.common.binaryfile;
+
+import com.google.protobuf.ByteString;
+import de.itvsh.ozg.pluto.grpc.binaryFile.GrpcUploadBinaryFileRequest;
+import de.itvsh.ozg.pluto.grpc.binaryFile.GrpcUploadBinaryFileRequest.Builder;
+
+public class GrpcUploadBinaryFileRequestTestFactory {
+
+	public static GrpcUploadBinaryFileRequest createDataRequest() {
+		return createBuilder().setFileContent(ByteString.copyFrom(BinaryFileTestFactory.DATA)).build();
+	}
+
+	public static GrpcUploadBinaryFileRequest createMetadataRequest() {
+		return createBuilder().setMetadata(GrpcUploadBinaryFileMetaDataTestFactory.create()).build();
+	}
+
+	public static Builder createBuilder() {
+		return GrpcUploadBinaryFileRequest.newBuilder();
+	}
+}
diff --git a/goofy-server/src/test/java/de/itvsh/goofy/common/binaryfile/UploadBinaryFileSizeValidatorTest.java b/goofy-server/src/test/java/de/itvsh/goofy/common/binaryfile/UploadBinaryFileSizeValidatorTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..753bb47fac1f59cbf04b8f43f542ca59bcee2dcf
--- /dev/null
+++ b/goofy-server/src/test/java/de/itvsh/goofy/common/binaryfile/UploadBinaryFileSizeValidatorTest.java
@@ -0,0 +1,135 @@
+/*
+ * 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.itvsh.goofy.common.binaryfile;
+
+import static org.assertj.core.api.Assertions.*;
+import static org.mockito.ArgumentMatchers.*;
+import static org.mockito.Mockito.*;
+
+import java.util.Collections;
+import java.util.Map;
+
+import javax.validation.ConstraintValidatorContext;
+
+import org.hibernate.validator.constraintvalidation.HibernateConstraintValidatorContext;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.Spy;
+import org.springframework.util.unit.DataSize;
+
+class UploadBinaryFileSizeValidatorTest {
+
+	@Spy
+	@InjectMocks
+	private UploadBinaryFileSizeValidator validator = new UploadBinaryFileSizeValidator();
+	@Mock
+	private BinaryFileProperties properties;
+
+	@Nested
+	class TestIsValid {
+
+		@Mock
+		private ConstraintValidatorContext context;
+		@Mock
+		private HibernateConstraintValidatorContext unwrappedContext;
+
+		@BeforeEach
+		void mockMaxValidSize() {
+			doReturn(unwrappedContext).when(validator).getUnwrappedContext(any(), any());
+			doReturn(DataSize.ofMegabytes(3)).when(validator).getMaxValidSize(anyString());
+		}
+
+		@Test
+		void shouldCallGetMaxValidSize() {
+			validator.isValid(UploadBinaryFileTestFactory.create(), context);
+
+			verify(validator).getMaxValidSize(UploadBinaryFileTestFactory.FIELD);
+		}
+
+		@Test
+		void shouldReturnTrue() {
+			var result = validator.isValid(UploadBinaryFileTestFactory.create(), context);
+
+			assertThat(result).isTrue();
+		}
+
+		@Test
+		void shouldReturnFalse() {
+			var result = validator.isValid(UploadBinaryFileTestFactory.createBuilder().size(DataSize.ofMegabytes(3).toBytes() + 1).build(), null);
+
+			assertThat(result).isFalse();
+		}
+
+		@Nested
+		class TestDynamicPayload {
+
+			@Captor
+			private ArgumentCaptor<DynamicViolationParameter> captor;
+
+			@Test
+			void shouldSetMaxValue() {
+				validator.isValid(UploadBinaryFileTestFactory.create(), context);
+
+				verify(unwrappedContext).withDynamicPayload(captor.capture());
+				assertThat(String.valueOf(captor.getValue().getMap().get("max"))).isEqualTo("3");
+			}
+		}
+	}
+
+	@Nested
+	class TestGetMaxValidSize {
+
+		@Test
+		void shouldReturnDefaultValueOnEmptyMap() {
+			when(properties.getMaxFileSize()).thenReturn(Collections.emptyMap());
+
+			var maxFileSize = validator.getMaxValidSize(BinaryFileTestFactory.FIELD);
+
+			assertThat(maxFileSize.toBytes()).isEqualTo(DataSize.ofMegabytes(UploadBinaryFileSizeValidator.DEFAULT_MAX_SIZE).toBytes());
+		}
+
+		@Test
+		void shouldReturnDefaultValueOnMissingField() {
+			when(properties.getMaxFileSize()).thenReturn(Map.of("not match", DataSize.ofMegabytes(3)));
+
+			var maxFileSize = validator.getMaxValidSize(BinaryFileTestFactory.FIELD);
+
+			assertThat(maxFileSize.toBytes()).isEqualTo(DataSize.ofMegabytes(UploadBinaryFileSizeValidator.DEFAULT_MAX_SIZE).toBytes());
+		}
+
+		@Test
+		void shouldReturnPropertiesValue() {
+			when(properties.getMaxFileSize()).thenReturn(Map.of(BinaryFileTestFactory.FIELD, DataSize.ofMegabytes(3)));
+
+			var maxFileSize = validator.getMaxValidSize(BinaryFileTestFactory.FIELD);
+
+			assertThat(maxFileSize.toBytes()).isEqualTo(DataSize.ofMegabytes(3).toBytes());
+		}
+	}
+}
\ No newline at end of file
diff --git a/goofy-server/src/test/java/de/itvsh/goofy/common/binaryfile/UploadBinaryFileTestFactory.java b/goofy-server/src/test/java/de/itvsh/goofy/common/binaryfile/UploadBinaryFileTestFactory.java
new file mode 100644
index 0000000000000000000000000000000000000000..d4347a210fde373571bc3c4d20e223b444239f82
--- /dev/null
+++ b/goofy-server/src/test/java/de/itvsh/goofy/common/binaryfile/UploadBinaryFileTestFactory.java
@@ -0,0 +1,51 @@
+/*
+ * 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.itvsh.goofy.common.binaryfile;
+
+import static de.itvsh.goofy.common.file.OzgFileTestFactory.*;
+
+import java.io.InputStream;
+
+import de.itvsh.goofy.vorgang.VorgangHeaderTestFactory;
+
+public class UploadBinaryFileTestFactory {
+
+	static final String VORGANG_ID = VorgangHeaderTestFactory.ID;
+	static final String FIELD = BinaryFileTestFactory.FIELD;
+	static final InputStream UPLOAD_STREAM = BinaryFileTestFactory.STREAM;
+
+	public static UploadBinaryFileRequest create() {
+		return createBuilder().build();
+	}
+
+	public static UploadBinaryFileRequest.UploadBinaryFileRequestBuilder createBuilder() {
+		return UploadBinaryFileRequest.builder()
+				.name(NAME)
+				.vorgangId(VORGANG_ID)
+				.field(FIELD)
+				.size(SIZE)
+				.contentType(CONTENT_TYPE)
+				.uploadStream(UPLOAD_STREAM);
+	}
+}
\ No newline at end of file
diff --git a/goofy-server/src/test/java/de/itvsh/goofy/common/callcontext/CallContextAttachingInterceptorTest.java b/goofy-server/src/test/java/de/itvsh/goofy/common/callcontext/CallContextAttachingInterceptorTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..e87a2de0026eefec0c2d7dbbbf4f33aa026c2538
--- /dev/null
+++ b/goofy-server/src/test/java/de/itvsh/goofy/common/callcontext/CallContextAttachingInterceptorTest.java
@@ -0,0 +1,80 @@
+/*
+ * 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.itvsh.goofy.common.callcontext;
+
+import static org.mockito.Mockito.*;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+
+import de.itvsh.goofy.common.callcontext.CallContextAttachingInterceptor.CallContextAttachingClientCall;
+import io.grpc.ClientCall;
+import io.grpc.ClientCall.Listener;
+import io.grpc.Metadata;
+
+class CallContextAttachingInterceptorTest<A, B> {
+
+	@InjectMocks
+	private CallContextAttachingInterceptor interceptor;
+	@Mock
+	private ContextService contextService;
+
+	private CallContextAttachingClientCall<A, B> clientCall;
+	@Mock
+	private ClientCall<A, B> delegateCall;
+
+	@Mock
+	private Listener<B> responseListener;
+
+	@Mock
+	private Metadata contextMetadata;
+	@Mock
+	private Metadata inputMetadata;
+
+	@BeforeEach
+	void initClientCall() {
+		clientCall = interceptor.new CallContextAttachingClientCall<A, B>(delegateCall);
+	}
+
+	@BeforeEach
+	void mockContextService() {
+		when(contextService.buildCallContextMetadata()).thenReturn(contextMetadata);
+	}
+
+	@Test
+	void shouldCallUserService() {
+		clientCall.start(responseListener, inputMetadata);
+
+		verify(contextService).buildCallContextMetadata();
+	}
+
+	@Test
+	void shouldMergeMetas() {
+		clientCall.start(responseListener, inputMetadata);
+
+		verify(inputMetadata).merge(contextMetadata);
+	}
+}
diff --git a/goofy-server/src/test/java/de/itvsh/goofy/common/callcontext/CallContextTestFactory.java b/goofy-server/src/test/java/de/itvsh/goofy/common/callcontext/CallContextTestFactory.java
new file mode 100644
index 0000000000000000000000000000000000000000..c20bb0a856114a34c80f9d615b1ee923a971b471
--- /dev/null
+++ b/goofy-server/src/test/java/de/itvsh/goofy/common/callcontext/CallContextTestFactory.java
@@ -0,0 +1,42 @@
+/*
+ * 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.itvsh.goofy.common.callcontext;
+
+import static de.itvsh.goofy.common.callcontext.ContextService.*;
+
+import java.util.Map;
+
+import de.itvsh.goofy.common.user.UserProfileTestFactory;
+
+public class CallContextTestFactory {
+
+	public static final String CLIENT_NAME = "TEST_CLIENT";
+
+	static Map<String, String> createContextMap() {
+		return Map.of(
+				KEY_USER_ID, UserProfileTestFactory.ID.toString(),
+				KEY_USER_NAME, UserProfileTestFactory.FULLNAME);
+
+	}
+}
diff --git a/goofy-server/src/test/java/de/itvsh/goofy/common/callcontext/ContextServiceTest.java b/goofy-server/src/test/java/de/itvsh/goofy/common/callcontext/ContextServiceTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..b31cfa1726d4be392982c17639c2ac5fdce5a1fa
--- /dev/null
+++ b/goofy-server/src/test/java/de/itvsh/goofy/common/callcontext/ContextServiceTest.java
@@ -0,0 +1,191 @@
+/*
+ * 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.itvsh.goofy.common.callcontext;
+
+import static de.itvsh.goofy.common.callcontext.ContextService.*;
+import static org.assertj.core.api.Assertions.*;
+import static org.mockito.ArgumentMatchers.*;
+import static org.mockito.Mockito.*;
+
+import java.util.Collection;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.Spy;
+import org.springframework.context.ApplicationContext;
+
+import com.thedeanda.lorem.LoremIpsum;
+
+import de.itvsh.goofy.RequestAttributes;
+import de.itvsh.goofy.RequestAttributesTestFactory;
+import de.itvsh.goofy.common.GrpcUtil;
+import de.itvsh.goofy.common.user.CurrentUserService;
+import de.itvsh.goofy.common.user.UserProfileTestFactory;
+import de.itvsh.goofy.common.user.UserRole;
+import de.itvsh.goofy.vorgang.ZustaendigeStelleTestFactory;
+import de.itvsh.ozg.pluto.grpc.command.GrpcUser;
+
+class ContextServiceTest {
+
+	static final String APPLICATION_ID = LoremIpsum.getInstance().getName();
+
+	@InjectMocks
+	private ContextService service;
+
+	@Mock
+	private CurrentUserService userService;
+	@Mock
+	private ApplicationContext context;
+	@Spy
+	private RequestAttributes reqAttributes = RequestAttributesTestFactory.create();
+
+	@BeforeEach
+	void initMocks() {
+		when(context.getId()).thenReturn(APPLICATION_ID);
+		when(userService.getUser()).thenReturn(UserProfileTestFactory.create());
+	}
+
+	@DisplayName("Get context metas")
+	@Nested
+	class TestGetContextMetas {
+
+		@BeforeEach
+		void mockUserService() {
+			when(userService.hasRole(any())).thenReturn(Boolean.TRUE);
+		}
+
+		@Test
+		void shouldHaveUserId() {
+			var metadata = service.buildCallContextMetadata();
+
+			assertThat(GrpcUtil.getFromHeaders(KEY_USER_ID, metadata)).isEqualTo(UserProfileTestFactory.ID.toString());
+		}
+
+		@Test
+		void shouldHaveUserName() {
+			var metadata = service.buildCallContextMetadata();
+
+			assertThat(GrpcUtil.getFromHeaders(KEY_USER_NAME, metadata)).isEqualTo(UserProfileTestFactory.FULLNAME);
+		}
+
+		@Test
+		void shouldHaveClientName() {
+			var metadata = service.buildCallContextMetadata();
+
+			assertThat(GrpcUtil.getFromHeaders(KEY_CLIENT_NAME, metadata)).isEqualTo(APPLICATION_ID);
+		}
+
+		@Test
+		void shouldLimitAccessToOrgaId() {
+			var metadata = service.buildCallContextMetadata();
+			metadata.put(GrpcUtil.createKeyOf(KEY_ACCESS_LIMITED_ORGAID), "orgaid_2".getBytes());
+
+			assertThat(GrpcUtil.getCollection(KEY_ACCESS_LIMITED_ORGAID, metadata)).isInstanceOf(Collection.class).hasSize(2)
+					.contains(ZustaendigeStelleTestFactory.ORGANISATIONSEINHEITEN_ID, "orgaid_2");
+		}
+
+		@DisplayName("access limited")
+		@Nested
+		class TestAccessLimitedByUserRole {
+
+			@Test
+			void shouldHaveTrueOnRoleVerwaltungUser() {
+				when(userService.hasRole(anyString())).thenReturn(Boolean.FALSE);
+
+				var metadata = service.buildCallContextMetadata();
+
+				assertThat(GrpcUtil.getFromHeaders(KEY_ACCESS_LIMITED, metadata)).isEqualTo(Boolean.TRUE.toString());
+			}
+
+			@Test
+			void shouldHaveFalseOnRoleVerwaltungPoststelle() {
+				when(userService.hasRole(UserRole.VERWALTUNG_POSTSTELLE)).thenReturn(Boolean.TRUE);
+
+				var metadata = service.buildCallContextMetadata();
+
+				assertThat(GrpcUtil.getFromHeaders(KEY_ACCESS_LIMITED, metadata)).isEqualTo(Boolean.FALSE.toString());
+			}
+
+			@Test
+			void shouldHaveFalseOnRoleEinheitlicherAnsprechpartner() {
+				when(userService.hasRole(UserRole.VERWALTUNG_POSTSTELLE)).thenReturn(Boolean.FALSE);
+				when(userService.hasRole(UserRole.EINHEITLICHER_ANSPRECHPARTNER)).thenReturn(Boolean.TRUE);
+
+				var metadata = service.buildCallContextMetadata();
+
+				assertThat(GrpcUtil.getFromHeaders(KEY_ACCESS_LIMITED, metadata)).isEqualTo(Boolean.FALSE.toString());
+			}
+		}
+	}
+
+	@Nested
+	class TestCreateCallContest {
+
+		@Test
+		void shouldCallContextGetApplicationName() {
+			service.createCallContext();
+
+			verify(context).getId();
+		}
+
+		@Test
+		void shouldContainClient() {
+			var callContext = service.createCallContext();
+
+			assertThat(callContext.getClient()).isEqualTo(APPLICATION_ID);
+		}
+
+		@Test
+		void shouldCallUserService() {
+			service.createCallContext();
+
+			verify(userService).getUser();
+		}
+
+		@Test
+		void shoultHaveUserId() {
+			var context = service.createCallContext();
+
+			assertThat(context.getUser()).extracting(GrpcUser::getId).isEqualTo(UserProfileTestFactory.ID.toString());
+		}
+
+		@Test
+		void shouldHaveFullName() {
+			var context = service.createCallContext();
+
+			assertThat(context.getUser()).extracting(GrpcUser::getName).isEqualTo(UserProfileTestFactory.FULLNAME);
+		}
+
+		@Test
+		void shouldHaveRequestId() {
+			var context = service.createCallContext();
+
+			assertThat(context.getRequestId()).isEqualTo(RequestAttributesTestFactory.REQUEST_ID);
+		}
+	}
+}
\ No newline at end of file
diff --git a/goofy-server/src/test/java/de/itvsh/goofy/common/clientattribute/ClientAttributeRemoteServiceTest.java b/goofy-server/src/test/java/de/itvsh/goofy/common/clientattribute/ClientAttributeRemoteServiceTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..7aac3f5263273ea80b1d231ff45208f8b43b452d
--- /dev/null
+++ b/goofy-server/src/test/java/de/itvsh/goofy/common/clientattribute/ClientAttributeRemoteServiceTest.java
@@ -0,0 +1,54 @@
+/*
+ * 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.itvsh.goofy.common.clientattribute;
+
+import static org.mockito.ArgumentMatchers.*;
+import static org.mockito.Mockito.*;
+
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.Spy;
+
+import de.itvsh.ozg.pluto.grpc.clientAttribute.ClientAttributeServiceGrpc.ClientAttributeServiceBlockingStub;
+
+class ClientAttributeRemoteServiceTest {
+	@Nested
+	class TestResetNewPostfachNachricht {
+		@Spy
+		@InjectMocks
+		private ClientAttributeRemoteService service;
+
+		@Mock
+		private ClientAttributeServiceBlockingStub serviceStub;
+
+		@Test
+		void shouldCallServiceStub() {
+			service.resetPostfachNachricht(any());
+
+			verify(serviceStub).update(any());
+		}
+	}
+}
diff --git a/goofy-server/src/test/java/de/itvsh/goofy/common/clientattribute/ClientAttributeServiceTest.java b/goofy-server/src/test/java/de/itvsh/goofy/common/clientattribute/ClientAttributeServiceTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..a3a6c9863c4be0b9a726e38da96731e0fa3cddb3
--- /dev/null
+++ b/goofy-server/src/test/java/de/itvsh/goofy/common/clientattribute/ClientAttributeServiceTest.java
@@ -0,0 +1,95 @@
+/*
+ * 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.itvsh.goofy.common.clientattribute;
+
+import static org.assertj.core.api.Assertions.*;
+import static org.mockito.ArgumentMatchers.*;
+import static org.mockito.Mockito.*;
+
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.Spy;
+
+import de.itvsh.goofy.vorgang.VorgangHeaderTestFactory;
+import de.itvsh.ozg.pluto.grpc.clientAttribute.GrpcUpdateClientAttributeRequest;
+
+class ClientAttributeServiceTest {
+	@Spy
+	@InjectMocks
+	private ClientAttributeService service = new ClientAttributeService();
+	@Mock
+	private ClientAttributeRemoteService remoteService;
+
+	@Nested
+	class TestResetNewPostfachNachricht {
+		@Test
+		void shouldCallRemoteService() {
+			service.resetPostfachNachricht(VorgangHeaderTestFactory.ID);
+
+			verify(remoteService).resetPostfachNachricht(any());
+		}
+	}
+
+	@Nested
+	class BuildRequest {
+
+		@Test
+		void shouldHaveAttribute() {
+			GrpcUpdateClientAttributeRequest request = service.buildResetNewPostfachNachricht(VorgangHeaderTestFactory.ID);
+
+			assertThat(request.getAttribute()).isNotNull();
+		}
+
+		@Test
+		void shouldHaveVorgangId() {
+			GrpcUpdateClientAttributeRequest request = service.buildResetNewPostfachNachricht(VorgangHeaderTestFactory.ID);
+
+			assertThat(request.getVorgangId()).isEqualTo(VorgangHeaderTestFactory.ID);
+		}
+
+		@Test
+		void shouldHaveAttributeName() {
+			GrpcUpdateClientAttributeRequest request = service.buildResetNewPostfachNachricht(VorgangHeaderTestFactory.ID);
+
+			assertThat(request.getAttribute().getAttributeName()).isEqualTo(ClientAttributeService.HAS_NEW_POSTFACH_NACHRICHT_ATTRIBUTE_NAME);
+		}
+
+		@Test
+		void shouldHaveClientName() {
+			GrpcUpdateClientAttributeRequest request = service.buildResetNewPostfachNachricht(VorgangHeaderTestFactory.ID);
+
+			assertThat(request.getAttribute().getClientName()).isEqualTo(ClientAttributeService.ORIGINAL_CLIENT_NAME);
+		}
+
+		@Test
+		void shouldHaveAttributeValue() {
+			GrpcUpdateClientAttributeRequest request = service.buildResetNewPostfachNachricht(VorgangHeaderTestFactory.ID);
+
+			assertThat(request.getAttribute().getValue()).isNotNull();
+			assertThat(request.getAttribute().getValue().getBoolValue()).isFalse();
+		}
+	}
+}
diff --git a/goofy-server/src/test/java/de/itvsh/goofy/common/clientattribute/GrpcClientAttributeTestFactory.java b/goofy-server/src/test/java/de/itvsh/goofy/common/clientattribute/GrpcClientAttributeTestFactory.java
new file mode 100644
index 0000000000000000000000000000000000000000..497731f92e6139c215017fa663143fd131475ca4
--- /dev/null
+++ b/goofy-server/src/test/java/de/itvsh/goofy/common/clientattribute/GrpcClientAttributeTestFactory.java
@@ -0,0 +1,49 @@
+/*
+ * 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.itvsh.goofy.common.clientattribute;
+
+import de.itvsh.goofy.common.callcontext.CallContextTestFactory;
+import de.itvsh.ozg.pluto.grpc.clientAttribute.GrpcAccessPermission;
+import de.itvsh.ozg.pluto.grpc.clientAttribute.GrpcClientAttribute;
+import de.itvsh.ozg.pluto.grpc.clientAttribute.GrpcClientAttributeValue;
+
+public class GrpcClientAttributeTestFactory {
+	static final String CLIENT_NAME = CallContextTestFactory.CLIENT_NAME;
+	static final GrpcAccessPermission ACCESS_PERMISSION = GrpcAccessPermission.READ_ONLY;
+	static final GrpcClientAttributeValue ATTRIBUTE_VALUE = GrpcClientAttributeValue.newBuilder().setBoolValue(true).build();
+
+	public static final String ATTRIBUTE_NAME = "testAttribute";
+
+	public static GrpcClientAttribute create() {
+		return createBuilder().build();
+	}
+
+	public static GrpcClientAttribute.Builder createBuilder() {
+		return GrpcClientAttribute.newBuilder()
+				.setAccess(ACCESS_PERMISSION)
+				.setClientName(CLIENT_NAME)
+				.setAttributeName(ATTRIBUTE_NAME)
+				.setValue(ATTRIBUTE_VALUE);
+	}
+}
diff --git a/goofy-server/src/test/java/de/itvsh/goofy/common/command/CommandBodyMapperTest.java b/goofy-server/src/test/java/de/itvsh/goofy/common/command/CommandBodyMapperTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..a16401ff88be9690e84bbf17efcc0f31530471ef
--- /dev/null
+++ b/goofy-server/src/test/java/de/itvsh/goofy/common/command/CommandBodyMapperTest.java
@@ -0,0 +1,311 @@
+/*
+ * 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.itvsh.goofy.common.command;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.commons.lang3.StringUtils;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.mapstruct.factory.Mappers;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Spy;
+
+import static org.assertj.core.api.Assertions.*;
+
+import de.itvsh.goofy.common.binaryfile.BinaryFileTestFactory;
+import de.itvsh.goofy.postfach.PostfachMailTestFactory;
+import de.itvsh.goofy.vorgang.VorgangHeaderTestFactory;
+import de.itvsh.goofy.wiedervorlage.WiedervorlageTestFactory;
+import de.itvsh.ozg.pluto.grpc.command.GrpcCommandBody;
+import de.itvsh.ozg.pluto.grpc.command.GrpcCommandBodyField;
+
+class CommandBodyMapperTest {
+
+	private static final String FIELD = "FIELD";
+	private static final String VALUE = "VALUE";
+
+	@Spy
+	private CommandBodyMapper mapper = Mappers.getMapper(CommandBodyMapper.class);
+
+	@DisplayName("Map from object to map")
+	@Nested
+	class TestFromObjectToMap {
+
+		@Test
+		void shouldMapAllValues() {
+			var commandAsObject = CommandTestFactory.create();
+
+			var mappedMap = mapper.fromObjectToMap(commandAsObject);
+
+			assertThat(mappedMap).hasSize(12)
+					.containsEntry("id", CommandTestFactory.ID)
+					.containsEntry("status", CommandTestFactory.STATUS.name())
+					.containsEntry("order", CommandTestFactory.ORDER.name())
+					.containsEntry("relationId", CommandTestFactory.RELATION_ID)
+					.containsEntry("vorgangId", CommandTestFactory.VORGANG_ID);
+		}
+
+		@Test
+		void shouldNotMapClass() {
+			var commandAsObject = CommandTestFactory.create();
+
+			var mappedMap = mapper.fromObjectToMap(commandAsObject);
+
+			assertThat(mappedMap).doesNotContainKey("class");
+		}
+
+		@Test
+		void shouldMapNullValue() {
+			var commandAsObject = CommandTestFactory.createBuilder().status(null).build();
+
+			var mappedMap = mapper.fromObjectToMap(commandAsObject);
+
+			assertThat(mappedMap).containsEntry("status", null);
+		}
+
+		@Test
+		void shouldNotMapVersion() {
+			var commandAsObject = WiedervorlageTestFactory.create();
+
+			var mappedMap = mapper.fromObjectToMap(commandAsObject);
+
+			assertThat(mappedMap).doesNotContainKey("version");
+		}
+	}
+
+	@Nested
+	class TestMapToBody {
+		@Test
+		void shouldReturnEmptyBodyForNull() {
+			var result = mapper.mapToBody(null);
+
+			assertThat(result).isNotNull();
+			assertThat(result.getFieldList()).isEmpty();
+		}
+
+		@Test
+		void shouldReturnBodyWithField() {
+			var result = mapper.mapToBody(buildBodyMap());
+
+			assertThat(result.getFieldList()).hasSize(1);
+			assertThat(result.getFieldList().get(0).getName()).isEqualTo("FIELD");
+			assertThat(result.getFieldList().get(0).getValue()).isEqualTo("VALUE");
+		}
+
+		private Map<String, String> buildBodyMap() {
+			return Map.of(FIELD, VALUE);
+		}
+
+		@Test
+		void shouldFilterEntriesWithNullValue() {
+			var result = mapper.mapToBody(buildBodyMapWithNullValue());
+
+			assertThat(result.getFieldList()).isEmpty();
+		}
+
+		private Map<String, String> buildBodyMapWithNullValue() {
+			HashMap<String, String> bodyMap = new HashMap<>();
+			bodyMap.put(FIELD, null);
+			return bodyMap;
+		}
+	}
+
+	@Nested
+	class TestMapToBodyFields {
+
+		@Captor
+		private ArgumentCaptor<Map<Object, Object>> mapCaptor;
+
+		@Test
+		void shouldReturnEmptyListOnNull() {
+			var result = mapper.mapToBodyFields((CommandBody) null);
+
+			assertThat(result).isEmpty();
+		}
+
+		@Test
+		void shouldContainFields() {
+			var fieldList = mapper.mapToBodyFields(PostfachMailTestFactory.create());
+
+			assertThat(fieldList).hasSize(13);
+		}
+
+		@Test
+		void shouldNOTContainClassEntry() {
+			var fieldList = mapper.mapToBodyFields(PostfachMailTestFactory.create());
+
+			assertThat(fieldList).isNotEmpty().allMatch(field -> !StringUtils.equals(field.getName(), "class"));
+		}
+
+		@Test
+		void shouldReturnField() {
+			var result = mapper.mapToBodyFields(buildBodyMap());
+
+			assertThat(result).hasSize(1);
+			assertThat(result.get(0).getName()).isEqualTo(FIELD);
+			assertThat(result.get(0).getValue()).isEqualTo(VALUE);
+		}
+
+		private Map<Object, Object> buildBodyMap() {
+			return Map.of(FIELD, VALUE);
+		}
+
+		@Test
+		void shouldMapNullAsEmptyValue() {
+			var result = mapper.mapToBodyFields(buildBodyMapWithNullValue());
+
+			assertThat(result).hasSize(1);
+			assertThat(result.get(0).getValue()).isEmpty();
+		}
+
+		private Map<Object, Object> buildBodyMapWithNullValue() {
+			HashMap<Object, Object> bodyMap = new HashMap<>();
+			bodyMap.put(FIELD, null);
+			return bodyMap;
+		}
+	}
+
+	@Nested
+	class TestMap {
+
+		@Test
+		void shouldReturnMap() {
+			var result = mapper
+					.map(GrpcCommandBody.newBuilder().addField(GrpcCommandBodyField.newBuilder().setName(FIELD).setValue(VALUE).build()).build());
+
+			assertThat(result).hasSize(1).containsEntry(FIELD, VALUE);
+		}
+	}
+
+	@Nested
+	class TestMapToBodyMap {
+
+		@Nested
+		class ForWiedervorlage {
+
+			private final CommandBody wiedervorlageCommandBody = WiedervorlageTestFactory.create();
+			private final CreateCommand command = CommandTestFactory.createCreateCommandBuilder().body(wiedervorlageCommandBody).build();
+			private final String itemName = "Wiedervorlage";
+
+			@Test
+			void shouldContainsItemName() {
+				var itemNameEntry = Map.entry(CommandBodyMapper.ITEM_NAME_PROPERTY, itemName);
+
+				var mappedBody = mapToBodyMap();
+
+				assertThat(mappedBody).contains(itemNameEntry);
+			}
+
+			@Test
+			void shouldContainsVorgangId() {
+				var vorgangIdEntry = Map.entry(CommandBodyMapper.VORGANG_ID_PROPERTY, VorgangHeaderTestFactory.ID);
+
+				var mappedBody = mapToBodyMap();
+
+				assertThat(mappedBody).contains(vorgangIdEntry);
+			}
+
+			@Nested
+			class CreatedItem {
+
+				@Test
+				void shouldContainsBetreff() {
+					var betreffEntry = Map.entry("betreff", WiedervorlageTestFactory.BETREFF);
+
+					var mappedItemMap = getMappedItemEntry();
+
+					assertThat(mappedItemMap).contains(betreffEntry);
+				}
+
+				@Test
+				void shouldContainsBeschreibung() {
+					var beschreibungEntry = Map.entry("beschreibung", WiedervorlageTestFactory.BESCHREIBUNG);
+
+					var mappedItemMap = getMappedItemEntry();
+
+					assertThat(mappedItemMap).contains(beschreibungEntry);
+				}
+
+				@Test
+				void shouldContainsFrist() {
+					var fristEntry = Map.entry("frist", WiedervorlageTestFactory.FRIST);
+
+					var mappedItemMap = getMappedItemEntry();
+
+					assertThat(mappedItemMap).contains(fristEntry);
+				}
+
+				@Test
+				void shouldContainsCreatedBy() {
+					var createdByEntry = Map.entry("createdBy", WiedervorlageTestFactory.CREATED_BY);
+
+					var mappedItemMap = getMappedItemEntry();
+
+					assertThat(mappedItemMap).contains(createdByEntry);
+				}
+
+				@Test
+				void shouldContainsCreatedAt() {
+					var createdAtEntry = Map.entry("createdAt", WiedervorlageTestFactory.CREATED_AT);
+
+					var mappedItemMap = getMappedItemEntry();
+
+					assertThat(mappedItemMap).contains(createdAtEntry);
+				}
+
+				@Test
+				void shouldContainsDone() {
+					var doneEntry = Map.entry("done", WiedervorlageTestFactory.DONE);
+
+					var mappedItemMap = getMappedItemEntry();
+
+					assertThat(mappedItemMap).contains(doneEntry);
+				}
+
+				@Test
+				void shouldContainsAttachments() {
+					var attachmentsEntry = Map.entry("attachments", List.of(BinaryFileTestFactory.FILE_ID));
+
+					var mappedItemMap = getMappedItemEntry();
+
+					assertThat(mappedItemMap).contains(attachmentsEntry);
+				}
+
+				@SuppressWarnings("unchecked")
+				private Map<String, Object> getMappedItemEntry() {
+					return (Map<String, Object>) mapToBodyMap().get(CommandBodyMapper.ITEM_PROPERTY);
+				}
+			}
+
+			private Map<String, Object> mapToBodyMap() {
+				return mapper.mapToBodyMap(command, itemName);
+			}
+		}
+	}
+}
\ No newline at end of file
diff --git a/goofy-server/src/test/java/de/itvsh/goofy/common/command/CommandByRelationControllerTest.java b/goofy-server/src/test/java/de/itvsh/goofy/common/command/CommandByRelationControllerTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..68b300976a7ce2545ae7457d3a1a4f1461848565
--- /dev/null
+++ b/goofy-server/src/test/java/de/itvsh/goofy/common/command/CommandByRelationControllerTest.java
@@ -0,0 +1,198 @@
+/*
+ * 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.itvsh.goofy.common.command;
+
+import static de.itvsh.goofy.common.command.CommandController.*;
+import static org.mockito.ArgumentMatchers.*;
+import static org.mockito.Mockito.*;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.Spy;
+import org.springframework.http.MediaType;
+import org.springframework.test.web.servlet.MockMvc;
+import org.springframework.test.web.servlet.ResultActions;
+import org.springframework.test.web.servlet.setup.MockMvcBuilders;
+
+import static org.assertj.core.api.Assertions.*;
+
+import de.itvsh.goofy.common.binaryfile.BinaryFileTestFactory;
+import de.itvsh.goofy.common.command.CommandController.CommandByRelationController;
+import de.itvsh.goofy.postfach.PostfachMail;
+import de.itvsh.goofy.postfach.PostfachMailTestFactory;
+import de.itvsh.goofy.vorgang.AntragstellerTestFactory;
+import de.itvsh.goofy.vorgang.VorgangController;
+import de.itvsh.goofy.vorgang.VorgangHeaderTestFactory;
+import de.itvsh.goofy.vorgang.VorgangWithEingangTestFactory;
+import de.itvsh.kop.common.test.TestUtils;
+
+class CommandByRelationControllerTest {
+
+	@Spy
+	@InjectMocks // NOSONAR
+	private CommandByRelationController controller;
+	@Mock
+	private CommandService service;
+	@Mock
+	private CommandModelAssembler modelAssembler;
+	@Mock
+	private VorgangController vorgangController;
+
+	private MockMvc mockMvc;
+
+	@BeforeEach
+	void initMockMvc() {
+		mockMvc = MockMvcBuilders.standaloneSetup(controller).build();
+	}
+
+	@Nested
+	@DisplayName("Create Command by Vorgang")
+	class CreateCommandByVorgang {
+
+		@Captor
+		private ArgumentCaptor<CreateCommand> createCommandCaptor;
+
+		@BeforeEach
+		void initTest() {
+			when(service.createCommand(any(CreateCommand.class), anyLong())).thenReturn(CommandTestFactory.create());
+		}
+
+		@DisplayName("should return CREATED")
+		@Test
+		void returnCreated() throws Exception {
+			doRequest().andExpect(status().isCreated());
+		}
+
+		@DisplayName("should call service")
+		@Test
+		void callService() throws Exception {
+			doRequest();
+
+			verify(service).createCommand(createCommandCaptor.capture(), eq(CommandTestFactory.RELATION_VERSION));
+			assertThat(createCommandCaptor.getValue().getOrder()).isEqualTo(CommandTestFactory.ORDER);
+			assertThat(createCommandCaptor.getValue().getVorgangId()).isEqualTo(CommandTestFactory.VORGANG_ID);
+			assertThat(createCommandCaptor.getValue().getRelationId()).isEqualTo(CommandTestFactory.RELATION_ID);
+			assertThat(createCommandCaptor.getValue().getRelationVersion()).isZero();
+		}
+
+		@DisplayName("should have location header")
+		@Test
+		void locationHeader() throws Exception {
+			doRequest()
+					.andExpect(header().stringValues("location", "http://localhost" + COMMANDS_PATH + "/" + CommandTestFactory.ID));
+		}
+
+		@Nested
+		@DisplayName("CreateCommand with postfach mail")
+
+		class WithPostfachMail {
+
+			@Test
+			void shouldPrepareForPostfachNachricht() throws Exception {
+				doReturn(CommandTestFactory.createCreateCommand()).when(controller).addPostfachIdToBody(any(), any());
+
+				doRequest(PostfachMailTestFactory.buildSendPostfachMailContent());
+
+				verify(controller).addPostfachIdToBody(createCommandCaptor.capture(), eq(VorgangHeaderTestFactory.ID));
+				assertThat(((PostfachMail) createCommandCaptor.getValue().getBody()).getAttachments().get(0))
+						.isEqualTo(BinaryFileTestFactory.FILE_ID);
+			}
+		}
+
+		private ResultActions doRequest() throws Exception {
+			return doRequest(
+					TestUtils.loadTextFile("jsonTemplates/command/createVorgangCommand.json.tmpl", CommandOrder.VORGANG_ANNEHMEN.name()));
+		}
+
+		private ResultActions doRequest(String content) throws Exception {
+			return mockMvc
+					.perform(post(CommandByRelationController.COMMAND_BY_RELATION_PATH, VorgangHeaderTestFactory.ID,
+							CommandTestFactory.RELATION_ID, VorgangHeaderTestFactory.VERSION)
+									.contentType(MediaType.APPLICATION_JSON)
+									.characterEncoding("UTF-8")
+									.content(content))
+					.andExpect(status().is2xxSuccessful());
+		}
+	}
+
+	@DisplayName("prepare command for postfach nachricht")
+	@Nested
+	class TestPrepareCommandForPostfachNachricht {
+
+		@BeforeEach
+		void mockVorgangController() {
+			when(vorgangController.getVorgang(anyString())).thenReturn(VorgangWithEingangTestFactory.create());
+		}
+
+		@Test
+		void shouldCallVorgangController() {
+			controller.addPostfachIdToBody(createPostfachCreateCommand(), VorgangHeaderTestFactory.ID);
+
+			verify(vorgangController).getVorgang(VorgangHeaderTestFactory.ID);
+		}
+
+		@Test
+		void shouldAddPostfachId() {
+			var preparedCommand = controller.addPostfachIdToBody(createPostfachCreateCommand(), VorgangHeaderTestFactory.ID);
+
+			assertThat(((PostfachMail) preparedCommand.getBody()).getPostfachId()).isEqualTo(AntragstellerTestFactory.POSTFACH_ID);
+		}
+
+		@Test
+		void shouldProceedWithEmptyAttachments() {
+			var createCommand = CommandTestFactory.createCreateCommandBuilder()
+					.body(PostfachMailTestFactory.createBuilder().clearAttachments().build()).build();
+
+			var preparedCommand = controller.addPostfachIdToBody(createCommand, VorgangHeaderTestFactory.ID);
+
+			assertThat(((PostfachMail) preparedCommand.getBody()).getAttachments()).isEmpty();
+		}
+
+		private CreateCommand createPostfachCreateCommand() {
+			return CommandTestFactory.createCreateCommandBuilder().body(PostfachMailTestFactory.create()).build();
+		}
+	}
+
+	@DisplayName("Create by vorgang with relation id")
+	@Nested
+	class TestCreateByVorgang {
+
+		private final CreateCommand command = CommandTestFactory.createCreateCommand();
+
+		@Test
+		void shouldCallSerice() {
+			controller.createCommand(command);
+
+			verify(service).createCommand(command);
+		}
+	}
+}
diff --git a/goofy-server/src/test/java/de/itvsh/goofy/common/command/CommandControllerTest.java b/goofy-server/src/test/java/de/itvsh/goofy/common/command/CommandControllerTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..9b86f6b939c2fc3f91e25ef4b1b8765350ee63a8
--- /dev/null
+++ b/goofy-server/src/test/java/de/itvsh/goofy/common/command/CommandControllerTest.java
@@ -0,0 +1,179 @@
+/*
+ * 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.itvsh.goofy.common.command;
+
+import static de.itvsh.goofy.common.command.CommandController.*;
+import static org.mockito.ArgumentMatchers.*;
+import static org.mockito.Mockito.*;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
+
+import java.util.stream.Stream;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.springframework.http.MediaType;
+import org.springframework.test.web.servlet.MockMvc;
+import org.springframework.test.web.servlet.ResultActions;
+import org.springframework.test.web.servlet.setup.MockMvcBuilders;
+
+import de.itvsh.goofy.common.errorhandling.ExceptionController;
+import de.itvsh.goofy.vorgang.VorgangHeaderTestFactory;
+import de.itvsh.kop.common.test.TestUtils;
+
+class CommandControllerTest {
+
+	@InjectMocks // NOSONAR
+	private CommandController controller;
+	@Mock
+	private CommandService service;
+	@Mock
+	private CommandModelAssembler modelAssembler;
+
+	private MockMvc mockMvc;
+
+	@BeforeEach
+	void initTest() {
+		mockMvc = MockMvcBuilders.standaloneSetup(controller).setControllerAdvice(new ExceptionController()).build();
+	}
+
+	@Nested
+	class TestGetCommand {
+
+		private final Command command = CommandTestFactory.create();
+
+		@BeforeEach
+		void initTest() {
+			when(service.getById(anyString())).thenReturn(command);
+		}
+
+		@Test
+		void shouldReturnOk() throws Exception {
+			doRequest().andExpect(status().isOk());
+		}
+
+		@Test
+		void shouldCallService() throws Exception {
+			doRequest();
+
+			verify(service).getById(CommandTestFactory.ID);
+		}
+
+		@Test
+		void shouldCallModelAssembler() throws Exception {
+			doRequest();
+
+			verify(modelAssembler).toModel(command);
+		}
+
+		private ResultActions doRequest() throws Exception {
+			return mockMvc.perform(get(CommandController.COMMANDS_PATH + "/" + CommandTestFactory.ID)).andExpect(status().is2xxSuccessful());
+		}
+	}
+
+	@Nested
+	@DisplayName("Revoke Command")
+	class TestRevokeCommand {
+
+		@Test
+		void shouldReturnOk() throws Exception {
+			when(service.revoke(anyString())).thenReturn(CommandTestFactory.create());
+
+			doRequest(CommandStatus.REVOKED).andExpect(status().isOk());
+		}
+
+		@Test
+		void shouldCallService() throws Exception {
+			when(service.revoke(anyString())).thenReturn(CommandTestFactory.create());
+
+			doRequest(CommandStatus.REVOKED);
+
+			verify(service).revoke(CommandTestFactory.ID);
+		}
+
+		@Test
+		void shouldReturnForbiddenForOtherStatus() throws Exception {
+			doRequest(CommandStatus.FINISHED).andExpect(status().isForbidden());
+		}
+
+		private ResultActions doRequest(CommandStatus statusToPatch) throws Exception {
+			return mockMvc.perform(patch(COMMANDS_PATH + "/" + CommandTestFactory.ID).contentType(MediaType.APPLICATION_JSON)
+					.content(TestUtils.loadTextFile("jsonTemplates/command/patchStatus.json", statusToPatch.name())));
+
+		}
+	}
+
+	@Nested
+	class TestExistsPendingCommands {
+
+		@Test
+		void shouldCallService() {
+			controller.existsPendingCommands(VorgangHeaderTestFactory.ID);
+
+			verify(service).existsPendingCommands(VorgangHeaderTestFactory.ID);
+		}
+	}
+
+	@Nested
+	class TestGetPendingCommands {
+
+		private final Command command = CommandTestFactory.create();
+		private final Stream<Command> pendingCommands = Stream.of(command);
+
+		@BeforeEach
+		void initTest() {
+			when(service.getPendingCommands(anyString())).thenReturn(pendingCommands);
+		}
+
+		@Test
+		void shouldReturnOk() throws Exception {
+			doRequest().andExpect(status().isOk());
+		}
+
+		@Test
+		void shouldCallService() throws Exception {
+			doRequest();
+
+			verify(service).getPendingCommands(VorgangHeaderTestFactory.ID);
+		}
+
+		@Test
+		void shouldCallModelAssembler() throws Exception {
+			doRequest();
+
+			verify(modelAssembler).toCollectionModel(pendingCommands);
+		}
+
+		private ResultActions doRequest() throws Exception {
+			return mockMvc.perform(get(CommandController.COMMANDS_PATH)
+					.param(CommandController.PARAM_PENDING, Boolean.toString(true))
+					.param(CommandController.PARAM_VORGANG_ID, VorgangHeaderTestFactory.ID))
+					.andExpect(status().is2xxSuccessful());
+		}
+	}
+}
\ No newline at end of file
diff --git a/goofy-server/src/test/java/de/itvsh/goofy/common/command/CommandITCase.java b/goofy-server/src/test/java/de/itvsh/goofy/common/command/CommandITCase.java
new file mode 100644
index 0000000000000000000000000000000000000000..402a9c75ecdfb984312fcfdbc4418cdaf35fa621
--- /dev/null
+++ b/goofy-server/src/test/java/de/itvsh/goofy/common/command/CommandITCase.java
@@ -0,0 +1,305 @@
+/*
+ * 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.itvsh.goofy.common.command;
+
+import static org.assertj.core.api.Assertions.*;
+import static org.mockito.ArgumentMatchers.*;
+import static org.mockito.Mockito.*;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
+
+import org.apache.commons.lang3.ArrayUtils;
+import org.apache.commons.lang3.RandomStringUtils;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.boot.test.mock.mockito.MockBean;
+import org.springframework.http.MediaType;
+import org.springframework.security.test.context.support.WithMockUser;
+import org.springframework.test.web.servlet.MockMvc;
+import org.springframework.test.web.servlet.ResultActions;
+
+import de.itvsh.goofy.common.ValidationMessageCodes;
+import de.itvsh.goofy.common.user.UserProfileTestFactory;
+import de.itvsh.goofy.postfach.PostfachMailTestFactory;
+import de.itvsh.goofy.vorgang.RedirectRequestTestFactory;
+import de.itvsh.goofy.vorgang.VorgangController;
+import de.itvsh.goofy.vorgang.VorgangHeaderTestFactory;
+import de.itvsh.goofy.vorgang.VorgangWithEingangTestFactory;
+import de.itvsh.goofy.vorgang.forwarding.RedirectRequest;
+import de.itvsh.kop.common.test.TestUtils;
+
+@AutoConfigureMockMvc
+@SpringBootTest
+public class CommandITCase {
+
+	@Autowired
+	private MockMvc mockMvc;
+	@MockBean
+	private CommandRemoteService commandRemoteService;
+	@MockBean
+	private VorgangController vorgangController;
+
+	@Nested
+	@WithMockUser
+	class TestAssignUser {
+
+		@Captor
+		private ArgumentCaptor<CreateCommand> commandCaptor;
+
+		@BeforeEach
+		void initTests() {
+			when(commandRemoteService.createCommand(any())).thenReturn(CommandTestFactory.create());
+		}
+
+		@Test
+		void shouldExtractUserId() throws Exception {
+			createCommand();
+
+			verify(commandRemoteService).createCommand(commandCaptor.capture());
+			assertThat(commandCaptor.getValue().getBody()).hasFieldOrPropertyWithValue("assignedTo", UserProfileTestFactory.ID);
+		}
+
+		private void createCommand() throws Exception {
+			mockMvc.perform(post(buildUrl()).content(createContent()).contentType(MediaType.APPLICATION_JSON))
+					.andExpect(status().isCreated());
+		}
+
+		private String createContent() {
+			return TestUtils.loadTextFile("jsonTemplates/command/createCommandWithBody.json.tmpl", CommandOrder.ASSIGN_USER.name(),
+					TestUtils.loadTextFile("jsonTemplates/command/commandAssignedToBody",
+							TestUtils.addQuote("/api/users/" + UserProfileTestFactory.ID.toString())));
+		}
+
+		private String buildUrl() {
+			return String.format("/api/vorgangs/%s/relations/%s/%d/commands", VorgangHeaderTestFactory.ID, VorgangHeaderTestFactory.ID,
+					VorgangHeaderTestFactory.VERSION);
+		}
+	}
+
+	@DisplayName("Validation")
+	@Nested
+	class TestValidation {
+
+		@DisplayName("of forwarding request")
+		@WithMockUser
+		@Nested
+		class TestRedirectRequest {
+
+			@DisplayName("with null/empty email")
+			@WithMockUser
+			@Nested
+			class TestOnNullOrEmptyEmail {
+
+				@Test
+				void shouldThrowErrorForEmailOnly() throws Exception {
+					var requestContent = buildRedirectRequest(RedirectRequestTestFactory.createBuilder()
+							.password(ArrayUtils.EMPTY_CHAR_ARRAY)
+							.email(null).build());
+
+					doRequest(requestContent).andExpect(status().isUnprocessableEntity())
+							.andExpect(jsonPath("$.issues.length()").value(1))
+							.andExpect(jsonPath("$.issues.[0].field").value("command.redirectRequest.email"))
+							.andExpect(jsonPath("$.issues.[0].messageCode").value(ValidationMessageCodes.FIELD_IS_EMPTY));
+				}
+			}
+
+			@DisplayName("with invalid email and password")
+			@WithMockUser
+			@Nested
+			class TestOnInvalidEmailAndPassword {
+
+				@Test
+				void shouldThrowExceptionForEachOne() throws Exception {
+					var requestContent = buildRedirectRequest(RedirectRequestTestFactory.createBuilder()
+							.password(ArrayUtils.EMPTY_CHAR_ARRAY)
+							.email("local@@domain.com")
+							.build());
+
+					doRequest(requestContent).andExpect(status().isUnprocessableEntity())
+							.andExpect(jsonPath("$.issues.length()").value(2));
+				}
+			}
+
+			@WithMockUser
+			@Nested
+			class TestEmail {
+
+				private final String FIELD = "email";
+
+				@Test
+				void shouldReturnErrorOnNullEMail() throws Exception {
+					var requestContent = buildRedirectRequestWithEmail(null);
+
+					doRequest(requestContent).andExpect(status().isUnprocessableEntity())
+							.andExpect(jsonPath("$.issues.length()").value(1))
+							.andExpect(jsonPath("$.issues.[0].field").value("command.redirectRequest." + FIELD))
+							.andExpect(jsonPath("$.issues.[0].messageCode").value(ValidationMessageCodes.FIELD_IS_EMPTY));
+				}
+
+				@Test
+				void shouldReturnErrorOnInvalidEMail() throws Exception {
+					var requestContent = buildRedirectRequestWithEmail("local@@domain.com");
+
+					doRequest(requestContent).andExpect(status().isUnprocessableEntity())
+							.andExpect(jsonPath("$.issues.length()").value(1))
+							.andExpect(jsonPath("$.issues.[0].field").value("command.redirectRequest." + FIELD))
+							.andExpect(jsonPath("$.issues.[0].messageCode").value(ValidationMessageCodes.FIELD_INVALID));
+				}
+
+				private String buildRedirectRequestWithEmail(String eMail) {
+					return buildRedirectRequest(RedirectRequestTestFactory.createBuilder().email(eMail).build());
+				}
+			}
+
+			@WithMockUser
+			@Nested
+			class TestPassword {
+
+				private final String FIELD = "password";
+
+				@Test
+				void shouldReturnErrorOnToShortPassword() throws Exception {
+					var requestContent = buildRedirectRequestWithPassword(RandomStringUtils.randomAlphabetic(7));
+
+					doRequest(requestContent).andExpect(status().isUnprocessableEntity())
+							.andExpect(jsonPath("$.issues.length()").value(1))
+							.andExpect(jsonPath("$.issues.[0].field").value("command.redirectRequest." + FIELD))
+							.andExpect(jsonPath("$.issues.[0].messageCode").value(ValidationMessageCodes.FIELD_SIZE))
+							.andExpect(jsonPath("$.issues.[0].parameters[0].name").value("min"))
+							.andExpect(jsonPath("$.issues.[0].parameters[0].value").value(8))
+							.andExpect(jsonPath("$.issues.[0].parameters[1].name").value("max"))
+							.andExpect(jsonPath("$.issues.[0].parameters[1].value").value(40));
+				}
+
+				@Test
+				void shouldReturnErrorOnToLongPassword() throws Exception {
+					var requestContent = buildRedirectRequestWithPassword(RandomStringUtils.randomAlphabetic(41));
+
+					doRequest(requestContent).andExpect(status().isUnprocessableEntity())
+							.andExpect(jsonPath("$.issues.length()").value(1))
+							.andExpect(jsonPath("$.issues.[0].field").value("command.redirectRequest." + FIELD))
+							.andExpect(jsonPath("$.issues.[0].messageCode").value(ValidationMessageCodes.FIELD_SIZE));
+				}
+
+				private String buildRedirectRequestWithPassword(String password) {
+					return buildRedirectRequest(RedirectRequestTestFactory.createBuilder().password(password.toCharArray()).build());
+				}
+			}
+
+			private String buildRedirectRequest(RedirectRequest request) {
+				return RedirectRequestTestFactory
+						.createRedirectRequestContent(CommandTestFactory.createCreateCommandBuilder().redirectRequest(request).build());
+			}
+		}
+
+		@WithMockUser
+		@Nested
+		class TestPostfachMail {
+
+			@BeforeEach
+			void mockVorgangController() {
+				when(vorgangController.getVorgang(anyString())).thenReturn(VorgangWithEingangTestFactory.create());
+			}
+
+			@WithMockUser
+			@Nested
+			class TestSubject {
+
+				@BeforeEach
+				void initMockCommand() {
+					when(commandRemoteService.createCommand(any(CreateCommand.class))).thenReturn(CommandTestFactory.create());
+				}
+
+				@Test
+				void shouldReturnErrorOnNullSubject() throws Exception {
+					var requestContent = PostfachMailTestFactory
+							.buildSendPostfachMailContent(PostfachMailTestFactory.createBuilder().subject(null).build());
+
+					doRequest(requestContent).andExpect(status().isUnprocessableEntity())
+							.andExpect(jsonPath("$.issues.length()").value(1))
+							.andExpect(jsonPath("$.issues.[0].field").value("command.body.subject"))
+							.andExpect(jsonPath("$.issues.[0].messageCode").value(ValidationMessageCodes.FIELD_IS_EMPTY));
+				}
+
+				@Test
+				void shouldReturnErrorOnTooLongSubject() throws Exception {
+					var requestContent = PostfachMailTestFactory
+							.buildSendPostfachMailContent(
+									PostfachMailTestFactory.createBuilder().subject(RandomStringUtils.randomAlphanumeric(71)).build());
+
+					doRequest(requestContent).andExpect(status().isUnprocessableEntity())
+							.andExpect(jsonPath("$.issues.length()").value(1))
+							.andExpect(jsonPath("$.issues.[0].field").value("command.body.subject"))
+							.andExpect(jsonPath("$.issues.[0].messageCode").value(ValidationMessageCodes.FIELD_MAX_SIZE));
+				}
+
+				@Test
+				void shouldProceedOnValidSubject() throws Exception {
+					var requestContent = PostfachMailTestFactory.buildSendPostfachMailContent();
+
+					doRequest(requestContent).andExpect(status().isCreated());
+				}
+			}
+
+			@WithMockUser
+			@Nested
+			class TestMailBody {
+
+				@Test
+				void shouldReturnErrorOnNull() throws Exception {
+					var request = PostfachMailTestFactory
+							.buildSendPostfachMailContent(PostfachMailTestFactory.createBuilder().mailBody(null).build());
+
+					doRequest(request).andExpect(status().isUnprocessableEntity())
+							.andExpect(jsonPath("$.issues.length()").value(1))
+							.andExpect(jsonPath("$.issues.[0].field").value("command.body.mailBody"))
+							.andExpect(jsonPath("$.issues.[0].messageCode").value(ValidationMessageCodes.FIELD_IS_EMPTY));
+				}
+
+				@Test
+				void shouldProceedOnValidInput() throws Exception {
+					when(commandRemoteService.createCommand(any(CreateCommand.class))).thenReturn(CommandTestFactory.create());
+
+					var content = PostfachMailTestFactory.buildSendPostfachMailContent();
+
+					doRequest(content).andExpect(status().isCreated());
+				}
+			}
+		}
+
+		ResultActions doRequest(String content) throws Exception {
+			return mockMvc.perform(post("/api/vorgangs/" + CommandTestFactory.VORGANG_ID + "/relations/" + CommandTestFactory.RELATION_ID + "/"
+					+ CommandTestFactory.RELATION_VERSION + "/commands")
+							.contentType(MediaType.APPLICATION_JSON)
+							.content(content));
+		}
+	}
+}
\ No newline at end of file
diff --git a/goofy-server/src/test/java/de/itvsh/goofy/common/command/CommandMapperTest.java b/goofy-server/src/test/java/de/itvsh/goofy/common/command/CommandMapperTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..d0bbf632db0b37356027d86174b49b21a4a56e31
--- /dev/null
+++ b/goofy-server/src/test/java/de/itvsh/goofy/common/command/CommandMapperTest.java
@@ -0,0 +1,166 @@
+/*
+ * 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.itvsh.goofy.common.command;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import java.util.Collections;
+import java.util.Map;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.mapstruct.factory.Mappers;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+
+import de.itvsh.goofy.common.TimeMapper;
+import de.itvsh.goofy.common.callcontext.CallContextMapper;
+import de.itvsh.goofy.common.user.UserIdMapper;
+import de.itvsh.goofy.vorgang.RedirectRequestTestFactory;
+import de.itvsh.kop.pluto.common.grpc.GrpcObjectMapper;
+import de.itvsh.ozg.pluto.grpc.command.GrpcCommand;
+import de.itvsh.ozg.pluto.grpc.command.GrpcOrder;
+import de.itvsh.ozg.pluto.grpc.command.GrpcRedirectRequest;
+
+class CommandMapperTest {
+
+	@InjectMocks
+	private CommandMapper mapper = Mappers.getMapper(CommandMapper.class);
+	@Mock
+	private UserIdMapper userIdMapper;
+	@Mock
+	private GrpcObjectMapper grpcObjectMapper;
+	@Mock
+	private CommandBodyMapper bodyMapper;
+	@Mock
+	private TimeMapper timeMapper;
+	@Mock
+	private CallContextMapper callContextMapper;
+
+	@Test
+	void shouldMapNonEnumOrder() {
+		var command = mapper.toCommand(GrpcCommandTestFactory.createBuilder()
+				.setOrder(GrpcOrder.UNDEFINED)
+				.setOrderString(CommandOrder.SEND_POSTFACH_NACHRICHT.name())
+				.build());
+
+		assertThat(command.getOrder()).isEqualTo(CommandOrder.SEND_POSTFACH_NACHRICHT);
+	}
+
+	@DisplayName("Map from grpc to command")
+	@Nested
+	class TestToCommand {
+
+		@DisplayName("redirect request")
+		@Nested
+		class TestRedirectRequest {
+
+			@Test
+			void shouldMapPasswordIfExists() {
+				var command = mapper.toCommand(GrpcCommandTestFactory.createBuilder()
+						.setRedirectRequest(GrpcRedirectRequest.newBuilder()
+								.setPassword(RedirectRequestTestFactory.PASSWORD_AS_STR)
+								.setEmail(RedirectRequestTestFactory.EMAIL)
+								.build())
+						.build());
+
+				assertThat(command.getRedirectRequest().getPassword()).isEqualTo(RedirectRequestTestFactory.PASSWORD);
+				assertThat(command.getRedirectRequest().getEmail()).isEqualTo(RedirectRequestTestFactory.EMAIL);
+			}
+
+			@Test
+			void shouldNotMapPasswordIfEmpty() {
+				var command = mapper.toCommand(GrpcCommandTestFactory.createBuilder()
+						.setRedirectRequest(GrpcRedirectRequest.newBuilder()
+								.setEmail(RedirectRequestTestFactory.EMAIL)
+								.build())
+						.build());
+
+				assertThat(command.getRedirectRequest().getPassword()).isNull();
+				assertThat(command.getRedirectRequest().getEmail()).isEqualTo(RedirectRequestTestFactory.EMAIL);
+			}
+		}
+	}
+
+	@DisplayName("Map Body")
+	@Nested
+	class TestMapBody {
+
+		private GrpcCommand command = GrpcCommandTestFactory.create();
+
+		@DisplayName("on existing body object at command")
+		@Nested
+		class TestOnExistingBodyObject {
+
+			@Test
+			void shouldCallGrpcObjectMapper() {
+				mapper.mapBody(command);
+
+				verify(grpcObjectMapper).mapFromGrpc(command.getBodyObj());
+			}
+
+			@Test
+			void shouldMapFromCommandBodyObject() {
+				var bodyObjectMap = Map.<String, Object>of("test-key", "test-value");
+				when(grpcObjectMapper.mapFromGrpc(any())).thenReturn(bodyObjectMap);
+
+				var result = mapper.mapBody(command);
+
+				assertThat(result).isEqualTo(bodyObjectMap);
+			}
+		}
+
+		@DisplayName("on empty body object at command")
+		@Nested
+		class TestOnEmptyBodyObject {
+
+			private Map<String, String> mappedBody = Map.of("test-key", "test-value");
+
+			@BeforeEach
+			void mockMapper() {
+				when(grpcObjectMapper.mapFromGrpc(any())).thenReturn(Collections.emptyMap());
+				when(bodyMapper.map(any())).thenReturn(mappedBody);
+			}
+
+			@Test
+			void shouldCallBodyMapper() {
+				mapper.mapBody(command);
+
+				verify(bodyMapper).map(command.getBody());
+			}
+
+			@Test
+			void shouldReturnMappedResult() {
+				var result = mapper.mapBody(command);
+
+				assertThat(result).isEqualTo(mappedBody);
+			}
+		}
+	}
+}
\ No newline at end of file
diff --git a/goofy-server/src/test/java/de/itvsh/goofy/common/command/CommandModelAssemblerTest.java b/goofy-server/src/test/java/de/itvsh/goofy/common/command/CommandModelAssemblerTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..4d3fcff8f14e75f83b73bd1f48f0cacb857a5b24
--- /dev/null
+++ b/goofy-server/src/test/java/de/itvsh/goofy/common/command/CommandModelAssemblerTest.java
@@ -0,0 +1,229 @@
+/*
+ * 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.itvsh.goofy.common.command;
+
+import static de.itvsh.goofy.common.command.CommandModelAssembler.REL_EFFECTED_RESOURCE;
+import static de.itvsh.goofy.common.command.CommandModelAssembler.REL_REVOKE;
+import static de.itvsh.goofy.common.command.CommandModelAssembler.REL_UPDATE;
+import static de.itvsh.goofy.common.command.CommandTestFactory.RELATION_ID;
+import static org.assertj.core.api.Assertions.assertThat;
+
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.EnumSource;
+import org.junit.jupiter.params.provider.EnumSource.Mode;
+import org.mockito.InjectMocks;
+import org.springframework.hateoas.EntityModel;
+import org.springframework.hateoas.IanaLinkRelations;
+import org.springframework.hateoas.Link;
+
+import de.itvsh.goofy.vorgang.VorgangHeaderTestFactory;
+
+class CommandModelAssemblerTest {
+
+	@InjectMocks // NOSONAR
+	private CommandModelAssembler modelAssembler;
+
+	private final String COMMAND_SINGLE_PATH = "/api/commands/" + CommandTestFactory.ID;
+
+	@Test
+	void shouldHaveSelfLink() {
+		var model = modelAssembler.toModel(CommandTestFactory.create());
+
+		assertThat(model.getLink(IanaLinkRelations.SELF)).isPresent().get().extracting(Link::getHref).isEqualTo(COMMAND_SINGLE_PATH);
+	}
+
+	@Test
+	void shouldHaveRevokeLink() {
+		var model = modelAssembler.toModel(CommandTestFactory.create());
+
+		assertThat(model.getLink(CommandModelAssembler.REL_REVOKE)).isPresent().get().extracting(Link::getHref).isEqualTo(COMMAND_SINGLE_PATH);
+	}
+
+	@Nested
+	@DisplayName("Link to effected Resource")
+	class TestEffectedResourceLink {
+
+		@Nested
+		class TestOnVorgang {
+
+			private final String VORGANG_URL = "/api/vorgangs/" + RELATION_ID;
+
+			@ParameterizedTest
+			@EnumSource(mode = Mode.INCLUDE, names = { "FINISHED", "REVOKED" })
+			void shouldBePresentOnStatus(CommandStatus status) {
+				var model = toModelWithStatus(status);
+
+				assertThat(model.getLink(REL_EFFECTED_RESOURCE)).isPresent().get().extracting(Link::getHref).isEqualTo(VORGANG_URL);
+			}
+
+			@ParameterizedTest
+			@EnumSource(mode = Mode.EXCLUDE, names = { "FINISHED", "REVOKED", "ERROR" })
+			void shouldNOTbePresentOtherWise(CommandStatus status) {
+				var model = toModelWithStatus(status);
+
+				assertThat(model.getLink(REL_EFFECTED_RESOURCE)).isEmpty();
+			}
+
+			private EntityModel<Command> toModelWithStatus(CommandStatus status) {
+				return modelAssembler.toModel(CommandTestFactory.createBuilder().status(status).build());
+			}
+		}
+
+		@Nested
+		class TestOnWiedervorlage {
+
+			private final String WIEDERVORLAGE_URL = "/api/wiedervorlages/" + RELATION_ID;
+
+			@ParameterizedTest
+			@EnumSource(mode = Mode.INCLUDE, names = { "CREATE_WIEDERVORLAGE", "EDIT_WIEDERVORLAGE",
+					"WIEDERVORLAGE_ERLEDIGEN", "WIEDERVORLAGE_WIEDEREROEFFNEN" })
+			void shouldHaveLinkToWiedervorlage(CommandOrder order) {
+				var model = toModelWithOrder(order);
+
+				assertThat(model.getLink(CommandModelAssembler.REL_EFFECTED_RESOURCE)).isPresent().get().extracting(Link::getHref)
+						.isEqualTo(WIEDERVORLAGE_URL);
+			}
+
+			private EntityModel<Command> toModelWithOrder(CommandOrder order) {
+				return modelAssembler.toModel(CommandTestFactory.createBuilder().order(order).build());
+			}
+		}
+
+		@Nested
+		class TestOnKommentar {
+
+			private final String KOMMENTAR_URL = "/api/kommentars/" + CommandTestFactory.RELATION_ID;
+
+			@ParameterizedTest
+			@EnumSource(mode = Mode.INCLUDE, names = { "CREATE_KOMMENTAR", "EDIT_KOMMENTAR" })
+			void shoulHaveLinkToKommentar(CommandOrder order) {
+				var model = toModelWithOrder(order);
+
+				assertThat(model.getLink(CommandModelAssembler.REL_EFFECTED_RESOURCE)).isPresent().get()
+						.extracting(Link::getHref).isEqualTo(KOMMENTAR_URL);
+			}
+
+			private EntityModel<Command> toModelWithOrder(CommandOrder order) {
+				return modelAssembler.toModel(CommandTestFactory.createBuilder().order(order).build());
+			}
+		}
+
+		@Nested
+		class TestOnForwarding {
+
+			private final String FORWARDING_URL = "/api/forwardings?vorgangId=" + VorgangHeaderTestFactory.ID;
+
+			@ParameterizedTest
+			@EnumSource(mode = Mode.INCLUDE, names = { "FORWARD_SUCCESSFULL", "FORWARD_FAILED", "REDIRECT_VORGANG" })
+			void shouldHaveLinkToForwarding(CommandOrder order) {
+				var model = toModelWithOrder(order);
+
+				assertThat(model.getLink(CommandModelAssembler.REL_EFFECTED_RESOURCE)).isPresent().get()
+						.extracting(Link::getHref).isEqualTo(FORWARDING_URL);
+			}
+
+			private EntityModel<Command> toModelWithOrder(CommandOrder order) {
+				return modelAssembler.toModel(CommandTestFactory.createBuilder().order(order).build());
+			}
+		}
+
+		@Nested
+		class TestOnPostfach {
+
+			private final String POSTFACH_URL = "/api/postfachMails?vorgangId=" + VorgangHeaderTestFactory.ID;
+
+			@ParameterizedTest
+			@EnumSource(mode = Mode.INCLUDE, names = { "SEND_POSTFACH_MAIL" })
+			void shouldHaveLinkToPostfach(CommandOrder order) {
+				var model = toModelWithOrder(order);
+
+				assertThat(model.getLink(CommandModelAssembler.REL_EFFECTED_RESOURCE)).isPresent().get()
+						.extracting(Link::getHref).isEqualTo(POSTFACH_URL);
+			}
+
+			private EntityModel<Command> toModelWithOrder(CommandOrder order) {
+				return modelAssembler.toModel(CommandTestFactory.createBuilder().order(order).build());
+			}
+		}
+
+		@ParameterizedTest
+		@EnumSource
+		void shouldBeAbleToBuildLinkForEveryOrder(CommandOrder order) {
+			var link = modelAssembler.effectedResourceLinkByOrder(CommandTestFactory.createBuilder().order(order).build());
+
+			assertThat(link).isNotNull();
+		}
+	}
+
+	@Nested
+	class TestRevokeLink {
+
+		@ParameterizedTest
+		@EnumSource(mode = Mode.EXCLUDE, names = { "CREATE_WIEDERVORLAGE", "WIEDERVORLAGE_ERLEDIGEN", "EDIT_WIEDERVORLAGE",
+				"WIEDERVORLAGE_WIEDEREROEFFNEN", "CREATE_KOMMENTAR", "EDIT_KOMMENTAR", "REDIRECT_VORGANG", "FORWARD_SUCCESSFULL",
+				"FORWARD_FAILED", "ASSIGN_USER", "SEND_POSTFACH_MAIL", "SEND_POSTFACH_NACHRICHT", "RESEND_POSTFACH_MAIL", "CREATE_ATTACHED_ITEM",
+				"UPDATE_ATTACHED_ITEM", "PATCH_ATTACHED_ITEM", "RECEIVE_POSTFACH_NACHRICHT" })
+		void shouldHaveLink(CommandOrder order) {
+			var model = toModelWithOrder(order);
+
+			assertThat(model.getLink(REL_REVOKE)).isPresent().get().extracting(Link::getHref).isEqualTo(COMMAND_SINGLE_PATH);
+		}
+
+		@ParameterizedTest
+		@EnumSource(names = { "CREATE_WIEDERVORLAGE", "WIEDERVORLAGE_ERLEDIGEN", "EDIT_WIEDERVORLAGE",
+				"WIEDERVORLAGE_WIEDEREROEFFNEN", "CREATE_KOMMENTAR", "EDIT_KOMMENTAR", "REDIRECT_VORGANG", "FORWARD_SUCCESSFULL" })
+		void shouldNotHaveLink(CommandOrder order) {
+			var model = toModelWithOrder(order);
+
+			assertThat(model.getLink(REL_REVOKE)).isNotPresent();
+		}
+
+		private EntityModel<Command> toModelWithOrder(CommandOrder order) {
+			return modelAssembler.toModel(CommandTestFactory.createBuilder().order(order).build());
+		}
+	}
+
+	@Nested
+	class TestUpdateLink {
+
+		@ParameterizedTest
+		@EnumSource(names = { "PENDING", "REVOKE_PENDING" })
+		void shouldHave(CommandStatus status) {
+			var model = modelAssembler.toModel(CommandTestFactory.createBuilder().status(status).build());
+
+			assertThat(model.getLink(REL_UPDATE)).isPresent().get().extracting(Link::getHref).isEqualTo(COMMAND_SINGLE_PATH);
+		}
+
+		@ParameterizedTest
+		@EnumSource(mode = Mode.EXCLUDE, names = { "PENDING", "REVOKE_PENDING" })
+		void shouldNotHaveLink(CommandStatus status) {
+			var model = modelAssembler.toModel(CommandTestFactory.createBuilder().status(status).build());
+
+			assertThat(model.getLink(REL_UPDATE)).isNotPresent();
+		}
+	}
+}
\ No newline at end of file
diff --git a/goofy-server/src/test/java/de/itvsh/goofy/common/command/CommandRemoteServiceTest.java b/goofy-server/src/test/java/de/itvsh/goofy/common/command/CommandRemoteServiceTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..b788a3d6b5c000463d107dd4e0a08f70838feda5
--- /dev/null
+++ b/goofy-server/src/test/java/de/itvsh/goofy/common/command/CommandRemoteServiceTest.java
@@ -0,0 +1,538 @@
+/*
+ * 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.itvsh.goofy.common.command;
+
+import static org.mockito.ArgumentMatchers.*;
+import static org.mockito.Mockito.*;
+
+import java.util.Collections;
+import java.util.Optional;
+import java.util.Set;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import org.apache.commons.lang3.ArrayUtils;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.EnumSource;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.Spy;
+
+import static org.assertj.core.api.Assertions.*;
+
+import de.itvsh.goofy.common.callcontext.ContextService;
+import de.itvsh.goofy.vorgang.RedirectRequestTestFactory;
+import de.itvsh.goofy.vorgang.VorgangHeaderTestFactory;
+import de.itvsh.goofy.wiedervorlage.WiedervorlageTestFactory;
+import de.itvsh.kop.pluto.common.grpc.GrpcObjectMapper;
+import de.itvsh.kop.pluto.common.grpc.GrpcObjectTestFactory;
+import de.itvsh.ozg.pluto.grpc.command.CommandServiceGrpc.CommandServiceBlockingStub;
+import de.itvsh.ozg.pluto.grpc.command.GrpcCallContext;
+import de.itvsh.ozg.pluto.grpc.command.GrpcCommand;
+import de.itvsh.ozg.pluto.grpc.command.GrpcCommandResponse;
+import de.itvsh.ozg.pluto.grpc.command.GrpcCommandsResponse;
+import de.itvsh.ozg.pluto.grpc.command.GrpcCreateCommandRequest;
+import de.itvsh.ozg.pluto.grpc.command.GrpcExistsPendingCommandsRequest;
+import de.itvsh.ozg.pluto.grpc.command.GrpcExistsPendingCommandsResponse;
+import de.itvsh.ozg.pluto.grpc.command.GrpcFindCommandsRequest;
+import de.itvsh.ozg.pluto.grpc.command.GrpcGetCommandRequest;
+import de.itvsh.ozg.pluto.grpc.command.GrpcGetPendingCommandsRequest;
+import de.itvsh.ozg.pluto.grpc.command.GrpcGetPendingCommandsResponse;
+import de.itvsh.ozg.pluto.grpc.command.GrpcRevokeCommandRequest;
+
+class CommandRemoteServiceTest {
+
+	@Spy
+	@InjectMocks
+	private CommandRemoteService service;
+	@Mock
+	private CommandServiceBlockingStub commandServiceStub;
+	@Mock
+	private CommandMapper mapper;
+	@Mock
+	private CommandBodyMapper bodyMapper;
+	@Mock
+	private GrpcObjectMapper grpcObjectMapper;
+	@Mock
+	private ContextService contextService;
+
+	private GrpcCallContext grpcCallContext = GrpcCallContext.newBuilder().build();
+
+	@Nested
+	@DisplayName("Create CreateCommand")
+	class TestCreateCommand {
+
+		private GrpcCreateCommandRequest request = GrpcCreateCommandRequest.newBuilder().build();
+		private CreateCommand createCommand = CommandTestFactory.createCreateCommand();
+
+		@Nested
+		class CallGrpcEndpoint {
+
+			private Command resultCommand = CommandTestFactory.create();
+
+			@BeforeEach
+			void initTest() {
+				doReturn(request).when(service).buildCreateCommandRequest(any());
+				when(commandServiceStub.createCommand(any())).thenReturn(GrpcCommandResponse.newBuilder().build());
+				when(mapper.toCommand(any())).thenReturn(resultCommand);
+			}
+
+			@Test
+			void shouldCreateRequest() {
+				service.createCommand(createCommand);
+
+				verify(service).buildCreateCommandRequest(createCommand);
+			}
+
+			@Test
+			void shouldCallServiceStub() {
+				service.createCommand(createCommand);
+
+				verify(commandServiceStub).createCommand(request);
+			}
+
+			@Test
+			void shouldReturnResultCommand() {
+				var result = service.createCommand(createCommand);
+
+				assertThat(result).isSameAs(resultCommand);
+			}
+		}
+
+		@DisplayName("build request")
+		@Nested
+		class TestBuildRequest {
+
+			@BeforeEach
+			void initTest() {
+				when(contextService.createCallContext()).thenReturn(grpcCallContext);
+				when(grpcObjectMapper.fromMap(any())).thenReturn(GrpcObjectTestFactory.create());
+			}
+
+			@Test
+			void shouldCreateRequest() {
+				var request = buildRequest();
+
+				assertThat(request).isNotNull();
+			}
+
+			@Test
+			void shouldHaveOrder() {
+				var request = buildRequest();
+
+				assertThat(request.getOrderString()).isEqualTo(CommandTestFactory.ORDER.name());
+			}
+
+			@Test
+			void shouldHaveCallContext() {
+				var request = buildRequest();
+
+				assertThat(request.getCallContext()).isSameAs(grpcCallContext);
+			}
+
+			@Test
+			void shouldHaveRelationId() {
+				var request = buildRequest();
+
+				assertThat(request.getRelationId()).isEqualTo(CommandTestFactory.RELATION_ID);
+			}
+
+			@Test
+			void shouldHaveVorgangId() {
+				var request = buildRequest();
+
+				assertThat(request.getVorgangId()).isEqualTo(CommandTestFactory.VORGANG_ID);
+			}
+
+			@Test
+			void shouldCallBodyMapper() {
+				buildRequest();
+
+				verify(bodyMapper).fromObjectToMap(any(CommandBody.class));
+			}
+
+			@Test
+			void shouldCallObjectMapper() {
+				buildRequest();
+
+				verify(grpcObjectMapper).fromMap(any());
+			}
+
+			@Test
+			void shouldHaveVersion() {
+				var request = buildRequest();
+
+				assertThat(request.getRelationVersion()).isEqualTo(VorgangHeaderTestFactory.VERSION);
+			}
+
+			@Test
+			void shouldHaveRedirectRequest() {
+				var command = CommandTestFactory.createCreateCommandBuilder().body(null).redirectRequest(RedirectRequestTestFactory.create()).build();
+
+				var request = service.buildCreateCommandRequest(command);
+
+				assertThat(request.getRedirectRequest().getPassword()).isEqualTo(RedirectRequestTestFactory.PASSWORD_AS_STR);
+				assertThat(request.getRedirectRequest().getEmail()).isEqualTo(RedirectRequestTestFactory.EMAIL);
+			}
+
+			@ParameterizedTest
+			@EnumSource
+			void shouldHaveOrder(CommandOrder order) {
+				var request = service.buildCreateCommandRequest(CommandTestFactory.createCreateCommandBuilder().order(order).build());
+
+				assertThat(request.getOrderString()).isEqualTo(order.name());
+			}
+
+			private GrpcCreateCommandRequest buildRequest() {
+				return service.buildCreateCommandRequest(
+						CommandTestFactory.createCreateCommandBuilder().body(WiedervorlageTestFactory.create()).build());
+			}
+		}
+
+		@DisplayName("build forward request")
+		@Nested
+		class TestBuildForwardRequest {
+
+			@Test
+			void shouldNotSetPassword() {
+				var request = RedirectRequestTestFactory.createBuilder().password(ArrayUtils.EMPTY_CHAR_ARRAY).build();
+
+				var result = service.buildForwardRequest(request);
+
+				assertThat(result.getPassword()).isBlank();
+			}
+
+			@Test
+			void shouldSetPassword() {
+				var request = RedirectRequestTestFactory.createBuilder().password(RedirectRequestTestFactory.PASSWORD).build();
+
+				var result = service.buildForwardRequest(request);
+
+				assertThat(result.getPassword()).isEqualTo(RedirectRequestTestFactory.PASSWORD_AS_STR);
+			}
+
+			@Test
+			void shouldSetEmail() {
+				var request = RedirectRequestTestFactory.create();
+
+				var result = service.buildForwardRequest(request);
+
+				assertThat(result.getEmail()).isEqualTo(RedirectRequestTestFactory.EMAIL);
+			}
+		}
+	}
+
+	@Nested
+	@DisplayName("Revoke Command")
+	class TestRevokeCommand {
+
+		@Nested
+		class CreateRequest {
+
+			@BeforeEach
+			void initTest() {
+				when(contextService.createCallContext()).thenReturn(grpcCallContext);
+			}
+
+			@Test
+			void shouldReturnRequest() {
+				var request = service.createRevokeRequest(CommandTestFactory.ID);
+
+				assertThat(request).isNotNull().isInstanceOf(GrpcRevokeCommandRequest.class);
+			}
+
+			@Test
+			void shouldHaveCallContext() {
+				var request = service.createRevokeRequest(CommandTestFactory.ID);
+
+				assertThat(request.getContext()).isNotNull().isSameAs(grpcCallContext);
+				verify(contextService).createCallContext();
+			}
+
+			@Test
+			void shouldHaveCommandId() {
+				var request = service.createRevokeRequest(CommandTestFactory.ID);
+
+				assertThat(request.getId()).isEqualTo(CommandTestFactory.ID);
+			}
+		}
+
+		@Nested
+		class SendRequest {
+
+			private GrpcRevokeCommandRequest request = GrpcRevokeCommandRequest.newBuilder().build();
+
+			@BeforeEach
+			void initTest() {
+				doReturn(request).when(service).createRevokeRequest(anyString());
+				when(commandServiceStub.revokeCommand(any())).thenReturn(GrpcCommandResponse.newBuilder().build());
+			}
+
+			@Test
+			void shouldCreateRequest() {
+				service.revokeCommand(CommandTestFactory.ID);
+
+				verify(service).createRevokeRequest(CommandTestFactory.ID);
+			}
+
+			@Test
+			void shouldCallGrpc() {
+				service.revokeCommand(CommandTestFactory.ID);
+
+				verify(commandServiceStub).revokeCommand(request);
+			}
+
+			@Test
+			void shouldMapCommand() {
+				service.revokeCommand(CommandTestFactory.ID);
+
+				verify(mapper).toCommand(any());
+			}
+		}
+	}
+
+	@Nested
+	@DisplayName("Get command")
+	class TestGetCommand {
+
+		@Nested
+		class CreateRequest {
+
+			@BeforeEach
+			void initTest() {
+				when(contextService.createCallContext()).thenReturn(grpcCallContext);
+			}
+
+			@Test
+			void shouldReturnRequest() {
+				var request = service.createGetCommandRequest(CommandTestFactory.ID);
+
+				assertThat(request).isNotNull().isInstanceOf(GrpcGetCommandRequest.class);
+			}
+
+			@Test
+			void shouldHaveCallContext() {
+				var request = service.createGetCommandRequest(CommandTestFactory.ID);
+
+				assertThat(request.getContext()).isNotNull().isSameAs(grpcCallContext);
+				verify(contextService).createCallContext();
+			}
+
+			@Test
+			void shouldHaveCommandId() {
+				var request = service.createGetCommandRequest(CommandTestFactory.ID);
+
+				assertThat(request.getId()).isEqualTo(CommandTestFactory.ID);
+			}
+		}
+
+		@DisplayName("send request")
+		@Nested
+		class SendRequest {
+
+			private GrpcGetCommandRequest request = GrpcGetCommandRequest.newBuilder().build();
+
+			@BeforeEach
+			void initTest() {
+				doReturn(request).when(service).createGetCommandRequest(anyString());
+				when(commandServiceStub.getCommand(any())).thenReturn(GrpcCommand.newBuilder().build());
+			}
+
+			@Test
+			void shouldCreateRequest() {
+				service.getCommand(CommandTestFactory.ID);
+
+				verify(service).createGetCommandRequest(CommandTestFactory.ID);
+			}
+
+			@Test
+			void shouldCallGrpc() {
+				service.getCommand(CommandTestFactory.ID);
+
+				verify(commandServiceStub).getCommand(request);
+			}
+
+			@Test
+			void shouldMapCommand() {
+				service.getCommand(CommandTestFactory.ID);
+
+				verify(mapper).toCommand(any());
+			}
+		}
+	}
+
+	@Nested
+	class TestExistsPendingCommands {
+
+		private final GrpcExistsPendingCommandsRequest request = GrpcExistsPendingCommandsRequest.newBuilder()
+				.setContext(grpcCallContext)
+				.setVorgangId(VorgangHeaderTestFactory.ID).build();
+
+		@BeforeEach
+		void mockServiceStub() {
+			when(commandServiceStub.existsPendingCommands(any()))
+					.thenReturn(GrpcExistsPendingCommandsResponse.newBuilder().setExistsPendingCommands(true).build());
+
+			when(contextService.createCallContext()).thenReturn(grpcCallContext);
+		}
+
+		@Test
+		void shouldCallServiceStub() {
+			service.existsPendingCommands(VorgangHeaderTestFactory.ID);
+
+			verify(commandServiceStub).existsPendingCommands(request);
+		}
+
+		@Test
+		void shouldCallContextService() {
+			service.existsPendingCommands(VorgangHeaderTestFactory.ID);
+
+			verify(contextService).createCallContext();
+		}
+
+		@Test
+		void shouldReturnValue() {
+			var result = service.existsPendingCommands(VorgangHeaderTestFactory.ID);
+
+			assertThat(result).isTrue();
+		}
+	}
+
+	@Nested
+	class TestFindCommands {
+
+		private final GrpcCommand grpcCommand = GrpcCommand.newBuilder().build();
+		private final GrpcCommandsResponse response = GrpcCommandsResponse.newBuilder().addAllCommand(Collections.singleton(grpcCommand)).build();
+
+		@Captor // NOSONAR
+		private ArgumentCaptor<GrpcFindCommandsRequest> requestCaptor;
+
+		@BeforeEach
+		void init() {
+			when(commandServiceStub.findCommands(any())).thenReturn(response);
+			when(contextService.createCallContext()).thenReturn(grpcCallContext);
+		}
+
+		@Test
+		void shouldCallServiceStub() {
+			service.findCommands(VorgangHeaderTestFactory.ID, Optional.empty(), Optional.empty());
+
+			verify(commandServiceStub).findCommands(notNull());
+		}
+
+		@Test
+		void shouldRequestWithVorgangId() {
+			service.findCommands(VorgangHeaderTestFactory.ID, Optional.empty(), Optional.empty());
+
+			verify(commandServiceStub).findCommands(requestCaptor.capture());
+
+			assertThat(requestCaptor.getValue().getVorgangId()).isEqualTo(VorgangHeaderTestFactory.ID);
+		}
+
+		@Test
+		void shouldRequestWithStatus() {
+			service.findCommands(VorgangHeaderTestFactory.ID, Optional.of(CommandStatus.FINISHED), Optional.empty());
+
+			verify(commandServiceStub).findCommands(requestCaptor.capture());
+
+			assertThat(requestCaptor.getValue().getStatusList()).contains(CommandStatus.FINISHED.name());
+		}
+
+		@Test
+		void shouldRequestWithOrder() {
+			service.findCommands(VorgangHeaderTestFactory.ID, Optional.empty(), Optional.of(CommandOrder.REDIRECT_VORGANG));
+
+			verify(commandServiceStub).findCommands(requestCaptor.capture());
+
+			assertThat(requestCaptor.getValue().getOrder().name()).isEqualTo(CommandOrder.REDIRECT_VORGANG.name());
+		}
+
+		@Test
+		void shouldCallMapper() {
+			service.findCommands(VorgangHeaderTestFactory.ID, Optional.empty(), Optional.empty()).forEach(command -> {
+			});
+
+			verify(mapper).toCommand(grpcCommand);
+		}
+	}
+
+	@Nested
+	class TestGetPendingCommands {
+
+		private final GrpcGetPendingCommandsRequest request = GrpcGetPendingCommandsRequest.newBuilder()
+				.setContext(grpcCallContext)
+				.setVorgangId(VorgangHeaderTestFactory.ID).build();
+		private final GrpcCommand grpcCommand = GrpcCommand.newBuilder().build();
+		private final Set<GrpcCommand> commandList = Collections.singleton(grpcCommand);
+		private final GrpcGetPendingCommandsResponse response = GrpcGetPendingCommandsResponse.newBuilder().addAllCommand(commandList).build();
+		private final Command command = CommandTestFactory.create();
+
+		@BeforeEach
+		void mockServiceStub() {
+			when(commandServiceStub.getPendingCommands(any())).thenReturn(response);
+			when(contextService.createCallContext()).thenReturn(grpcCallContext);
+		}
+
+		@Test
+		void shouldCallServiceStub() {
+			service.getPendingCommands(VorgangHeaderTestFactory.ID);
+
+			verify(commandServiceStub).getPendingCommands(request);
+		}
+
+		@Test
+		void shouldCallContextService() {
+			service.getPendingCommands(VorgangHeaderTestFactory.ID);
+
+			verify(contextService).createCallContext();
+		}
+
+		@Test
+		void shouldCallMapper() {
+			var result = service.getPendingCommands(VorgangHeaderTestFactory.ID);
+			forceStreamOperationsDone(result);
+
+			verify(mapper).toCommand(grpcCommand);
+		}
+
+		@Test
+		void shouldReturnValue() {
+			when(mapper.toCommand(any())).thenReturn(command);
+
+			var result = service.getPendingCommands(VorgangHeaderTestFactory.ID);
+
+			assertThat(result.collect(Collectors.toList()).get(0)).isEqualTo(command);
+		}
+
+		private void forceStreamOperationsDone(Stream<Command> stream) {
+			stream.collect(Collectors.toList()).stream();
+		}
+	}
+}
\ No newline at end of file
diff --git a/goofy-server/src/test/java/de/itvsh/goofy/common/command/CommandServiceTest.java b/goofy-server/src/test/java/de/itvsh/goofy/common/command/CommandServiceTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..cbec114cd7a33ee409c9c272ffa909582aac69be
--- /dev/null
+++ b/goofy-server/src/test/java/de/itvsh/goofy/common/command/CommandServiceTest.java
@@ -0,0 +1,177 @@
+/*
+ * 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.itvsh.goofy.common.command;
+
+import static org.mockito.ArgumentMatchers.*;
+import static org.mockito.Mockito.*;
+
+import java.util.Optional;
+
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+
+import static org.assertj.core.api.Assertions.*;
+
+import de.itvsh.goofy.vorgang.VorgangHeaderTestFactory;
+
+class CommandServiceTest {
+
+	@InjectMocks
+	private CommandService service;
+	@Mock
+	private CommandRemoteService remoteService;
+
+	@Captor
+	private ArgumentCaptor<CreateCommand> createCommandCaptor;
+
+	@Nested
+	class TestCreateCommand {
+
+		private final CreateCommand command = CommandTestFactory.createCreateCommand();
+
+		@Nested
+		class TestWithCreateCommandOnly {
+
+			@Test
+			void shouldCallRemoteService() {
+				service.createCommand(command);
+
+				verify(remoteService).createCommand(any(CreateCommand.class));
+			}
+
+			@Test
+			void shouldHaveSetRelationVersion() {
+				service.createCommand(command);
+
+				verify(remoteService).createCommand(createCommandCaptor.capture());
+				assertThat(createCommandCaptor.getValue().getRelationVersion()).isEqualTo(CommandService.NO_RELATION_VERSION);
+			}
+		}
+
+		@Nested
+		class TestWithRelationVersion {
+
+			@Test
+			void shouldCallRemoteService() {
+				service.createCommand(command, CommandTestFactory.RELATION_VERSION);
+
+				verify(remoteService).createCommand(any(CreateCommand.class));
+			}
+
+			@Test
+			void shouldHaveSetRelationVersion() {
+				service.createCommand(command, CommandTestFactory.RELATION_VERSION);
+
+				verify(remoteService).createCommand(createCommandCaptor.capture());
+				assertThat(createCommandCaptor.getValue().getRelationVersion()).isEqualTo(CommandTestFactory.RELATION_VERSION);
+			}
+		}
+	}
+
+	@Nested
+	class TestCreateCommandWithItemName {
+
+		private final CreateCommand command = CommandTestFactory.createCreateCommand();
+
+		@Nested
+		class WithoutValidation {
+
+			@Test
+			void shouldCallRemoteService() {
+				service.createCommand(command, CommandTestFactory.RELATION_VERSION);
+
+				verify(remoteService).createCommand(any(CreateCommand.class));
+			}
+
+			@Test
+			void shouldHaveSetRelationVersion() {
+				service.createCommand(command, CommandTestFactory.RELATION_VERSION);
+
+				verify(remoteService).createCommand(createCommandCaptor.capture());
+				assertThat(createCommandCaptor.getValue().getRelationVersion()).isEqualTo(CommandTestFactory.RELATION_VERSION);
+			}
+		}
+	}
+
+	@Nested
+	class TestGetById {
+
+		@Test
+		void shouldCallRemoteService() {
+			service.getById(CommandTestFactory.RELATION_ID);
+
+			verify(remoteService).getCommand(any());
+		}
+	}
+
+	@Nested
+	class TestRevoke {
+
+		@Test
+		void shouldCallRemoteService() {
+			service.revoke(CommandTestFactory.RELATION_ID);
+
+			verify(remoteService).revokeCommand(CommandTestFactory.RELATION_ID);
+		}
+	}
+
+	@Nested
+	class TestExistsPendingCommands {
+
+		@Test
+		void shouldCallRemoteService() {
+			service.existsPendingCommands(VorgangHeaderTestFactory.ID);
+
+			verify(remoteService).existsPendingCommands(VorgangHeaderTestFactory.ID);
+		}
+	}
+
+	@Nested
+	class TestGetPendingCommands {
+
+		@Test
+		void shouldCallRemoteService() {
+			service.getPendingCommands(VorgangHeaderTestFactory.ID);
+
+			verify(remoteService).getPendingCommands(VorgangHeaderTestFactory.ID);
+		}
+	}
+
+	@DisplayName("Find finished commands")
+	@Nested
+	class TestFindFinishedCommands {
+
+		@Test
+		void shouldCallRemoteService() {
+			service.findFinishedCommands(VorgangHeaderTestFactory.ID);
+
+			verify(remoteService).findCommands(VorgangHeaderTestFactory.ID, Optional.of(CommandStatus.FINISHED), Optional.empty());
+		}
+	}
+}
\ No newline at end of file
diff --git a/goofy-server/src/test/java/de/itvsh/goofy/common/command/CommandTestFactory.java b/goofy-server/src/test/java/de/itvsh/goofy/common/command/CommandTestFactory.java
new file mode 100644
index 0000000000000000000000000000000000000000..d77c2ec82c55a316cf20cfcb744a7179386a762b
--- /dev/null
+++ b/goofy-server/src/test/java/de/itvsh/goofy/common/command/CommandTestFactory.java
@@ -0,0 +1,67 @@
+/*
+ * 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.itvsh.goofy.common.command;
+
+import java.util.UUID;
+
+import de.itvsh.goofy.common.user.UserProfileTestFactory;
+import de.itvsh.goofy.vorgang.VorgangHeaderTestFactory;
+import de.itvsh.goofy.vorgang.forwarding.ForwardingTestFactory;
+
+public class CommandTestFactory {
+
+	public static final String ID = UUID.randomUUID().toString();
+	public static final String VORGANG_ID = VorgangHeaderTestFactory.ID;
+	public static final CommandStatus STATUS = CommandStatus.FINISHED;
+	public static final CommandOrder ORDER = CommandOrder.VORGANG_ANNEHMEN;
+
+	public static final String RELATION_ID = ForwardingTestFactory.ID;
+	public static final long RELATION_VERSION = VorgangHeaderTestFactory.VERSION;
+
+	public static Command create() {
+		return createBuilder().build();
+	}
+
+	public static Command.CommandBuilder createBuilder() {
+		return Command.builder()
+				.id(ID)
+				.vorgangId(VORGANG_ID)
+				.relationId(RELATION_ID)
+				.status(STATUS)
+				.order(ORDER)
+				.createdBy(UserProfileTestFactory.ID);
+	}
+
+	public static CreateCommand createCreateCommand() {
+		return createCreateCommandBuilder().build();
+	}
+
+	public static CreateCommand.CreateCommandBuilder createCreateCommandBuilder() {
+		return CreateCommand.builder()
+				.vorgangId(VORGANG_ID)
+				.relationId(RELATION_ID)
+				.relationVersion(RELATION_VERSION)
+				.order(ORDER);
+	}
+}
\ No newline at end of file
diff --git a/goofy-server/src/test/java/de/itvsh/goofy/common/command/GrpcCommandResponseTestFactory.java b/goofy-server/src/test/java/de/itvsh/goofy/common/command/GrpcCommandResponseTestFactory.java
new file mode 100644
index 0000000000000000000000000000000000000000..0fac6a84fdb88d6d35adbd421a8557017cf01aa5
--- /dev/null
+++ b/goofy-server/src/test/java/de/itvsh/goofy/common/command/GrpcCommandResponseTestFactory.java
@@ -0,0 +1,49 @@
+/*
+ * 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.itvsh.goofy.common.command;
+
+import de.itvsh.ozg.pluto.grpc.command.GrpcCommand;
+import de.itvsh.ozg.pluto.grpc.command.GrpcCommandResponse;
+
+public class GrpcCommandResponseTestFactory {
+
+	public static final String COMMAND_ID = CommandTestFactory.ID;
+	public static final CommandStatus COMMAND_STATUS = CommandStatus.PENDING;
+
+	public static GrpcCommandResponse create() {
+		return createBuilder().build();
+	}
+
+	public static GrpcCommandResponse.Builder createBuilder() {
+		return GrpcCommandResponse.newBuilder()
+				.setCommand(createGrpcCommand());
+	}
+
+	public static GrpcCommand createGrpcCommand() {
+		return GrpcCommand.newBuilder()
+				.setId(COMMAND_ID)
+				.setStatus(COMMAND_STATUS.name())
+				.build();
+	}
+}
diff --git a/goofy-server/src/test/java/de/itvsh/goofy/common/command/GrpcCommandTestFactory.java b/goofy-server/src/test/java/de/itvsh/goofy/common/command/GrpcCommandTestFactory.java
new file mode 100644
index 0000000000000000000000000000000000000000..04cc987a767a5b759b79599664c6af33a89dc208
--- /dev/null
+++ b/goofy-server/src/test/java/de/itvsh/goofy/common/command/GrpcCommandTestFactory.java
@@ -0,0 +1,62 @@
+/*
+ * 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.itvsh.goofy.common.command;
+
+import java.util.UUID;
+
+import de.itvsh.goofy.common.GrpcCallContextTestFactory;
+import de.itvsh.ozg.pluto.grpc.command.GrpcCallContext;
+import de.itvsh.ozg.pluto.grpc.command.GrpcCommand;
+import de.itvsh.ozg.pluto.grpc.command.GrpcCreateCommandRequest;
+import de.itvsh.ozg.pluto.grpc.command.GrpcOrder;
+
+public class GrpcCommandTestFactory {
+
+	private static final String RELATION_ID = UUID.randomUUID().toString();
+	private static final CommandStatus STATUS = CommandStatus.PENDING;
+	private static final GrpcOrder ORDER = GrpcOrder.RESEND_POSTFACH_MAIL;
+	private static final GrpcCallContext CALL_CONTEXT = GrpcCallContextTestFactory.create();
+
+	public static GrpcCommand create() {
+		return createBuilder().build();
+	}
+
+	public static GrpcCommand.Builder createBuilder() {
+		return GrpcCommand.newBuilder()
+				.setStatus(STATUS.name())
+				.setRelationId(RELATION_ID)
+				.setOrder(ORDER);
+	}
+
+	public static GrpcCreateCommandRequest createCommandRequest() {
+		return createCommandRequestBuilder().build();
+	}
+
+	public static GrpcCreateCommandRequest.Builder createCommandRequestBuilder() {
+		return GrpcCreateCommandRequest.newBuilder()
+				.setCallContext(CALL_CONTEXT)
+				.setRelationId(RELATION_ID)
+				.setOrder(ORDER);
+	}
+}
\ No newline at end of file
diff --git a/goofy-server/src/test/java/de/itvsh/goofy/common/command/GrpcPostfachCommandTestFactory.java b/goofy-server/src/test/java/de/itvsh/goofy/common/command/GrpcPostfachCommandTestFactory.java
new file mode 100644
index 0000000000000000000000000000000000000000..301137a91ab3db22039888261bc86f9fe61a4ddc
--- /dev/null
+++ b/goofy-server/src/test/java/de/itvsh/goofy/common/command/GrpcPostfachCommandTestFactory.java
@@ -0,0 +1,47 @@
+/*
+ * 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.itvsh.goofy.common.command;
+
+import de.itvsh.goofy.common.GrpcCallContextTestFactory;
+import de.itvsh.goofy.postfach.PostfachMailTestFactory;
+import de.itvsh.ozg.mail.postfach.GrpcResendPostfachMailRequest;
+import de.itvsh.ozg.pluto.grpc.command.GrpcCallContext;
+
+public class GrpcPostfachCommandTestFactory {
+
+	private static final String POSTFACH_ID = PostfachMailTestFactory.ID;
+	private static final String COMMAND_ID = CommandTestFactory.ID;
+	private static final GrpcCallContext CALL_CONTEXT = GrpcCallContextTestFactory.create();
+
+	public static GrpcResendPostfachMailRequest createResendPostfachMailRequest() {
+		return createResendPostfachMailRequestBuilder().build();
+	}
+
+	public static GrpcResendPostfachMailRequest.Builder createResendPostfachMailRequestBuilder() {
+		return GrpcResendPostfachMailRequest.newBuilder()
+				.setContext(CALL_CONTEXT)
+				.setPostfachMailId(POSTFACH_ID)
+				.setCommandId(COMMAND_ID);
+	}
+}
\ No newline at end of file
diff --git a/goofy-server/src/test/java/de/itvsh/goofy/common/downloadtoken/DownloadTokenAuthenticationFilterITCase.java b/goofy-server/src/test/java/de/itvsh/goofy/common/downloadtoken/DownloadTokenAuthenticationFilterITCase.java
new file mode 100644
index 0000000000000000000000000000000000000000..0365dea20587353016cbbaf3764a9f38907a936d
--- /dev/null
+++ b/goofy-server/src/test/java/de/itvsh/goofy/common/downloadtoken/DownloadTokenAuthenticationFilterITCase.java
@@ -0,0 +1,93 @@
+/*
+ * 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.itvsh.goofy.common.downloadtoken;
+
+import static org.mockito.ArgumentMatchers.*;
+import static org.mockito.Mockito.*;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
+
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.boot.test.mock.mockito.SpyBean;
+import org.springframework.security.test.context.support.WithMockUser;
+import org.springframework.test.web.servlet.MockMvc;
+import org.springframework.test.web.servlet.ResultActions;
+
+@SpringBootTest
+@AutoConfigureMockMvc
+@WithMockUser
+class DownloadTokenAuthenticationFilterITCase {
+
+	@SpyBean
+	private DownloadTokenAuthenticationFilter filter;
+
+	@Autowired
+	private MockMvc mockMvc;
+
+	@Autowired
+	private DownloadTokenProperties downloadTokenProperties;
+
+	@Test
+	void shouldNotCallFilter() throws Exception {
+		performRequest(DownloadTokenController.DOWNLOAD_TOKEN_PATH).andExpect(status().isBadRequest());
+
+		verify(filter, never()).doFilterInternal(any(), any(), any());
+	}
+
+	@Test
+	void shouldCallFilter() throws Exception {
+		String token = DownloadTokenTestFactory.createToken(downloadTokenProperties.getSecret(), downloadTokenProperties.getValidity());
+
+		performRequest(DownloadTokenController.DOWNLOAD_TOKEN_PATH + "?" + DownloadTokenController.PARAM_TOKEN + "=" + token)
+				.andExpect(status().isOk());
+
+		verify(filter).doFilterInternal(any(), any(), any());
+	}
+
+	@Test
+	void shouldCallFilterWhenNoOrganisationseinheitIds() throws Exception {
+		String token = DownloadTokenTestFactory.createTokenBuilder(downloadTokenProperties.getSecret(), downloadTokenProperties.getValidity())
+				.setClaims(DownloadTokenTestFactory.CLAIMS_WITHOUT_ORGANSIATIONSEINHEIT_IDS).compact();
+
+		performRequest(DownloadTokenController.DOWNLOAD_TOKEN_PATH + "?" + DownloadTokenController.PARAM_TOKEN + "=" + token)
+				.andExpect(status().isOk());
+
+		verify(filter).doFilterInternal(any(), any(), any());
+	}
+
+	@Test
+	void shouldReturnUnauthorised() throws Exception {
+		String token = DownloadTokenTestFactory.createToken("badSecret", downloadTokenProperties.getValidity());
+
+		performRequest(DownloadTokenController.DOWNLOAD_TOKEN_PATH + "?" + DownloadTokenController.PARAM_TOKEN + "=" + token)
+				.andExpect(status().isUnauthorized());
+	}
+
+	ResultActions performRequest(String path) throws Exception {
+		return mockMvc.perform(get(path));
+	}
+}
\ No newline at end of file
diff --git a/goofy-server/src/test/java/de/itvsh/goofy/common/downloadtoken/DownloadTokenAuthenticationFilterTest.java b/goofy-server/src/test/java/de/itvsh/goofy/common/downloadtoken/DownloadTokenAuthenticationFilterTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..f1d33d7a6fe127785885610691c7680b417befd5
--- /dev/null
+++ b/goofy-server/src/test/java/de/itvsh/goofy/common/downloadtoken/DownloadTokenAuthenticationFilterTest.java
@@ -0,0 +1,84 @@
+/*
+ * 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.itvsh.goofy.common.downloadtoken;
+
+import static org.mockito.ArgumentMatchers.*;
+import static org.mockito.Mockito.*;
+
+import java.io.IOException;
+import java.util.UUID;
+
+import javax.servlet.FilterChain;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+
+class DownloadTokenAuthenticationFilterTest {
+
+	@InjectMocks
+	private DownloadTokenAuthenticationFilter filter;
+	@Mock
+	private FilterChain filterChain;
+	@Mock
+	private HttpServletRequest request;
+	@Mock
+	private HttpServletResponse response;
+	@Mock
+	private DownloadTokenService downloadTokenService;
+
+	@Test
+	void shouldCallDoFilter() throws IOException, ServletException {
+		doFilterInternal();
+
+		verify(filterChain).doFilter(request, response);
+	}
+
+	@Nested
+	class TestWithTokenParamter {
+
+		final String TOKEN = UUID.randomUUID().toString();
+
+		@BeforeEach
+		void mockRequestParameter() {
+			when(request.getParameter(anyString())).thenReturn(TOKEN);
+		}
+
+		@Test
+		void shouldVerifyTokenIfPresent() throws Exception {
+			doFilterInternal();
+
+			verify(downloadTokenService).handleToken(request, TOKEN);
+		}
+	}
+
+	private void doFilterInternal() throws ServletException, IOException {
+		filter.doFilterInternal(request, response, filterChain);
+	}
+}
\ No newline at end of file
diff --git a/goofy-server/src/test/java/de/itvsh/goofy/common/downloadtoken/DownloadTokenControllerTest.java b/goofy-server/src/test/java/de/itvsh/goofy/common/downloadtoken/DownloadTokenControllerTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..d03b7d35726665147e4a790af2e6dfc561306684
--- /dev/null
+++ b/goofy-server/src/test/java/de/itvsh/goofy/common/downloadtoken/DownloadTokenControllerTest.java
@@ -0,0 +1,100 @@
+/*
+ * 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.itvsh.goofy.common.downloadtoken;
+
+import static org.mockito.ArgumentMatchers.*;
+import static org.mockito.Mockito.*;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
+
+import java.util.UUID;
+
+import org.apache.commons.lang3.StringUtils;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.springframework.http.MediaType;
+import org.springframework.test.web.servlet.MockMvc;
+import org.springframework.test.web.servlet.ResultActions;
+import org.springframework.test.web.servlet.setup.MockMvcBuilders;
+
+import de.itvsh.goofy.common.binaryfile.BinaryFileTestFactory;
+import de.itvsh.kop.common.test.TestUtils;
+
+class DownloadTokenControllerTest {
+
+	@InjectMocks // NOSONAR
+	private DownloadTokenController controller;
+	@Mock
+	private DownloadTokenService service;
+
+	private MockMvc mockMvc;
+
+	@BeforeEach
+	void initTest() {
+		mockMvc = MockMvcBuilders.standaloneSetup(controller).build();
+	}
+
+	final static String TOKEN = UUID.randomUUID().toString();
+
+	@BeforeEach
+	void mock() {
+		when(service.createToken(any())).thenReturn(TOKEN);
+	}
+
+	@Test
+	void shouldGenerateToken() throws Exception {
+		callEndpoint(createRequest(BinaryFileTestFactory.ID));
+
+		verify(service).createToken(any());
+	}
+
+	@Test
+	void shouldGenerateTokenWhenNoFileId() throws Exception {
+		callEndpoint(createRequest(StringUtils.EMPTY));
+
+		verify(service).createToken(any());
+	}
+
+	@Test
+	void shouldHaveReponse() throws Exception {
+		callEndpoint(createRequest(BinaryFileTestFactory.ID))
+				.andExpect(header().string("Location", "http://localhost" + DownloadTokenController.DOWNLOAD_TOKEN_PATH + "?"
+						+ DownloadTokenController.PARAM_TOKEN + "=" + TOKEN));
+	}
+
+	private ResultActions callEndpoint(String content) throws Exception {
+		return mockMvc.perform(
+				post(DownloadTokenController.DOWNLOAD_TOKEN_PATH)
+						.contentType(MediaType.APPLICATION_JSON)
+						.content(content))
+				.andExpect(status().isCreated());
+	}
+
+	private String createRequest(String fileId) {
+		return TestUtils.loadTextFile("jsonTemplates/downloadTokenRequest.json.tmpl", fileId);
+	}
+
+}
\ No newline at end of file
diff --git a/goofy-server/src/test/java/de/itvsh/goofy/common/downloadtoken/DownloadTokenServiceTest.java b/goofy-server/src/test/java/de/itvsh/goofy/common/downloadtoken/DownloadTokenServiceTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..bf9d88ebc233f3c1a16d8602a443717ba3ca3069
--- /dev/null
+++ b/goofy-server/src/test/java/de/itvsh/goofy/common/downloadtoken/DownloadTokenServiceTest.java
@@ -0,0 +1,167 @@
+/*
+ * 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.itvsh.goofy.common.downloadtoken;
+
+import static de.itvsh.goofy.JwtTokenUtil.*;
+import static org.assertj.core.api.Assertions.*;
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.ArgumentMatchers.*;
+import static org.mockito.Mockito.*;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Optional;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+
+import com.auth0.jwt.exceptions.JWTVerificationException;
+
+import de.itvsh.goofy.JwtTokenUtil;
+import de.itvsh.goofy.common.binaryfile.FileId;
+import de.itvsh.goofy.common.user.CurrentUserService;
+import de.itvsh.goofy.common.user.UserProfile;
+import de.itvsh.goofy.common.user.UserProfileTestFactory;
+import de.itvsh.kop.common.errorhandling.TechnicalException;
+import io.jsonwebtoken.Claims;
+
+class DownloadTokenServiceTest {
+
+	@InjectMocks
+	private DownloadTokenService service;
+	@Mock
+	private CurrentUserService userService;
+	@Mock
+	private JwtTokenUtil jwtTokenUtil;
+	@Mock
+	private HttpServletRequest request;
+	@Mock
+	private HttpServletResponse response;
+
+	private final static String FAKE_TOKEN = "xxx";
+	private final static Collection<String> ORGE_IDS = List.of("258994");
+
+	@Nested
+	class TestCreateToken {
+
+		final FileId fileId = FileId.createNew();
+
+		final UserProfile user = UserProfileTestFactory.create();
+
+		@BeforeEach
+		void mockUserService() {
+			when(userService.getUser()).thenReturn(user);
+		}
+
+		@Test
+		void shouldCallUserService() {
+			service.createToken(fileId);
+
+			verify(userService).getUser();
+		}
+
+		@Test
+		void shouldCallJwtTokenUtil() {
+			service.createToken(fileId);
+
+			verify(jwtTokenUtil).generateToken(fileId, user);
+		}
+	}
+
+	@Nested
+	class TestHandleToken {
+
+		@Test
+		void shouldVerifyTokenIfPresent() throws Exception {
+			mockClaims(FIRSTNAME_CLAIM, LASTNAME_CLAIM);
+
+			handleToken();
+
+			verify(jwtTokenUtil).verifyToken(FAKE_TOKEN);
+		}
+
+		@Test
+		void shouldGetUserFromTokenWithoutUserFirstname() {
+			mockClaims(null, FIRSTNAME_CLAIM);
+
+			UserProfile user = service.getUserFromToken(FAKE_TOKEN).getUser();
+
+			assertThat(user).isNotNull();
+		}
+
+		@Test
+		void shouldGetUserFromTokenWithoutUserLastname() {
+			mockClaims(FIRSTNAME_CLAIM, null);
+
+			UserProfile user = service.getUserFromToken(FAKE_TOKEN).getUser();
+
+			assertThat(user).isNotNull();
+		}
+
+		@Test
+		void shouldGetOrganisationseinheitIdsFromToken() {
+			mockClaims(FIRSTNAME_CLAIM, LASTNAME_CLAIM);
+
+			UserProfile user = service.getUserFromToken(FAKE_TOKEN).getUser();
+
+			assertThat(user.getOrganisationseinheitIds()).isEqualTo(ORGE_IDS);
+		}
+
+		private void mockClaims(String firstnameClaim, String lastnameClaim) {
+			Claims claims = mock(Claims.class);
+
+			when(claims.get(FIRSTNAME_CLAIM, String.class)).thenReturn(firstnameClaim);
+			when(claims.get(LASTNAME_CLAIM, String.class)).thenReturn(lastnameClaim);
+			when(claims.get(USERID_CLAIM, String.class)).thenReturn(UserProfileTestFactory.ID.toString());
+
+			when(jwtTokenUtil.getOrganisationseinheitIdsFromToken(any())).thenReturn(ORGE_IDS);
+			when(jwtTokenUtil.getAllClaimsFromToken(any())).thenReturn(Optional.of(claims));
+		}
+
+		@Test
+		void shouldThrowExceptionOnInvalidToken() throws Exception {
+			doThrow(JWTVerificationException.class).when(jwtTokenUtil).verifyToken(anyString());
+
+			assertThrows(TechnicalException.class, () -> handleToken());
+		}
+
+		@Test
+		void shouldNotFilterOnInvalidToken() throws Exception {
+			doThrow(JWTVerificationException.class).when(jwtTokenUtil).verifyToken(anyString());
+
+			assertThrows(TechnicalException.class, () -> handleToken());
+		}
+
+	}
+
+	void handleToken() {
+		service.handleToken(request, FAKE_TOKEN);
+	}
+}
\ No newline at end of file
diff --git a/goofy-server/src/test/java/de/itvsh/goofy/common/downloadtoken/DownloadTokenTestFactory.java b/goofy-server/src/test/java/de/itvsh/goofy/common/downloadtoken/DownloadTokenTestFactory.java
new file mode 100644
index 0000000000000000000000000000000000000000..a359d826e46fa38969410c469a5f3cd5278ff863
--- /dev/null
+++ b/goofy-server/src/test/java/de/itvsh/goofy/common/downloadtoken/DownloadTokenTestFactory.java
@@ -0,0 +1,72 @@
+/*
+ * 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.itvsh.goofy.common.downloadtoken;
+
+import static de.itvsh.goofy.JwtTokenUtil.*;
+
+import java.util.Collection;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import de.itvsh.goofy.common.user.UserProfileTestFactory;
+import io.jsonwebtoken.JwtBuilder;
+import io.jsonwebtoken.Jwts;
+import io.jsonwebtoken.SignatureAlgorithm;
+
+public class DownloadTokenTestFactory {
+	static final String TYP = "typ";
+	static final String SUBJECT = "subject";
+	static final String FIRSTNAME_CLAIM_VALUE = UserProfileTestFactory.FIRSTNAME;
+	static final String LASTNAME_CLAIM_VALUE = UserProfileTestFactory.LASTNAME;
+	final static Collection<String> ORGE_IDS = List.of("258994");
+	static final List<?> ROLE_CLAIM_VALUE = List.of();
+	static final long VALIDITY = 5000;
+	static final Map<String, Object> CLAIMS = new HashMap<>(Map.of(
+			FIRSTNAME_CLAIM, FIRSTNAME_CLAIM_VALUE,
+			LASTNAME_CLAIM, LASTNAME_CLAIM_VALUE,
+			ROLE_CLAIM, ROLE_CLAIM_VALUE,
+			ORGANSIATIONSEINHEIT_IDS_CLAIM, ORGE_IDS));
+	static final Map<String, Object> CLAIMS_WITHOUT_ORGANSIATIONSEINHEIT_IDS = new HashMap<>(Map.of(
+			FIRSTNAME_CLAIM, FIRSTNAME_CLAIM_VALUE,
+			LASTNAME_CLAIM, LASTNAME_CLAIM_VALUE,
+			ROLE_CLAIM, ROLE_CLAIM_VALUE));
+
+	public static String createToken(String secret, long validity) {
+		return createTokenBuilder(secret, validity).compact();
+	}
+
+	public static JwtBuilder createTokenBuilder(String secret, long validity) {
+		return Jwts.builder()
+				.setClaims(CLAIMS)
+				.setSubject(SUBJECT)
+				.setHeaderParam(TYP, TOKEN_TYPE)
+				.setIssuer(TOKEN_ISSUER).setIssuedAt(new Date(System.currentTimeMillis()))
+				.setExpiration(new Date(System.currentTimeMillis() + validity))
+				.setAudience(TOKEN_AUDIENCE)
+				.signWith(SignatureAlgorithm.HS512, secret.getBytes());
+	}
+
+}
diff --git a/goofy-server/src/test/java/de/itvsh/goofy/common/errorhandling/ExceptionControllerTest.java b/goofy-server/src/test/java/de/itvsh/goofy/common/errorhandling/ExceptionControllerTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..43a942e5cb3b91fe34af064d0157411d759d17d7
--- /dev/null
+++ b/goofy-server/src/test/java/de/itvsh/goofy/common/errorhandling/ExceptionControllerTest.java
@@ -0,0 +1,341 @@
+/*
+ * 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.itvsh.goofy.common.errorhandling;
+
+import static org.assertj.core.api.Assertions.*;
+import static org.mockito.Mockito.*;
+
+import java.util.Collections;
+import java.util.Map;
+
+import javax.validation.ConstraintViolationException;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.mockito.InjectMocks;
+import org.mockito.Spy;
+import org.springframework.security.access.AccessDeniedException;
+
+import de.itvsh.goofy.common.binaryfile.DynamicViolationParameter;
+import de.itvsh.kop.common.errorhandling.TechnicalException;
+
+class ExceptionControllerTest {
+
+	@Spy
+	@InjectMocks
+	private ExceptionController exceptionController;
+
+	@Nested
+	class TestHandleFunctionalException {
+
+		private final FunctionalException exception = new FunctionalException(() -> ExceptionTestFactory.MESSAGE_CODE);
+
+		@Test
+		void shouldHaveMessageCode() {
+			var error = handleException();
+
+			assertThat(error.getIssues()).hasSize(1);
+			assertThat(error.getIssues().get(0).getMessageCode()).isEqualTo(ExceptionTestFactory.MESSAGE_CODE);
+		}
+
+		@Test
+		void shouldHaveMessage() {
+			var error = handleException();
+
+			assertThat(error.getIssues()).hasSize(1);
+			assertThat(error.getIssues().get(0).getMessage()).isEqualTo(exception.getMessage());
+			assertThat(error.getIssues().get(0).getMessage()).contains(exception.getExceptionId());
+		}
+
+		@Test
+		void shouldHaveExceptionId() {
+			var error = handleException();
+
+			assertThat(error.getIssues()).hasSize(1);
+			assertThat(error.getIssues().get(0).getExceptionId()).isEqualTo(exception.getExceptionId());
+		}
+
+		private ApiError handleException() {
+			return exceptionController.handleFunctionalException(exception);
+		}
+	}
+
+	@Nested
+	class TestHandleAccessDeniedException {
+
+		private final AccessDeniedException exception = new AccessDeniedException(ExceptionTestFactory.MESSAGE);
+
+		@BeforeEach
+		void mockExceptionId() {
+			doReturn(ExceptionTestFactory.EXCEPTION_ID).when(exceptionController).createExceptionId();
+		}
+
+		@Test
+		void shouldHaveMessageCode() {
+			var error = handleException();
+
+			assertThat(error.getIssues()).hasSize(1);
+			assertThat(error.getIssues().get(0).getMessageCode()).isEqualTo(ExceptionController.ACCESS_DENIED_MESSAGE_CODE);
+		}
+
+		@Test
+		void shouldHaveMessage() {
+			var error = handleException();
+
+			assertThat(error.getIssues()).hasSize(1);
+			assertThat(error.getIssues().get(0).getMessage()).contains(exception.getMessage());
+			assertThat(error.getIssues().get(0).getMessage()).contains(ExceptionTestFactory.EXCEPTION_ID);
+		}
+
+		@Test
+		void shouldHaveExceptionId() {
+			var error = handleException();
+
+			assertThat(error.getIssues()).hasSize(1);
+			assertThat(error.getIssues().get(0).getExceptionId()).isEqualTo(ExceptionTestFactory.EXCEPTION_ID);
+		}
+
+		private ApiError handleException() {
+			return exceptionController.handleAccessDeniedException(exception);
+		}
+	}
+
+	@Nested
+	class TestHandleResourceNotFoundException {
+
+		private final ResourceNotFoundException exception = new ResourceNotFoundException(String.class, 42L);
+
+		@Test
+		void shouldHaveMessageCode() {
+			var error = handleException();
+
+			assertThat(error.getIssues()).hasSize(1);
+			assertThat(error.getIssues().get(0).getMessageCode()).isEqualTo(ExceptionController.RESOURCE_NOT_FOUNT_MESSAGE_CODE);
+		}
+
+		@Test
+		void shouldHaveMessage() {
+			var error = handleException();
+
+			assertThat(error.getIssues()).hasSize(1);
+			assertThat(error.getIssues().get(0).getMessage()).isEqualTo(exception.getMessage());
+			assertThat(error.getIssues().get(0).getMessage()).contains(exception.getExceptionId());
+		}
+
+		@Test
+		void shouldHaveExceptionId() {
+			var error = handleException();
+
+			assertThat(error.getIssues()).hasSize(1);
+			assertThat(error.getIssues().get(0).getExceptionId()).isEqualTo(exception.getExceptionId());
+		}
+
+		private ApiError handleException() {
+			return exceptionController.handleResourceNotFoundException(exception);
+		}
+	}
+
+	@Nested
+	class TestContraintValidationException {
+
+		private final ConstraintViolationException exception = new ConstraintViolationException(ExceptionTestFactory.MESSAGE,
+				Collections.singleton(ExceptionTestFactory.buildMockedConstraintViolation()));
+
+		@BeforeEach
+		void mockExceptionId() {
+			doReturn(ExceptionTestFactory.EXCEPTION_ID).when(exceptionController).createExceptionId();
+		}
+
+		@Test
+		void shouldHaveField() {
+			var error = handleException();
+
+			assertThat(error.getIssues()).hasSize(1);
+			assertThat(error.getIssues().get(0).getField()).isEqualTo(ExceptionTestFactory.PATH_FIELD);
+		}
+
+		@Test
+		void shouldHaveMessageCode() {
+			var error = handleException();
+
+			assertThat(error.getIssues()).hasSize(1);
+			assertThat(error.getIssues().get(0).getMessageCode()).isEqualTo(ExceptionTestFactory.MESSAGE_CODE);
+		}
+
+		@Test
+		void shouldHaveMessage() {
+			var error = handleException();
+
+			assertThat(error.getIssues()).hasSize(1);
+			assertThat(error.getIssues().get(0).getMessage()).isEqualTo(ExceptionTestFactory.MESSAGE);
+		}
+
+		@Test
+		void shouldHaveExceptionId() {
+			var error = handleException();
+
+			assertThat(error.getIssues().get(0).getExceptionId()).isEqualTo(ExceptionTestFactory.EXCEPTION_ID);
+		}
+
+		@Test
+		void shouldHaveIssueParameter() {
+			var error = handleException();
+
+			var issueParameter = error.getIssues().get(0).getParameters().get(0);
+			assertThat(issueParameter.getName()).isEqualTo(ExceptionTestFactory.PARAM_NAME);
+			assertThat(issueParameter.getValue()).isEqualTo(ExceptionTestFactory.PARAM_DYNAMIC_VALUE);
+		}
+
+		@Nested
+		class TestWithDynamicPayload {
+
+			private final DynamicViolationParameter dynamicViolationParameter = DynamicViolationParameter.builder()
+					.map(Map.of(ExceptionTestFactory.PARAM_NAME, ExceptionTestFactory.PARAM_DYNAMIC_VALUE)).build();
+			private final ConstraintViolationException exceptionWithDynamicPayload = new ConstraintViolationException(ExceptionTestFactory.MESSAGE,
+					Collections.singleton(ExceptionTestFactory.buildMockedConstraintViolationWithDynamicPayload(dynamicViolationParameter)));
+
+			@Test
+			void shouldHaveReplacedParams() {
+				var error = handleExceptionWithDynamicPayload();
+
+				var issueParameter = error.getIssues().get(0).getParameters().get(0);
+				assertThat(issueParameter.getName()).isEqualTo(ExceptionTestFactory.PARAM_NAME);
+				assertThat(issueParameter.getValue()).isEqualTo(ExceptionTestFactory.PARAM_DYNAMIC_VALUE);
+			}
+
+			private ApiError handleExceptionWithDynamicPayload() {
+				return exceptionController.handleConstraintViolationException(exceptionWithDynamicPayload);
+			}
+		}
+
+		private ApiError handleException() {
+			return exceptionController.handleConstraintViolationException(exception);
+		}
+	}
+
+	@Nested
+	class TestHandleTechnicalException {
+
+		private final TechnicalException exception = new TechnicalException(ExceptionTestFactory.MESSAGE, new Throwable());
+
+		@Test
+		void shouldHaveMessageCode() {
+			var error = handleException();
+
+			assertThat(error.getIssues()).hasSize(1);
+			assertThat(error.getIssues().get(0).getMessageCode()).isEqualTo(ExceptionController.RUNTIME_MESSAGE_CODE);
+		}
+
+		@Test
+		void shouldHaveMessage() {
+			var error = handleException();
+
+			assertThat(error.getIssues()).hasSize(1);
+			assertThat(error.getIssues().get(0).getMessage()).isEqualTo(exception.getMessage());
+			assertThat(error.getIssues().get(0).getMessage()).contains(exception.getExceptionId());
+		}
+
+		@Test
+		void shouldHaveExceptionId() {
+			var error = handleException();
+
+			assertThat(error.getIssues()).hasSize(1);
+			assertThat(error.getIssues().get(0).getExceptionId()).isEqualTo(exception.getExceptionId());
+		}
+
+		private ApiError handleException() {
+			return exceptionController.handleTechnicalException(exception);
+		}
+	}
+
+	@Nested
+	class TestHandleRuntimeException {
+
+		private final RuntimeException exception = new RuntimeException(ExceptionTestFactory.MESSAGE);
+
+		@BeforeEach
+		void mockExceptionId() {
+			doReturn(ExceptionTestFactory.EXCEPTION_ID).when(exceptionController).createExceptionId();
+		}
+
+		@Test
+		void shouldHaveMessageCode() {
+			var error = handleException();
+
+			assertThat(error.getIssues()).hasSize(1);
+			assertThat(error.getIssues().get(0).getMessageCode()).isEqualTo(ExceptionController.RUNTIME_MESSAGE_CODE);
+		}
+
+		@Test
+		void shouldHaveMessage() {
+			var error = handleException();
+
+			assertThat(error.getIssues()).hasSize(1);
+			assertThat(error.getIssues().get(0).getMessage()).contains(exception.getMessage());
+			assertThat(error.getIssues().get(0).getMessage()).contains(ExceptionTestFactory.EXCEPTION_ID);
+		}
+
+		@Test
+		void shouldHaveExceptionId() {
+			var error = handleException();
+
+			assertThat(error.getIssues()).hasSize(1);
+			assertThat(error.getIssues().get(0).getExceptionId()).isEqualTo(ExceptionTestFactory.EXCEPTION_ID);
+		}
+
+		private ApiError handleException() {
+			return exceptionController.handleRuntimeException(exception);
+		}
+	}
+
+	@DisplayName("Handle ServiceUnavailableException")
+	@Nested
+	class TestHandleServiceUnavailableException {
+
+		private final ServiceUnavailableException exception = new ServiceUnavailableException(MessageCode.USER_MANAGER_SERVICE_UNAVAILABLE,
+				new Throwable());
+
+		@Test
+		void shouldHaveMessageCode() {
+			var error = handleException();
+
+			assertThat(error.getIssues()).hasSize(1);
+			assertThat(error.getIssues().get(0).getMessageCode()).isEqualTo(MessageCode.USER_MANAGER_SERVICE_UNAVAILABLE);
+		}
+
+		@Test
+		void shouldHaveMessage() {
+			var error = handleException();
+
+			assertThat(error.getIssues()).hasSize(1);
+			assertThat(error.getIssues().get(0).getMessage()).isEqualTo("Service Unavailable");
+		}
+
+		private ApiError handleException() {
+			return exceptionController.handleServiceUnavailableException(exception);
+		}
+	}
+}
\ No newline at end of file
diff --git a/goofy-server/src/test/java/de/itvsh/goofy/common/errorhandling/ExceptionTestFactory.java b/goofy-server/src/test/java/de/itvsh/goofy/common/errorhandling/ExceptionTestFactory.java
new file mode 100644
index 0000000000000000000000000000000000000000..fb78df1413c5d3000ca330c56775e1c6a532e886
--- /dev/null
+++ b/goofy-server/src/test/java/de/itvsh/goofy/common/errorhandling/ExceptionTestFactory.java
@@ -0,0 +1,77 @@
+/*
+ * 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.itvsh.goofy.common.errorhandling;
+
+import static org.mockito.ArgumentMatchers.*;
+import static org.mockito.Mockito.*;
+
+import java.util.Map;
+import java.util.UUID;
+
+import javax.validation.ConstraintViolation;
+import javax.validation.Path;
+import javax.validation.metadata.ConstraintDescriptor;
+
+import org.hibernate.validator.engine.HibernateConstraintViolation;
+
+import com.thedeanda.lorem.LoremIpsum;
+
+import de.itvsh.goofy.common.binaryfile.DynamicViolationParameter;
+
+public class ExceptionTestFactory {
+
+	static final String EXCEPTION_ID = UUID.randomUUID().toString();
+
+	static final String PARAM_NAME = "param";
+	static final String PARAM_VALUE = "3";
+	static final String PARAM_DYNAMIC_VALUE = "20";
+	static final String MESSAGE = LoremIpsum.getInstance().getWords(5);
+	static final String MESSAGE_CODE = "message.code";
+	private static final String PATH_SUFFIX = "createCommandByRelation";
+	static final String PATH_FIELD = "command.wiedervorlage.betreff";
+	private static final String PATH = PATH_SUFFIX + "." + PATH_FIELD;
+
+	public static ConstraintViolation<?> buildMockedConstraintViolation() {
+		return ExceptionTestFactory.buildMockedConstraintViolationWithDynamicPayload(null);
+	}
+
+	@SuppressWarnings({ "rawtypes", "unchecked" })
+	public static <T> ConstraintViolation<T> buildMockedConstraintViolationWithDynamicPayload(DynamicViolationParameter dynamicViolationParameter) {
+		ConstraintViolation violation = mock(ConstraintViolation.class);
+		HibernateConstraintViolation hibernateViolation = mock(HibernateConstraintViolation.class);
+		ConstraintDescriptor constraintDescriptor = mock(ConstraintDescriptor.class);
+
+		var path = mock(Path.class);
+		when(path.toString()).thenReturn(PATH);
+		when(violation.getPropertyPath()).thenReturn(path);
+		when(violation.getMessageTemplate()).thenReturn("{" + MESSAGE_CODE + "}");
+		when(violation.getMessage()).thenReturn(MESSAGE);
+		when(violation.getConstraintDescriptor()).thenReturn(constraintDescriptor);
+		when(constraintDescriptor.getAttributes()).thenReturn(Map.of(PARAM_NAME, PARAM_DYNAMIC_VALUE));
+		when(violation.unwrap(any())).thenReturn(hibernateViolation);
+		when(hibernateViolation.getDynamicPayload(any())).thenReturn(dynamicViolationParameter);
+
+		return violation;
+	}
+}
\ No newline at end of file
diff --git a/goofy-server/src/test/java/de/itvsh/goofy/common/errorhandling/FunctionalExceptionTest.java b/goofy-server/src/test/java/de/itvsh/goofy/common/errorhandling/FunctionalExceptionTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..008a38dd297e49b24f51ace309ff66613293b5c5
--- /dev/null
+++ b/goofy-server/src/test/java/de/itvsh/goofy/common/errorhandling/FunctionalExceptionTest.java
@@ -0,0 +1,40 @@
+/*
+ * 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.itvsh.goofy.common.errorhandling;
+
+import static org.assertj.core.api.Assertions.*;
+
+import org.apache.commons.lang3.StringUtils;
+import org.junit.jupiter.api.Test;
+
+class FunctionalExceptionTest {
+
+	@Test
+	void shouldIncludeExceptionId() {
+		var exception = new FunctionalException(() -> StringUtils.EMPTY);
+
+		assertThat(exception.getMessage()).contains(exception.getExceptionId());
+		assertThat(exception.getExceptionId()).isNotNull();
+	}
+}
\ No newline at end of file
diff --git a/goofy-server/src/test/java/de/itvsh/goofy/common/errorhandling/GrpcExceptionControllerTest.java b/goofy-server/src/test/java/de/itvsh/goofy/common/errorhandling/GrpcExceptionControllerTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..3266409a65934538d5331351df8a13ddeb3de5f6
--- /dev/null
+++ b/goofy-server/src/test/java/de/itvsh/goofy/common/errorhandling/GrpcExceptionControllerTest.java
@@ -0,0 +1,296 @@
+/*
+ * 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.itvsh.goofy.common.errorhandling;
+
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.mockito.InjectMocks;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+
+import static org.assertj.core.api.Assertions.*;
+
+import io.grpc.StatusRuntimeException;
+
+class GrpcExceptionControllerTest {
+
+	@InjectMocks
+	private GrpcExceptionController grpcExceptionController;
+
+	@Nested
+	class TestNotFoundStatusException {
+
+		private final StatusRuntimeException notFoundException = GrpcExceptionTestFactory.createGrpcNotFoundStatusRuntimeException();
+
+		@Test
+		void shouldHaveMessageCode() {
+			var response = grpcExceptionController.handleStatusRuntimeException(notFoundException);
+
+			assertThat(response.getBody().getIssues()).hasSize(1);
+			assertThat(response.getBody().getIssues().get(0).getMessageCode())
+					.isEqualTo(GrpcExceptionController.GRPC_NOT_FOUND_ERROR_CODE_KEY.getErrorCode());
+		}
+
+		@Test
+		void shouldHaveMessage() {
+			var response = grpcExceptionController.handleStatusRuntimeException(notFoundException);
+
+			assertThat(response.getBody().getIssues()).hasSize(1);
+			assertThat(response.getBody().getIssues().get(0).getMessage()).isEqualTo("NOT_FOUND: " + GrpcExceptionTestFactory.NOT_FOUND_DESCRIPTION);
+		}
+
+		@Test
+		void shouldHaveHttpStatusCode() {
+			var response = grpcExceptionController.handleStatusRuntimeException(notFoundException);
+
+			assertThat(response.getStatusCode()).isEqualTo(HttpStatus.NOT_FOUND);
+		}
+
+		@Test
+		void shouldNotHaveExceptionId() {
+			var response = grpcExceptionController.handleStatusRuntimeException(notFoundException);
+
+			assertThat(response.getBody().getIssues()).hasSize(1);
+			assertThat(response.getBody().getIssues().get(0).getExceptionId()).isNull();
+		}
+
+		@Nested
+		class TestWithMetaData {
+
+			private final StatusRuntimeException notFoundExceptionWithMetadata = GrpcExceptionTestFactory
+					.createGrpcNotFoundStatusException(GrpcExceptionTestFactory.createMetaData());
+
+			@Test
+			void shouldHaveExceptionId() {
+				var response = grpcExceptionController.handleStatusRuntimeException(notFoundExceptionWithMetadata);
+
+				assertThat(response.getBody().getIssues()).hasSize(1);
+				assertThat(response.getBody().getIssues().get(0).getExceptionId()).isEqualTo(GrpcExceptionTestFactory.METADATA_EXCEPTION_ID_VALUE);
+			}
+		}
+	}
+
+	@Nested
+	class TestInternalStatusException {
+
+		private final StatusRuntimeException internalException = GrpcExceptionTestFactory.createGrpcInternalStatusException();
+
+		@Test
+		void shouldHaveMessageCode() {
+			var response = grpcExceptionController.handleStatusRuntimeException(internalException);
+
+			assertThat(response.getBody().getIssues()).hasSize(1);
+			assertThat(response.getBody().getIssues().get(0).getMessageCode())
+					.isEqualTo(GrpcExceptionController.GRPC_INTERNAL_ERROR_CODE_KEY.getErrorCode());
+		}
+
+		@Test
+		void shouldHaveMessage() {
+			var response = grpcExceptionController.handleStatusRuntimeException(internalException);
+
+			assertThat(response.getBody().getIssues()).hasSize(1);
+			assertThat(response.getBody().getIssues().get(0).getMessage()).isEqualTo("INTERNAL: " + GrpcExceptionTestFactory.TECHNICAL_DESCRIPTION);
+		}
+
+		@Test
+		void shouldHaveHttpStatusCode() {
+			var response = grpcExceptionController.handleStatusRuntimeException(internalException);
+
+			assertThat(response.getStatusCode()).isEqualTo(HttpStatus.INTERNAL_SERVER_ERROR);
+		}
+
+		@Test
+		void shouldNotHaveExceptionId() {
+			var response = grpcExceptionController.handleStatusRuntimeException(internalException);
+
+			assertThat(response.getBody().getIssues()).hasSize(1);
+			assertThat(response.getBody().getIssues().get(0).getExceptionId()).isNull();
+		}
+
+		@Nested
+		class TestWithMetaData {
+
+			private final StatusRuntimeException internalExceptionWithMetadata = GrpcExceptionTestFactory
+					.createGrpcInternalStatusException(GrpcExceptionTestFactory.createMetaData());
+
+			@Test
+			void shouldHaveErrorCodeAsMessageCode() {
+				var response = grpcExceptionController.handleStatusRuntimeException(internalExceptionWithMetadata);
+
+				assertThat(response.getBody().getIssues()).hasSize(1);
+				assertThat(response.getBody().getIssues().get(0).getMessageCode()).isEqualTo(GrpcExceptionTestFactory.METADATA_ERROR_CODE_VALUE);
+			}
+
+			@Test
+			void shouldHaveExceptionId() {
+				var response = grpcExceptionController.handleStatusRuntimeException(internalExceptionWithMetadata);
+
+				assertThat(response.getBody().getIssues()).hasSize(1);
+				assertThat(response.getBody().getIssues().get(0).getExceptionId()).isEqualTo(GrpcExceptionTestFactory.METADATA_EXCEPTION_ID_VALUE);
+			}
+		}
+	}
+
+	@Nested
+	class TestStatusUnavailableException {
+
+		private final StatusRuntimeException unavailableException = GrpcExceptionTestFactory.createGrpcUnavailableStatusException();
+
+		@Test
+		void shouldHaveMessageCode() {
+			var response = handleUnavailableException();
+
+			assertThat(response.getBody().getIssues()).hasSize(1);
+			assertThat(response.getBody().getIssues().get(0).getMessageCode())
+					.isEqualTo(GrpcExceptionController.GRPC_INTERNAL_ERROR_CODE_KEY.getErrorCode());
+		}
+
+		@Test
+		void shouldHaveMessage() {
+			var response = handleUnavailableException();
+
+			assertThat(response.getBody().getIssues()).hasSize(1);
+			assertThat(response.getBody().getIssues().get(0).getMessage()).isEqualTo("UNAVAILABLE");
+		}
+
+		@Test
+		void shouldHaveHttpStatusCode() {
+			var response = handleUnavailableException();
+
+			assertThat(response.getStatusCode()).isEqualTo(HttpStatus.SERVICE_UNAVAILABLE);
+		}
+
+		@Test
+		void shouldNotHaveExceptionId() {
+			var response = handleUnavailableException();
+
+			assertThat(response.getBody().getIssues()).hasSize(1);
+			assertThat(response.getBody().getIssues().get(0).getExceptionId()).isNotNull();
+		}
+
+		private ResponseEntity<ApiError> handleUnavailableException() {
+			return handleException(unavailableException);
+		}
+
+		@Nested
+		class TestWithMetadata {
+
+			private final StatusRuntimeException unavailableExceptionWithMetadata = GrpcExceptionTestFactory
+					.createGrpcUnavailableStatusException(GrpcExceptionTestFactory.createMetaData());
+
+			@Test
+			void shouldNotHaveExceptionId() {
+				var response = handleUnavailableException();
+
+				assertThat(response.getBody().getIssues()).hasSize(1);
+				assertThat(response.getBody().getIssues().get(0).getExceptionId()).isEqualTo(GrpcExceptionTestFactory.METADATA_EXCEPTION_ID_VALUE);
+			}
+
+			private ResponseEntity<ApiError> handleUnavailableException() {
+				return handleException(unavailableExceptionWithMetadata);
+			}
+		}
+	}
+
+	@Nested
+	class TestPermissionDeniedException {
+
+		private final StatusRuntimeException permissionDeniedException = GrpcExceptionTestFactory.createGrpcPermissionDeniedStatusException();
+
+		@Test
+		void shouldHaveMessageCode() {
+			var response = handlePermissionDeniedException();
+
+			assertThat(response.getBody().getIssues()).hasSize(1);
+			assertThat(response.getBody().getIssues().get(0).getMessageCode())
+					.isEqualTo(GrpcExceptionController.GRPC_PERMISSION_DENIED_CODE_KEY.getErrorCode());
+		}
+
+		@Test
+		void shouldHaveMessage() {
+			var response = handlePermissionDeniedException();
+
+			assertThat(response.getBody().getIssues()).hasSize(1);
+			assertThat(response.getBody().getIssues().get(0).getMessage())
+					.isEqualTo("PERMISSION_DENIED: " + GrpcExceptionTestFactory.PERMISSION_DENIED_DESCRIPTION);
+		}
+
+		@Test
+		void shouldHaveHttpStatusCode() {
+			var response = handlePermissionDeniedException();
+
+			assertThat(response.getStatusCode()).isEqualTo(HttpStatus.FORBIDDEN);
+		}
+
+		@Test
+		void shouldNotHaveExceptionId() {
+			var response = handlePermissionDeniedException();
+
+			assertThat(response.getBody().getIssues()).hasSize(1);
+			assertThat(response.getBody().getIssues().get(0).getExceptionId()).isNotNull();
+		}
+
+		private ResponseEntity<ApiError> handlePermissionDeniedException() {
+			return handleException(permissionDeniedException);
+		}
+	}
+
+	private ResponseEntity<ApiError> handleException(StatusRuntimeException exception) {
+		return grpcExceptionController.handleStatusRuntimeException(exception);
+	}
+
+	@Nested
+	class TestMapToIssueParams {
+
+		@Test
+		void shouldMapParams() {
+			var params = grpcExceptionController.mapToIssueParams(
+					GrpcExceptionTestFactory.createMetaDataWithSingleEntry("param_key", "param_value"));
+
+			assertThat(params).hasSize(1);
+			assertThat(params.get(0).getName()).isEqualTo("param_key");
+			assertThat(params.get(0).getValue()).isEqualTo("param_value");
+		}
+
+		@Test
+		void shouldNotMapErrorCode() {
+			var metadata = GrpcExceptionTestFactory.createMetaDataWithSingleEntry(
+					GrpcExceptionController.KEY_ERROR_CODE, GrpcExceptionTestFactory.METADATA_ERROR_CODE_VALUE);
+
+			var params = grpcExceptionController.mapToIssueParams(metadata);
+
+			assertThat(params).isEmpty();
+		}
+
+		@Test
+		void shouldNotMapExceptionId() {
+			var metadata = GrpcExceptionTestFactory.createMetaDataWithSingleEntry(
+					GrpcExceptionController.KEY_EXCEPTION_ID, GrpcExceptionTestFactory.METADATA_EXCEPTION_ID_VALUE);
+
+			var params = grpcExceptionController.mapToIssueParams(metadata);
+
+			assertThat(params).isEmpty();
+		}
+	}
+}
\ No newline at end of file
diff --git a/goofy-server/src/test/java/de/itvsh/goofy/common/errorhandling/GrpcExceptionTestFactory.java b/goofy-server/src/test/java/de/itvsh/goofy/common/errorhandling/GrpcExceptionTestFactory.java
new file mode 100644
index 0000000000000000000000000000000000000000..0ec55dbaa1237e010c71bca0819075447f13a602
--- /dev/null
+++ b/goofy-server/src/test/java/de/itvsh/goofy/common/errorhandling/GrpcExceptionTestFactory.java
@@ -0,0 +1,100 @@
+/*
+ * 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.itvsh.goofy.common.errorhandling;
+
+import org.springframework.security.access.AccessDeniedException;
+
+import io.grpc.Metadata;
+import io.grpc.Metadata.Key;
+import io.grpc.Status;
+import io.grpc.StatusRuntimeException;
+
+public class GrpcExceptionTestFactory {
+
+	public static final String NOT_FOUND_DESCRIPTION = "Not Found Exception message";
+	public static final String TECHNICAL_DESCRIPTION = "Technical Exception message";
+	public static final String PERMISSION_DENIED_DESCRIPTION = "PermissionsDenied Exception message";
+	public static final String PERMISSION_DENIED_CAUSE_MSG = "AccessDenied cause message";
+
+	public static final String METADATA_ERROR_CODE_VALUE = "Error_Code";
+	public static final String METADATA_EXCEPTION_ID_VALUE = "42";
+
+	public static StatusRuntimeException createGrpcUnavailableStatusException(Metadata metadata) {
+		return new StatusRuntimeException(buildUnavailableStatus(), metadata);
+	}
+
+	public static StatusRuntimeException createGrpcUnavailableStatusException() {
+		return new StatusRuntimeException(buildUnavailableStatus());
+	}
+
+	public static Status buildUnavailableStatus() {
+		return Status.UNAVAILABLE.withCause(new RuntimeException());
+	}
+
+	public static StatusRuntimeException createGrpcNotFoundStatusException(Metadata metadata) {
+		return new StatusRuntimeException(buildNotFoundStatus(), metadata);
+	}
+
+	public static StatusRuntimeException createGrpcNotFoundStatusRuntimeException() {
+		return new StatusRuntimeException(buildNotFoundStatus());
+	}
+
+	private static Status buildNotFoundStatus() {
+		return Status.NOT_FOUND.withDescription(NOT_FOUND_DESCRIPTION).withCause(new RuntimeException());
+	}
+
+	public static StatusRuntimeException createGrpcInternalStatusException(Metadata metaData) {
+		return new StatusRuntimeException(buildInternalStatus(), metaData);
+	}
+
+	public static StatusRuntimeException createGrpcInternalStatusException() {
+		return new StatusRuntimeException(buildInternalStatus());
+	}
+
+	private static Status buildInternalStatus() {
+		return Status.INTERNAL.withDescription(TECHNICAL_DESCRIPTION).withCause(new RuntimeException());
+	}
+
+	public static StatusRuntimeException createGrpcPermissionDeniedStatusException() {
+		return new StatusRuntimeException(buildPermissionDeniedStatus());
+	}
+
+	public static Status buildPermissionDeniedStatus() {
+		return Status.PERMISSION_DENIED.withDescription(PERMISSION_DENIED_DESCRIPTION)
+				.withCause(new AccessDeniedException(PERMISSION_DENIED_CAUSE_MSG));
+	}
+
+	public static Metadata createMetaData() {
+		Metadata data = new Metadata();
+		data.put(Key.of(GrpcExceptionController.KEY_ERROR_CODE, Metadata.ASCII_STRING_MARSHALLER), METADATA_ERROR_CODE_VALUE);
+		data.put(Key.of(GrpcExceptionController.KEY_EXCEPTION_ID, Metadata.ASCII_STRING_MARSHALLER), METADATA_EXCEPTION_ID_VALUE);
+		return data;
+	}
+
+	public static Metadata createMetaDataWithSingleEntry(String key, String value) {
+		Metadata data = new Metadata();
+		data.put(Key.of(key, Metadata.ASCII_STRING_MARSHALLER), value);
+		return data;
+	}
+}
\ No newline at end of file
diff --git a/goofy-server/src/test/java/de/itvsh/goofy/common/file/GrpcOzgFileTestFactory.java b/goofy-server/src/test/java/de/itvsh/goofy/common/file/GrpcOzgFileTestFactory.java
new file mode 100644
index 0000000000000000000000000000000000000000..db54d831cce4895b41a7fb5c947ab98dde836f2a
--- /dev/null
+++ b/goofy-server/src/test/java/de/itvsh/goofy/common/file/GrpcOzgFileTestFactory.java
@@ -0,0 +1,45 @@
+/*
+ * 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.itvsh.goofy.common.file;
+
+import de.itvsh.ozg.pluto.grpc.file.GrpcOzgFile;
+
+public class GrpcOzgFileTestFactory {
+
+	static final String ID = OzgFileTestFactory.ID.toString();
+	static final String NAME = OzgFileTestFactory.NAME;
+	static final long SIZE = OzgFileTestFactory.SIZE;
+
+	public static GrpcOzgFile create() {
+		return createBuilder().build();
+	}
+
+	public static GrpcOzgFile.Builder createBuilder() {
+		return GrpcOzgFile.newBuilder()
+				.setId(ID)
+				.setName(NAME)
+				.setSize(SIZE)
+				.setContentType(OzgFileTestFactory.CONTENT_TYPE);
+	}
+}
\ No newline at end of file
diff --git a/goofy-server/src/test/java/de/itvsh/goofy/common/file/OzgFileMapperTest.java b/goofy-server/src/test/java/de/itvsh/goofy/common/file/OzgFileMapperTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..10a6a542057974e39d81fa019cb14dbd47b1d1c6
--- /dev/null
+++ b/goofy-server/src/test/java/de/itvsh/goofy/common/file/OzgFileMapperTest.java
@@ -0,0 +1,41 @@
+/*
+ * 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.itvsh.goofy.common.file;
+
+import static org.assertj.core.api.Assertions.*;
+
+import org.junit.jupiter.api.Test;
+import org.mapstruct.factory.Mappers;
+
+class OzgFileMapperTest {
+
+	private final OzgFileMapper mapper = Mappers.getMapper(OzgFileMapper.class);
+
+	@Test
+	void shouldMap() {
+		var file = mapper.toFile(GrpcOzgFileTestFactory.create());
+
+		assertThat(file).isNotNull().usingRecursiveComparison().isEqualTo(OzgFileTestFactory.create());
+	}
+}
\ No newline at end of file
diff --git a/goofy-server/src/test/java/de/itvsh/goofy/common/file/OzgFileRemoteServiceTest.java b/goofy-server/src/test/java/de/itvsh/goofy/common/file/OzgFileRemoteServiceTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..00885ed7a141b8139785a47bb9d3208011905421
--- /dev/null
+++ b/goofy-server/src/test/java/de/itvsh/goofy/common/file/OzgFileRemoteServiceTest.java
@@ -0,0 +1,149 @@
+/*
+ * 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.itvsh.goofy.common.file;
+
+import static org.mockito.ArgumentMatchers.*;
+import static org.mockito.Mockito.*;
+
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+
+import de.itvsh.goofy.attachment.GrpcGetAttachmentsResponseTestFactory;
+import de.itvsh.goofy.common.GrpcCallContextTestFactory;
+import de.itvsh.goofy.common.callcontext.ContextService;
+import de.itvsh.goofy.representation.GrpcGetRepresentationsResponseTestFactory;
+import de.itvsh.goofy.vorgang.EingangTestFactory;
+import de.itvsh.ozg.pluto.grpc.command.GrpcCallContext;
+import de.itvsh.ozg.pluto.grpc.file.FileServiceGrpc.FileServiceBlockingStub;
+import de.itvsh.ozg.pluto.grpc.file.GrpcGetAttachmentsRequest;
+import de.itvsh.ozg.pluto.grpc.file.GrpcGetAttachmentsResponse;
+import de.itvsh.ozg.pluto.grpc.file.GrpcGetRepresentationsRequest;
+import de.itvsh.ozg.pluto.grpc.file.GrpcGetRepresentationsResponse;
+
+class OzgFileRemoteServiceTest {
+
+	@InjectMocks
+	private OzgFileRemoteService remoteService;
+	@Mock
+	private FileServiceBlockingStub serviceStub;
+	@Mock
+	private OzgFileMapper mapper;
+	@Mock
+	private ContextService contextService;
+
+	private GrpcCallContext callContext = GrpcCallContextTestFactory.create();
+
+	@BeforeEach
+	void mockContextService() {
+		when(contextService.createCallContext()).thenReturn(callContext);
+	}
+
+	@Nested
+	class TestGetAttachmentsByEingang {
+
+		private final GrpcGetAttachmentsResponse response = GrpcGetAttachmentsResponseTestFactory.create();
+
+		@BeforeEach
+		void mock() {
+			when(serviceStub.getAttachments(any())).thenReturn(response);
+		}
+
+		@Test
+		void shouldCallContextService() {
+			doServiceCall();
+
+			verify(contextService).createCallContext();
+		}
+
+		@Test
+		void shouldCallFileStub() {
+			doServiceCall();
+
+			verify(serviceStub).getAttachments(any(GrpcGetAttachmentsRequest.class));
+		}
+
+		@Test
+		void shouldCallMapper() {
+			var ozgFileAsStream = doServiceCall();
+			ozgFileAsStream = forceActionsDone(ozgFileAsStream);
+
+			verify(mapper).toFile(GrpcOzgFileTestFactory.create());
+		}
+
+		private Stream<OzgFile> forceActionsDone(Stream<OzgFile> stream) {
+			return stream.collect(Collectors.toList()).stream();
+		}
+
+		private Stream<OzgFile> doServiceCall() {
+			return remoteService.getAttachmentsByEingang(EingangTestFactory.ID);
+		}
+	}
+
+	@Nested
+	class TestGetRepresentationsByEingang {
+
+		private final GrpcGetRepresentationsResponse response = GrpcGetRepresentationsResponseTestFactory.create();
+
+		@BeforeEach
+		void mock() {
+			when(serviceStub.getRepresentations(any())).thenReturn(response);
+		}
+
+		@Test
+		void shouldCallContextService() {
+			doServiceCall();
+
+			verify(contextService).createCallContext();
+		}
+
+		@Test
+		void shouldCallFileStub() {
+			doServiceCall();
+
+			verify(serviceStub).getRepresentations(any(GrpcGetRepresentationsRequest.class));
+		}
+
+		@Test
+		void shouldCallMapper() {
+			var ozgFileAsStream = doServiceCall();
+			ozgFileAsStream = forceActionsDone(ozgFileAsStream);
+
+			verify(mapper).toFile(GrpcOzgFileTestFactory.create());
+		}
+
+		private Stream<OzgFile> forceActionsDone(Stream<OzgFile> stream) {
+			return stream.collect(Collectors.toList()).stream();
+		}
+
+		private Stream<OzgFile> doServiceCall() {
+			return remoteService.getRepresentationsByEingang(EingangTestFactory.ID);
+		}
+	}
+}
\ No newline at end of file
diff --git a/goofy-server/src/test/java/de/itvsh/goofy/common/file/OzgFileServiceTest.java b/goofy-server/src/test/java/de/itvsh/goofy/common/file/OzgFileServiceTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..3fe599a9ea69e15dd60f4bf4623d6bd4c8532498
--- /dev/null
+++ b/goofy-server/src/test/java/de/itvsh/goofy/common/file/OzgFileServiceTest.java
@@ -0,0 +1,63 @@
+/*
+ * 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.itvsh.goofy.common.file;
+
+import static org.mockito.Mockito.*;
+
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+
+import de.itvsh.goofy.vorgang.EingangTestFactory;
+
+class OzgFileServiceTest {
+
+	@InjectMocks
+	private OzgFileService service;
+	@Mock
+	private OzgFileRemoteService remoteService;
+
+	@Nested
+	class TestGetAttachmentsByEingang {
+
+		@Test
+		void shouldCallRemoteService() {
+			service.getAttachmentsByEingang(EingangTestFactory.ID);
+
+			verify(remoteService).getAttachmentsByEingang(EingangTestFactory.ID);
+		}
+	}
+
+	@Nested
+	class TestGetRepresentationsByEingang {
+
+		@Test
+		void shouldCallRemoteService() {
+			service.getRepresentationsByEingang(EingangTestFactory.ID);
+
+			verify(remoteService).getRepresentationsByEingang(EingangTestFactory.ID);
+		}
+	}
+}
\ No newline at end of file
diff --git a/goofy-server/src/test/java/de/itvsh/goofy/common/file/OzgFileTestFactory.java b/goofy-server/src/test/java/de/itvsh/goofy/common/file/OzgFileTestFactory.java
new file mode 100644
index 0000000000000000000000000000000000000000..00e17cc371bc2e42c7148425e3d801880b93cb4d
--- /dev/null
+++ b/goofy-server/src/test/java/de/itvsh/goofy/common/file/OzgFileTestFactory.java
@@ -0,0 +1,49 @@
+/*
+ * 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.itvsh.goofy.common.file;
+
+import java.util.UUID;
+
+import de.itvsh.goofy.common.binaryfile.BinaryFileTestFactory;
+import de.itvsh.goofy.common.binaryfile.FileId;
+
+public class OzgFileTestFactory {
+
+	public static final FileId ID = FileId.from(UUID.randomUUID().toString());
+	public static final String NAME = BinaryFileTestFactory.NAME;
+	public static final long SIZE = BinaryFileTestFactory.SIZE;
+	public static final String CONTENT_TYPE = BinaryFileTestFactory.CONTENT_TYPE;
+
+	public static OzgFile create() {
+		return createBuilder().build();
+	}
+
+	public static OzgFile.OzgFileBuilder createBuilder() {
+		return OzgFile.builder()
+				.id(ID)
+				.name(NAME)
+				.size(SIZE)
+				.contentType(CONTENT_TYPE);
+	}
+}
\ No newline at end of file
diff --git a/goofy-server/src/test/java/de/itvsh/goofy/common/user/CurrentUserServiceTest.java b/goofy-server/src/test/java/de/itvsh/goofy/common/user/CurrentUserServiceTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..9ab969601d9b8db47d6fe13b79b8c1254d5eedb0
--- /dev/null
+++ b/goofy-server/src/test/java/de/itvsh/goofy/common/user/CurrentUserServiceTest.java
@@ -0,0 +1,76 @@
+/*
+ * 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.itvsh.goofy.common.user;
+
+import static org.assertj.core.api.Assertions.*;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.mockito.Spy;
+
+class CurrentUserServiceTest {
+
+	@Spy
+	private CurrentUserService service;
+
+	@Nested
+	class TestGetOrganisationseinheit {
+
+		@Test
+		void shouldReturnOrganisationseinheitIdsFromMap() {
+			Map<String, Object> claims = Map.of(CurrentUserService.USER_ATTRIBUTE_ORGANISATIONSEINHEIT_ID, List.of("1", "2"));
+
+			var result = service.getOrganisationseinheitId(claims);
+
+			assertThat(result).contains("1").contains("2");
+		}
+
+		@Test
+		void shouldReturnOrgaIdAsString() {
+			var result = service.getOrganisationseinheitId(Map.of(CurrentUserService.USER_ATTRIBUTE_ORGANISATIONSEINHEIT_ID, List.of("1", 2)));
+
+			assertThat(result).contains("1").contains("2");
+		}
+
+		@Test
+		void shouldReturnEmptyList() {
+			Map<String, Object> claims = Map.of(CurrentUserService.USER_ATTRIBUTE_ORGANISATIONSEINHEIT_ID, Collections.emptyList());
+
+			var result = service.getOrganisationseinheitId(claims);
+
+			assertThat(result).isEmpty();
+		}
+
+		@Test
+		void shouldReturnEmptyListIfNotExists() {
+			var result = service.getOrganisationseinheitId(Collections.emptyMap());
+
+			assertThat(result).isEmpty();
+		}
+	}
+}
\ No newline at end of file
diff --git a/goofy-server/src/test/java/de/itvsh/goofy/common/user/GrpcUserTestFactory.java b/goofy-server/src/test/java/de/itvsh/goofy/common/user/GrpcUserTestFactory.java
new file mode 100644
index 0000000000000000000000000000000000000000..d4aa272f6699fc73f3595545e4ebce88dcbd44f3
--- /dev/null
+++ b/goofy-server/src/test/java/de/itvsh/goofy/common/user/GrpcUserTestFactory.java
@@ -0,0 +1,47 @@
+/*
+ * 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.itvsh.goofy.common.user;
+
+import java.util.Collections;
+import java.util.UUID;
+
+import de.itvsh.ozg.pluto.grpc.command.GrpcUser;
+
+public class GrpcUserTestFactory {
+
+	public static final String ID = UUID.randomUUID().toString();
+	public static final String NAME = UserProfileTestFactory.FULLNAME;
+	public static final String ROLE = UserProfileTestFactory.ROLE;
+
+	public static GrpcUser create() {
+		return createBuilder().build();
+	}
+
+	public static GrpcUser.Builder createBuilder() {
+		return GrpcUser.newBuilder()
+				.setId(ID)
+				.setName(NAME)
+				.addAllRoles(Collections.singleton(ROLE));
+	}
+}
\ No newline at end of file
diff --git a/goofy-server/src/test/java/de/itvsh/goofy/common/user/UserProfileTestFactory.java b/goofy-server/src/test/java/de/itvsh/goofy/common/user/UserProfileTestFactory.java
new file mode 100644
index 0000000000000000000000000000000000000000..0996dce79bae9174a0b708d289ae15cc3063d615
--- /dev/null
+++ b/goofy-server/src/test/java/de/itvsh/goofy/common/user/UserProfileTestFactory.java
@@ -0,0 +1,57 @@
+/*
+ * 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.itvsh.goofy.common.user;
+
+import java.util.Collections;
+import java.util.UUID;
+
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.authority.SimpleGrantedAuthority;
+
+import de.itvsh.goofy.vorgang.ZustaendigeStelleTestFactory;
+
+public class UserProfileTestFactory {
+
+	public static final UserId ID = UserId.from(UUID.randomUUID().toString());
+	public static final String FIRSTNAME = "Vaneßa";
+	public static final String LASTNAME = "Täst";
+	public static final String FULLNAME = String.format("%s %s", FIRSTNAME, LASTNAME);
+	public static final String ROLE = "TEST_USER";
+	public static final GrantedAuthority AUTHORITY = new SimpleGrantedAuthority(ROLE);
+
+	public static final String SYSTEM_USER = "system_user_example";
+
+	public static UserProfile create() {
+		return createBuilder().build();
+	}
+
+	public static UserProfile.UserProfileBuilder createBuilder() {
+		return UserProfile.builder()
+				.id(ID)
+				.firstName(FIRSTNAME)
+				.lastName(LASTNAME)
+				.organisationseinheitId(ZustaendigeStelleTestFactory.ORGANISATIONSEINHEITEN_ID)
+				.authorities(Collections.singleton(AUTHORITY));
+	}
+}
diff --git a/goofy-server/src/test/java/de/itvsh/goofy/common/user/UserRemoteServiceTest.java b/goofy-server/src/test/java/de/itvsh/goofy/common/user/UserRemoteServiceTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..704771cf430758ed65f82454f20723500dc5fd35
--- /dev/null
+++ b/goofy-server/src/test/java/de/itvsh/goofy/common/user/UserRemoteServiceTest.java
@@ -0,0 +1,261 @@
+/*
+ * 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.itvsh.goofy.common.user;
+
+import static org.assertj.core.api.Assertions.*;
+import static org.mockito.ArgumentMatchers.*;
+import static org.mockito.Mockito.*;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.Spy;
+import org.springframework.http.HttpEntity;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpMethod;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.client.HttpClientErrorException;
+import org.springframework.web.client.RestClientException;
+import org.springframework.web.client.RestTemplate;
+
+import de.itvsh.goofy.common.errorhandling.ServiceUnavailableException;
+
+class UserRemoteServiceTest {
+
+	@Spy
+	@InjectMocks
+	private UserRemoteService service;
+	@Mock
+	private UserManagerProperties userManagerProperties;
+	@Mock
+	private UserManagerUrlProvider userManagerUrlProvider;
+
+	@Mock
+	private RestTemplate restTemplate;
+
+	@DisplayName("Get userId")
+	@Nested
+	class TestGetUserId {
+
+		private final String internalUrlTemplate = "DummyInternalUrlTemplate";
+
+		@DisplayName("with configured usermanager")
+		@Nested
+		class TestWithConfiguredUserManager {
+
+			@BeforeEach
+			void mock() {
+				when(userManagerProperties.getFullInternalUrlTemplate()).thenReturn(internalUrlTemplate);
+				when(userManagerUrlProvider.isConfiguredForInternalUserId()).thenReturn(true);
+			}
+
+			@DisplayName("on valid response")
+			@Nested
+			class TestSuccess {
+
+				@BeforeEach
+				void mock() {
+					when(restTemplate.getForObject(anyString(), eq(String.class), anyString())).thenReturn(UserProfileTestFactory.ID.toString());
+				}
+
+				@Test
+				void shouldReturnResponseAsUserId() {
+					var userId = service.getUserId(UserProfileTestFactory.ID);
+
+					assertThat(userId).hasValue(UserProfileTestFactory.ID);
+				}
+			}
+
+			@DisplayName("on error response")
+			@Nested
+			class TestErrorCases {
+
+				@Test
+				void shouldHandleEmptyValue() {
+					when(restTemplate.getForObject(anyString(), eq(String.class), anyString())).thenReturn("");
+
+					var res = service.getUserId(UserProfileTestFactory.ID);
+
+					assertThat(res).isNotPresent();
+				}
+
+				@Test
+				void shouldHandleError() {
+					when(restTemplate.getForObject(anyString(), eq(String.class), anyString())).thenThrow(new RestClientException("Test error"));
+
+					var res = service.getUserId(UserProfileTestFactory.ID);
+
+					assertThat(res).isNotPresent();
+				}
+			}
+		}
+
+		@DisplayName("with not configured usermanager")
+		@Nested
+		class TestOnNotConfiguredUserManager {
+
+			@BeforeEach
+			void mock() {
+				when(userManagerUrlProvider.isConfiguredForInternalUserId()).thenReturn(false);
+			}
+
+			@Test
+			void shouldNotCallUserManagerProperties() {
+				service.getUserId(UserProfileTestFactory.ID);
+
+				verify(userManagerProperties, never()).getFullInternalUrlTemplate();
+			}
+
+			@Test
+			void shouldReturnEmptyOptional() {
+				var user = service.getUserId(UserProfileTestFactory.ID);
+
+				assertThat(user).isNotPresent();
+			}
+		}
+	}
+
+	@DisplayName("Get user")
+	@Nested
+	class TestGetUser {
+
+		private final String profileUri = "DummyProfileTemplate/" + UserProfileTestFactory.ID;
+		private final String dummyToken = "Token";
+
+		@BeforeEach
+		void mock() {
+			doReturn(profileUri).when(service).buildUserProfileUri(any());
+			doReturn(dummyToken).when(service).getToken();
+		}
+
+		@DisplayName("on valid response")
+		@Nested
+		class TestOnValidResponse {
+
+			private final Map<String, Object> bodyMap = new LinkedHashMap<>(Map.of(UserRemoteService.FIRST_NAME_KEY, UserProfileTestFactory.FIRSTNAME,
+					UserRemoteService.LAST_NAME_KEY, UserProfileTestFactory.LASTNAME));
+			private final ResponseEntity<Object> response = new ResponseEntity<>(bodyMap, HttpStatus.OK);
+
+			@BeforeEach
+			void mock() {
+				when(restTemplate.exchange(anyString(), eq(HttpMethod.GET), any(), eq(Object.class))).thenReturn(response);
+			}
+
+			@Test
+			void shouldCallRestTemplate() {
+				var headers = new HttpHeaders();
+				headers.add("Authorization", "Bearer " + dummyToken);
+				var httpEntity = new HttpEntity<>(headers);
+
+				service.getUser(UserProfileTestFactory.ID);
+
+				verify(restTemplate).exchange(profileUri, HttpMethod.GET, httpEntity, Object.class);
+			}
+
+			@Test
+			void shouldBuildUrl() {
+				service.getUser(UserProfileTestFactory.ID);
+
+				verify(service).buildUserProfileUri(UserProfileTestFactory.ID);
+			}
+
+			@Test
+			void shouldReturnUser() {
+				var loadedUser = service.getUser(UserProfileTestFactory.ID);
+
+				assertThat(loadedUser.getFirstName()).isEqualTo(UserProfileTestFactory.FIRSTNAME);
+				assertThat(loadedUser.getLastName()).isEqualTo(UserProfileTestFactory.LASTNAME);
+			}
+		}
+
+		@DisplayName("on error response")
+		@Nested
+		class TestOnErrorResponse {
+
+			private final HttpClientErrorException httpClientErrorException = new HttpClientErrorException(HttpStatus.SERVICE_UNAVAILABLE,
+					"Test error");
+			private final IllegalArgumentException illegalArgumentException = new IllegalArgumentException();
+
+			private final HttpClientErrorException notFoundException = new HttpClientErrorException(HttpStatus.NOT_FOUND, "Test error");
+
+			@Test
+			void shouldThrowServiceUnavailablExceptionOnException() {
+				when(restTemplate.exchange(anyString(), eq(HttpMethod.GET), any(), eq(Object.class))).thenThrow(httpClientErrorException);
+
+				assertThatThrownBy(() -> service.getUser(UserProfileTestFactory.ID)).isInstanceOf(ServiceUnavailableException.class)
+						.hasCause(httpClientErrorException);
+			}
+
+			@Test
+			void shouldThrowServiceUnavailablExceptionOnIlleglaArgumentException() {
+				when(restTemplate.exchange(anyString(), eq(HttpMethod.GET), any(), eq(Object.class))).thenThrow(illegalArgumentException);
+
+				assertThatThrownBy(() -> service.getUser(UserProfileTestFactory.ID)).isInstanceOf(ServiceUnavailableException.class)
+						.hasCause(illegalArgumentException);
+			}
+
+			@Test
+			void shouldReturnEmptyOptionalOnNotFoundException() {
+				when(restTemplate.exchange(anyString(), eq(HttpMethod.GET), any(), eq(Object.class))).thenThrow(notFoundException);
+
+				var user = service.getUser(UserProfileTestFactory.ID);
+
+				assertThat(user).isNull();
+			}
+		}
+	}
+
+	@DisplayName("Build user profile uri")
+	@Nested
+	class TestBuildUserProfileUri {
+
+		private final String profileUriTemplate = "DummyProfileTemplate/%s";
+
+		@BeforeEach
+		void mock() {
+			when(userManagerUrlProvider.getInternalUserIdTemplate()).thenReturn(profileUriTemplate);
+		}
+
+		@Test
+		void shouldCallUserManagerUrlProvider() {
+			service.buildUserProfileUri(UserProfileTestFactory.ID);
+
+			verify(userManagerUrlProvider).getInternalUserIdTemplate();
+		}
+
+		@Test
+		void shouldReturnUserProfileUri() {
+			var uri = service.buildUserProfileUri(UserProfileTestFactory.ID);
+
+			assertThat(uri).isEqualTo("DummyProfileTemplate/" + UserProfileTestFactory.ID);
+		}
+	}
+}
\ No newline at end of file
diff --git a/goofy-server/src/test/java/de/itvsh/goofy/common/user/UserServiceTest.java b/goofy-server/src/test/java/de/itvsh/goofy/common/user/UserServiceTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..d8092e887d3b4bd0bb20b48a159fa994a3014915
--- /dev/null
+++ b/goofy-server/src/test/java/de/itvsh/goofy/common/user/UserServiceTest.java
@@ -0,0 +1,52 @@
+/*
+ * 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.itvsh.goofy.common.user;
+
+import static org.mockito.Mockito.*;
+
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+
+class UserServiceTest {
+
+	@InjectMocks
+	private UserService service;
+	@Mock
+	private UserRemoteService remoteService;
+
+	@DisplayName("Get by id")
+	@Nested
+	class TestGetById {
+
+		@Test
+		void shouldCallRemoteService() {
+			service.getById(UserProfileTestFactory.ID);
+
+			verify(remoteService).getUser(UserProfileTestFactory.ID);
+		}
+	}
+}
diff --git a/goofy-server/src/test/java/de/itvsh/goofy/historie/HistorieCommandHandlerTest.java b/goofy-server/src/test/java/de/itvsh/goofy/historie/HistorieCommandHandlerTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..449c0f20f288ed2ce2a01dd8836930295f524b4d
--- /dev/null
+++ b/goofy-server/src/test/java/de/itvsh/goofy/historie/HistorieCommandHandlerTest.java
@@ -0,0 +1,181 @@
+/*
+ * 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.itvsh.goofy.historie;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.util.Map;
+
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.CsvSource;
+
+import de.itvsh.goofy.common.command.Command;
+import de.itvsh.goofy.common.command.CommandOrder;
+import de.itvsh.goofy.common.command.CommandTestFactory;
+import de.itvsh.goofy.postfach.PostfachMail;
+import de.itvsh.goofy.wiedervorlage.Wiedervorlage;
+
+class HistorieCommandHandlerTest {
+
+	private HistorieCommandHandler orderTranslator = new HistorieCommandHandler();
+
+	@DisplayName("postfach nachricht verification")
+	@Nested
+	class TestIsOutgoingPostfachNachrichtenByMailService {
+
+		private final Command matchingCommand = CommandTestFactory.createBuilder()
+				.order(CommandOrder.CREATE_ATTACHED_ITEM)
+				.body(Map.of("item", Map.of(HistorieCommandHandler.DIRECTION, HistorieCommandHandler.DIRECTION_OUTGOING),
+						HistorieCommandHandler.CLIENT, HistorieCommandHandler.MAIL_SERVICE))
+				.build();
+
+		@DisplayName("should return true if the command is CREATED_ATTACHED_ITEM order outgoing by mailserice")
+		@Test
+		void shouldReturnTrue() {
+			var result = orderTranslator.isOutgoingPostfachNachrichtByMailService(matchingCommand);
+
+			assertThat(result).isTrue();
+		}
+
+		@Test
+		void shouldReturnFalseOnIncoming() {
+			var nonMatchingCommand = matchingCommand.toBuilder()
+					.body(Map.of("item", Map.of(HistorieCommandHandler.DIRECTION, HistorieCommandHandler.DIRECTION_INCOMMING),
+							HistorieCommandHandler.CLIENT, HistorieCommandHandler.MAIL_SERVICE))
+					.build();
+
+			var result = orderTranslator.isOutgoingPostfachNachrichtByMailService(nonMatchingCommand);
+
+			assertThat(result).isFalse();
+		}
+
+		@Test
+		void shouldReturnFalseOnOtherClient() {
+			var nonMatchingCommand = matchingCommand.toBuilder()
+					.body(Map.of("item", Map.of(HistorieCommandHandler.DIRECTION, HistorieCommandHandler.DIRECTION_OUTGOING),
+							HistorieCommandHandler.CLIENT, "Goofy"))
+					.build();
+
+			var result = orderTranslator.isOutgoingPostfachNachrichtByMailService(nonMatchingCommand);
+
+			assertThat(result).isFalse();
+		}
+	}
+
+	@DisplayName("translate order")
+	@Nested
+	class TestTranslateOrders {
+		@ParameterizedTest
+		@CsvSource(value = { "CREATE_KOMMENTAR;Kommentar", "CREATE_WIEDERVORLAGE;Wiedervorlage" }, delimiter = ';')
+		void shouldTranslateToCreateOrder(String target, String itemName) {
+			var command = orderTranslator.translateOrder(createCommand(itemName, CommandOrder.CREATE_ATTACHED_ITEM));
+
+			assertThat(command.getOrder().name()).isEqualTo(target);
+		}
+
+		@ParameterizedTest
+		@CsvSource(value = { "EDIT_KOMMENTAR;Kommentar", "EDIT_WIEDERVORLAGE;Wiedervorlage" }, delimiter = ';')
+		void shouldTranslateToEditOrder(String target, String itemName) {
+			var command = orderTranslator.translateOrder(createCommand(itemName, CommandOrder.UPDATE_ATTACHED_ITEM));
+
+			assertThat(command.getOrder().name()).isEqualTo(target);
+		}
+
+		@ParameterizedTest
+		@CsvSource(value = { "WIEDERVORLAGE_ERLEDIGEN;true", "WIEDERVORLAGE_WIEDEREROEFFNEN;false" }, delimiter = ';')
+		void shouldTranslateToWiedervolageErledigenOrWiedervolageWiedereroeffnen(String target, String doneValue) {
+			var command = orderTranslator.translateOrder(
+					createCommand(Wiedervorlage.class.getSimpleName(), CommandOrder.PATCH_ATTACHED_ITEM, Boolean.valueOf(doneValue)));
+
+			assertThat(command.getOrder().name()).isEqualTo(target);
+		}
+
+		@ParameterizedTest
+		@CsvSource(value = { "RECEIVE_POSTFACH_NACHRICHT;IN" }, delimiter = ';')
+		void shouldTranslateToReveivedIncomminNachricht(String target, String direction) {
+			var command = orderTranslator.translateOrder(
+					createCommand("MailService", CommandOrder.CREATE_ATTACHED_ITEM, direction));
+
+			assertThat(command.getOrder().name()).isEqualTo(target);
+		}
+
+		@Test
+		void shouldHandleMissingDone() {
+			var command = orderTranslator.translateOrder(createCommand(Wiedervorlage.class.getSimpleName(), CommandOrder.PATCH_ATTACHED_ITEM));
+
+			assertThat(command.getOrder()).isEqualTo(CommandOrder.PATCH_ATTACHED_ITEM);
+		}
+
+		@Test
+		void shouldHandleUnkownOrder() {
+			var command = orderTranslator
+					.translateOrder(createCommand(Wiedervorlage.class.getSimpleName(), CommandOrder.VORGANG_ABSCHLIESSEN));
+
+			assertThat(command.getOrder()).isEqualTo(CommandOrder.VORGANG_ABSCHLIESSEN);
+		}
+
+		@Test
+		void shouldHandleMissingItemName() {
+			var command = CommandTestFactory.createBuilder().order(CommandOrder.EDIT_KOMMENTAR).body(Map.of("item", Map.of("value", "test"))).build();
+
+			var translatedCommand = orderTranslator.translateOrder(command);
+
+			assertThat(translatedCommand.getOrder()).isEqualTo(CommandOrder.EDIT_KOMMENTAR);
+		}
+
+		private Command createCommand(String itemName, CommandOrder order) {
+			return CommandTestFactory.createBuilder().order(order).body(Map.of("itemName", itemName, "item", Map.of("value", "test"))).build();
+		}
+
+		private Command createCommand(String itemName, CommandOrder order, boolean done) {
+			return CommandTestFactory.createBuilder().order(order).body(Map.of("itemName", itemName, "item", Map.of("done", done)))
+					.build();
+		}
+
+		private Command createCommand(String client, CommandOrder order, String direction) {
+			return CommandTestFactory.createBuilder().order(order)
+					.body(Map.of(HistorieCommandHandler.CLIENT, client, "itemName", PostfachMail.class.getSimpleName(), "item",
+							Map.of(HistorieCommandHandler.DIRECTION, direction)))
+					.build();
+		}
+
+	}
+
+	@DisplayName("SEND_POSTFACH_MAIL")
+	@Nested
+	class TestSendPostfachMail {
+
+		private Command command = CommandTestFactory.createBuilder().order(CommandOrder.SEND_POSTFACH_MAIL).build();
+
+		@Test
+		void shouldHandleUnkownOrder() {
+			var translatedCommand = orderTranslator.translateOrder(command);
+
+			assertThat(translatedCommand.getOrder()).isEqualTo(CommandOrder.SEND_POSTFACH_NACHRICHT);
+		}
+	}
+}
\ No newline at end of file
diff --git a/goofy-server/src/test/java/de/itvsh/goofy/historie/HistorieControllerITCase.java b/goofy-server/src/test/java/de/itvsh/goofy/historie/HistorieControllerITCase.java
new file mode 100644
index 0000000000000000000000000000000000000000..760221978ea9e26f94440ad8f64162d7e5790d0b
--- /dev/null
+++ b/goofy-server/src/test/java/de/itvsh/goofy/historie/HistorieControllerITCase.java
@@ -0,0 +1,91 @@
+/*
+ * 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.itvsh.goofy.historie;
+
+import static org.mockito.ArgumentMatchers.*;
+import static org.mockito.Mockito.*;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
+
+import java.util.List;
+import java.util.stream.Stream;
+
+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.servlet.AutoConfigureMockMvc;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.boot.test.mock.mockito.MockBean;
+import org.springframework.security.test.context.support.WithMockUser;
+import org.springframework.test.web.servlet.MockMvc;
+
+import de.itvsh.goofy.common.command.Command;
+import de.itvsh.goofy.common.command.CommandOrder;
+import de.itvsh.goofy.common.command.CommandService;
+import de.itvsh.goofy.common.command.CommandTestFactory;
+import de.itvsh.goofy.vorgang.VorgangHeaderTestFactory;
+
+@WithMockUser
+@AutoConfigureMockMvc
+@SpringBootTest
+class HistorieControllerITCase {
+	private final String PATH = HistorieController.PATH + "?vorgangId=";
+
+	@MockBean
+	private CommandService service;
+
+	@Autowired
+	private MockMvc mockMvc;
+
+	@WithMockUser
+	@Nested
+	class TestGetHistorieList {
+
+		@BeforeEach
+		void init() {
+			when(service.findFinishedCommands(any())).thenReturn(createCommandStream());
+		}
+
+		private Stream<Command> createCommandStream() {
+			return List.of(CommandTestFactory.create()).stream();
+		}
+
+		@Test
+		void shouldReturnStatusOk() throws Exception {
+			mockMvc.perform(get(PATH + VorgangHeaderTestFactory.ID)).andExpect(status().isOk());
+		}
+
+		@Test
+		void shouldContainCommands() throws Exception {
+			var response = mockMvc.perform(get(PATH + VorgangHeaderTestFactory.ID)).andExpect(status().isOk());
+
+			response.andExpect(jsonPath("$._embedded.commandList[0].order")
+					.value(CommandOrder.VORGANG_ANNEHMEN.name()))
+					.andExpect(jsonPath("$._embedded.commandList[0]").isNotEmpty());
+
+		}
+	}
+
+}
diff --git a/goofy-server/src/test/java/de/itvsh/goofy/historie/HistorieControllerTest.java b/goofy-server/src/test/java/de/itvsh/goofy/historie/HistorieControllerTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..2d135aeb36600129a6df7b847476a6d264040ba2
--- /dev/null
+++ b/goofy-server/src/test/java/de/itvsh/goofy/historie/HistorieControllerTest.java
@@ -0,0 +1,83 @@
+/*
+ * 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.itvsh.goofy.historie;
+
+import static org.mockito.ArgumentMatchers.*;
+import static org.mockito.Mockito.*;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
+
+import java.util.List;
+import java.util.stream.Stream;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.Spy;
+import org.springframework.test.web.servlet.MockMvc;
+import org.springframework.test.web.servlet.setup.MockMvcBuilders;
+
+import de.itvsh.goofy.common.command.Command;
+import de.itvsh.goofy.common.command.CommandTestFactory;
+import de.itvsh.goofy.vorgang.VorgangHeaderTestFactory;
+
+class HistorieControllerTest {
+	private final String PATH = HistorieController.PATH + "?vorgangId=";
+
+	@Nested
+	class TestGetHistorieList {
+		@Spy
+		@InjectMocks
+		private HistorieController controller;
+
+		@Mock
+		private HistorieService service;
+
+		@Mock
+		private HistorieModelAssembler modelAssembler;
+
+		private MockMvc mockMvc;
+
+		@BeforeEach
+		void init() {
+			mockMvc = MockMvcBuilders.standaloneSetup(controller).build();
+			when(service.findFinishedCommands(any())).thenReturn(createCommandStream());
+		}
+
+		private Stream<Command> createCommandStream() {
+			return List.of(CommandTestFactory.create()).stream();
+		}
+
+		@Test
+		void shouldCallService() throws Exception {
+			mockMvc.perform(get(PATH + VorgangHeaderTestFactory.ID)).andExpect(status().isOk());
+
+			verify(service).findFinishedCommands(any());
+		}
+
+	}
+
+}
diff --git a/goofy-server/src/test/java/de/itvsh/goofy/historie/HistorieModelAssemblerTest.java b/goofy-server/src/test/java/de/itvsh/goofy/historie/HistorieModelAssemblerTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..7f4c9f11f337d6a62c4edd1c2b24bb29f4128754
--- /dev/null
+++ b/goofy-server/src/test/java/de/itvsh/goofy/historie/HistorieModelAssemblerTest.java
@@ -0,0 +1,152 @@
+/*
+ * 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.itvsh.goofy.historie;
+
+import static de.itvsh.goofy.common.UserProfileUrlProviderTestFactory.*;
+import static org.assertj.core.api.Assertions.*;
+import static org.mockito.Mockito.*;
+
+import java.util.Map;
+
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.springframework.hateoas.IanaLinkRelations;
+import org.springframework.hateoas.Link;
+
+import de.itvsh.goofy.common.UserProfileUrlProvider;
+import de.itvsh.goofy.common.command.CommandTestFactory;
+import de.itvsh.goofy.common.user.UserId;
+import de.itvsh.goofy.common.user.UserManagerUrlProvider;
+import de.itvsh.goofy.common.user.UserProfileTestFactory;
+
+class HistorieModelAssemblerTest {
+
+	private static final String CREATED_BY = "createdBy";
+
+	@InjectMocks
+	private HistorieModelAssembler modelAssembler;
+	@Mock
+	private UserManagerUrlProvider userManagerUrlProvider;
+
+	private final String COMMAND_SINGLE_PATH = "/api/histories/" + CommandTestFactory.ID;
+
+	private UserProfileUrlProvider urlProvider = new UserProfileUrlProvider();
+
+	@Test
+	void shouldHaveSelfLink() {
+		initUserProfileUrlProvider(urlProvider);
+
+		var model = modelAssembler.toModel(CommandTestFactory.create());
+
+		assertThat(model.getLink(IanaLinkRelations.SELF)).isPresent().get().extracting(Link::getHref).isEqualTo(COMMAND_SINGLE_PATH);
+	}
+
+	@DisplayName("AssignedTo Link")
+	@Nested
+	class TestAssignedToLink {
+
+		private final String userProfileTemplateDummy = "UserProfileTemplateDummy/%s";
+
+		@Nested
+		class TesOnConfiguredUserManager {
+
+			@Test
+			void shouldBePresentOnExistingValue() {
+				when(userManagerUrlProvider.isConfiguredForUserProfile()).thenReturn(true);
+				when(userManagerUrlProvider.getUserProfileTemplate()).thenReturn(userProfileTemplateDummy);
+
+				var model = modelAssembler.toModel(CommandTestFactory.createBuilder()
+						.body(Map.of(HistorieModelAssembler.ASSIGNED_TO_BODY_FIELD, UserProfileTestFactory.ID)).build());
+
+				assertThat(model.getLink(HistorieModelAssembler.REL_ASSIGNED_TO)).isPresent().get().extracting(Link::getHref)
+						.isEqualTo("UserProfileTemplateDummy/" + UserProfileTestFactory.ID);
+			}
+
+			@Test
+			void shouldNotBePresentOnMissingValue() {
+				var model = modelAssembler.toModel(CommandTestFactory.create());
+
+				assertThat(model.getLink(HistorieModelAssembler.REL_ASSIGNED_TO)).isNotPresent();
+			}
+		}
+
+		@Nested
+		class TestOnNotConfiguredUserManager {
+
+			@Test
+			void shouldBePresentOnExistingValue() {
+				when(userManagerUrlProvider.isConfiguredForUserProfile()).thenReturn(false);
+
+				var model = modelAssembler.toModel(CommandTestFactory.createBuilder()
+						.body(Map.of(HistorieModelAssembler.ASSIGNED_TO_BODY_FIELD, UserProfileTestFactory.ID)).build());
+
+				assertThat(model.getLink(HistorieModelAssembler.REL_ASSIGNED_TO)).isNotPresent();
+			}
+
+			@Test
+			void shouldNotGetTemplateFromUserManagerUrlProvider() {
+				when(userManagerUrlProvider.isConfiguredForUserProfile()).thenReturn(false);
+
+				modelAssembler.toModel(CommandTestFactory.createBuilder()
+						.body(Map.of(HistorieModelAssembler.ASSIGNED_TO_BODY_FIELD, UserProfileTestFactory.ID)).build());
+
+				verify(userManagerUrlProvider, never()).getUserProfileTemplate();
+			}
+		}
+	}
+
+	@DisplayName("createdBy Link")
+
+	@Nested
+	class TestCreatedByLink {
+		@Test
+		void shouldExistingAtUser() {
+			initUserProfileUrlProvider(urlProvider);
+
+			var model = modelAssembler.toModel(CommandTestFactory.create());
+
+			assertThat(model.getLink(CREATED_BY)).isPresent().get().extracting(Link::getHref)
+					.isEqualTo(ROOT_URL + USER_PROFILES_API_PATH + UserProfileTestFactory.ID);
+		}
+
+		@Test
+		void shouldNotExistingAtSystemNotificationMAnager() {
+			var model = modelAssembler
+					.toModel(CommandTestFactory.createBuilder().createdBy(UserId.from(HistorieModelAssembler.SYSTEM_NOTIFICATION_MANAGER_PREFIX))
+							.build());
+
+			assertThat(model.getLink(CREATED_BY)).isNotPresent();
+		}
+
+		@Test
+		void shouldNotBePresentOnNullValue() {
+			var model = modelAssembler.toModel(CommandTestFactory.createBuilder().createdBy(null).build());
+
+			assertThat(model.getLink(CREATED_BY)).isNotPresent();
+		}
+	}
+}
\ No newline at end of file
diff --git a/goofy-server/src/test/java/de/itvsh/goofy/historie/HistorieServiceTest.java b/goofy-server/src/test/java/de/itvsh/goofy/historie/HistorieServiceTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..4c18d0bcd9f93b1882331a7db46d8d832b4df5b5
--- /dev/null
+++ b/goofy-server/src/test/java/de/itvsh/goofy/historie/HistorieServiceTest.java
@@ -0,0 +1,93 @@
+/*
+ * 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.itvsh.goofy.historie;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import java.util.List;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+
+import de.itvsh.goofy.common.command.Command;
+import de.itvsh.goofy.common.command.CommandService;
+import de.itvsh.goofy.common.command.CommandTestFactory;
+import de.itvsh.goofy.vorgang.VorgangHeaderTestFactory;
+
+class HistorieServiceTest {
+
+	@InjectMocks
+	private HistorieService historieService;
+	@Mock
+	private CommandService commandService;
+	@Mock
+	private HistorieCommandHandler commandHandler;
+
+	@DisplayName("Find finished commands")
+	@Nested
+	class TestFindFinishedCommands {
+
+		@DisplayName("process flow")
+		@Nested
+		class TestProcessFlow {
+
+			private Command responseCommand = CommandTestFactory.create();
+
+			@BeforeEach
+			void initMock() {
+				when(commandService.findFinishedCommands(any())).thenReturn(List.of(responseCommand).stream());
+				when(commandHandler.isHistorieCommand(any())).thenReturn(true);
+				when(commandHandler.translateOrder(any())).thenReturn(CommandTestFactory.create());
+			}
+
+			@Test
+			void shouldCallService() {
+				historieService.findFinishedCommands(CommandTestFactory.VORGANG_ID).toList();
+
+				verify(commandService).findFinishedCommands(VorgangHeaderTestFactory.ID);
+			}
+
+			@Test
+			void shouldFilterInvalidCommands() {
+				historieService.findFinishedCommands(CommandTestFactory.VORGANG_ID).toList();
+
+				verify(commandHandler).isHistorieCommand(responseCommand);
+			}
+
+			@Test
+			void shouldTranslateCommandOrderByCommandHandler() {
+				historieService.findFinishedCommands(CommandTestFactory.VORGANG_ID).toList();
+
+				verify(commandHandler).translateOrder(responseCommand);
+			}
+		}
+
+	}
+}
\ No newline at end of file
diff --git a/goofy-server/src/test/java/de/itvsh/goofy/kommentar/KommentarByVorgangControllerTest.java b/goofy-server/src/test/java/de/itvsh/goofy/kommentar/KommentarByVorgangControllerTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..6ddb92d97b80bbfab96b6a951babe0e01ba9463b
--- /dev/null
+++ b/goofy-server/src/test/java/de/itvsh/goofy/kommentar/KommentarByVorgangControllerTest.java
@@ -0,0 +1,107 @@
+/*
+ * 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.itvsh.goofy.kommentar;
+
+import static org.assertj.core.api.Assertions.*;
+import static org.mockito.ArgumentMatchers.*;
+import static org.mockito.Mockito.*;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
+
+import java.time.ZonedDateTime;
+import java.util.UUID;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.mockito.ArgumentMatchers;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.springframework.test.web.servlet.MockMvc;
+import org.springframework.test.web.servlet.ResultActions;
+import org.springframework.test.web.servlet.setup.MockMvcBuilders;
+
+import de.itvsh.goofy.kommentar.KommentarController.KommentarByVorgangController;
+import de.itvsh.goofy.vorgang.VorgangHeaderTestFactory;
+
+class KommentarByVorgangControllerTest {
+
+	@InjectMocks
+	private KommentarByVorgangController controller;
+
+	@Mock
+	private KommentarService service;
+
+	@Mock
+	private KommentarModelAssembler modelAssembler;
+
+	private MockMvc mockMvc;
+
+	@BeforeEach
+	void initMockMvc() {
+		mockMvc = MockMvcBuilders.standaloneSetup(controller).build();
+	}
+
+	@Nested
+	class TestGetAll {
+
+		@Test
+		void shouldCallEndpoint() throws Exception {
+			ResultActions callEndpoint = callEndpoint();
+
+			callEndpoint.andExpect(status().isOk());
+		}
+
+		@Test
+		void shouldCallService() throws Exception {
+			callEndpoint();
+
+			verify(service).findByVorgangId(VorgangHeaderTestFactory.ID);
+		}
+
+		@Test
+		void shouldCallModelAssembler() throws Exception {
+			callEndpoint();
+
+			verify(modelAssembler).toCollectionModel(ArgumentMatchers.<Stream<Kommentar>>any(), anyString());
+		}
+
+		private ResultActions callEndpoint() throws Exception {
+			return mockMvc.perform(get(KommentarByVorgangController.KOMMENTAR_BY_VORGANG_PATH + "/" + VorgangHeaderTestFactory.ID + "/kommentars"));
+		}
+
+		@Test
+		void shouldSortByCreatedAt() {
+			var firstId = UUID.randomUUID().toString();
+			Kommentar first = KommentarTestFactory.createBuilder().id(firstId).createdAt(ZonedDateTime.now()).build();
+
+			var kommentarList = controller.sortByCreatedAt(Stream.of(KommentarTestFactory.create(), first)).collect(Collectors.toList());
+
+			assertThat(kommentarList.get(0).getId()).isEqualTo(first.getId());
+			assertThat(kommentarList.get(1).getId()).isEqualTo(KommentarTestFactory.ID);
+		}
+	}
+}
\ No newline at end of file
diff --git a/goofy-server/src/test/java/de/itvsh/goofy/kommentar/KommentarCommandControllerTest.java b/goofy-server/src/test/java/de/itvsh/goofy/kommentar/KommentarCommandControllerTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..7664e060cdec3d312752811b8e065dfafbee92b2
--- /dev/null
+++ b/goofy-server/src/test/java/de/itvsh/goofy/kommentar/KommentarCommandControllerTest.java
@@ -0,0 +1,137 @@
+/*
+ * 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.itvsh.goofy.kommentar;
+
+import static de.itvsh.goofy.kommentar.KommentarCommandTestFactory.*;
+import static org.mockito.ArgumentMatchers.*;
+import static org.mockito.Mockito.*;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.Spy;
+import org.springframework.http.MediaType;
+import org.springframework.test.web.servlet.MockMvc;
+import org.springframework.test.web.servlet.ResultActions;
+import org.springframework.test.web.servlet.setup.MockMvcBuilders;
+
+import de.itvsh.goofy.common.command.CommandTestFactory;
+import de.itvsh.goofy.common.command.CreateCommand;
+import de.itvsh.goofy.kommentar.KommentarCommandController.KommentarCommandByVorgangController;
+import de.itvsh.goofy.vorgang.VorgangHeaderTestFactory;
+
+class KommentarCommandControllerTest {
+
+	@Captor
+	private ArgumentCaptor<CreateCommand> commandCaptor;
+
+	@Nested
+	class TestCreateKommentarCommandByVorgang {
+
+		@Spy
+		@InjectMocks
+		private KommentarCommandByVorgangController controller;
+		@Mock
+		private KommentarService service;
+
+		private MockMvc mockMvc;
+
+		@BeforeEach
+		void init() {
+			mockMvc = MockMvcBuilders.standaloneSetup(controller).build();
+
+			when(service.createKommentar(any(), any())).thenReturn(CommandTestFactory.create());
+		}
+
+		@Test
+		void shouldCallService() throws Exception {
+			doRequest();
+
+			verify(service).createKommentar(any(Kommentar.class), eq(VorgangHeaderTestFactory.ID));
+		}
+
+		@Test
+		void shouldReturnCreated() throws Exception {
+			doRequest().andExpect(status().isCreated());
+		}
+
+		private ResultActions doRequest() throws Exception {
+			return mockMvc.perform(
+					post(KommentarCommandByVorgangController.KOMMENTAR_COMMANDS_BY_VORGANG, VorgangHeaderTestFactory.ID)
+							.content(createValidRequestContent()).contentType(MediaType.APPLICATION_JSON))
+					.andExpect(status().is2xxSuccessful());
+		}
+	}
+
+	@Nested
+	class TestCreateEditKommentarCommand {
+
+		private final static String REPONSE_HEADER = "http://localhost/api/commands/" + KommentarCommandTestFactory.ID;
+
+		@Spy
+		@InjectMocks
+		private KommentarCommandController controller;
+		@Mock
+		private KommentarService service;
+
+		private MockMvc mockMvc;
+
+		@BeforeEach
+		void init() {
+			mockMvc = MockMvcBuilders.standaloneSetup(controller).build();
+
+			when(service.editKommentar(any(), any(), anyLong())).thenReturn(CommandTestFactory.create());
+		}
+
+		@Test
+		void shouldCallService() throws Exception {
+			doRequest();
+
+			verify(service).editKommentar(any(Kommentar.class), eq(KommentarTestFactory.ID), eq(KommentarTestFactory.VERSION));
+		}
+
+		@Test
+		void shouldReturnCreated() throws Exception {
+			doRequest().andExpect(status().isCreated());
+		}
+
+		@Test
+		void shouldRespondWithLocationHeader() throws Exception {
+			doRequest().andExpect(header().string("Location", REPONSE_HEADER));
+		}
+
+		ResultActions doRequest() throws Exception {
+			String content = createValidRequestContent();
+			return mockMvc.perform(post(KommentarCommandController.KOMMENTAR_COMMANDS, KommentarTestFactory.ID, KommentarTestFactory.VERSION)
+					.content(content).contentType(MediaType.APPLICATION_JSON))
+					.andExpect(status().is2xxSuccessful());
+		}
+	}
+}
diff --git a/goofy-server/src/test/java/de/itvsh/goofy/kommentar/KommentarCommandITCase.java b/goofy-server/src/test/java/de/itvsh/goofy/kommentar/KommentarCommandITCase.java
new file mode 100644
index 0000000000000000000000000000000000000000..46c346ff38d1bd5165f893a9d8d67d23b807bd10
--- /dev/null
+++ b/goofy-server/src/test/java/de/itvsh/goofy/kommentar/KommentarCommandITCase.java
@@ -0,0 +1,201 @@
+/*
+ * 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.itvsh.goofy.kommentar;
+
+import static de.itvsh.goofy.kommentar.KommentarCommandTestFactory.*;
+import static org.mockito.ArgumentMatchers.*;
+import static org.mockito.Mockito.*;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
+
+import org.apache.commons.lang3.StringUtils;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.DisplayName;
+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.servlet.AutoConfigureMockMvc;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.boot.test.mock.mockito.MockBean;
+import org.springframework.http.MediaType;
+import org.springframework.security.test.context.support.WithMockUser;
+import org.springframework.test.web.servlet.MockMvc;
+import org.springframework.test.web.servlet.ResultActions;
+
+import de.itvsh.goofy.common.ValidationMessageCodes;
+import de.itvsh.goofy.common.command.CommandOrder;
+import de.itvsh.goofy.common.command.CommandRemoteService;
+import de.itvsh.goofy.common.command.CommandTestFactory;
+import de.itvsh.goofy.kommentar.KommentarCommandController.KommentarCommandByVorgangController;
+import de.itvsh.goofy.vorgang.VorgangHeaderTestFactory;
+
+@AutoConfigureMockMvc
+@SpringBootTest
+@WithMockUser
+class KommentarCommandITCase {
+
+	@Autowired
+	private MockMvc mockMvc;
+	@MockBean
+	private KommentarRemoteService remoteService;
+	@MockBean
+	private CommandRemoteService commandRemoteService;
+
+	@WithMockUser
+	@Nested
+	class TestCreateCommandByKommentarId {
+
+		@BeforeEach
+		void initTest() {
+			when(remoteService.getById(any())).thenReturn(KommentarTestFactory.create());
+			when(commandRemoteService.createCommand(any())).thenReturn(CommandTestFactory.create());
+		}
+
+		@Test
+		void shouldCreateCommand() throws Exception {
+			doRequestByKommentarId(createValidRequestContent()).andExpect(status().isCreated());
+
+			verify(commandRemoteService).createCommand(any());
+		}
+
+		@WithMockUser
+		@DisplayName("should return validation error")
+		@Nested
+		class TestValidation {
+
+			@DisplayName("for null Text")
+			@Test
+			void createCommandWithInvalidText() throws Exception {
+				String content = buildContentWithText(null);
+
+				doRequestByKommentarId(content).andExpect(status().isUnprocessableEntity())
+						.andExpect(jsonPath("$.issues.length()").value(1))
+						.andExpect(jsonPath("$.issues.[0].field").value("kommentar.text"))
+						.andExpect(jsonPath("$.issues.[0].messageCode").value(ValidationMessageCodes.FIELD_IS_EMPTY));
+			}
+
+			@DisplayName("for empty String in Text")
+			@Test
+			void createcommandWithShortText() throws Exception {
+				String content = buildContentWithText(StringUtils.EMPTY);
+
+				doRequestByKommentarId(content).andExpect(status().isUnprocessableEntity())
+						.andExpect(jsonPath("$.issues.[0].field").value("kommentar.text"));
+
+			}
+
+			@DisplayName("for invalid text should have parameter")
+			@Test
+			void minMaxParameter() throws Exception {
+				String content = buildContentWithText(StringUtils.EMPTY);
+
+				doRequestByKommentarId(content).andExpect(status().isUnprocessableEntity())
+						.andExpect(jsonPath("$.issues[0].parameters.length()").value(2));
+			}
+
+			private String buildContentWithText(String text) {
+				return createRequestContent(KommentarCommandTestFactory.createBuilder()
+						.order(CommandOrder.CREATE_ATTACHED_ITEM)
+						.kommentar(KommentarTestFactory.createBuilder().text(text).build())
+						.build());
+			}
+		}
+
+		private ResultActions doRequestByKommentarId(String content) throws Exception {
+			return mockMvc.perform(post(KommentarCommandController.KOMMENTAR_COMMANDS, KommentarTestFactory.ID, KommentarTestFactory.VERSION)
+					.contentType(MediaType.APPLICATION_JSON)
+					.content(content));
+		}
+	}
+
+	@WithMockUser
+	@Nested
+	class TestCreateCommandByVorgangId {
+
+		private static final long RELATION_ID_ON_CREATE = -1;
+
+		@BeforeEach
+		void initTest() {
+			when(remoteService.getById(any())).thenReturn(KommentarTestFactory.create());
+			when(commandRemoteService.createCommand(any())).thenReturn(CommandTestFactory.create());
+		}
+
+		@Test
+		void shouldCreateCommand() throws Exception {
+			doRequestByVorgangId(createValidRequestContent()).andExpect(status().isCreated());
+
+			verify(commandRemoteService).createCommand(any());
+		}
+
+		@WithMockUser
+		@DisplayName("should return validation error")
+		@Nested
+		class TestValidation {
+
+			@DisplayName("for null Text")
+			@Test
+			void createCommandWithInvalidText() throws Exception {
+				String content = buildContentWithText(null);
+
+				doRequestByVorgangId(content).andExpect(status().isUnprocessableEntity())
+						.andExpect(jsonPath("$.issues.length()").value(1))
+						.andExpect(jsonPath("$.issues.[0].field").value("kommentar.text"))
+						.andExpect(jsonPath("$.issues.[0].messageCode").value(ValidationMessageCodes.FIELD_IS_EMPTY));
+			}
+
+			@DisplayName("for empty String in Text")
+			@Test
+			void createcommandWithShortText() throws Exception {
+				String content = buildContentWithText(StringUtils.EMPTY);
+
+				doRequestByVorgangId(content).andExpect(status().isUnprocessableEntity())
+						.andExpect(jsonPath("$.issues.[0].field").value("kommentar.text"));
+
+			}
+
+			@DisplayName("for invalid text should have parameter")
+			@Test
+			void minMaxParameter() throws Exception {
+				String content = buildContentWithText(StringUtils.EMPTY);
+
+				doRequestByVorgangId(content).andExpect(status().isUnprocessableEntity())
+						.andExpect(jsonPath("$.issues[0].parameters.length()").value(2));
+			}
+
+			private String buildContentWithText(String text) {
+				return createRequestContent(KommentarCommandTestFactory.createBuilder()
+						.order(CommandOrder.CREATE_KOMMENTAR)
+						.kommentar(KommentarTestFactory.createBuilder().text(text).build())
+						.build());
+			}
+		}
+
+		private ResultActions doRequestByVorgangId(String content) throws Exception {
+			return mockMvc.perform(post(KommentarCommandByVorgangController.KOMMENTAR_COMMANDS_BY_VORGANG, VorgangHeaderTestFactory.ID,
+					RELATION_ID_ON_CREATE)
+							.contentType(MediaType.APPLICATION_JSON)
+							.content(content));
+		}
+	}
+}
\ No newline at end of file
diff --git a/goofy-server/src/test/java/de/itvsh/goofy/kommentar/KommentarCommandTestFactory.java b/goofy-server/src/test/java/de/itvsh/goofy/kommentar/KommentarCommandTestFactory.java
new file mode 100644
index 0000000000000000000000000000000000000000..f275416ce83addd173b6cb96031a7d356a7d5f40
--- /dev/null
+++ b/goofy-server/src/test/java/de/itvsh/goofy/kommentar/KommentarCommandTestFactory.java
@@ -0,0 +1,61 @@
+/*
+ * 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.itvsh.goofy.kommentar;
+
+import java.util.Objects;
+
+import de.itvsh.goofy.common.command.CommandOrder;
+import de.itvsh.goofy.common.command.CommandTestFactory;
+import de.itvsh.kop.common.test.TestUtils;
+
+public class KommentarCommandTestFactory {
+
+	public static final String ID = CommandTestFactory.ID;
+	public static final CommandOrder ORDER = CommandOrder.CREATE_KOMMENTAR;
+
+	public static KommentarCommand create() {
+		return createBuilder().build();
+	}
+
+	public static KommentarCommand.KommentarCommandBuilder createBuilder() {
+		return KommentarCommand.builder()
+				.id(ID)
+				.order(ORDER)
+				.kommentar(KommentarTestFactory.create());
+	}
+
+	public static String createValidRequestContent() {
+		return createRequestContent(create());
+	}
+
+	public static String createRequestContent(KommentarCommand command) {
+		return TestUtils.loadTextFile("jsonTemplates/command/createCommandWithKommentar.json.tmpl",
+				command.getOrder().name(),
+				addTuedelchen(command.getKommentar().getText()));
+	}
+
+	private static String addTuedelchen(String str) {
+		return Objects.isNull(str) ? null : String.format("\"%s\"", str);
+	}
+}
diff --git a/goofy-server/src/test/java/de/itvsh/goofy/kommentar/KommentarControllerTest.java b/goofy-server/src/test/java/de/itvsh/goofy/kommentar/KommentarControllerTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..e9d439110b85b4c83fc3819b1468d015f4811645
--- /dev/null
+++ b/goofy-server/src/test/java/de/itvsh/goofy/kommentar/KommentarControllerTest.java
@@ -0,0 +1,95 @@
+/*
+ * 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.itvsh.goofy.kommentar;
+
+import static org.mockito.ArgumentMatchers.*;
+import static org.mockito.Mockito.*;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.springframework.test.web.servlet.MockMvc;
+import org.springframework.test.web.servlet.ResultActions;
+import org.springframework.test.web.servlet.setup.MockMvcBuilders;
+
+class KommentarControllerTest {
+
+	private final String PATH = KommentarController.KOMMENTAR_PATH + "/{id}";
+
+	@InjectMocks
+	private KommentarController controller;
+
+	@Mock
+	private KommentarService service;
+
+	@Mock
+	private KommentarModelAssembler modelAssembler;
+
+	private MockMvc mockMvc;
+
+	@BeforeEach
+	void initTest() {
+		mockMvc = MockMvcBuilders.standaloneSetup(controller).build();
+	}
+
+	@Nested
+	class TestGetById {
+
+		private Kommentar kommentar = KommentarTestFactory.create();
+
+		@BeforeEach
+		void mockService() {
+			when(service.getById(any())).thenReturn(kommentar);
+		}
+
+		@Test
+		void shouldReturnValue() throws Exception {
+			ResultActions callEndpoint = callEndpoint();
+
+			callEndpoint.andExpect(status().isOk());
+		}
+
+		@Test
+		void shouldCallService() throws Exception {
+			callEndpoint();
+
+			verify(service).getById(KommentarTestFactory.ID);
+		}
+
+		@Test
+		void shouldCallModelAssembler() throws Exception {
+			callEndpoint();
+
+			verify(modelAssembler).toModel(kommentar);
+		}
+
+		private ResultActions callEndpoint() throws Exception {
+			return mockMvc.perform(get(PATH, KommentarTestFactory.ID));
+		}
+	}
+}
\ No newline at end of file
diff --git a/goofy-server/src/test/java/de/itvsh/goofy/kommentar/KommentarMapperTest.java b/goofy-server/src/test/java/de/itvsh/goofy/kommentar/KommentarMapperTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..090f110faa7c9639bdcb2b52b0e47e3d3076980a
--- /dev/null
+++ b/goofy-server/src/test/java/de/itvsh/goofy/kommentar/KommentarMapperTest.java
@@ -0,0 +1,118 @@
+/*
+ * 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.itvsh.goofy.kommentar;
+
+import static org.mockito.ArgumentMatchers.*;
+import static org.mockito.Mockito.*;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.mapstruct.factory.Mappers;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.Spy;
+
+import static org.assertj.core.api.Assertions.*;
+
+import de.itvsh.goofy.vorgang.GrpcVorgangAttachedItemTestFactory;
+import de.itvsh.kop.pluto.common.grpc.GrpcObjectMapper;
+
+class KommentarMapperTest {
+
+	@Spy
+	@InjectMocks
+	private KommentarMapper mapper = Mappers.getMapper(KommentarMapper.class);
+	@Mock
+	private GrpcObjectMapper grpcObjectMapper;
+
+	@DisplayName("Map from item")
+	@Nested
+	class TestFromItem {
+
+		@BeforeEach
+		void mockMapper() {
+			when(grpcObjectMapper.mapFromGrpc(any())).thenReturn(KommentarTestFactory.createAsMap());
+		}
+
+		@Test
+		void shouldCallGrpcObjectMapper() {
+			mapper.fromItem(GrpcVorgangAttachedItemTestFactory.create());
+
+			verify(grpcObjectMapper).mapFromGrpc(GrpcVorgangAttachedItemTestFactory.ITEM);
+		}
+
+		@Test
+		void shouldCallMapperFromItemMap() {
+			mapper.fromItem(GrpcVorgangAttachedItemTestFactory.create());
+
+			verify(mapper).fromItemMap(KommentarTestFactory.createAsMap(), KommentarTestFactory.VERSION);
+		}
+	}
+
+	@DisplayName("Map from item map")
+	@Nested
+	class TestFromItemMap {
+
+		@Test
+		void shouldMapId() {
+			var kommentar = map();
+
+			assertThat(kommentar.getId()).isEqualTo(KommentarTestFactory.ID);
+		}
+
+		@Test
+		void shouldMapVersion() {
+			var kommentar = map();
+
+			assertThat(kommentar.getVersion()).isEqualTo(KommentarTestFactory.VERSION);
+		}
+
+		@Test
+		void shouldMapCreatedBy() {
+			var kommentar = map();
+
+			assertThat(kommentar.getCreatedBy()).isEqualTo(KommentarTestFactory.CREATED_BY);
+		}
+
+		@Test
+		void shouldMapCreatedAt() {
+			var kommentar = map();
+
+			assertThat(kommentar.getCreatedAt()).isEqualTo(KommentarTestFactory.CREATED_AT);
+		}
+
+		@Test
+		void shouldMapText() {
+			var kommentar = map();
+
+			assertThat(kommentar.getText()).isEqualTo(KommentarTestFactory.TEXT);
+		}
+	}
+
+	private Kommentar map() {
+		return mapper.fromItemMap(KommentarTestFactory.createAsMap(), KommentarTestFactory.VERSION);
+	}
+}
diff --git a/goofy-server/src/test/java/de/itvsh/goofy/kommentar/KommentarModelAssemblerTest.java b/goofy-server/src/test/java/de/itvsh/goofy/kommentar/KommentarModelAssemblerTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..79aa91ceb04ad3e475e410cd629cb4eba4011317
--- /dev/null
+++ b/goofy-server/src/test/java/de/itvsh/goofy/kommentar/KommentarModelAssemblerTest.java
@@ -0,0 +1,114 @@
+/*
+ * 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.itvsh.goofy.kommentar;
+
+import static de.itvsh.goofy.common.UserProfileUrlProviderTestFactory.*;
+import static org.assertj.core.api.Assertions.*;
+
+import java.util.Collections;
+
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.springframework.hateoas.EntityModel;
+import org.springframework.hateoas.IanaLinkRelations;
+import org.springframework.hateoas.Link;
+import org.springframework.hateoas.server.EntityLinks;
+
+import de.itvsh.goofy.common.UserProfileUrlProvider;
+import de.itvsh.goofy.common.UserProfileUrlProviderTestFactory;
+import de.itvsh.goofy.vorgang.VorgangHeaderTestFactory;
+
+class KommentarModelAssemblerTest {
+
+	private final String PATH = KommentarController.KOMMENTAR_PATH + "/";
+	private static final String REL_CREATED_BY = "createdBy";
+	private static final String USER_MANAGER_URL = UserProfileUrlProviderTestFactory.ROOT_URL
+			+ UserProfileUrlProviderTestFactory.USER_PROFILES_API_PATH;
+
+	@InjectMocks
+	private KommentarModelAssembler modelAssembler;
+
+	@Mock
+	private EntityLinks entityLinks;
+
+	@Nested
+	class TestLinksOnModel {
+		final String COMMAND_BY_KOMMENTAR_PATH = //
+				KommentarCommandController.KOMMENTAR_COMMANDS
+						.replace("{kommentarId}", KommentarTestFactory.ID)
+						.replace("{kommentarVersion}", String.valueOf(KommentarTestFactory.VERSION));
+
+		@Test
+		void shouldHaveSelfLink() {
+			var link = toModel().getLink(IanaLinkRelations.SELF);
+
+			assertThat(link).isPresent();
+			assertThat(link.get().getHref()).isEqualTo(PATH + KommentarTestFactory.ID);
+		}
+
+		@Test
+		void shouldHaveEditLink() {
+			var link = toModel();
+
+			assertThat(link.getLink(KommentarModelAssembler.REL_EDIT)).isPresent().get().extracting(Link::getHref)
+					.isEqualTo(COMMAND_BY_KOMMENTAR_PATH);
+		}
+
+		@Test
+		void shouldHaveCreatedByLink() {
+			UserProfileUrlProvider urlProvider = new UserProfileUrlProvider();
+			initUserProfileUrlProvider(urlProvider);
+
+			var link = toModel();
+
+			assertThat(link.getLink(REL_CREATED_BY)).isPresent().get().extracting(Link::getHref)
+					.isEqualTo(USER_MANAGER_URL + KommentarTestFactory.CREATED_BY);
+		}
+
+		EntityModel<Kommentar> toModel() {
+			return modelAssembler.toModel(KommentarTestFactory.create());
+		}
+	}
+
+	@Nested
+	class TestLinksOnCollectionModel {
+
+		@Test
+		void shouldHaveCreateKommentarLink() {
+			UserProfileUrlProvider urlProvider = new UserProfileUrlProvider();
+			initUserProfileUrlProvider(urlProvider);
+
+			var collectionModel = modelAssembler.toCollectionModel(Collections.singleton(KommentarTestFactory.create()).stream(),
+					VorgangHeaderTestFactory.ID);
+
+			var link = collectionModel.getLink(KommentarModelAssembler.REL_CREATE);
+
+			assertThat(link).isPresent();
+			assertThat(link.get().getHref()).isEqualTo(
+					"/api/vorgangs/" + VorgangHeaderTestFactory.ID + "/kommentarCommands");
+		}
+	}
+}
\ No newline at end of file
diff --git a/goofy-server/src/test/java/de/itvsh/goofy/kommentar/KommentarRemoteServiceTest.java b/goofy-server/src/test/java/de/itvsh/goofy/kommentar/KommentarRemoteServiceTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..290953882b05f29ac739bf50fa972f9f70f2f159
--- /dev/null
+++ b/goofy-server/src/test/java/de/itvsh/goofy/kommentar/KommentarRemoteServiceTest.java
@@ -0,0 +1,127 @@
+/*
+ * 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.itvsh.goofy.kommentar;
+
+import static org.mockito.ArgumentMatchers.*;
+import static org.mockito.Mockito.*;
+
+import java.util.stream.Stream;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.Spy;
+
+import de.itvsh.goofy.vorgang.GrpcVorgangAttachedItemTestFactory;
+import de.itvsh.goofy.vorgang.VorgangHeaderTestFactory;
+import de.itvsh.ozg.pluto.vorgangAttachedItem.GrpcFindVorgangAttachedItemRequest;
+import de.itvsh.ozg.pluto.vorgangAttachedItem.GrpcFindVorgangAttachedItemResponse;
+import de.itvsh.ozg.pluto.vorgangAttachedItem.GrpcVorgangAttachedItem;
+import de.itvsh.ozg.pluto.vorgangAttachedItem.GrpcVorgangAttachedItemRequest;
+import de.itvsh.ozg.pluto.vorgangAttachedItem.GrpcVorgangAttachedItemResponse;
+import de.itvsh.ozg.pluto.vorgangAttachedItem.VorgangAttachedItemServiceGrpc.VorgangAttachedItemServiceBlockingStub;
+
+class KommentarRemoteServiceTest {
+
+	@Spy
+	@InjectMocks
+	private KommentarRemoteService service;
+	@Mock
+	private VorgangAttachedItemServiceBlockingStub vorgangAttachedItemServiceStub;
+	@Mock
+	private KommentarMapper mapper;
+
+	private GrpcVorgangAttachedItem kommentar = GrpcVorgangAttachedItemTestFactory.create();
+
+	@DisplayName("Find by vorgangId")
+	@Nested
+	class TestFindByVorgangId {
+
+		private GrpcFindVorgangAttachedItemRequest request = GrpcFindVorgangAttachedItemRequest.newBuilder()
+				.setVorgangId(VorgangHeaderTestFactory.ID)
+				.setItemName(KommentarRemoteService.ITEM_NAME)
+				.build();
+		private GrpcFindVorgangAttachedItemResponse response = GrpcFindVorgangAttachedItemResponse.newBuilder()
+				.clearVorgangAttachedItems()
+				.addVorgangAttachedItems(kommentar).build();
+
+		@BeforeEach
+		void mockStub() {
+			when(vorgangAttachedItemServiceStub.find(any())).thenReturn(response);
+		}
+
+		@Test
+		void shouldCallStub() {
+			service.findByVorgangId(VorgangHeaderTestFactory.ID);
+
+			verify(vorgangAttachedItemServiceStub).find(request);
+		}
+
+		@Test
+		void shouldCallMapper() {
+			var result = service.findByVorgangId(VorgangHeaderTestFactory.ID);
+			collectStreamElementsToTriggerLazyStream(result);
+
+			verify(mapper).fromItem(any());
+		}
+
+		private void collectStreamElementsToTriggerLazyStream(Stream<Kommentar> stream) {
+			stream.toList();
+		}
+	}
+
+	@DisplayName("Find by id")
+	@Nested
+	class TestGetById {
+		private GrpcVorgangAttachedItemResponse response = GrpcVorgangAttachedItemResponse.newBuilder()
+				.setVorgangAttachedItem(GrpcVorgangAttachedItemTestFactory.create())
+				.build();
+		private GrpcVorgangAttachedItemRequest request = GrpcVorgangAttachedItemRequest.newBuilder()
+				.setId(KommentarTestFactory.ID)
+				.build();
+
+		@BeforeEach
+		void mockStub() {
+			when(vorgangAttachedItemServiceStub.getById(any())).thenReturn(response);
+			when(mapper.fromItem(any())).thenReturn(KommentarTestFactory.create());
+		}
+
+		@Test
+		void shouldCallStub() {
+			service.getById(KommentarTestFactory.ID);
+
+			verify(vorgangAttachedItemServiceStub).getById(request);
+		}
+
+		@Test
+		void shouldCallMapper() {
+			service.getById(KommentarTestFactory.ID);
+
+			verify(mapper).fromItem(any());
+		}
+	}
+}
diff --git a/goofy-server/src/test/java/de/itvsh/goofy/kommentar/KommentarServiceTest.java b/goofy-server/src/test/java/de/itvsh/goofy/kommentar/KommentarServiceTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..d0d7d3f020820329e61013df0148052d5df0ee3c
--- /dev/null
+++ b/goofy-server/src/test/java/de/itvsh/goofy/kommentar/KommentarServiceTest.java
@@ -0,0 +1,188 @@
+/*
+ * 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.itvsh.goofy.kommentar;
+
+import static org.mockito.ArgumentMatchers.*;
+import static org.mockito.Mockito.*;
+
+import java.time.ZonedDateTime;
+import java.time.temporal.ChronoUnit;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.Spy;
+
+import static org.assertj.core.api.Assertions.*;
+
+import de.itvsh.goofy.common.attacheditem.VorgangAttachedItemService;
+import de.itvsh.goofy.common.command.Command;
+import de.itvsh.goofy.common.command.CommandTestFactory;
+import de.itvsh.goofy.common.user.CurrentUserService;
+import de.itvsh.goofy.common.user.UserProfileTestFactory;
+import de.itvsh.goofy.vorgang.VorgangHeaderTestFactory;
+
+class KommentarServiceTest {
+
+	@Spy
+	@InjectMocks
+	private KommentarService service;
+	@Mock
+	private KommentarRemoteService remoteService;
+	@Mock
+	private VorgangAttachedItemService vorgangAttachedItemService;
+	@Mock
+	private CurrentUserService currentUserService;
+
+	@DisplayName("Create kommentar")
+	@Nested
+	class TestCreateKommentar {
+
+		@DisplayName("should")
+		@Nested
+		class TestCalls {
+
+			@BeforeEach
+			void mockServices() {
+				doReturn(KommentarTestFactory.create()).when(service).addCreated(any());
+				when(vorgangAttachedItemService.createNewKommentar(any(), any())).thenReturn(CommandTestFactory.create());
+			}
+
+			@DisplayName("add created")
+			@Test
+			void shouldAddCreated() {
+				callCreateKommentar();
+
+				verify(service).addCreated(any(Kommentar.class));
+			}
+
+			@DisplayName("call vorgangattacheditem service")
+			@Test
+			void shouldCallVorgangAttachedItemService() {
+				callCreateKommentar();
+
+				verify(vorgangAttachedItemService).createNewKommentar(any(Kommentar.class), eq(VorgangHeaderTestFactory.ID));
+			}
+		}
+
+		@DisplayName("Add created")
+		@Nested
+		class TestAddCreated {
+
+			@BeforeEach
+			void mockServices() {
+				when(currentUserService.getUserId()).thenReturn(UserProfileTestFactory.ID);
+			}
+
+			@Test
+			void shouldSetCreatedAt() throws Exception {
+				var kommentar = callAddCreated();
+
+				assertThat(kommentar.getCreatedAt()).isNotNull().isCloseTo(ZonedDateTime.now(), within(2, ChronoUnit.SECONDS));
+			}
+
+			@Test
+			void shouldSetCreatedBy() throws Exception {
+				var kommentar = callAddCreated();
+
+				assertThat(kommentar.getCreatedBy()).isEqualTo(UserProfileTestFactory.ID.toString());
+			}
+
+			private Kommentar callAddCreated() {
+				return service.addCreated(KommentarTestFactory.createBuilder().createdAt(null).createdBy(null).build());
+			}
+		}
+
+		private Command callCreateKommentar() {
+			return service.createKommentar(KommentarTestFactory.create(), VorgangHeaderTestFactory.ID);
+		}
+	}
+
+	@DisplayName("Edit kommentar")
+	@Nested
+	class TestEditKommentar {
+
+		@Captor
+		private ArgumentCaptor<Kommentar> kommentarCaptor;
+
+		@BeforeEach
+		void mockKommentarService() {
+			when(remoteService.getById(anyString())).thenReturn(KommentarTestFactory.createBuilder().text("text needs to be override").build());
+		}
+
+		@Test
+		void shouldCallGetById() {
+			callEditKommentar();
+
+			verify(service).getById(KommentarTestFactory.ID);
+		}
+
+		@Test
+		void shouldReplaceText() {
+			callEditKommentar();
+
+			verify(vorgangAttachedItemService).editKommentar(kommentarCaptor.capture(), eq(KommentarTestFactory.ID),
+					eq(KommentarTestFactory.VERSION));
+			assertThat(kommentarCaptor.getValue().getText()).isEqualTo(KommentarTestFactory.TEXT);
+		}
+
+		@Test
+		void shouldCallVorgangAttachedItemService() {
+			callEditKommentar();
+
+			verify(vorgangAttachedItemService).editKommentar(any(Kommentar.class), eq(KommentarTestFactory.ID), eq(KommentarTestFactory.VERSION));
+		}
+
+		private Command callEditKommentar() {
+			return service.editKommentar(KommentarTestFactory.create(), KommentarTestFactory.ID, KommentarTestFactory.VERSION);
+		}
+	}
+
+	@Nested
+	class TestGetById {
+
+		@Test
+		void shouldCallRemoteService() {
+			service.getById(KommentarTestFactory.ID);
+
+			verify(remoteService).getById(KommentarTestFactory.ID);
+		}
+	}
+
+	@Nested
+	class TestFindByVorgangId {
+
+		@Test
+		void shouldCallRemoteService() {
+			service.findByVorgangId(VorgangHeaderTestFactory.ID);
+
+			verify(remoteService).findByVorgangId(VorgangHeaderTestFactory.ID);
+		}
+	}
+}
diff --git a/goofy-server/src/test/java/de/itvsh/goofy/kommentar/KommentarTestFactory.java b/goofy-server/src/test/java/de/itvsh/goofy/kommentar/KommentarTestFactory.java
new file mode 100644
index 0000000000000000000000000000000000000000..7fb92bf4d98526faf1833bc3185f91e82e399ce3
--- /dev/null
+++ b/goofy-server/src/test/java/de/itvsh/goofy/kommentar/KommentarTestFactory.java
@@ -0,0 +1,70 @@
+/*
+ * 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.itvsh.goofy.kommentar;
+
+import java.time.ZonedDateTime;
+import java.util.Map;
+import java.util.UUID;
+
+import com.thedeanda.lorem.Lorem;
+import com.thedeanda.lorem.LoremIpsum;
+
+import de.itvsh.goofy.common.user.UserProfileTestFactory;
+import de.itvsh.goofy.vorgang.VorgangHeaderTestFactory;
+
+public class KommentarTestFactory {
+
+	private static Lorem lorem = LoremIpsum.getInstance();
+
+	public static final String ID = UUID.randomUUID().toString();
+	public static final long VERSION = 73;
+
+	public static final String CREATED_BY = UserProfileTestFactory.ID.toString();
+	public static final String CREATED_AT_STR = "2021-01-10T10:30:00Z";
+	public static final ZonedDateTime CREATED_AT = ZonedDateTime.parse(CREATED_AT_STR);
+
+	public static final String TEXT = lorem.getWords(15);
+
+	public static Kommentar create() {
+		return createBuilder().build();
+	}
+
+	public static Kommentar.KommentarBuilder createBuilder() {
+		return Kommentar.builder()
+				.id(ID)
+				.vorgangId(VorgangHeaderTestFactory.ID)
+				.version(VERSION)
+				.text(TEXT)
+				.createdBy(CREATED_BY)
+				.createdAt(CREATED_AT);
+	}
+
+	public static Map<String, Object> createAsMap() {
+		return Map.of(
+				KommentarMapper.ID, ID,
+				KommentarMapper.TEXT, TEXT,
+				KommentarMapper.CREATED_BY, CREATED_BY,
+				KommentarMapper.CREATED_AT, CREATED_AT_STR);
+	}
+}
diff --git a/goofy-server/src/test/java/de/itvsh/goofy/postfach/GrpcPostfachMailTestFactory.java b/goofy-server/src/test/java/de/itvsh/goofy/postfach/GrpcPostfachMailTestFactory.java
new file mode 100644
index 0000000000000000000000000000000000000000..62f52df85d5fa33e4864d422f70768df92bafc14
--- /dev/null
+++ b/goofy-server/src/test/java/de/itvsh/goofy/postfach/GrpcPostfachMailTestFactory.java
@@ -0,0 +1,70 @@
+/*
+ * 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.itvsh.goofy.postfach;
+
+import static de.itvsh.goofy.postfach.PostfachMailTestFactory.*;
+
+import java.util.Arrays;
+
+import de.itvsh.goofy.common.binaryfile.BinaryFileTestFactory;
+import de.itvsh.ozg.mail.postfach.GrpcDirection;
+import de.itvsh.ozg.mail.postfach.GrpcFindPostfachMailResponse;
+import de.itvsh.ozg.mail.postfach.GrpcFindPostfachMailsResponse;
+import de.itvsh.ozg.mail.postfach.GrpcPostfachMail;
+
+public class GrpcPostfachMailTestFactory {
+
+	public static GrpcPostfachMail create() {
+		return createBuilder().build();
+	}
+
+	public static GrpcPostfachMail.Builder createBuilder() {
+		return GrpcPostfachMail.newBuilder()
+				.setId(ID)
+				.setVorgangId(VORGANG_ID)
+				.setPostfachId(POSTFACH_ID)
+				.setCreatedAt(CREATED_AT_STR)
+				.setCreatedBy(CREATED_BY.toString())
+				.setSentAt(SENT_AT_STR)
+				.setSentSuccessful(SENT_SUCCESSFUL)
+				.setMessageCode(MESSAGE_CODE)
+				.setDirection(GrpcDirection.OUT)
+				.setReplyOption(REPLY_OPTION.name())
+				.setSubject(SUBJECT)
+				.setMailBody(MAIL_BODY)
+				.addAttachment(BinaryFileTestFactory.ID);
+	}
+
+	public static GrpcFindPostfachMailsResponse createFindPostfachMailsResponse() {
+		return GrpcFindPostfachMailsResponse.newBuilder()
+				.addAllMails(Arrays.asList(create()))
+				.build();
+	}
+
+	public static GrpcFindPostfachMailResponse createFindPostfachMailResponse() {
+		return GrpcFindPostfachMailResponse.newBuilder()
+				.setNachricht(create())
+				.build();
+	}
+}
\ No newline at end of file
diff --git a/goofy-server/src/test/java/de/itvsh/goofy/postfach/PostfachMailCommandControllerTest.java b/goofy-server/src/test/java/de/itvsh/goofy/postfach/PostfachMailCommandControllerTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..99507542223557c88e854baad788598e9357eb81
--- /dev/null
+++ b/goofy-server/src/test/java/de/itvsh/goofy/postfach/PostfachMailCommandControllerTest.java
@@ -0,0 +1,101 @@
+/*
+ * 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.itvsh.goofy.postfach;
+
+import static org.mockito.ArgumentMatchers.*;
+import static org.mockito.Mockito.*;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.springframework.http.MediaType;
+import org.springframework.test.web.servlet.MockMvc;
+import org.springframework.test.web.servlet.ResultActions;
+import org.springframework.test.web.servlet.setup.MockMvcBuilders;
+
+import de.itvsh.goofy.common.command.CommandController.CommandByRelationController;
+import de.itvsh.goofy.common.command.CommandTestFactory;
+import de.itvsh.goofy.common.command.CreateCommand;
+import de.itvsh.goofy.postfach.PostfachMailController.PostfachMailCommandController;
+import de.itvsh.goofy.vorgang.VorgangHeaderTestFactory;
+
+class PostfachMailCommandControllerTest {
+
+	@InjectMocks
+	private PostfachMailCommandController controller;
+	@Mock
+	private PostfachMailService service;
+	@Mock
+	private CommandByRelationController commandByRelationController;
+
+	private MockMvc mockMvc;
+
+	@BeforeEach
+	void initMockMvc() {
+		mockMvc = MockMvcBuilders.standaloneSetup(controller).build();
+	}
+
+	@Nested
+	class TestCreateCommand {
+
+		@Captor
+		private ArgumentCaptor<CreateCommand> createCommandCaptor;
+
+		@BeforeEach
+		void mockController() {
+			when(commandByRelationController.createCommand(any(CreateCommand.class))).thenReturn(CommandTestFactory.create());
+		}
+
+		@Test
+		void shouldCallCommandByRelationController() throws Exception {
+			doRequest();
+
+			verify(commandByRelationController).createCommand(createCommandCaptor.capture());
+		}
+
+		@Test
+		void shouldCallServiceResend() throws Exception {
+			doRequest();
+
+			verify(service).resendPostfachMail(CommandTestFactory.ID, PostfachMailTestFactory.ID);
+		}
+
+		@Test
+		void shouldReturnResponse() throws Exception {
+			doRequest().andExpect(status().isCreated());
+		}
+
+		private ResultActions doRequest() throws Exception {
+			return mockMvc.perform(post("/api/vorgangs/" + VorgangHeaderTestFactory.ID + "/postfachMails/" + PostfachMailTestFactory.ID + "/commands")
+					.contentType(MediaType.APPLICATION_JSON)
+					.content(PostfachMailTestFactory.buildResendPostfachMailContent()));
+		}
+	}
+}
\ No newline at end of file
diff --git a/goofy-server/src/test/java/de/itvsh/goofy/postfach/PostfachMailControllerTest.java b/goofy-server/src/test/java/de/itvsh/goofy/postfach/PostfachMailControllerTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..99cd81c922032994f8984c9ff2867b22850cf7d1
--- /dev/null
+++ b/goofy-server/src/test/java/de/itvsh/goofy/postfach/PostfachMailControllerTest.java
@@ -0,0 +1,281 @@
+/*
+ * 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.itvsh.goofy.postfach;
+
+import static org.assertj.core.api.Assertions.*;
+import static org.mockito.ArgumentMatchers.*;
+import static org.mockito.Mockito.*;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.Date;
+import java.util.Optional;
+import java.util.stream.Stream;
+
+import org.apache.commons.lang3.StringUtils;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Disabled;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.mockito.ArgumentMatchers;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.Spy;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.MediaType;
+import org.springframework.test.web.servlet.MockMvc;
+import org.springframework.test.web.servlet.ResultActions;
+import org.springframework.test.web.servlet.setup.MockMvcBuilders;
+import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
+
+import de.itvsh.goofy.common.binaryfile.BinaryFileController;
+import de.itvsh.goofy.vorgang.Antragsteller;
+import de.itvsh.goofy.vorgang.EingangTestFactory;
+import de.itvsh.goofy.vorgang.VorgangController;
+import de.itvsh.goofy.vorgang.VorgangHeaderTestFactory;
+import de.itvsh.goofy.vorgang.VorgangWithEingang;
+import de.itvsh.goofy.vorgang.VorgangWithEingangTestFactory;
+import lombok.SneakyThrows;
+
+class PostfachMailControllerTest {
+
+	@Spy
+	@InjectMocks // NOSONAR
+	private PostfachMailController controller;
+	@Mock
+	private PostfachMailService service;
+	@Mock
+	private PostfachMailModelAssembler assembler;
+	@Mock
+	private VorgangController vorgangController;
+	@Mock
+	private BinaryFileController binaryFileController;
+
+	private MockMvc mockMvc;
+
+	@BeforeEach
+	void initTest() {
+		mockMvc = MockMvcBuilders.standaloneSetup(controller).build();
+	}
+
+	@DisplayName("Get all")
+	@Nested
+	class TestGetAll {
+
+		@BeforeEach
+		void mockVorgangController() {
+			when(vorgangController.getVorgang(anyString())).thenReturn(VorgangWithEingangTestFactory.create());
+		}
+
+		@Test
+		void shouldCallService() throws Exception {
+			doRequest();
+
+			verify(service).getAll(VorgangHeaderTestFactory.ID);
+		}
+
+		@Test
+		void shouldCallVorgangController() throws Exception {
+			doRequest();
+
+			verify(vorgangController).getVorgang(VorgangHeaderTestFactory.ID);
+		}
+
+		@Test
+		void shouldCallModelAssembler() throws Exception {
+			doRequest();
+
+			verify(assembler).toCollectionModel(ArgumentMatchers.<Stream<PostfachMail>>any(), any(),
+					ArgumentMatchers.<Optional<String>>any());
+		}
+
+		private void doRequest() throws Exception {
+			mockMvc.perform(get(PostfachMailController.PATH + "?" + PostfachMailController.PARAM_VORGANG_ID + "=" + VorgangHeaderTestFactory.ID))
+					.andExpect(status().isOk());
+		}
+	}
+
+	@DisplayName("Get all as pdf")
+	@Nested
+	class TestGetAllAsPdf {
+
+		@Mock
+		private StreamingResponseBody streamingBody;
+		private VorgangWithEingang vorgang = VorgangWithEingangTestFactory.create();
+
+		@BeforeEach
+		void mockService() {
+			when(vorgangController.getVorgang(anyString())).thenReturn(vorgang);
+		}
+
+		@Test
+		void shouldGetVorgang() {
+			doRequest();
+
+			verify(vorgangController).getVorgang(VorgangHeaderTestFactory.ID);
+		}
+
+		@Disabled("FIXME: Kippt um, wenn man alles Tests ausfuehrt")
+		@Test
+		void shouldCallService() {
+			doRequest();
+
+			verify(service).getAllAsPdf(eq(vorgang), any(OutputStream.class));
+		}
+
+		@Test
+		void shouldBuildResponseEntity() {
+			doReturn(streamingBody).when(controller).createDownloadStreamingBody(vorgang);
+
+			doRequest();
+
+			verify(controller).buildResponseEntity(vorgang, streamingBody);
+		}
+
+		@SneakyThrows
+		@Test
+		void shouldReturnResponse() {
+			doReturn(streamingBody).when(controller).createDownloadStreamingBody(vorgang);
+
+			doRequest()
+					.andExpect(header().string(HttpHeaders.CONTENT_DISPOSITION,
+							String.format(PostfachMailController.PDF_NAME_TEMPLATE, VorgangHeaderTestFactory.NUMMER,
+									PostfachMailController.PDF_NAME_DATE_FORMATTER.format(new Date()))))
+					.andExpect(content().contentType(MediaType.APPLICATION_PDF));
+		}
+
+		@SneakyThrows
+		private ResultActions doRequest() {
+			return mockMvc
+					.perform(get(PostfachMailController.PATH + "?" + PostfachMailController.PARAM_VORGANG_ID + "=" + VorgangHeaderTestFactory.ID)
+							.accept(MediaType.APPLICATION_PDF_VALUE))
+					.andExpect(status().isOk());
+		}
+	}
+
+	@Nested
+	class TestCreateDownloadStreamingBody {
+
+		@Mock
+		private OutputStream out;
+		private VorgangWithEingang vorgang = VorgangWithEingangTestFactory.create();
+
+		@Test
+		void shouldCallServiceGetAllAsPdf() throws IOException {
+			controller.createDownloadStreamingBody(vorgang).writeTo(out);
+
+			verify(service).getAllAsPdf(eq(vorgang), any());
+		}
+	}
+
+	@DisplayName("Get postfachId")
+	@Nested
+	class TestGetPostfachId {
+
+		@DisplayName("without antragsteller")
+		@Nested
+		class TestWithoutAntragsteller {
+
+			@Test
+			void shouldReturnEmptyOptional() {
+				var result = controller.getPostfachId(buildVorgangWithoutAntragsteller());
+
+				assertThat(result).isNotPresent();
+			}
+
+			private VorgangWithEingang buildVorgangWithoutAntragsteller() {
+				return VorgangWithEingangTestFactory.createBuilder().eingang(EingangTestFactory.createBuilder().antragsteller(null).build()).build();
+			}
+		}
+
+		@DisplayName("with empty postfachId")
+		@Nested
+		class TestWithEmptyPostfachId {
+
+			@DisplayName("should proceed with empty postfachId")
+			@Test
+			void shouldReturnEmptyOptional() {
+				var result = controller.getPostfachId(buildVorgangWithEmptyPostfachId());
+
+				assertThat(result).isNotPresent();
+			}
+
+			private VorgangWithEingang buildVorgangWithEmptyPostfachId() {
+				return VorgangWithEingangTestFactory.createBuilder()
+						.eingang(EingangTestFactory.createBuilder()
+								.antragsteller(Antragsteller.builder().postfachId(StringUtils.EMPTY).build())
+								.build())
+						.build();
+			}
+		}
+	}
+
+	@DisplayName("Find postfach attachments")
+	@Nested
+	class TestFindPostfachAttachments {
+
+		@BeforeEach
+		void init() {
+			when(service.findById(any())).thenReturn(PostfachMailTestFactory.create());
+		}
+
+		@Test
+		void shouldReturnSuccess() throws Exception {
+			doRequest().andExpect(status().isOk());
+		}
+
+		@Test
+		void shouldCallService() throws Exception {
+			doRequest();
+
+			verify(service).findById(PostfachNachrichtId.from(PostfachMailTestFactory.ID));
+		}
+
+		@Test
+		void shouldCallFileController() throws Exception {
+			doRequest();
+
+			verify(binaryFileController).getFiles(PostfachMailTestFactory.ATTACHMENTS);
+		}
+
+		private ResultActions doRequest() throws Exception {
+			return mockMvc.perform(get(PostfachMailController.PATH + "/{nachrichtId}/attachments", PostfachMailTestFactory.ID));
+		}
+	}
+
+	@DisplayName("Is postfach configured")
+	@Nested
+	class TestIsPostfachConfigured {
+
+		@Test
+		void shouldCallService() {
+			controller.isPostfachConfigured();
+
+			verify(service).isPostfachConfigured();
+		}
+	}
+}
\ No newline at end of file
diff --git a/goofy-server/src/test/java/de/itvsh/goofy/postfach/PostfachMailITCase.java b/goofy-server/src/test/java/de/itvsh/goofy/postfach/PostfachMailITCase.java
new file mode 100644
index 0000000000000000000000000000000000000000..1c88d94109514117d63bc98f563ea33b5a14e344
--- /dev/null
+++ b/goofy-server/src/test/java/de/itvsh/goofy/postfach/PostfachMailITCase.java
@@ -0,0 +1,89 @@
+/*
+ * 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.itvsh.goofy.postfach;
+
+import static org.mockito.ArgumentMatchers.*;
+import static org.mockito.Mockito.*;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.mockito.Mock;
+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.boot.test.mock.mockito.SpyBean;
+import org.springframework.security.test.context.support.WithMockUser;
+import org.springframework.test.util.ReflectionTestUtils;
+
+import de.itvsh.goofy.CallScope;
+import de.itvsh.goofy.vorgang.EingangTestFactory;
+import de.itvsh.goofy.vorgang.VorgangController;
+import de.itvsh.goofy.vorgang.VorgangHeaderTestFactory;
+import de.itvsh.goofy.vorgang.VorgangWithEingangTestFactory;
+import de.itvsh.ozg.mail.postfach.PostfachServiceGrpc.PostfachServiceBlockingStub;
+
+@SpringBootTest
+public class PostfachMailITCase {
+
+	@Autowired
+	private PostfachMailController controller;
+
+	@SpyBean
+	private PostfachMailRemoteService postfachRemoteService;
+	@Mock
+	private PostfachServiceBlockingStub serviceStub;
+	@MockBean
+	private VorgangController vorgangController;
+
+	@Autowired
+	private CallScope callScope;
+
+	@BeforeEach
+	void initTest() {
+		ReflectionTestUtils.setField(postfachRemoteService, "serviceStub", serviceStub);
+
+		callScope.startScope();
+	}
+
+	@Nested
+	@WithMockUser
+	class TestGetAll {
+
+		@BeforeEach
+		void mock() {
+			when(serviceStub.findPostfachMails(any())).thenReturn(GrpcPostfachMailTestFactory.createFindPostfachMailsResponse());
+			when(vorgangController.getVorgang(anyString())).thenReturn(
+					VorgangWithEingangTestFactory.createBuilder().eingang(EingangTestFactory.createBuilder().antragsteller(null).build()).build());
+		}
+
+		@Test
+		void shouldProceedWithoutAntragsteller() {
+			controller.getAll(VorgangHeaderTestFactory.ID);
+
+			verify(vorgangController).getVorgang(VorgangHeaderTestFactory.ID);
+			verify(postfachRemoteService).findPostfachMails(VorgangHeaderTestFactory.ID);
+		}
+	}
+}
\ No newline at end of file
diff --git a/goofy-server/src/test/java/de/itvsh/goofy/postfach/PostfachMailMapperTest.java b/goofy-server/src/test/java/de/itvsh/goofy/postfach/PostfachMailMapperTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..b95f46d6beed27e99ca8fc062d45c7744b7d9406
--- /dev/null
+++ b/goofy-server/src/test/java/de/itvsh/goofy/postfach/PostfachMailMapperTest.java
@@ -0,0 +1,68 @@
+/*
+ * 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.itvsh.goofy.postfach;
+
+import static org.mockito.ArgumentMatchers.*;
+import static org.mockito.Mockito.*;
+
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.mapstruct.factory.Mappers;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.Spy;
+
+import static org.assertj.core.api.Assertions.*;
+
+import de.itvsh.goofy.common.TimeMapper;
+import de.itvsh.goofy.common.user.UserIdMapper;
+import de.itvsh.ozg.mail.postfach.GrpcPostfachMail;
+
+class PostfachMailMapperTest {
+
+	@InjectMocks // NOSONAR
+	private PostfachMailMapper mapper = Mappers.getMapper(PostfachMailMapper.class);
+	@Spy
+	private UserIdMapper userIdMapper = Mappers.getMapper(UserIdMapper.class);
+
+	@Mock
+	private TimeMapper timeMapper;
+
+	@Nested
+	class TestGrpcPostfachMailToPostfachMail {
+
+		@Test
+		void shouldMap() {
+			when(timeMapper.parseString(PostfachMailTestFactory.CREATED_AT_STR)).thenReturn(PostfachMailTestFactory.CREATED_AT);
+			when(timeMapper.parseString(PostfachMailTestFactory.SENT_AT_STR)).thenReturn(PostfachMailTestFactory.SENT_AT);
+
+			GrpcPostfachMail grpcPostfachMail = GrpcPostfachMailTestFactory.create();
+
+			var postfachMail = mapper.toPostfachMail(grpcPostfachMail);
+
+			verify(timeMapper, times(2)).parseString(anyString());
+			assertThat(postfachMail).isNotNull().usingRecursiveComparison().isEqualTo(PostfachMailTestFactory.create());
+		}
+	}
+}
\ No newline at end of file
diff --git a/goofy-server/src/test/java/de/itvsh/goofy/postfach/PostfachMailModelAssemblerTest.java b/goofy-server/src/test/java/de/itvsh/goofy/postfach/PostfachMailModelAssemblerTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..1214f6a74d39fb64a904f51852fd8f9f3cd27951
--- /dev/null
+++ b/goofy-server/src/test/java/de/itvsh/goofy/postfach/PostfachMailModelAssemblerTest.java
@@ -0,0 +1,293 @@
+/*
+ * 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.itvsh.goofy.postfach;
+
+import static org.assertj.core.api.Assertions.*;
+import static org.mockito.Mockito.*;
+
+import java.util.List;
+import java.util.Optional;
+import java.util.UUID;
+import java.util.stream.Stream;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.springframework.hateoas.CollectionModel;
+import org.springframework.hateoas.EntityModel;
+import org.springframework.hateoas.IanaLinkRelations;
+import org.springframework.hateoas.Link;
+
+import de.itvsh.goofy.common.user.UserId;
+import de.itvsh.goofy.common.user.UserManagerUrlProvider;
+import de.itvsh.goofy.vorgang.VorgangHeaderTestFactory;
+import de.itvsh.goofy.vorgang.VorgangWithEingangTestFactory;
+
+class PostfachMailModelAssemblerTest {
+
+	private static final String ROOT_URL = "http://localhost:9191";
+	private static final String USER_PROFILES_API_PATH = "/api/usersProfiles/";
+
+	@InjectMocks
+	private PostfachMailModelAssembler modelAssembler;
+	@Mock
+	private UserManagerUrlProvider userManagerUrlProvider;
+
+	@BeforeEach
+	void mock() {
+		when(userManagerUrlProvider.isConfiguredForUserProfile()).thenReturn(false);
+	}
+
+	@Nested
+	class TestLinksOnModel {
+
+		@Test
+		void shouldHaveSelfLink() {
+			var link = modelAssembler.toModel(PostfachMailTestFactory.create()).getLink(IanaLinkRelations.SELF_VALUE);
+
+			assertThat(link).isPresent().get().extracting(Link::getHref)
+					.isEqualTo(PostfachMailController.PATH + "/" + PostfachMailTestFactory.ID);
+		}
+
+		@Nested
+		class ResendLink {
+
+			@DisplayName("if 'sentSuccessful' is false, add link")
+			@Test
+			void shouldHaveResendLink() {
+				var link = modelAssembler.toModel(PostfachMailTestFactory.createBuilder().sentSuccessful(false).build())
+						.getLink(PostfachMailModelAssembler.REL_RESEND_POSTFACH_MAIL);
+
+				assertThat(link).isPresent().get().extracting(Link::getHref)
+						.isEqualTo(
+								"/api/vorgangs/" + PostfachMailTestFactory.VORGANG_ID + "/postfachMails/" + PostfachMailTestFactory.ID + "/commands");
+			}
+
+			@DisplayName("if 'sentSuccessful' is true, hide link")
+			@Test
+			void shouldNotHaveResendLink() {
+				var link = modelAssembler.toModel(PostfachMailTestFactory.createBuilder().sentSuccessful(true).build())
+						.getLink(PostfachMailModelAssembler.REL_RESEND_POSTFACH_MAIL);
+
+				assertThat(link).isNotPresent();
+			}
+		}
+
+		@Nested
+		class ResetHasNewPostfachNachricht {
+			Stream<PostfachMail> mails = List.of(PostfachMailTestFactory.create()).stream();
+
+			@Test
+			void shouldHaveLink() {
+				var link = modelAssembler.toCollectionModel(mails, VorgangWithEingangTestFactory.create(), Optional.of(PostfachMailTestFactory.ID))
+						.getLink(PostfachMailModelAssembler.REL_RESET_NEW_POSTFACH_MAIL);
+
+				assertThat(link).isPresent();
+			}
+
+			@Test
+			void shouldNotNaveLink() {
+				var link = modelAssembler
+						.toCollectionModel(mails, VorgangWithEingangTestFactory.createBuilder().hasNewPostfachNachricht(false).build(),
+								Optional.of(PostfachMailTestFactory.ID))
+						.getLink(PostfachMailModelAssembler.REL_RESET_NEW_POSTFACH_MAIL);
+
+				assertThat(link).isNotPresent();
+			}
+		}
+
+		@Nested
+		class ToAttachments {
+
+			@Test
+			@DisplayName("should be present if attachments present")
+			void shouldBePresent() {
+				var link = modelAssembler.toModel(PostfachMailTestFactory.create()).getLink(PostfachMailModelAssembler.REL_ATTACHMENTS);
+
+				assertThat(link).isPresent().get().extracting(Link::getHref)
+						.isEqualTo("/api/postfachMails/" + PostfachMailTestFactory.ID + "/attachments");
+			}
+
+			@Test
+			@DisplayName("should NOT be present if there are no attachments")
+			void shouldNOTBePresent() {
+				var link = modelAssembler.toModel(PostfachMailTestFactory.createBuilder().clearAttachments().build())
+						.getLink(PostfachMailModelAssembler.REL_ATTACHMENTS);
+
+				assertThat(link).isNotPresent();
+			}
+		}
+
+		@DisplayName("created by link")
+		@Nested
+		class CreatedByLink {
+
+			@Nested
+			class TestOnConfiguredUserManager {
+
+				private final String userProfileTemplate = "UserProfileTemplateDummy/%s";
+
+				@BeforeEach
+				void mock() {
+					when(userManagerUrlProvider.isConfiguredForUserProfile()).thenReturn(true);
+				}
+
+				@Test
+				void shouldBePresent() {
+					when(userManagerUrlProvider.getUserProfileTemplate()).thenReturn(userProfileTemplate);
+
+					var link = modelAssembler.toModel(PostfachMailTestFactory.create()).getLink(PostfachMailModelAssembler.REL_CREATED_BY);
+
+					assertThat(link).isPresent().get().extracting(Link::getHref)
+							.isEqualTo("UserProfileTemplateDummy/" + PostfachMailTestFactory.CREATED_BY);
+				}
+
+				@DisplayName("should not be present if the value of createdBy starts with 'system'")
+				@Test
+				void shouldNOTBePresentOnSystemUser() {
+					var link = modelAssembler
+							.toModel(PostfachMailTestFactory.createBuilder()
+									.createdBy(UserId.from(PostfachMailModelAssembler.SYSTEM_USER_IDENTIFIER + UUID.randomUUID().toString())).build())
+							.getLink(PostfachMailModelAssembler.REL_CREATED_BY);
+
+					assertThat(link).isNotPresent();
+				}
+
+				@Test
+				void shouldNotBePresentOnNull() {
+					var link = modelAssembler.toModel(PostfachMailTestFactory.createBuilder().createdBy(null).build())
+							.getLink(PostfachMailModelAssembler.REL_CREATED_BY);
+
+					assertThat(link).isNotPresent();
+				}
+			}
+
+			@Nested
+			class TestOnNonConfiguredUserManager {
+
+				@BeforeEach
+				void mock() {
+					when(userManagerUrlProvider.isConfiguredForUserProfile()).thenReturn(false);
+				}
+
+				@Test
+				void shouldNOTBePresentOnNotConfiguredUserManager() {
+					var link = modelAssembler
+							.toModel(PostfachMailTestFactory.createBuilder()
+									.createdBy(UserId.from(PostfachMailModelAssembler.SYSTEM_USER_IDENTIFIER + UUID.randomUUID().toString())).build())
+							.getLink(PostfachMailModelAssembler.REL_CREATED_BY);
+
+					assertThat(link).isNotPresent();
+				}
+
+				@Test
+				void shouldNotCallUserManagerUrlProvider() {
+					modelAssembler
+							.toModel(PostfachMailTestFactory.createBuilder()
+									.createdBy(UserId.from(PostfachMailModelAssembler.SYSTEM_USER_IDENTIFIER + UUID.randomUUID().toString())).build())
+							.getLink(PostfachMailModelAssembler.REL_CREATED_BY);
+
+					verify(userManagerUrlProvider, never()).getUserProfileTemplate();
+				}
+
+				@Test
+				void shouldNotBePresentOnNull() {
+					var link = modelAssembler.toModel(PostfachMailTestFactory.createBuilder().createdBy(null).build())
+							.getLink(PostfachMailModelAssembler.REL_CREATED_BY);
+
+					assertThat(link).isNotPresent();
+				}
+			}
+		}
+	}
+
+	@Nested
+	class TestLinksOnCollectionModel {
+
+		@Test
+		void shouldHaveSelfLink() {
+			var link = modelAssembler
+					.toCollectionModel(Stream.of(PostfachMailTestFactory.create()), VorgangWithEingangTestFactory.create(),
+							Optional.empty())
+					.getLink(IanaLinkRelations.SELF_VALUE);
+
+			assertThat(link).isPresent();
+			assertThat(link.get().getHref()).isEqualTo(PostfachMailController.PATH);
+		}
+
+		@Nested
+		class SendPostfachMailLink {
+
+			private final String LINKREL = PostfachMailModelAssembler.REL_SEND_POSTFACH_MAIL;
+
+			@Test
+			void shouldHaveLink() {
+				var link = toCollectionModel(Optional.of(PostfachMailTestFactory.POSTFACH_ID)).getLink(LINKREL);
+
+				assertThat(link).isPresent();
+				assertThat(link.get().getHref())
+						.isEqualTo(String.format("/api/vorgangs/%s/relations/%s/%s/commands",
+								VorgangHeaderTestFactory.ID, VorgangHeaderTestFactory.ID, VorgangHeaderTestFactory.VERSION));
+			}
+
+			@Test
+			void shouldNotHaveLink() {
+				var link = toCollectionModel(Optional.empty()).getLink(LINKREL);
+
+				assertThat(link).isNotPresent();
+			}
+		}
+
+		@Nested
+		class UploadAttachmentLink {
+
+			private final String LINKREL = PostfachMailModelAssembler.REL_UPLOAD_ATTACHMENT;
+
+			@Test
+			void shouldHaveLink() {
+				var link = toCollectionModel(Optional.of(PostfachMailTestFactory.POSTFACH_ID)).getLink(LINKREL);
+
+				assertThat(link).isPresent();
+				assertThat(link.get().getHref())
+						.isEqualTo(String.format("/api/binaryFiles/%s/postfachNachrichtAttachment/file", VorgangHeaderTestFactory.ID));
+			}
+
+			@Test
+			void shouldNotHaveLink() {
+				var link = toCollectionModel(Optional.empty()).getLink(LINKREL);
+
+				assertThat(link).isNotPresent();
+			}
+
+		}
+
+		private CollectionModel<EntityModel<PostfachMail>> toCollectionModel(Optional<String> postfachId) {
+			return modelAssembler.toCollectionModel(Stream.of(PostfachMailTestFactory.create()), VorgangWithEingangTestFactory.create(),
+					postfachId);
+		}
+	}
+}
\ No newline at end of file
diff --git a/goofy-server/src/test/java/de/itvsh/goofy/postfach/PostfachMailRemoteServiceTest.java b/goofy-server/src/test/java/de/itvsh/goofy/postfach/PostfachMailRemoteServiceTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..5926a4d7f6d3e20292d867239a78414d741a5498
--- /dev/null
+++ b/goofy-server/src/test/java/de/itvsh/goofy/postfach/PostfachMailRemoteServiceTest.java
@@ -0,0 +1,190 @@
+/*
+ * 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.itvsh.goofy.postfach;
+
+import static org.mockito.ArgumentMatchers.*;
+import static org.mockito.Mockito.*;
+
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+
+import static org.assertj.core.api.Assertions.*;
+
+import de.itvsh.goofy.common.callcontext.ContextService;
+import de.itvsh.goofy.common.command.CommandTestFactory;
+import de.itvsh.goofy.vorgang.VorgangHeaderTestFactory;
+import de.itvsh.ozg.mail.postfach.GrpcFindPostfachMailRequest;
+import de.itvsh.ozg.mail.postfach.GrpcFindPostfachMailsRequest;
+import de.itvsh.ozg.mail.postfach.GrpcIsPostfachConfiguredRequest;
+import de.itvsh.ozg.mail.postfach.GrpcIsPostfachConfiguredResponse;
+import de.itvsh.ozg.mail.postfach.GrpcPostfachMail;
+import de.itvsh.ozg.mail.postfach.GrpcResendPostfachMailRequest;
+import de.itvsh.ozg.mail.postfach.PostfachServiceGrpc.PostfachServiceBlockingStub;
+import de.itvsh.ozg.pluto.grpc.command.GrpcCallContext;
+
+class PostfachMailRemoteServiceTest {
+
+	@InjectMocks // NOSONAR
+	private PostfachMailRemoteService service;
+	@Mock
+	private PostfachServiceBlockingStub serviceStub;
+	@Mock
+	private PostfachMailMapper postfachNachrichtMapper;
+	@Mock
+	private ContextService contextService;
+
+	private GrpcCallContext callContext = GrpcCallContext.newBuilder().build();
+
+	@BeforeEach
+	void initMocks() {
+		when(contextService.createCallContext()).thenReturn(callContext);
+	}
+
+	@Nested
+	class TestFindPostfachMail {
+
+		@Captor
+		private ArgumentCaptor<GrpcFindPostfachMailRequest> requestCaptor;
+
+		@BeforeEach
+		void init() {
+			when(serviceStub.findPostfachMail(any())).thenReturn(GrpcPostfachMailTestFactory.createFindPostfachMailResponse());
+		}
+
+		@Test
+		void shouldCallStub() {
+			service.findById(PostfachMailTestFactory.NACHRICHT_ID);
+
+			verify(serviceStub).findPostfachMail(any(GrpcFindPostfachMailRequest.class));
+		}
+
+		@Test
+		void shouldCallCallContextService() {
+			service.findById(PostfachMailTestFactory.NACHRICHT_ID);
+
+			verify(contextService).createCallContext();
+		}
+
+		@Test
+		void shouldContainNachrichtId() {
+			service.findById(PostfachMailTestFactory.NACHRICHT_ID);
+
+			verify(serviceStub).findPostfachMail(requestCaptor.capture());
+
+			assertThat(requestCaptor.getValue().getNachrichtId()).isEqualTo(PostfachMailTestFactory.ID);
+		}
+
+		@Test
+		void shouldCallMapper() {
+			service.findById(PostfachMailTestFactory.NACHRICHT_ID);
+
+			verify(postfachNachrichtMapper).toPostfachMail(any());
+		}
+	}
+
+	@Nested
+	class TestFindPostfachMails {
+
+		@BeforeEach
+		void mockServiceStub() {
+			when(serviceStub.findPostfachMails(any())).thenReturn(GrpcPostfachMailTestFactory.createFindPostfachMailsResponse());
+		}
+
+		@Test
+		void shouldCallStub() {
+			service.findPostfachMails(VorgangHeaderTestFactory.ID);
+
+			verify(serviceStub).findPostfachMails(any(GrpcFindPostfachMailsRequest.class));
+		}
+
+		@Test
+		void shouldCallCallContextService() {
+			service.findPostfachMails(VorgangHeaderTestFactory.ID);
+
+			verify(contextService).createCallContext();
+		}
+
+		@Test
+		void shouldCallMapper() {
+			var result = service.findPostfachMails(VorgangHeaderTestFactory.ID);
+			collectStreamElementsToTriggerLazyStream(result);
+
+			verify(postfachNachrichtMapper).toPostfachMail(any(GrpcPostfachMail.class));
+		}
+
+		void collectStreamElementsToTriggerLazyStream(Stream<PostfachMail> stream) {
+			stream.collect(Collectors.toList());
+		}
+	}
+
+	@Nested
+	class TestResendPostfachMail {
+
+		@Test
+		void shouldCallStub() {
+			service.resendPostfachMail(CommandTestFactory.ID, PostfachMailTestFactory.ID);
+
+			verify(serviceStub).resendPostfachMail(any(GrpcResendPostfachMailRequest.class));
+		}
+
+		@Test
+		void shouldCallContextService() {
+			service.resendPostfachMail(CommandTestFactory.ID, PostfachMailTestFactory.ID);
+
+			verify(contextService).createCallContext();
+		}
+	}
+
+	@Nested
+	class TestIsPostfachConfigured {
+
+		@BeforeEach
+		void mockStub() {
+			when(serviceStub.isPostfachConfigured(any(GrpcIsPostfachConfiguredRequest.class)))
+					.thenReturn(GrpcIsPostfachConfiguredResponse.newBuilder().setIsConfigured(true).build());
+		}
+
+		@Test
+		void shouldCallServiceStub() {
+			service.isPostfachConfigured();
+
+			verify(serviceStub).isPostfachConfigured(any(GrpcIsPostfachConfiguredRequest.class));
+		}
+
+		@Test
+		void shouldCallContextService() {
+			service.isPostfachConfigured();
+
+			verify(contextService).createCallContext();
+		}
+	}
+}
\ No newline at end of file
diff --git a/goofy-server/src/test/java/de/itvsh/goofy/postfach/PostfachMailServiceTest.java b/goofy-server/src/test/java/de/itvsh/goofy/postfach/PostfachMailServiceTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..5fee86fc5c2f088e5acec53f45c3ee9cc976db71
--- /dev/null
+++ b/goofy-server/src/test/java/de/itvsh/goofy/postfach/PostfachMailServiceTest.java
@@ -0,0 +1,420 @@
+/*
+ * 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.itvsh.goofy.postfach;
+
+import static org.assertj.core.api.Assertions.*;
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.ArgumentMatchers.*;
+import static org.mockito.Mockito.*;
+
+import java.io.OutputStream;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.stream.Stream;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.Spy;
+
+import de.itvsh.goofy.common.binaryfile.BinaryFileService;
+import de.itvsh.goofy.common.binaryfile.BinaryFileTestFactory;
+import de.itvsh.goofy.common.command.CommandTestFactory;
+import de.itvsh.goofy.common.errorhandling.ResourceNotFoundException;
+import de.itvsh.goofy.common.file.OzgFile;
+import de.itvsh.goofy.common.file.OzgFileTestFactory;
+import de.itvsh.goofy.common.user.UserId;
+import de.itvsh.goofy.common.user.UserProfile;
+import de.itvsh.goofy.common.user.UserProfileTestFactory;
+import de.itvsh.goofy.common.user.UserService;
+import de.itvsh.goofy.vorgang.VorgangHeaderTestFactory;
+import de.itvsh.goofy.vorgang.VorgangWithEingang;
+import de.itvsh.goofy.vorgang.VorgangWithEingangTestFactory;
+
+class PostfachMailServiceTest {
+
+	@Spy
+	@InjectMocks // NOSONAR
+	private PostfachMailService service;
+	@Mock
+	private PostfachNachrichtPdfService pdfService;
+	@Mock
+	private PostfachMailRemoteService remoteService;
+	@Mock
+	private BinaryFileService fileService;
+	@Mock
+	private UserService userService;
+
+	@DisplayName("Get all")
+	@Nested
+	class TestGetAll {
+
+		@Test
+		void shouldCallRemoteService() {
+			service.getAll(VorgangHeaderTestFactory.ID);
+
+			verify(remoteService).findPostfachMails(VorgangHeaderTestFactory.ID);
+		}
+	}
+
+	@DisplayName("Find by id")
+	@Nested
+	class TestFindById {
+
+		private PostfachMail mail = PostfachMailTestFactory.create();
+
+		@BeforeEach
+		void init() {
+			when(remoteService.findById(any())).thenReturn(Optional.of(mail));
+		}
+
+		@Test
+		void shouldCallRemoteService() {
+			service.findById(PostfachMailTestFactory.NACHRICHT_ID);
+
+			verify(remoteService).findById(PostfachMailTestFactory.NACHRICHT_ID);
+		}
+
+		@Test
+		void shouldThrowExceptionIfNotFound() {
+			when(remoteService.findById(any())).thenReturn(Optional.empty());
+
+			assertThrows(ResourceNotFoundException.class, () -> service.findById(PostfachMailTestFactory.NACHRICHT_ID));
+		}
+
+		@Test
+		void shouldReturnNachricht() {
+			var result = service.findById(PostfachMailTestFactory.NACHRICHT_ID);
+
+			assertThat(result).isSameAs(mail);
+		}
+	}
+
+	@DisplayName("Resend postfach mail")
+	@Nested
+	class TestResendPostfachMail {
+
+		@Test
+		void shouldCallRemoteService() {
+			service.resendPostfachMail(CommandTestFactory.ID, PostfachMailTestFactory.ID);
+
+			verify(remoteService).resendPostfachMail(CommandTestFactory.ID, PostfachMailTestFactory.ID);
+		}
+	}
+
+	@DisplayName("Is postfach configured")
+	@Nested
+	class TestIsPostfachConfigured {
+
+		@Test
+		void shouldCallRemoteServiceOnMissingProperty() {
+			service.isPostfachConfigured();
+
+			verify(remoteService).isPostfachConfigured();
+		}
+
+		@Test
+		void shouldNotCallRemoteServiceOnExistingProperty() throws Exception {
+			setIsConfigured();
+
+			service.isPostfachConfigured();
+
+			verifyNoInteractions(remoteService);
+		}
+
+		private void setIsConfigured() throws Exception {
+			var isPostfachConfigured = PostfachMailService.class.getDeclaredField("isPostfachConfigured");
+			isPostfachConfigured.setAccessible(true);
+			isPostfachConfigured.set(service, true);
+		}
+	}
+
+	@DisplayName("Get all as pdf")
+	@Nested
+	class TestGetAllAsPdf {
+
+		@Mock
+		private OutputStream outputStream;
+
+		private final VorgangWithEingang vorgang = VorgangWithEingangTestFactory.create();
+
+		private final Stream<PostfachNachrichtPdfData> postfachNachrichtPdfModelDataList = Stream
+				.of(PostfachNachrichtPdfData.builder().build());// TODO TestFactory erstellen
+
+		@DisplayName("should call")
+		@Nested
+		class TestCall {
+
+			@BeforeEach
+			void mock() {
+				doReturn(postfachNachrichtPdfModelDataList).when(service).buildPostfachNachrichtPdfDataList(anyString());
+			}
+
+			@Test
+			void buildPostfachNachrichtPdfModelDataList() {
+				service.getAllAsPdf(vorgang, outputStream);
+
+				verify(service).buildPostfachNachrichtPdfDataList(VorgangHeaderTestFactory.ID);
+			}
+
+			@Test
+			void pdfService() {
+				service.getAllAsPdf(vorgang, outputStream);
+
+				verify(pdfService).getAllAsPdf(vorgang, postfachNachrichtPdfModelDataList, outputStream);
+			}
+		}
+
+		@DisplayName("build postfach nachrichten pdf data")
+		@Nested
+		class TestBuildPostfachNachrichtPdfDataList {
+
+			private final PostfachMail postfachNachricht = PostfachMailTestFactory.create();
+
+			@DisplayName("should call")
+			@Nested
+			class TestCall {
+
+				private final Stream<PostfachMail> postfachMail = Stream.of(postfachNachricht);
+				private final Stream<OzgFile> ozgFile = Stream.of(OzgFileTestFactory.create());
+
+				@BeforeEach
+				void mock() {
+					when(remoteService.findPostfachMails(anyString())).thenReturn(postfachMail);
+					when(fileService.getFiles(anyList())).thenReturn(ozgFile);
+				}
+
+				@Test
+				void shouldCallRemoteService() {
+					service.buildPostfachNachrichtPdfDataList(VorgangHeaderTestFactory.ID);
+
+					verify(remoteService).findPostfachMails(VorgangHeaderTestFactory.ID);
+				}
+
+				@Test
+				void shouldCallBinaryFileService() {
+					service.buildPostfachNachrichtPdfDataList(VorgangHeaderTestFactory.ID);
+
+					verify(fileService).getFiles(List.of(BinaryFileTestFactory.FILE_ID));
+				}
+
+				@Test
+				void shouldCallBuildPostfachNachrichtPdfData() {
+					doReturn(PostfachNachrichtPdfData.builder().build()).when(service).buildPostfachNachrichtPdfData(any(), any());
+
+					service.buildPostfachNachrichtPdfDataList(VorgangHeaderTestFactory.ID).toList();
+
+					verify(service).buildPostfachNachrichtPdfData(postfachNachricht, Map.of(OzgFileTestFactory.ID, OzgFileTestFactory.NAME));
+				}
+			}
+
+			@DisplayName("for single postfachNachricht")
+			@Nested
+			class TestBuildPostfachNachrichtPdfData {
+
+				private final UserProfile user = UserProfileTestFactory.create();
+
+				@BeforeEach
+				void mock() {
+					when(userService.getById(any(UserId.class))).thenReturn(user);
+				}
+
+				@Test
+				void shouldCallUserService() {
+					buildPostfachNachrichtPdfData();
+
+					verify(userService).getById(UserProfileTestFactory.ID);
+				}
+
+				@Test
+				void shouldHaveSetCreatedAt() {
+					var postfachNachricht = buildPostfachNachrichtPdfData();
+
+					assertThat(postfachNachricht.getCreatedAt()).isEqualTo(PostfachMailTestFactory.CREATED_AT);
+				}
+
+				@Test
+				void shouldHaveSetCreatedByName() {
+					var postfachNachricht = buildPostfachNachrichtPdfData();
+
+					assertThat(postfachNachricht.getUser()).isEqualTo(user);
+				}
+
+				@Test
+				void shouldHaveSetSubject() {
+					var postfachNachricht = buildPostfachNachrichtPdfData();
+
+					assertThat(postfachNachricht.getSubject()).isEqualTo(PostfachMailTestFactory.SUBJECT);
+				}
+
+				@Test
+				void shouldHaveSetMailBody() {
+					var postfachNachricht = buildPostfachNachrichtPdfData();
+
+					assertThat(postfachNachricht.getMailBody()).isEqualTo(PostfachMailTestFactory.MAIL_BODY);
+				}
+
+				@Test
+				void shouldHaveSetAttachmentNames() {
+					var postfachNachricht = buildPostfachNachrichtPdfData();
+
+					assertThat(postfachNachricht.getAttachmentNames()).isEqualTo(List.of(OzgFileTestFactory.NAME));
+				}
+			}
+
+			@DisplayName("user")
+			@Nested
+			class TestUser {
+
+				@DisplayName("exists")
+				@Nested
+				class TestOnNonNull {
+
+					private final UserProfile user = UserProfileTestFactory.create();
+
+					@BeforeEach
+					void mock() {
+						when(userService.getById(any(UserId.class))).thenReturn(user);
+					}
+
+					@Test
+					void shouldCallUserService() {
+						buildPostfachNachrichtPdfData();
+
+						verify(userService).getById(UserProfileTestFactory.ID);
+					}
+
+					@Test
+					void shouldHaveSetCreatedByName() {
+						var postfachNachricht = buildPostfachNachrichtPdfData();
+
+						assertThat(postfachNachricht.getUser()).isEqualTo(user);
+					}
+				}
+
+				@DisplayName("not exists")
+				@Nested
+				class TestOnNull {
+
+					private final PostfachMail postfachNachrichtWithoutCreatedBy = PostfachMailTestFactory.createBuilder().createdBy(null)
+							.build();
+
+					@Test
+					void shouldNotCallUserService() {
+						buildPostfachNachrichtPdfDataWithoutUser();
+
+						verify(userService, never()).getById(any());
+					}
+
+					@Test
+					void shouldHaveSetNullAsUser() {
+						var postfachNachricht = buildPostfachNachrichtPdfDataWithoutUser();
+
+						assertThat(postfachNachricht.getUser()).isNull();
+					}
+
+					private PostfachNachrichtPdfData buildPostfachNachrichtPdfDataWithoutUser() {
+						return service.buildPostfachNachrichtPdfData(postfachNachrichtWithoutCreatedBy,
+								Map.of(BinaryFileTestFactory.FILE_ID, OzgFileTestFactory.NAME));
+					}
+				}
+			}
+
+			private PostfachNachrichtPdfData buildPostfachNachrichtPdfData() {
+				return service.buildPostfachNachrichtPdfData(postfachNachricht, Map.of(BinaryFileTestFactory.FILE_ID, OzgFileTestFactory.NAME));
+			}
+		}
+
+		@DisplayName("get file ids from all attachments")
+		@Nested
+		class TestGetFileIdsFromAllAttachments {
+
+			@Test
+			void shouldReturnFileIdsFromPostfachMail() {
+				var fileIds = service.getFileIdsFromAllAttachments(Stream.of(PostfachMailTestFactory.create()));
+
+				assertThat(fileIds).containsExactly(BinaryFileTestFactory.FILE_ID);
+			}
+
+			@Test
+			void shouldReturnEmptyListOnNoAttachments() {
+				var fileIds = service.getFileIdsFromAllAttachments(Stream.of(PostfachMailTestFactory.createBuilder().clearAttachments().build()));
+
+				assertThat(fileIds).isEmpty();
+			}
+		}
+
+		@DisplayName("get User")
+		@Nested
+		class TestGetUser {
+
+			@DisplayName("on existing client user")
+			@Nested
+			class TestOnExistingClientUser {
+
+				@Test
+				void shouldCallUserServiceOnExistingUser() {
+					service.getUser(PostfachMailTestFactory.create());
+
+					verify(userService).getById(UserProfileTestFactory.ID);
+				}
+
+				@Test
+				void shouldReturnUserProfileWithId() {
+					when(userService.getById(any())).thenReturn(UserProfileTestFactory.create());
+
+					var user = service.getUser(PostfachMailTestFactory.create());
+
+					assertThat(user.getId()).isEqualTo(UserProfileTestFactory.ID);
+				}
+			}
+
+			@DisplayName("on existing system user")
+			@Nested
+			class TestOnSystemUser {
+
+				private final PostfachMail postfachMail = PostfachMailTestFactory.createBuilder()
+						.createdBy(UserId.from(UserProfileTestFactory.SYSTEM_USER)).build();
+
+				@Test
+				void shouldNotCallUserService() {
+					service.getUser(postfachMail);
+
+					verify(userService, never()).getById(UserProfileTestFactory.ID);
+				}
+
+				@Test
+				void shouldReturnUserProfileWithId() {
+					var user = service.getUser(postfachMail);
+
+					assertThat(user.getId()).hasToString(UserProfileTestFactory.SYSTEM_USER);
+				}
+			}
+		}
+	}
+}
\ No newline at end of file
diff --git a/goofy-server/src/test/java/de/itvsh/goofy/postfach/PostfachMailTestFactory.java b/goofy-server/src/test/java/de/itvsh/goofy/postfach/PostfachMailTestFactory.java
new file mode 100644
index 0000000000000000000000000000000000000000..067f3f49766abfaf915b037a3d02dd7a77f3c07e
--- /dev/null
+++ b/goofy-server/src/test/java/de/itvsh/goofy/postfach/PostfachMailTestFactory.java
@@ -0,0 +1,112 @@
+/*
+ * 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.itvsh.goofy.postfach;
+
+import java.time.ZonedDateTime;
+import java.util.List;
+import java.util.UUID;
+
+import org.apache.commons.lang3.RandomStringUtils;
+
+import com.thedeanda.lorem.LoremIpsum;
+
+import de.itvsh.goofy.common.binaryfile.BinaryFileTestFactory;
+import de.itvsh.goofy.common.binaryfile.FileId;
+import de.itvsh.goofy.common.command.CommandOrder;
+import de.itvsh.goofy.common.user.UserId;
+import de.itvsh.goofy.common.user.UserProfileTestFactory;
+import de.itvsh.goofy.postfach.PostfachMail.Direction;
+import de.itvsh.goofy.vorgang.VorgangHeaderTestFactory;
+import de.itvsh.kop.common.test.TestUtils;
+
+public class PostfachMailTestFactory {
+
+	public static final String ID = UUID.randomUUID().toString();
+	public static final PostfachNachrichtId NACHRICHT_ID = PostfachNachrichtId.from(ID);
+	public static final String VORGANG_ID = VorgangHeaderTestFactory.ID;
+	public static final String POSTFACH_ID = UUID.randomUUID().toString();
+	public static final String CREATED_AT_STR = "2000-01-01T01:00:00Z";
+	public static final ZonedDateTime CREATED_AT = ZonedDateTime.parse(CREATED_AT_STR);
+	public static final UserId CREATED_BY = UserProfileTestFactory.ID;
+	public static final Direction DIRECTION = Direction.OUT;
+	public static final String RECEIVER = LoremIpsum.getInstance().getEmail();
+	public static final String SUBJECT = RandomStringUtils.randomAlphanumeric(70);
+	public static final String MAIL_BODY = LoremIpsum.getInstance().getParagraphs(1, 1);
+	public static final ReplyOption REPLY_OPTION = ReplyOption.POSSIBLE;
+	static final boolean SENT_SUCCESSFUL = true;
+	public static final String SENT_AT_STR = "2000-01-01T10:00:00Z";
+	public static final ZonedDateTime SENT_AT = ZonedDateTime.parse(SENT_AT_STR);
+	public static final String MESSAGE_CODE = LoremIpsum.getInstance().getWords(3).concat(".");
+
+	public static final List<FileId> ATTACHMENTS = List.of(BinaryFileTestFactory.FILE_ID);
+
+	public static PostfachMail create() {
+		return createBuilder().build();
+	}
+
+	public static PostfachMail createLikeFromClient() {
+		return createBuilder()
+				.createdAt(null)
+				.createdBy(null)
+				.direction(null)
+				.build();
+	}
+
+	public static PostfachMail.PostfachMailBuilder createBuilder() {
+		return PostfachMail.builder()
+				.id(ID)
+				.vorgangId(VORGANG_ID)
+				.postfachId(POSTFACH_ID)
+				.createdAt(CREATED_AT)
+				.createdBy(CREATED_BY)
+				.sentAt(SENT_AT)
+				.sentSuccessful(SENT_SUCCESSFUL)
+				.messageCode(MESSAGE_CODE)
+				.direction(DIRECTION)
+				.subject(SUBJECT)
+				.mailBody(MAIL_BODY)
+				.replyOption(REPLY_OPTION)
+				.attachments(ATTACHMENTS);
+	}
+
+	public static String buildSendPostfachMailContent() {
+		return buildSendPostfachMailContent(create());
+	}
+
+	public static String buildSendPostfachMailContent(PostfachMail postfachMail) {
+		return buildSendPostfachMailContent(postfachMail, CommandOrder.SEND_POSTFACH_NACHRICHT);
+	}
+
+	public static String buildSendPostfachMailContent(PostfachMail postfachMail, CommandOrder order) {
+		return TestUtils.loadTextFile("jsonTemplates/command/createCommandWithPostfachMail.json.tmpl", order.name(),
+				postfachMail.getReplyOption().name(),
+				TestUtils.addQuote(postfachMail.getSubject()),
+				TestUtils.addQuote(postfachMail.getMailBody()),
+				"api/binaryFiles/" + postfachMail.getAttachments().get(0).toString());
+	}
+
+	public static String buildResendPostfachMailContent() {
+		return TestUtils.loadTextFile("jsonTemplates/command/createCommandWithBody.json.tmpl", CommandOrder.RESEND_POSTFACH_MAIL.name(), null);
+	}
+}
\ No newline at end of file
diff --git a/goofy-server/src/test/java/de/itvsh/goofy/postfach/PostfachNachrichtComparatorTest.java b/goofy-server/src/test/java/de/itvsh/goofy/postfach/PostfachNachrichtComparatorTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..47486f527582eeb60c4977377a0a4077c8ff1f4e
--- /dev/null
+++ b/goofy-server/src/test/java/de/itvsh/goofy/postfach/PostfachNachrichtComparatorTest.java
@@ -0,0 +1,79 @@
+/*
+ * 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.itvsh.goofy.postfach;
+
+import static org.assertj.core.api.Assertions.*;
+
+import java.time.ZonedDateTime;
+
+import org.junit.jupiter.api.Test;
+
+class PostfachNachrichtComparatorTest {
+
+	private PostfachNachrichtComparator comparator = new PostfachNachrichtComparator();
+
+	@Test
+	void shouldCompareCreatedAt() {
+		var shouldBeFirst = buildPostfachMail(ZonedDateTime.parse("2020-02-01T01:00:00Z"), null);
+		var shouldBeSecond = buildPostfachMail(ZonedDateTime.parse("2020-01-01T01:00:00Z"), null);
+
+		var result = comparator.compare(shouldBeSecond, shouldBeFirst);
+
+		assertThat(result).isEqualTo(-1);
+	}
+
+	@Test
+	void shouldCompareCreatedAtWithSentAtNull() {
+		var shouldBeFirst = buildPostfachMail(ZonedDateTime.parse("2020-02-01T01:00:00Z"), ZonedDateTime.parse("2020-05-01T01:00:00Z"));
+		var shouldBeSecond = buildPostfachMail(ZonedDateTime.parse("2020-01-01T01:00:00Z"), null);
+
+		var result = comparator.compare(shouldBeSecond, shouldBeFirst);
+
+		assertThat(result).isEqualTo(-1);
+	}
+
+	@Test
+	void shouldCompareSentAt() {
+		var shouldBeFirst = buildPostfachMail(ZonedDateTime.parse("2020-05-01T01:00:00Z"), ZonedDateTime.parse("2020-10-01T01:00:00Z"));
+		var shouldBeSecond = buildPostfachMail(ZonedDateTime.parse("2020-10-01T01:00:00Z"), ZonedDateTime.parse("2020-05-01T01:00:00Z"));
+
+		var result = comparator.compare(shouldBeSecond, shouldBeFirst);
+
+		assertThat(result).isEqualTo(-1);
+	}
+
+	@Test
+	void shouldCompareSentAtWithCreateAtNull() {
+		var shouldBeFirst = buildPostfachMail(ZonedDateTime.parse("2020-01-01T01:00:00Z"), ZonedDateTime.parse("2020-10-01T01:00:00Z"));
+		var shouldBeSecond = buildPostfachMail(null, ZonedDateTime.parse("2020-05-01T01:00:00Z"));
+
+		var result = comparator.compare(shouldBeSecond, shouldBeFirst);
+
+		assertThat(result).isEqualTo(-1);
+	}
+
+	private PostfachMail buildPostfachMail(ZonedDateTime createdAt, ZonedDateTime sentAt) {
+		return PostfachMailTestFactory.createBuilder().createdAt(createdAt).sentAt(sentAt).build();
+	}
+}
\ No newline at end of file
diff --git a/goofy-server/src/test/java/de/itvsh/goofy/postfach/PostfachNachrichtPdfDataTestFactory.java b/goofy-server/src/test/java/de/itvsh/goofy/postfach/PostfachNachrichtPdfDataTestFactory.java
new file mode 100644
index 0000000000000000000000000000000000000000..c913fdf0e8044eb9845657f04c785b03ee3ab66a
--- /dev/null
+++ b/goofy-server/src/test/java/de/itvsh/goofy/postfach/PostfachNachrichtPdfDataTestFactory.java
@@ -0,0 +1,45 @@
+/*
+ * 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.itvsh.goofy.postfach;
+
+import java.util.List;
+
+import de.itvsh.goofy.common.binaryfile.BinaryFileTestFactory;
+import de.itvsh.goofy.common.user.UserProfileTestFactory;
+
+public class PostfachNachrichtPdfDataTestFactory {
+
+	public static PostfachNachrichtPdfData create() {
+		return createBuilder().build();
+	}
+
+	public static PostfachNachrichtPdfData.PostfachNachrichtPdfDataBuilder createBuilder() {
+		return PostfachNachrichtPdfData.builder()
+				.createdAt(PostfachMailTestFactory.CREATED_AT)
+				.user(UserProfileTestFactory.create())
+				.subject(PostfachMailTestFactory.SUBJECT)
+				.mailBody(PostfachMailTestFactory.MAIL_BODY)
+				.attachmentNames(List.of(BinaryFileTestFactory.NAME));
+	}
+}
\ No newline at end of file
diff --git a/goofy-server/src/test/java/de/itvsh/goofy/postfach/PostfachNachrichtPdfServiceITCase.java b/goofy-server/src/test/java/de/itvsh/goofy/postfach/PostfachNachrichtPdfServiceITCase.java
new file mode 100644
index 0000000000000000000000000000000000000000..a4ada1e59fa75268a52d42cef5e90924d605d3c3
--- /dev/null
+++ b/goofy-server/src/test/java/de/itvsh/goofy/postfach/PostfachNachrichtPdfServiceITCase.java
@@ -0,0 +1,134 @@
+/*
+ * 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.itvsh.goofy.postfach;
+
+import static org.assertj.core.api.Assertions.*;
+import static org.mockito.ArgumentMatchers.*;
+import static org.mockito.Mockito.*;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.OutputStream;
+import java.util.Collections;
+import java.util.List;
+import java.util.stream.Stream;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.DisplayName;
+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 de.itvsh.goofy.common.user.UserId;
+import de.itvsh.goofy.common.user.UserProfileTestFactory;
+import de.itvsh.goofy.common.user.UserRemoteService;
+import de.itvsh.goofy.vorgang.EingangTestFactory;
+import de.itvsh.goofy.vorgang.VorgangWithEingang;
+import de.itvsh.goofy.vorgang.VorgangWithEingangTestFactory;
+import lombok.SneakyThrows;
+
+@SpringBootTest
+class PostfachNachrichtPdfServiceITCase {
+
+	@Autowired
+	private PostfachNachrichtPdfService service;
+	@MockBean
+	private UserRemoteService userRemoteService;
+
+	@DisplayName("Generate pdf file")
+	@Nested
+	class TestGeneratePdfFile {
+
+		@BeforeEach
+		void mock() {
+			when(userRemoteService.getUser(any(UserId.class))).thenReturn(UserProfileTestFactory.create());
+		}
+
+		@SneakyThrows
+		@Test
+		void generatePdfFile() {
+			var tempFile = createTempFile();
+
+			getAllAsPdf(VorgangWithEingangTestFactory.create(), new FileOutputStream(tempFile));
+
+			assertThat(tempFile).isNotEmpty();
+		}
+
+		@SneakyThrows
+		@Test
+		void generatePdfFileAntragstellerNotSet() {
+			var tempFile = createTempFile();
+
+			getAllAsPdf(buildVorgangAntragstellerNotSet(), new FileOutputStream(tempFile));
+
+			assertThat(tempFile).isNotEmpty();
+		}
+
+		@SneakyThrows
+		@Test
+		void generatePdfFileNoAttachments() {
+			var tempFile = createTempFile();
+
+			getAllAsPdf(buildVorgangAntragstellerNotSet(),
+					Stream.of(PostfachNachrichtPdfDataTestFactory.createBuilder().attachmentNames(Collections.emptyList()).build()),
+					new FileOutputStream(tempFile));
+
+			assertThat(tempFile).isNotEmpty();
+		}
+
+		@SneakyThrows
+		private File createTempFile() {
+			var tempFile = File.createTempFile("kop_nachricht_", ".pdf");
+			// tempFile.deleteOnExit();
+			return tempFile;
+		}
+
+		@SneakyThrows
+		private void getAllAsPdf(VorgangWithEingang vorgang, OutputStream out) {
+			getAllAsPdf(vorgang, buildPostfachMails(), out);
+		}
+
+		@SneakyThrows
+		private void getAllAsPdf(VorgangWithEingang vorgang, Stream<PostfachNachrichtPdfData> postfachNachrichten, OutputStream out) {
+			service.getAllAsPdf(vorgang, postfachNachrichten, out);
+			out.close();
+		}
+	}
+
+	private VorgangWithEingang buildVorgangAntragstellerNotSet() {
+		return VorgangWithEingangTestFactory.createBuilder().eingang(EingangTestFactory.createBuilder().antragsteller(null).build()).build();
+	}
+
+	private Stream<PostfachNachrichtPdfData> buildPostfachMails() {
+		return Stream.of(PostfachNachrichtPdfDataTestFactory.createBuilder().subject("hase")
+				.attachmentNames(List.of("Hase.png", "Hasenlied.mscz", "Erweitertes-Führungszeugniß.pdf", "Haftbefehl.pdf"))
+				.build(),
+				PostfachNachrichtPdfDataTestFactory.create(),
+				PostfachNachrichtPdfDataTestFactory.create(),
+				PostfachNachrichtPdfDataTestFactory.create(),
+				PostfachNachrichtPdfDataTestFactory.create());
+	}
+}
\ No newline at end of file
diff --git a/goofy-server/src/test/java/de/itvsh/goofy/postfach/PostfachNachrichtPdfServiceTest.java b/goofy-server/src/test/java/de/itvsh/goofy/postfach/PostfachNachrichtPdfServiceTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..e396dac31372b3480c8d1b20abae859ccbf983a9
--- /dev/null
+++ b/goofy-server/src/test/java/de/itvsh/goofy/postfach/PostfachNachrichtPdfServiceTest.java
@@ -0,0 +1,350 @@
+/*
+ * 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.itvsh.goofy.postfach;
+
+import static org.assertj.core.api.Assertions.*;
+import static org.mockito.ArgumentMatchers.*;
+import static org.mockito.Mockito.*;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.stream.Stream;
+
+import org.apache.commons.lang3.StringUtils;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.Spy;
+import org.springframework.core.io.Resource;
+import org.springframework.util.ReflectionUtils;
+
+import de.itvsh.goofy.common.binaryfile.BinaryFileTestFactory;
+import de.itvsh.goofy.common.user.UserProfileTestFactory;
+import de.itvsh.goofy.postfach.PostfachMail.Direction;
+import de.itvsh.goofy.postfach.PostfachNachrichtPdfModel.Nachricht;
+import de.itvsh.goofy.vorgang.AntragstellerTestFactory;
+import de.itvsh.goofy.vorgang.VorgangHeaderTestFactory;
+import de.itvsh.goofy.vorgang.VorgangWithEingangTestFactory;
+import de.itvsh.kop.common.errorhandling.TechnicalException;
+import de.itvsh.kop.common.pdf.PdfService;
+import lombok.SneakyThrows;
+
+class PostfachNachrichtPdfServiceTest {
+
+	@Spy
+	@InjectMocks
+	private PostfachNachrichtPdfService service;
+	@Mock
+	private PdfService pdfService;
+
+	@DisplayName("Get all as pdf")
+	@Nested
+	class TestGetAllAsPdf {
+
+		@Mock
+		private OutputStream output;
+
+		@DisplayName("on getting template")
+		@Nested
+		class TestGetTemplate {
+
+			@Mock
+			private Resource pdfTemplate;
+
+			@BeforeEach
+			void mockPdfTemplate() {
+				var field = ReflectionUtils.findField(PostfachNachrichtPdfService.class, "pdfTemplate");
+				field.setAccessible(true);
+				ReflectionUtils.setField(field, service, pdfTemplate);
+			}
+
+			@SneakyThrows
+			@Test
+			void shouldGetInputStreamFromResource() {
+				when(pdfTemplate.getInputStream()).thenReturn(InputStream.nullInputStream());
+
+				service.getTemplate();
+
+				verify(pdfTemplate).getInputStream();
+			}
+
+			@SneakyThrows
+			@Test
+			void shouldReturnIfExists() {
+				var inputStream = InputStream.nullInputStream();
+				when(pdfTemplate.getInputStream()).thenReturn(inputStream);
+
+				var templateInputStream = service.getTemplate();
+
+				assertThat(templateInputStream).isEqualTo(inputStream);
+			}
+
+			@SneakyThrows
+			@Test
+			void shouldThrowExceptionIfMissing() {
+				when(pdfTemplate.getInputStream()).thenThrow(IOException.class);
+
+				assertThatThrownBy(() -> service.getTemplate()).isInstanceOf(TechnicalException.class);
+			}
+		}
+
+		@DisplayName("build model")
+		@Nested
+		class TestBuildModel {
+
+			@DisplayName("by vorgang")
+			@Nested
+			class TestByVorgang {
+
+				@Test
+				void shouldSetVorgangNummer() {
+					var model = service.buildModel(VorgangWithEingangTestFactory.create(), Stream.empty());
+
+					assertThat(model.getVorgangNummer()).isEqualTo(VorgangHeaderTestFactory.NUMMER);
+				}
+
+				@Test
+				void shouldSetVorgangName() {
+					var model = service.buildModel(VorgangWithEingangTestFactory.create(), Stream.empty());
+
+					assertThat(model.getVorgangName()).isEqualTo(VorgangHeaderTestFactory.NAME);
+				}
+
+				@Nested
+				class TestSetIsFirst {
+
+					@Test
+					void shouldSetIsFirstTrueOnFirstMessage() {
+						var model = service.buildModel(VorgangWithEingangTestFactory.create(),
+								Stream.of(PostfachNachrichtPdfDataTestFactory.create()));
+
+						assertThat(model.getNachrichten().get(0).isFirst()).isTrue();
+					}
+
+					@Test
+					void shouldSetIsFirstFalseOnOtherMessage() {
+						var model = service.buildModel(VorgangWithEingangTestFactory.create(),
+								Stream.of(PostfachNachrichtPdfDataTestFactory.create(), PostfachNachrichtPdfDataTestFactory.create(),
+										PostfachNachrichtPdfDataTestFactory.create()));
+
+						assertThat(model.getNachrichten().get(1).isFirst()).isFalse();
+						assertThat(model.getNachrichten().get(2).isFirst()).isFalse();
+					}
+				}
+			}
+
+			@DisplayName("by Antragsteller")
+			@Nested
+			class TestMapAntragsteller {
+
+				@Test
+				void shouldMapAntragstellerAnrede() {
+					var modelBuilder = mapAntragsteller();
+
+					assertThat(modelBuilder.build().getAntragstellerAnrede()).isEqualTo(AntragstellerTestFactory.ANREDE);
+				}
+
+				@Test
+				void shouldMapAntragstellerVorname() {
+					var modelBuilder = mapAntragsteller();
+
+					assertThat(modelBuilder.build().getAntragstellerVorname()).isEqualTo(AntragstellerTestFactory.VORNAME);
+				}
+
+				@Test
+				void shouldMapAntragstellerNachname() {
+					var modelBuilder = mapAntragsteller();
+
+					assertThat(modelBuilder.build().getAntragstellerNachname()).isEqualTo(AntragstellerTestFactory.NACHNAME);
+				}
+
+				@Test
+				void shouldMapAntragstellerStrasse() {
+					var modelBuilder = mapAntragsteller();
+
+					assertThat(modelBuilder.build().getAntragstellerStrasse()).isEqualTo(AntragstellerTestFactory.STRASSE);
+				}
+
+				@Test
+				void shouldMapAntragstellerHausnummer() {
+					var modelBuilder = mapAntragsteller();
+
+					assertThat(modelBuilder.build().getAntragstellerHausnummer()).isEqualTo(AntragstellerTestFactory.HAUSNUMMER);
+				}
+
+				@Test
+				void shouldMapAntragstellerPlz() {
+					var modelBuilder = mapAntragsteller();
+
+					assertThat(modelBuilder.build().getAntragstellerPlz()).isEqualTo(AntragstellerTestFactory.PLZ);
+				}
+
+				@Test
+				void shouldMapAntragstellerOrt() {
+					var modelBuilder = mapAntragsteller();
+
+					assertThat(modelBuilder.build().getAntragstellerOrt()).isEqualTo(AntragstellerTestFactory.ORT);
+				}
+
+				private PostfachNachrichtPdfModel.PostfachNachrichtPdfModelBuilder mapAntragsteller() {
+					var modelBuilder = PostfachNachrichtPdfModel.builder();
+
+					service.mapAntragsteller(modelBuilder, AntragstellerTestFactory.create());
+
+					return modelBuilder;
+				}
+			}
+
+			@DisplayName("by postfachnachricht pdf data")
+			@Nested
+			class TestMapPostfachNachricht {
+
+				@Test
+				void shouldMapNachrichtSubject() {
+					var nachricht = mapNachricht();
+
+					assertThat(nachricht.getSubject()).isEqualTo(PostfachMailTestFactory.SUBJECT);
+				}
+
+				@Test
+				void shouldMapNachrichtMailBody() {
+					var nachricht = mapNachricht();
+
+					assertThat(nachricht.getMailBody()).isEqualTo(PostfachMailTestFactory.MAIL_BODY);
+				}
+
+				@Test
+				void shouldMapNachrichtCreatedAt() {
+					var nachricht = mapNachricht();
+
+					assertThat(nachricht.getCreatedAt()).isEqualTo("01.01.2000 01:00:00");
+				}
+
+				@Test
+				void shouldMapNachrichtCreatedBy() {
+					var nachricht = mapNachricht();
+
+					assertThat(nachricht.getCreatedBy()).isEqualTo(UserProfileTestFactory.FULLNAME);
+				}
+
+				@Test
+				void shouldMapNachrichtAttachments() {
+					var nachricht = mapNachricht();
+
+					assertThat(nachricht.getAttachments()).containsExactly(BinaryFileTestFactory.NAME);
+				}
+
+				private Nachricht mapNachricht() {
+					return service.mapPostfachNachricht(PostfachNachrichtPdfDataTestFactory.create(), AntragstellerTestFactory.create());
+				}
+
+				@DisplayName("for incoming nachricht")
+				@Nested
+				class TestDirectionIn {
+
+					@DisplayName("build absender name")
+					@Nested
+					class TestBuildUserName {
+
+						private final PostfachNachrichtPdfData data = PostfachNachrichtPdfDataTestFactory.createBuilder().direction(Direction.IN)
+								.build();
+
+						@Test
+						void shouldReturnAntragstellerIfExists() {
+							var name = service.buildAbsenderName(data, AntragstellerTestFactory.create());
+
+							assertThat(name).isEqualTo(AntragstellerTestFactory.VORNAME + " " + AntragstellerTestFactory.NACHNAME);
+						}
+
+						@Test
+						void shouldReturnFallbackNameForAntragsteller() {
+							var name = service.buildAbsenderName(data, null);
+
+							assertThat(name).isEqualTo(PostfachNachrichtPdfService.FALLBACK_ANTRAGSTELLER_NAME);
+						}
+
+						@Test
+						void shouldReturnEmptyStringOnNullAntragstellerName() {
+							var name = service.buildAbsenderName(data, AntragstellerTestFactory.createBuilder().vorname(null).nachname(null).build());
+
+							assertThat(name).isEqualTo(StringUtils.EMPTY);
+						}
+					}
+				}
+
+				@DisplayName("for outgoing nachricht")
+				@Nested
+				class TestDirectionOut {
+
+					@DisplayName("build absender name")
+					@Nested
+					class TestBuildUserName {
+
+						private final PostfachNachrichtPdfData data = PostfachNachrichtPdfDataTestFactory.createBuilder().direction(Direction.OUT)
+								.build();
+
+						@Test
+						void shouldReturnUserProfileNameIfExists() {
+							var name = service.buildAbsenderName(data, AntragstellerTestFactory.create());
+
+							assertThat(name).isEqualTo(UserProfileTestFactory.FIRSTNAME + " " + UserProfileTestFactory.LASTNAME);
+						}
+
+						@Test
+						void shouldReturnFallbackNameForUserProfile() {
+							var name = service.buildAbsenderName(
+									PostfachNachrichtPdfDataTestFactory.createBuilder().direction(Direction.OUT).user(null).build(),
+									AntragstellerTestFactory.create());
+
+							assertThat(name).isEqualTo(PostfachNachrichtPdfService.FALLBACK_USER_NAME);
+						}
+
+						@Test
+						void shouldReturnEmptyStringOnNullName() {
+							var data = PostfachNachrichtPdfDataTestFactory.createBuilder().direction(Direction.OUT)
+									.user(UserProfileTestFactory.createBuilder().firstName(null).lastName(null).build()).build();
+
+							var name = service.buildAbsenderName(data, AntragstellerTestFactory.create());
+
+							assertThat(name).isEmpty();
+						}
+					}
+				}
+			}
+		}
+
+		@Test
+		void shouldCallPdfService() {
+			doReturn(InputStream.nullInputStream()).when(service).getTemplate();
+
+			service.getAllAsPdf(VorgangWithEingangTestFactory.create(), Stream.of(PostfachNachrichtPdfDataTestFactory.create()), output);
+
+			verify(pdfService).createPdf(any(InputStream.class), eq(output), any(PostfachNachrichtPdfModel.class));
+		}
+	}
+}
\ No newline at end of file
diff --git a/goofy-server/src/test/java/de/itvsh/goofy/representation/GrpcGetRepresentationsResponseTestFactory.java b/goofy-server/src/test/java/de/itvsh/goofy/representation/GrpcGetRepresentationsResponseTestFactory.java
new file mode 100644
index 0000000000000000000000000000000000000000..39d329d667b1ce4aa45c94d8fc3338d565da5f67
--- /dev/null
+++ b/goofy-server/src/test/java/de/itvsh/goofy/representation/GrpcGetRepresentationsResponseTestFactory.java
@@ -0,0 +1,44 @@
+/*
+ * 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.itvsh.goofy.representation;
+
+import java.util.Collections;
+
+import de.itvsh.goofy.common.file.GrpcOzgFileTestFactory;
+import de.itvsh.ozg.pluto.grpc.file.GrpcGetRepresentationsResponse;
+import de.itvsh.ozg.pluto.grpc.file.GrpcOzgFile;
+
+public class GrpcGetRepresentationsResponseTestFactory {
+
+	private static GrpcOzgFile GRPC_FILE = GrpcOzgFileTestFactory.create();
+
+	public static GrpcGetRepresentationsResponse create() {
+		return createBuilder().build();
+	}
+
+	public static GrpcGetRepresentationsResponse.Builder createBuilder() {
+		return GrpcGetRepresentationsResponse.newBuilder()
+				.addAllFile(Collections.singleton(GRPC_FILE));
+	}
+}
\ No newline at end of file
diff --git a/goofy-server/src/test/java/de/itvsh/goofy/representation/RepresentationControllerTest.java b/goofy-server/src/test/java/de/itvsh/goofy/representation/RepresentationControllerTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..7b95e7fabe95fbe3e931ab4e435abb721f0318f3
--- /dev/null
+++ b/goofy-server/src/test/java/de/itvsh/goofy/representation/RepresentationControllerTest.java
@@ -0,0 +1,96 @@
+/*
+ * 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.itvsh.goofy.representation;
+
+import static org.mockito.ArgumentMatchers.*;
+import static org.mockito.Mockito.*;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
+
+import java.util.stream.Stream;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.mockito.ArgumentMatchers;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.springframework.test.web.servlet.MockMvc;
+import org.springframework.test.web.servlet.setup.MockMvcBuilders;
+
+import de.itvsh.goofy.common.binaryfile.BinaryFileModelAssembler;
+import de.itvsh.goofy.common.file.OzgFile;
+import de.itvsh.goofy.common.file.OzgFileService;
+import de.itvsh.goofy.common.file.OzgFileTestFactory;
+
+class RepresentationControllerTest {
+
+	private final String PATH = RepresentationController.REPRESENTATIONS_PATH;
+
+	@InjectMocks
+	private RepresentationController controller;
+	@Mock
+	private BinaryFileModelAssembler modelAssembler;
+	@Mock
+	private OzgFileService fileService;
+
+	private MockMvc mockMvc;
+
+	@BeforeEach
+	void init() {
+		mockMvc = MockMvcBuilders.standaloneSetup(controller).build();
+	}
+
+	@Nested
+	class TestGetRepresentationsByEingang {
+
+		@BeforeEach
+		void mockFileService() {
+			when(fileService.getRepresentationsByEingang(anyString())).thenReturn(Stream.of(OzgFileTestFactory.create()));
+		}
+
+		@Test
+		void shouldCallEndpoint() throws Exception {
+			callEndpoint();
+		}
+
+		@Test
+		void shoudlCallFileService() throws Exception {
+			callEndpoint();
+
+			verify(fileService).getRepresentationsByEingang(any());
+		}
+
+		@Test
+		void shouldCallModelAssembler() throws Exception {
+			callEndpoint();
+
+			verify(modelAssembler).toCollectionModel(ArgumentMatchers.<Stream<OzgFile>>any());
+		}
+
+		private void callEndpoint() throws Exception {
+			mockMvc.perform(get(PATH).param(RepresentationController.PARAM_EINGANG_ID, "1")).andExpect(status().isOk());
+		}
+	}
+}
\ No newline at end of file
diff --git a/goofy-server/src/test/java/de/itvsh/goofy/system/GrpcGetSystemStatusResponseTestFactory.java b/goofy-server/src/test/java/de/itvsh/goofy/system/GrpcGetSystemStatusResponseTestFactory.java
new file mode 100644
index 0000000000000000000000000000000000000000..540f0951ee1486e5104fbd8c37817d0edfc740bf
--- /dev/null
+++ b/goofy-server/src/test/java/de/itvsh/goofy/system/GrpcGetSystemStatusResponseTestFactory.java
@@ -0,0 +1,40 @@
+/*
+ * 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.itvsh.goofy.system;
+
+import de.itvsh.ozg.pluto.system.GrpcGetSystemStatusResponse;
+
+public class GrpcGetSystemStatusResponseTestFactory {
+
+	public static GrpcGetSystemStatusResponse create() {
+		return createBuilder().build();
+	}
+
+	public static GrpcGetSystemStatusResponse.Builder createBuilder() {
+		return GrpcGetSystemStatusResponse.newBuilder()
+				.setIsSearchServerAvailable(true)
+				.setJavaVersion(PlutoSystemStatusTestFactory.JAVA_VERSION)
+				.setBuildVersion(PlutoSystemStatusTestFactory.BUILD_VERSION);
+	}
+}
diff --git a/goofy-server/src/test/java/de/itvsh/goofy/system/PlutoSystemStatusTestFactory.java b/goofy-server/src/test/java/de/itvsh/goofy/system/PlutoSystemStatusTestFactory.java
new file mode 100644
index 0000000000000000000000000000000000000000..55b5a3e5ba5c8760dd73d964d369d1c9b3541e2d
--- /dev/null
+++ b/goofy-server/src/test/java/de/itvsh/goofy/system/PlutoSystemStatusTestFactory.java
@@ -0,0 +1,43 @@
+/*
+ * 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.itvsh.goofy.system;
+
+import de.itvsh.goofy.system.PlutoSystemStatus.PlutoSystemStatusBuilder;
+
+public class PlutoSystemStatusTestFactory {
+
+	public static final String JAVA_VERSION = "17";
+	public static final String BUILD_VERSION = "b1";
+
+	public static PlutoSystemStatus create() {
+		return createBuilder().build();
+	}
+
+	private static PlutoSystemStatusBuilder createBuilder() {
+		return PlutoSystemStatus.builder()
+				.isSearchServerAvailable(true)
+				.javaVersion(JAVA_VERSION)
+				.buildVersion(BUILD_VERSION);
+	}
+}
diff --git a/goofy-server/src/test/java/de/itvsh/goofy/system/RemoteSystemStatusMapperTest.java b/goofy-server/src/test/java/de/itvsh/goofy/system/RemoteSystemStatusMapperTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..835faacd705c078fce895b72a8bdbb19687db4aa
--- /dev/null
+++ b/goofy-server/src/test/java/de/itvsh/goofy/system/RemoteSystemStatusMapperTest.java
@@ -0,0 +1,43 @@
+/*
+ * 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.itvsh.goofy.system;
+
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import org.mapstruct.factory.Mappers;
+
+import static org.assertj.core.api.Assertions.*;
+
+class RemoteSystemStatusMapperTest {
+
+	private RemoteSystemStatusMapper mapper = Mappers.getMapper(RemoteSystemStatusMapper.class);
+
+	@Test
+	@DisplayName("should map gRPC response to PlutoSystemStatus")
+	void shouldMapGrpcResponse() {
+		var plutoSystemStatus = mapper.fromGrpc(GrpcGetSystemStatusResponseTestFactory.create());
+
+		assertThat(plutoSystemStatus).isEqualTo(PlutoSystemStatusTestFactory.create());
+	}
+}
\ No newline at end of file
diff --git a/goofy-server/src/test/java/de/itvsh/goofy/system/SystemStatusRemoteServiceTest.java b/goofy-server/src/test/java/de/itvsh/goofy/system/SystemStatusRemoteServiceTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..1acc5bd8cfd75ccdff3bb4b4d57582358dba5ad6
--- /dev/null
+++ b/goofy-server/src/test/java/de/itvsh/goofy/system/SystemStatusRemoteServiceTest.java
@@ -0,0 +1,75 @@
+/*
+ * 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.itvsh.goofy.system;
+
+import de.itvsh.ozg.pluto.system.GrpcGetSystemStatusRequest;
+import de.itvsh.ozg.pluto.system.SystemStatusServiceGrpc.SystemStatusServiceBlockingStub;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+
+import static org.assertj.core.api.Assertions.*;
+import static org.mockito.ArgumentMatchers.*;
+import static org.mockito.Mockito.*;
+
+class SystemStatusRemoteServiceTest {
+
+	@InjectMocks
+	private SystemStatusRemoteService service;
+
+	@Mock
+	private SystemStatusServiceBlockingStub serviceStub;
+	@Mock
+	private RemoteSystemStatusMapper mapper;
+
+	@Nested
+	class TestGetSystemStatus {
+
+		private static final GrpcGetSystemStatusRequest SYSTEM_STATUS_REQUEST = GrpcGetSystemStatusRequest.newBuilder().build();
+		private static final PlutoSystemStatus PLUTO_SYSTEM_STATUS = PlutoSystemStatusTestFactory.create();
+
+		@BeforeEach
+		void init() {
+			when(mapper.fromGrpc(any())).thenReturn(PLUTO_SYSTEM_STATUS);
+		}
+
+		@Test
+		void shouldCallRemoteService() {
+			service.getSystemStatus();
+
+			verify(serviceStub).getSystemStatus(SYSTEM_STATUS_REQUEST);
+		}
+
+		@Test
+		void shouldGetSearchServerStatus() {
+			when(serviceStub.getSystemStatus(any())).thenReturn(GrpcGetSystemStatusResponseTestFactory.create());
+
+			var plutoSystemStatus  = service.getSystemStatus();
+
+			assertThat(plutoSystemStatus).isEqualTo(PLUTO_SYSTEM_STATUS);
+		}
+	}
+}
\ No newline at end of file
diff --git a/goofy-server/src/test/java/de/itvsh/goofy/vorgang/AntragstellerMapperTest.java b/goofy-server/src/test/java/de/itvsh/goofy/vorgang/AntragstellerMapperTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..27aafd8faa13a3b641421f7c53176cee79de8440
--- /dev/null
+++ b/goofy-server/src/test/java/de/itvsh/goofy/vorgang/AntragstellerMapperTest.java
@@ -0,0 +1,56 @@
+/*
+ * 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.itvsh.goofy.vorgang;
+
+import static org.assertj.core.api.Assertions.*;
+import static org.mockito.Mockito.*;
+
+import org.junit.jupiter.api.Test;
+import org.mapstruct.factory.Mappers;
+import org.mockito.InjectMocks;
+import org.mockito.Spy;
+
+import de.itvsh.kop.pluto.common.grpc.GrpcFormDataMapper;
+
+class AntragstellerMapperTest {
+
+	@InjectMocks
+	private final AntragstellerMapper mapper = Mappers.getMapper(AntragstellerMapper.class);
+	@Spy
+	private final GrpcFormDataMapper grpcFormDataMapper = Mappers.getMapper(GrpcFormDataMapper.class);
+
+	@Test
+	void shouldMap() {
+		var antragsteller = mapper.toAntragsteller(GrpcAntragstellerTestFactory.create());
+
+		assertThat(antragsteller).usingRecursiveComparison().ignoringFields("otherData").isEqualTo(AntragstellerTestFactory.create());
+	}
+
+	@Test
+	void shouldCallGrpcFormDataMapper() {
+		mapper.toAntragsteller(GrpcAntragstellerTestFactory.create());
+
+		verify(grpcFormDataMapper).mapFromFormData(GrpcAntragstellerTestFactory.OTHER_DATA);
+	}
+}
\ No newline at end of file
diff --git a/goofy-server/src/test/java/de/itvsh/goofy/vorgang/AntragstellerTestFactory.java b/goofy-server/src/test/java/de/itvsh/goofy/vorgang/AntragstellerTestFactory.java
new file mode 100644
index 0000000000000000000000000000000000000000..cc86bacb70a535fa6f5ef7fe3c87c44593a97998
--- /dev/null
+++ b/goofy-server/src/test/java/de/itvsh/goofy/vorgang/AntragstellerTestFactory.java
@@ -0,0 +1,70 @@
+/*
+ * 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.itvsh.goofy.vorgang;
+
+import java.util.Map;
+
+public class AntragstellerTestFactory {
+
+	public final static String ANREDE = GrpcAntragstellerTestFactory.ANREDE;
+	public final static String NACHNAME = GrpcAntragstellerTestFactory.NACHNAME;
+	public final static String VORNAME = GrpcAntragstellerTestFactory.VORNAME;
+	public final static String GEBURTSDATUM_STR = GrpcAntragstellerTestFactory.GEBURTSDATUM_STR;
+	public final static String GEBURTSORT = GrpcAntragstellerTestFactory.GEBURTSORT;
+	public final static String GEBURTSNAME = GrpcAntragstellerTestFactory.GEBURTSNAME;
+	public final static String EMAIL = GrpcAntragstellerTestFactory.EMAIL;
+	public final static String TELEFON = GrpcAntragstellerTestFactory.TELEFON;
+	public final static String STRASSE = GrpcAntragstellerTestFactory.STRASSE;
+	public final static String HAUSNUMMER = GrpcAntragstellerTestFactory.HAUSNUMMER;
+	public final static String PLZ = GrpcAntragstellerTestFactory.PLZ;
+	public final static String ORT = GrpcAntragstellerTestFactory.ORT;
+	public final static String POSTFACH_ID = GrpcAntragstellerTestFactory.POSTFACH_ID;
+
+	public final static String OTHER_DATA_KEY = "Test";
+	public final static String OTHER_DATA_VALUE = "Eintrag";
+
+	public static final Map<String, Object> OTHER_DATA = Map.of(OTHER_DATA_KEY, OTHER_DATA_VALUE);
+
+	public static Antragsteller create() {
+		return createBuilder().build();
+	}
+
+	public static Antragsteller.AntragstellerBuilder createBuilder() {
+		return Antragsteller.builder()
+				.anrede(ANREDE)
+				.vorname(VORNAME)
+				.nachname(NACHNAME)
+				.geburtsdatum(GEBURTSDATUM_STR)
+				.geburtsname(GEBURTSNAME)
+				.geburtsort(GEBURTSORT)
+				.email(EMAIL)
+				.telefon(TELEFON)
+				.strasse(STRASSE)
+				.hausnummer(HAUSNUMMER)
+				.plz(PLZ)
+				.ort(ORT)
+				.postfachId(POSTFACH_ID)
+				.otherData(OTHER_DATA);
+	}
+}
\ No newline at end of file
diff --git a/goofy-server/src/test/java/de/itvsh/goofy/vorgang/ClientAttributeUtilsTest.java b/goofy-server/src/test/java/de/itvsh/goofy/vorgang/ClientAttributeUtilsTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..89ced0248fb47f2195373a99c4aa15e7dda3bfce
--- /dev/null
+++ b/goofy-server/src/test/java/de/itvsh/goofy/vorgang/ClientAttributeUtilsTest.java
@@ -0,0 +1,60 @@
+/*
+ * 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.itvsh.goofy.vorgang;
+
+import static org.assertj.core.api.Assertions.*;
+
+import java.util.List;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+
+import de.itvsh.goofy.common.clientattribute.GrpcClientAttributeTestFactory;
+import de.itvsh.ozg.pluto.grpc.clientAttribute.GrpcClientAttribute;
+
+class ClientAttributeUtilsTest {
+	@Nested
+	class TestClientAttributeUtils {
+		List<GrpcClientAttribute> attributeList;
+
+		@BeforeEach
+		void init() {
+			attributeList = List.of(
+					GrpcClientAttributeTestFactory.createBuilder().setAttributeName("name1").build(),
+					GrpcClientAttributeTestFactory.create(),
+					GrpcClientAttributeTestFactory.createBuilder().setAttributeName("name2").build());
+		}
+
+		@Test
+		void findByName() {
+			assertThat(ClientAttributeUtils.findByName(GrpcClientAttributeTestFactory.ATTRIBUTE_NAME, attributeList)).isNotNull();
+		}
+
+		@Test
+		void findNotByName() {
+			assertThat(ClientAttributeUtils.findByName("not_in_list", attributeList)).isEmpty();
+		}
+	}
+}
diff --git a/goofy-server/src/test/java/de/itvsh/goofy/vorgang/EingangHeaderMapperTest.java b/goofy-server/src/test/java/de/itvsh/goofy/vorgang/EingangHeaderMapperTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..e5f834cf117c9ecc130b1a0b88ecc94363b0abe3
--- /dev/null
+++ b/goofy-server/src/test/java/de/itvsh/goofy/vorgang/EingangHeaderMapperTest.java
@@ -0,0 +1,57 @@
+/*
+ * 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.itvsh.goofy.vorgang;
+
+import static org.assertj.core.api.Assertions.*;
+
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.mapstruct.factory.Mappers;
+
+class EingangHeaderMapperTest {
+
+	private EingangHeaderMapper mapper = Mappers.getMapper(EingangHeaderMapper.class);
+
+	@Nested
+	class TestGrpcEingangHeaderToGoofyEingangHeader {
+
+		@Test
+		void shouldMapCreatedAt() {
+			var eingang = callMapper();
+
+			assertThat(eingang.getCreatedAt()).isEqualTo(GrpcEingangHeaderTestFactory.CREATED_AT);
+		}
+
+		@Test
+		void shouldMapRequestId() {
+			var eingang = callMapper();
+
+			assertThat(eingang.getRequestId()).isEqualTo(GrpcEingangHeaderTestFactory.REQUEST_ID);
+		}
+
+		private EingangHeader callMapper() {
+			return mapper.toEingangHeader(GrpcEingangHeaderTestFactory.create());
+		}
+	}
+}
diff --git a/goofy-server/src/test/java/de/itvsh/goofy/vorgang/EingangHeaderTestFactory.java b/goofy-server/src/test/java/de/itvsh/goofy/vorgang/EingangHeaderTestFactory.java
new file mode 100644
index 0000000000000000000000000000000000000000..b4f89ae2ad24314281e11ea1bfadd0b489888c6a
--- /dev/null
+++ b/goofy-server/src/test/java/de/itvsh/goofy/vorgang/EingangHeaderTestFactory.java
@@ -0,0 +1,35 @@
+/*
+ * 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.itvsh.goofy.vorgang;
+
+public class EingangHeaderTestFactory {
+
+	public static EingangHeader create() {
+		return createBuilder().build();
+	}
+
+	public static EingangHeader.EingangHeaderBuilder createBuilder() {
+		return EingangHeader.builder();
+	}
+}
\ No newline at end of file
diff --git a/goofy-server/src/test/java/de/itvsh/goofy/vorgang/EingangMapperTest.java b/goofy-server/src/test/java/de/itvsh/goofy/vorgang/EingangMapperTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..0f6f13dae72731e1ce2b682e486d50b0e58a9bb0
--- /dev/null
+++ b/goofy-server/src/test/java/de/itvsh/goofy/vorgang/EingangMapperTest.java
@@ -0,0 +1,102 @@
+/*
+ * 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.itvsh.goofy.vorgang;
+
+import static org.assertj.core.api.Assertions.*;
+import static org.mockito.ArgumentMatchers.*;
+import static org.mockito.Mockito.*;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.mapstruct.factory.Mappers;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.Spy;
+
+import de.itvsh.kop.pluto.common.grpc.GrpcFormDataMapper;
+import de.itvsh.ozg.pluto.vorgang.GrpcEingang;
+
+class EingangMapperTest {
+
+	@InjectMocks
+	private EingangMapper mapper = Mappers.getMapper(EingangMapper.class);
+
+	@Mock
+	private AntragstellerMapper antragstellerMapper;
+	@Mock
+	private EingangHeaderMapper eingangHeaderMapper;
+	@Spy
+	private GrpcFormDataMapper formDataMapper = Mappers.getMapper(GrpcFormDataMapper.class);
+
+	@Nested
+	class TestGrpcEingangToEingang {
+
+		private final Antragsteller antragsteller = AntragstellerTestFactory.create();
+		private final EingangHeader eingangHeader = EingangHeaderTestFactory.create();
+
+		@BeforeEach
+		void mockMapperReturnValues() {
+			lenient().when(antragstellerMapper.toAntragsteller(any())).thenReturn(antragsteller);
+			lenient().when(eingangHeaderMapper.toEingangHeader(any())).thenReturn(eingangHeader);
+		}
+
+		@Test
+		void shouldMapIdToId() {
+			GrpcEingang grpcEingang = GrpcEingangTestFactory.create();
+
+			var eingang = mapper.fromGrpc(grpcEingang);
+
+			assertThat(eingang.getId()).isEqualTo(GrpcEingangTestFactory.ID);
+		}
+
+		@Test
+		void shouldHaveAntragsteller() {
+			GrpcEingang grpcEingang = GrpcEingangTestFactory.create();
+
+			var eingang = mapper.fromGrpc(grpcEingang);
+
+			assertThat(eingang.getAntragsteller()).isEqualTo(antragsteller);
+		}
+
+		@Test
+		void shouldHaveEingangHeader() {
+			GrpcEingang grpcEingang = GrpcEingangTestFactory.create();
+
+			var eingang = mapper.fromGrpc(grpcEingang);
+
+			assertThat(eingang.getHeader()).isEqualTo(eingangHeader);
+		}
+
+		@Test
+		void shouldProceedWithEmptyValues() {
+			GrpcEingang grpcEingang = GrpcEingang.newBuilder().build();
+
+			var eingang = mapper.fromGrpc(grpcEingang);
+
+			assertThat(eingang.getAntragsteller()).isNull();
+			assertThat(eingang.getHeader()).isNull();
+		}
+	}
+}
\ No newline at end of file
diff --git a/goofy-server/src/test/java/de/itvsh/goofy/vorgang/EingangTestFactory.java b/goofy-server/src/test/java/de/itvsh/goofy/vorgang/EingangTestFactory.java
new file mode 100644
index 0000000000000000000000000000000000000000..d306803e7cb0e6d931e01d0b28339c1e6edbbb0d
--- /dev/null
+++ b/goofy-server/src/test/java/de/itvsh/goofy/vorgang/EingangTestFactory.java
@@ -0,0 +1,59 @@
+/*
+ * 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.itvsh.goofy.vorgang;
+
+import java.util.Map;
+import java.util.UUID;
+
+public class EingangTestFactory {
+
+	public static final String ID = UUID.randomUUID().toString();
+
+	public static final String SINGLE_FIELD_NAME = "name";
+	public static final String SINGLE_FIELD_VALUE = "Thea";
+	public static final String NUMERIC_FIELD_NAME = "alter";
+	public static final Integer NUMERIC_FIELD_VALUE = 42;
+
+	public static final String SUBFORM_FIELD_NAME = "E-Mail";
+	public static final String SUBFORM_FIELD_VALUE = "thea@burger-portal.sh.de";
+	public static final String SUBFORM_NAME = "kontakt";
+	public static final Map<String, Object> SUBFORM = Map.of(SUBFORM_FIELD_NAME, SUBFORM_FIELD_VALUE);
+
+	public static final Map<String, Object> AS_MAP = Map.of(SINGLE_FIELD_NAME, SINGLE_FIELD_VALUE, SUBFORM_NAME, SUBFORM);
+
+	public static Eingang create() {
+		return createBuilder().build();
+	}
+
+	public static Eingang.EingangBuilder createBuilder() {
+		return Eingang.builder()
+				.id(ID)
+				.antragsteller(AntragstellerTestFactory.create())
+				.header(EingangHeaderTestFactory.create())
+				.zustaendigeStelle(ZustaendigeStelleTestFactory.create())
+				.formData(Map.of(SINGLE_FIELD_NAME, SINGLE_FIELD_VALUE,
+						NUMERIC_FIELD_NAME, NUMERIC_FIELD_VALUE,
+						SUBFORM_NAME, SUBFORM));
+	}
+}
\ No newline at end of file
diff --git a/goofy-server/src/test/java/de/itvsh/goofy/vorgang/FindVorgaengeRequestCriteriaTestFactory.java b/goofy-server/src/test/java/de/itvsh/goofy/vorgang/FindVorgaengeRequestCriteriaTestFactory.java
new file mode 100644
index 0000000000000000000000000000000000000000..2cffb2acbe6c3339ebcfb011e4efa1ea2720ebed
--- /dev/null
+++ b/goofy-server/src/test/java/de/itvsh/goofy/vorgang/FindVorgaengeRequestCriteriaTestFactory.java
@@ -0,0 +1,51 @@
+/*
+ * 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.itvsh.goofy.vorgang;
+
+import java.util.Optional;
+
+import com.thedeanda.lorem.LoremIpsum;
+
+import de.itvsh.goofy.common.user.UserProfileTestFactory;
+
+public class FindVorgaengeRequestCriteriaTestFactory {
+
+	public static int LIMIT = 100;
+	public static int OFFSET = 0;
+	public static String SEARCH_BY = LoremIpsum.getInstance().getWords(2);
+	public static OrderBy ORDER_BY = OrderBy.PRIORITY;
+
+	public static FindVorgaengeHeaderRequestCriteria create() {
+		return createBuilder().build();
+	}
+
+	public static FindVorgaengeHeaderRequestCriteria.FindVorgaengeHeaderRequestCriteriaBuilder createBuilder() {
+		return FindVorgaengeHeaderRequestCriteria.builder()
+				.limit(LIMIT)
+				.offset(OFFSET)
+				.searchBy(Optional.of(SEARCH_BY))
+				.orderBy(ORDER_BY)
+				.assignedTo(Optional.of(UserProfileTestFactory.ID));
+	}
+}
\ No newline at end of file
diff --git a/goofy-server/src/test/java/de/itvsh/goofy/vorgang/GrpcAntragstellerTestFactory.java b/goofy-server/src/test/java/de/itvsh/goofy/vorgang/GrpcAntragstellerTestFactory.java
new file mode 100644
index 0000000000000000000000000000000000000000..bf72d78420b89af619477bd5af76176839627a23
--- /dev/null
+++ b/goofy-server/src/test/java/de/itvsh/goofy/vorgang/GrpcAntragstellerTestFactory.java
@@ -0,0 +1,77 @@
+/*
+ * 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.itvsh.goofy.vorgang;
+
+import java.util.UUID;
+
+import com.thedeanda.lorem.LoremIpsum;
+
+import de.itvsh.ozg.pluto.vorgang.GrpcAntragsteller;
+import de.itvsh.ozg.pluto.vorgang.GrpcFormData;
+import de.itvsh.ozg.pluto.vorgang.GrpcFormField;
+
+public class GrpcAntragstellerTestFactory {
+
+	public final static String ANREDE = "HERR/FRAU";
+	public final static String NACHNAME = LoremIpsum.getInstance().getLastName();
+	public final static String VORNAME = LoremIpsum.getInstance().getFirstName();
+	public final static String GEBURTSDATUM_STR = "1995-03-21";
+	public final static String GEBURTSORT = LoremIpsum.getInstance().getCountry();
+	public final static String GEBURTSNAME = LoremIpsum.getInstance().getFirstName();
+	public final static String EMAIL = LoremIpsum.getInstance().getEmail();
+	public static final String TELEFON = "+ 49 4621 9654";
+	public static final String STRASSE = "Lachstrasse";
+	public static final String HAUSNUMMER = "8484";
+	public static final String PLZ = "12345";
+	public static final String ORT = "Wohlfuehlhausen";
+	public static final String POSTFACH_ID = UUID.randomUUID().toString();
+
+	public final static GrpcFormData OTHER_DATA = GrpcFormData.newBuilder()
+			.addField(GrpcFormField.newBuilder()
+					.setName(AntragstellerTestFactory.OTHER_DATA_KEY)
+					.setValue(AntragstellerTestFactory.OTHER_DATA_VALUE).build())
+			.build();
+
+	public static GrpcAntragsteller create() {
+		return createBuilder().build();
+	}
+
+	public static GrpcAntragsteller.Builder createBuilder() {
+		return GrpcAntragsteller.newBuilder()
+				.setAnrede(ANREDE)
+				.setGeburtsdatum(GEBURTSDATUM_STR)
+				.setNachname(NACHNAME)
+				.setVorname(VORNAME)
+				.setGeburtsname(GEBURTSNAME)
+				.setGeburtsort(GEBURTSORT)
+				.setEmail(EMAIL)
+				.setTelefon(TELEFON)
+				.setStrasse(STRASSE)
+				.setHausnummer(HAUSNUMMER)
+				.setPlz(PLZ)
+				.setOrt(ORT)
+				.setPostfachId(POSTFACH_ID)
+				.setOtherData(OTHER_DATA);
+	}
+}
\ No newline at end of file
diff --git a/goofy-server/src/test/java/de/itvsh/goofy/vorgang/GrpcClientAttributeTestFactory.java b/goofy-server/src/test/java/de/itvsh/goofy/vorgang/GrpcClientAttributeTestFactory.java
new file mode 100644
index 0000000000000000000000000000000000000000..e174266e6ceb9c7fc104496ccb9f99dde64a02eb
--- /dev/null
+++ b/goofy-server/src/test/java/de/itvsh/goofy/vorgang/GrpcClientAttributeTestFactory.java
@@ -0,0 +1,54 @@
+/*
+ * 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.itvsh.goofy.vorgang;
+
+import de.itvsh.goofy.common.callcontext.CallContextTestFactory;
+import de.itvsh.ozg.pluto.grpc.clientAttribute.GrpcAccessPermission;
+import de.itvsh.ozg.pluto.grpc.clientAttribute.GrpcClientAttribute;
+import de.itvsh.ozg.pluto.grpc.clientAttribute.GrpcClientAttribute.Builder;
+import de.itvsh.ozg.pluto.grpc.clientAttribute.GrpcClientAttributeValue;
+
+class GrpcClientAttributeTestFactory {
+
+	static final String CLIENT = CallContextTestFactory.CLIENT_NAME;
+	static final GrpcAccessPermission ACCESS = GrpcAccessPermission.READ_WRITE;
+
+	static final GrpcClientAttribute create() {
+		return createBuilder().build();
+	}
+
+	static final Builder createBuilder() {
+		return GrpcClientAttribute.newBuilder()
+				.setClientName(CLIENT)
+				.setAccess(ACCESS);
+	}
+
+	static final GrpcClientAttribute createWith(String attributeName, boolean value) {
+		return createBuilder().setAttributeName(attributeName)
+				.setValue(GrpcClientAttributeValue.newBuilder().setBoolValue(value).build())
+				.build();
+	}
+
+}
diff --git a/goofy-server/src/test/java/de/itvsh/goofy/vorgang/GrpcEingangHeaderTestFactory.java b/goofy-server/src/test/java/de/itvsh/goofy/vorgang/GrpcEingangHeaderTestFactory.java
new file mode 100644
index 0000000000000000000000000000000000000000..6496d0fd6ea1a8ff9acdca68621a79213d74ac01
--- /dev/null
+++ b/goofy-server/src/test/java/de/itvsh/goofy/vorgang/GrpcEingangHeaderTestFactory.java
@@ -0,0 +1,62 @@
+/*
+ * 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.itvsh.goofy.vorgang;
+
+import java.time.ZonedDateTime;
+import java.util.UUID;
+
+import com.thedeanda.lorem.LoremIpsum;
+
+import de.itvsh.ozg.pluto.vorgang.GrpcEingangHeader;
+
+public class GrpcEingangHeaderTestFactory {
+
+	public final static String REQUEST_ID = UUID.randomUUID().toString();
+	private final static String CREATED_AT_STR = "2021-01-10T10:30:00Z";
+	public final static ZonedDateTime CREATED_AT = ZonedDateTime.parse(CREATED_AT_STR);
+	private final static String FORM_ID = UUID.randomUUID().toString();
+	public final static String FORM_NAME = LoremIpsum.getInstance().getWords(1);
+	private final static String SENDER = LoremIpsum.getInstance().getName();
+	private final static String CUSTOMER = LoremIpsum.getInstance().getName();
+	private final static String CUSTOMER_ID = UUID.randomUUID().toString();
+	private final static String CLIENT = LoremIpsum.getInstance().getName();
+	private final static String CLIENT_ID = UUID.randomUUID().toString();
+
+	public static GrpcEingangHeader create() {
+		return createBuilder().build();
+	}
+
+	public static GrpcEingangHeader.Builder createBuilder() {
+		return GrpcEingangHeader.newBuilder()
+				.setRequestId(REQUEST_ID)
+				.setCreatedAt(CREATED_AT_STR)
+				.setFormId(FORM_ID)
+				.setFormName(FORM_NAME)
+				.setSender(SENDER)
+				.setCustomer(CUSTOMER)
+				.setCustomerId(CUSTOMER_ID)
+				.setClient(CLIENT)
+				.setClientId(CLIENT_ID);
+	}
+}
\ No newline at end of file
diff --git a/goofy-server/src/test/java/de/itvsh/goofy/vorgang/GrpcEingangTestFactory.java b/goofy-server/src/test/java/de/itvsh/goofy/vorgang/GrpcEingangTestFactory.java
new file mode 100644
index 0000000000000000000000000000000000000000..22b544cb0afe67d0fce833cef23d440545647149
--- /dev/null
+++ b/goofy-server/src/test/java/de/itvsh/goofy/vorgang/GrpcEingangTestFactory.java
@@ -0,0 +1,48 @@
+/*
+ * 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.itvsh.goofy.vorgang;
+
+import java.util.UUID;
+
+import de.itvsh.ozg.pluto.vorgang.GrpcEingang;
+import de.itvsh.ozg.pluto.vorgang.GrpcFormData;
+import de.itvsh.ozg.pluto.vorgang.GrpcSubForm;
+
+public class GrpcEingangTestFactory {
+
+	public static final String ID = UUID.randomUUID().toString();
+	public static final GrpcSubForm SUB_FORM = GrpcSubFormTestFactory.create();
+
+	public static GrpcEingang create() {
+		return createBuilder().build();
+	}
+
+	public static GrpcEingang.Builder createBuilder() {
+		return GrpcEingang.newBuilder()
+				.setId(ID)
+				.setHeader(GrpcEingangHeaderTestFactory.create())
+				.setAntragsteller(GrpcAntragstellerTestFactory.create())
+				.setFormData(GrpcFormData.newBuilder().addForm(GrpcSubFormTestFactory.create()).build());
+	}
+}
\ No newline at end of file
diff --git a/goofy-server/src/test/java/de/itvsh/goofy/vorgang/GrpcFilterByTestFactory.java b/goofy-server/src/test/java/de/itvsh/goofy/vorgang/GrpcFilterByTestFactory.java
new file mode 100644
index 0000000000000000000000000000000000000000..2e8ccb0747c8b5f76c86001c0c58f5e15fc9a8c8
--- /dev/null
+++ b/goofy-server/src/test/java/de/itvsh/goofy/vorgang/GrpcFilterByTestFactory.java
@@ -0,0 +1,43 @@
+/*
+ * 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.itvsh.goofy.vorgang;
+
+import de.itvsh.goofy.vorgang.Vorgang.VorgangStatus;
+import de.itvsh.ozg.pluto.vorgang.GrpcFilterBy;
+
+public class GrpcFilterByTestFactory {
+
+	static final boolean FILTER_BY_ORGANISATIONSEINHEITEN_ID = true;
+
+	public static GrpcFilterBy create() {
+		return createBuilder().build();
+	}
+
+	public static GrpcFilterBy.Builder createBuilder() {
+		return GrpcFilterBy.newBuilder()
+				.setFilterByOrganisationseinheitenId(FILTER_BY_ORGANISATIONSEINHEITEN_ID)
+				.addStatus(VorgangStatus.NEU.name())
+				.addOrganisationseinheitId(ZustaendigeStelleTestFactory.ORGANISATIONSEINHEITEN_ID);
+	}
+}
diff --git a/goofy-server/src/test/java/de/itvsh/goofy/vorgang/GrpcFindVorgangRequestTestFactory.java b/goofy-server/src/test/java/de/itvsh/goofy/vorgang/GrpcFindVorgangRequestTestFactory.java
new file mode 100644
index 0000000000000000000000000000000000000000..eb628a4da330d9bd9b5175963846b1a13f392f9f
--- /dev/null
+++ b/goofy-server/src/test/java/de/itvsh/goofy/vorgang/GrpcFindVorgangRequestTestFactory.java
@@ -0,0 +1,46 @@
+/*
+ * 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.itvsh.goofy.vorgang;
+
+import static de.itvsh.goofy.vorgang.FindVorgaengeRequestCriteriaTestFactory.*;
+
+import de.itvsh.ozg.pluto.vorgang.GrpcFilterBy;
+import de.itvsh.ozg.pluto.vorgang.GrpcFindVorgangRequest;
+
+public class GrpcFindVorgangRequestTestFactory {
+
+	final static GrpcFilterBy FILTER_BY = GrpcFilterByTestFactory.create();
+
+	public static GrpcFindVorgangRequest create() {
+		return createBuilder().build();
+	}
+
+	public static GrpcFindVorgangRequest.Builder createBuilder() {
+		return GrpcFindVorgangRequest.newBuilder()
+				.setFilterBy(FILTER_BY)
+				.setSearchBy(SEARCH_BY)
+				.setLimit(LIMIT)
+				.setOffset(OFFSET);
+	}
+}
\ No newline at end of file
diff --git a/goofy-server/src/test/java/de/itvsh/goofy/vorgang/GrpcFormDataMapperTest.java b/goofy-server/src/test/java/de/itvsh/goofy/vorgang/GrpcFormDataMapperTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..4aa87a74e8029bcde863721a02609a0e88eeed8d
--- /dev/null
+++ b/goofy-server/src/test/java/de/itvsh/goofy/vorgang/GrpcFormDataMapperTest.java
@@ -0,0 +1,232 @@
+/*
+ * 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.itvsh.goofy.vorgang;
+
+import static org.assertj.core.api.Assertions.*;
+import static org.mockito.ArgumentMatchers.*;
+import static org.mockito.Mockito.*;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.mapstruct.factory.Mappers;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+
+import de.itvsh.kop.pluto.common.grpc.GrpcFormDataMapper;
+import de.itvsh.ozg.pluto.vorgang.GrpcFormData;
+import de.itvsh.ozg.pluto.vorgang.GrpcFormField;
+import de.itvsh.ozg.pluto.vorgang.GrpcSubForm;
+
+class GrpcFormDataMapperTest {
+
+	@InjectMocks
+	private GrpcFormDataMapper MAPPER_INSTANCE = Mappers.getMapper(GrpcFormDataMapper.class);
+
+	@Mock
+	private GrpcFormDataMapper grpcFormDataMapper;
+
+	@BeforeEach
+	void mockMapperReturnValues() {
+		lenient().when(grpcFormDataMapper.mapToFormData(any()))
+				.thenReturn(GrpcFormData.newBuilder().addField(GrpcFormFieldTestFactory.create()).build());
+	}
+
+	@Nested
+	class TestSimpleValueMapping {
+
+		@Test
+		void shouldHaveField() {
+
+			GrpcFormData formData = MAPPER_INSTANCE.mapToFormData(Map.of("key", "value"));
+
+			assertThat(formData.getFieldList()).hasSize(1);
+		}
+
+		@Test
+		void shouldNotHaveSubForms() {
+
+			GrpcFormData formData = MAPPER_INSTANCE.mapToFormData(Map.of("key", "value"));
+
+			assertThat(formData.getFormList()).isEmpty();
+		}
+
+		@Test
+		void shouldHaveNameField() {
+
+			GrpcFormData formData = MAPPER_INSTANCE.mapToFormData(Map.of("key", "value"));
+
+			assertThat(formData.getField(0).getName()).isEqualTo("key");
+			assertThat(formData.getField(0).getValue()).isEqualTo("value");
+		}
+	}
+
+	@Nested
+	class TestSubFormMapping {
+
+		@Test
+		void shouldHaveSubForm() {
+
+			GrpcFormData formData = MAPPER_INSTANCE.mapToFormData(Map.of("key", Map.of("subKey", "value")));
+
+			assertThat(formData.getFormList()).hasSize(1);
+		}
+
+		@Test
+		void shouldNotHaveFields() {
+
+			GrpcFormData formData = MAPPER_INSTANCE.mapToFormData(Map.of("key", Map.of("subKey", "value")));
+
+			assertThat(formData.getFieldList()).isEmpty();
+		}
+
+		@Test
+		void shouldHaveSubFormField() {
+
+			GrpcFormData formData = MAPPER_INSTANCE.mapToFormData(Map.of("key", Map.of("subKey", "value")));
+
+			assertThat(formData.getForm(0).getFieldList()).hasSize(1);
+			assertThat(formData.getForm(0).getTitle()).isEqualTo("key");
+			assertThat(formData.getForm(0).getField(0).getName()).isEqualTo("subKey");
+			assertThat(formData.getForm(0).getField(0).getValue()).isEqualTo("value");
+		}
+	}
+
+	@Nested
+	class TestMapStringListsToFields {
+
+		@Test
+		void emptyMapShouldNotBeMapped() {
+
+			List<GrpcFormField> fields = MAPPER_INSTANCE.mapStringListsToFields(Collections.emptyMap());
+
+			assertThat(fields).isEmpty();
+		}
+
+		@Test
+		void simpleValueShouldNotBeMapped() {
+
+			List<GrpcFormField> fields = MAPPER_INSTANCE.mapStringListsToFields(Map.of("a", "b"));
+
+			assertThat(fields).isEmpty();
+		}
+
+		@Test
+		void listObjectValuesShouldBeMapped() {
+
+			List<GrpcFormField> fields = MAPPER_INSTANCE.mapStringListsToFields(Map.of("key", List.of("value1", "value2")));
+
+			assertThat(fields).hasSize(2);
+			assertThat(fields.get(0).getName()).isEqualTo("key");
+			assertThat(fields.get(0).getValue()).isEqualTo("value1");
+
+			assertThat(fields.get(1).getName()).isEqualTo("key");
+			assertThat(fields.get(1).getValue()).isEqualTo("value2");
+		}
+	}
+
+	@Nested
+	class TestMapObjectListsToFields {
+
+		@Test
+		void simpleValueShouldNotBeMapped() {
+
+			List<GrpcSubForm> fields = MAPPER_INSTANCE.mapObjectListsToFields(Map.of("a", "b"));
+
+			assertThat(fields).isEmpty();
+		}
+
+		@Test
+		void listOfSimpleValueShouldNotBeMapped() {
+
+			List<GrpcSubForm> fields = MAPPER_INSTANCE.mapObjectListsToFields(Map.of("a", List.of("l1", "l2")));
+
+			assertThat(fields).isEmpty();
+		}
+
+		@Test
+		void listOfObjectsShouldBeMappedToSubForms() {
+
+			List<GrpcSubForm> fields = MAPPER_INSTANCE
+					.mapObjectListsToFields(Map.of("key", List.of(Collections.emptyMap(), Collections.emptyMap())));
+
+			assertThat(fields).hasSize(2);
+		}
+
+		@Test
+		void listOfObjectsShouldMappedToSubFormTitle() {
+
+			List<GrpcSubForm> fields = MAPPER_INSTANCE
+					.mapObjectListsToFields(Map.of("key", List.of(Collections.emptyMap(), Collections.emptyMap())));
+
+			assertThat(fields.get(0).getTitle()).isEqualTo("key");
+			assertThat(fields.get(1).getTitle()).isEqualTo("key");
+		}
+
+		@Test
+		void listOfObjectsShouldBeMapped() {
+
+			List<GrpcSubForm> fields = MAPPER_INSTANCE
+					.mapObjectListsToFields(Map.of("key", List.of(Map.of("a1", "a2"))));
+
+			assertThat(fields.get(0).getFieldCount()).isEqualTo(1);
+			assertThat(fields.get(0).getField(0).getName()).isEqualTo("a1");
+			assertThat(fields.get(0).getField(0).getValue()).isEqualTo("a2");
+		}
+
+		@Test
+		void doubleNestedListObjectValuesShouldBeMapped() {
+			GrpcFormData formData = MAPPER_INSTANCE
+					.mapToFormData(Map.of("key1", Map.of("key2", List.of("value1", "value2"))));
+
+			assertThat(formData.getForm(0).getFieldCount()).isEqualTo(2);
+		}
+
+		@Test
+		void multipleNestedListObjectValuesShouldBeMapped() {
+			GrpcFormData formData = MAPPER_INSTANCE
+					.mapToFormData(Map.of("key1", Map.of("key2", Map.of("key3", List.of("value1", "value2")))));
+
+			assertThat(formData.getForm(0).getSubForm(0).getFieldCount()).isEqualTo(2);
+		}
+	}
+
+	@Nested
+	class TestMapListOfMixedValuesInFormData {
+
+		@Test
+		void shouldMapListOfStrings() {
+
+			GrpcFormData formData = MAPPER_INSTANCE
+					.mapToFormData(Map.of("key", List.of("value1", "value2", Map.of("internKey1", "internValue1"))));
+
+			assertThat(formData.getFieldCount()).isEqualTo(2);
+			assertThat(formData.getFormCount()).isEqualTo(1);
+		}
+	}
+}
diff --git a/goofy-server/src/test/java/de/itvsh/goofy/vorgang/GrpcFormFieldTestFactory.java b/goofy-server/src/test/java/de/itvsh/goofy/vorgang/GrpcFormFieldTestFactory.java
new file mode 100644
index 0000000000000000000000000000000000000000..f110b59201a0f49d7101c48b76d97db274e61128
--- /dev/null
+++ b/goofy-server/src/test/java/de/itvsh/goofy/vorgang/GrpcFormFieldTestFactory.java
@@ -0,0 +1,42 @@
+/*
+ * 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.itvsh.goofy.vorgang;
+
+import de.itvsh.ozg.pluto.vorgang.GrpcFormField;
+
+public class GrpcFormFieldTestFactory {
+
+	public static final String TEST_NAME = "name";
+	public static final String TEST_VALUE = "value";
+
+	public static GrpcFormField create() {
+		return createBuilder().build();
+	}
+
+	private static GrpcFormField.Builder createBuilder() {
+		return GrpcFormField.newBuilder()
+				.setName(TEST_NAME)
+				.setValue(TEST_VALUE);
+	}
+}
\ No newline at end of file
diff --git a/goofy-server/src/test/java/de/itvsh/goofy/vorgang/GrpcSubFormTestFactory.java b/goofy-server/src/test/java/de/itvsh/goofy/vorgang/GrpcSubFormTestFactory.java
new file mode 100644
index 0000000000000000000000000000000000000000..e6970f8f9c7ccbc176512811b606f3ab48295f1d
--- /dev/null
+++ b/goofy-server/src/test/java/de/itvsh/goofy/vorgang/GrpcSubFormTestFactory.java
@@ -0,0 +1,51 @@
+/*
+ * 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.itvsh.goofy.vorgang;
+
+import de.itvsh.ozg.pluto.vorgang.GrpcFormField;
+import de.itvsh.ozg.pluto.vorgang.GrpcSubForm;
+
+public class GrpcSubFormTestFactory {
+
+	public static final String TITLE = "person";
+
+	public static final String FIELD_NAME = EingangTestFactory.SINGLE_FIELD_NAME;
+	public static final String FIELD_VALUE = EingangTestFactory.SINGLE_FIELD_VALUE;
+
+	public static final String SUBFORM_NAME = EingangTestFactory.SUBFORM_NAME;
+	public static final String SUBFORM_FIELD_NAME = EingangTestFactory.SUBFORM_FIELD_NAME;
+	public static final String SUBFORM_FIELD_VALUE = EingangTestFactory.SUBFORM_FIELD_VALUE;
+
+	public static GrpcSubForm create() {
+		return createBuilder().build();
+	}
+
+	public static GrpcSubForm.Builder createBuilder() {
+		return GrpcSubForm.newBuilder()
+				.setTitle(TITLE)
+				.addField(GrpcFormField.newBuilder().setName(FIELD_NAME).setValue(FIELD_VALUE).build())
+				.addSubForm(GrpcSubForm.newBuilder().setTitle(SUBFORM_NAME).addField(
+						GrpcFormField.newBuilder().setName(SUBFORM_FIELD_NAME).setValue(SUBFORM_FIELD_VALUE).build()));
+	}
+}
diff --git a/goofy-server/src/test/java/de/itvsh/goofy/vorgang/GrpcVorgangAttachedItemTestFactory.java b/goofy-server/src/test/java/de/itvsh/goofy/vorgang/GrpcVorgangAttachedItemTestFactory.java
new file mode 100644
index 0000000000000000000000000000000000000000..be259bc7187a38847f31f1175fa2e5f9391feedf
--- /dev/null
+++ b/goofy-server/src/test/java/de/itvsh/goofy/vorgang/GrpcVorgangAttachedItemTestFactory.java
@@ -0,0 +1,63 @@
+/*
+ * 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.itvsh.goofy.vorgang;
+
+import java.util.UUID;
+
+import com.thedeanda.lorem.LoremIpsum;
+
+import de.itvsh.ozg.pluto.common.GrpcObject;
+import de.itvsh.ozg.pluto.common.GrpcProperty;
+import de.itvsh.ozg.pluto.vorgangAttachedItem.GrpcVorgangAttachedItem;
+
+public class GrpcVorgangAttachedItemTestFactory {
+
+	public final static String ID = UUID.randomUUID().toString();
+	public final static String CLIENT = LoremIpsum.getInstance().getWords(1);
+	public final static String ITEM_NAME = LoremIpsum.getInstance().getWords(1);
+	public final static String ITEM_KEY = LoremIpsum.getInstance().getWords(1);
+	public final static String ITEM_VALUE = LoremIpsum.getInstance().getWords(1);
+	public final static long VERSION = 73;
+
+	public static final GrpcObject ITEM = GrpcObject.newBuilder()
+			.addProperty(GrpcProperty.newBuilder()
+					.setName(ITEM_KEY)
+					.addValue(ITEM_VALUE)
+					.build())
+			.build();
+
+	public static GrpcVorgangAttachedItem create() {
+		return createBuilder().build();
+	}
+
+	public static GrpcVorgangAttachedItem.Builder createBuilder() {
+		return GrpcVorgangAttachedItem.newBuilder()
+				.setId(ID)
+				.setClient(CLIENT)
+				.setVorgangId(VorgangHeaderTestFactory.ID)
+				.setItemName(ITEM_NAME)
+				.setItem(ITEM)
+				.setVersion(VERSION);
+	}
+}
\ No newline at end of file
diff --git a/goofy-server/src/test/java/de/itvsh/goofy/vorgang/GrpcVorgangHeaderTestFactory.java b/goofy-server/src/test/java/de/itvsh/goofy/vorgang/GrpcVorgangHeaderTestFactory.java
new file mode 100644
index 0000000000000000000000000000000000000000..12eb20fa5a7de3f3f91fe06b50e6bb6c76d8d09d
--- /dev/null
+++ b/goofy-server/src/test/java/de/itvsh/goofy/vorgang/GrpcVorgangHeaderTestFactory.java
@@ -0,0 +1,45 @@
+/*
+ * 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.itvsh.goofy.vorgang;
+
+import static de.itvsh.goofy.vorgang.VorgangHeaderTestFactory.*;
+
+import de.itvsh.ozg.pluto.vorgang.GrpcVorgangHeader;
+
+public class GrpcVorgangHeaderTestFactory {
+
+	public static GrpcVorgangHeader create() {
+		return createBuilder().build();
+	}
+
+	public static GrpcVorgangHeader.Builder createBuilder() {
+		return GrpcVorgangHeader.newBuilder()
+				.setNummer(NUMMER)
+				.setCreatedAt(CREATED_AT_STR)
+				.setAktenzeichen(AKTENZEICHEN)
+				.setAssignedTo(ASSIGNED_TO)
+				.setName(NAME)
+				.setStatus(STATUS.name());
+	}
+}
\ No newline at end of file
diff --git a/goofy-server/src/test/java/de/itvsh/goofy/vorgang/GrpcVorgangTestFactory.java b/goofy-server/src/test/java/de/itvsh/goofy/vorgang/GrpcVorgangTestFactory.java
new file mode 100644
index 0000000000000000000000000000000000000000..d1bee97e6b0fee577dcf8a6665b1c8eb0a356db3
--- /dev/null
+++ b/goofy-server/src/test/java/de/itvsh/goofy/vorgang/GrpcVorgangTestFactory.java
@@ -0,0 +1,66 @@
+/*
+ * 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.itvsh.goofy.vorgang;
+
+import java.time.LocalDateTime;
+import java.util.UUID;
+
+import com.thedeanda.lorem.LoremIpsum;
+
+import de.itvsh.ozg.pluto.vorgang.GrpcFindVorgangResponse;
+import de.itvsh.ozg.pluto.vorgang.GrpcVorgangHeader;
+
+public class GrpcVorgangTestFactory {
+
+	private static final int TOTAL = 100;
+
+	private static final String ID = UUID.randomUUID().toString();
+	private static final String NAME = LoremIpsum.getInstance().getWords(10);
+	private static final String CREATED_AT = LocalDateTime.now().toString();
+	private static final String STATUS = "NEU";
+	private static final String NUMMER = "VorgangNummer123";
+
+	public static GrpcFindVorgangResponse createVorgangResponse() {
+		return createVorgangResponseBuilder().build();
+	}
+
+	private static GrpcFindVorgangResponse.Builder createVorgangResponseBuilder() {
+		return GrpcFindVorgangResponse.newBuilder()
+				.setTotal(TOTAL)
+				.addVorgang(create());
+	}
+
+	private static GrpcVorgangHeader create() {
+		return createBuilder().build();
+	}
+
+	private static GrpcVorgangHeader.Builder createBuilder() {
+		return GrpcVorgangHeader.newBuilder()
+				.setId(ID)
+				.setName(NAME)
+				.setNummer(NUMMER)
+				.setCreatedAt(CREATED_AT)
+				.setStatus(STATUS);
+	}
+}
\ No newline at end of file
diff --git a/goofy-server/src/test/java/de/itvsh/goofy/vorgang/GrpcVorgangWithEingangResponseTestFactory.java b/goofy-server/src/test/java/de/itvsh/goofy/vorgang/GrpcVorgangWithEingangResponseTestFactory.java
new file mode 100644
index 0000000000000000000000000000000000000000..1eefbc10c3d95c66d043e06b1fcc116af2e307d4
--- /dev/null
+++ b/goofy-server/src/test/java/de/itvsh/goofy/vorgang/GrpcVorgangWithEingangResponseTestFactory.java
@@ -0,0 +1,37 @@
+/*
+ * 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.itvsh.goofy.vorgang;
+
+import de.itvsh.ozg.pluto.vorgang.GrpcFindVorgangWithEingangResponse;
+
+class GrpcVorgangWithEingangResponseTestFactory {
+
+	static GrpcFindVorgangWithEingangResponse createVorgangWithEingangResponse() {
+		return createVorgangWithEingangResponseBuilder().build();
+	}
+
+	private static GrpcFindVorgangWithEingangResponse.Builder createVorgangWithEingangResponseBuilder() {
+		return GrpcFindVorgangWithEingangResponse.newBuilder().setVorgangWithEingang(GrpcVorgangWithEingangTestFactory.create());
+	}
+}
\ No newline at end of file
diff --git a/goofy-server/src/test/java/de/itvsh/goofy/vorgang/GrpcVorgangWithEingangTestFactory.java b/goofy-server/src/test/java/de/itvsh/goofy/vorgang/GrpcVorgangWithEingangTestFactory.java
new file mode 100644
index 0000000000000000000000000000000000000000..d763fa2f147d8e16398a3f09fdc04423611f2ffa
--- /dev/null
+++ b/goofy-server/src/test/java/de/itvsh/goofy/vorgang/GrpcVorgangWithEingangTestFactory.java
@@ -0,0 +1,57 @@
+/*
+ * 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.itvsh.goofy.vorgang;
+
+import static de.itvsh.goofy.vorgang.VorgangHeaderTestFactory.*;
+
+import de.itvsh.goofy.common.clientattribute.ClientAttributeService;
+import de.itvsh.ozg.pluto.grpc.clientAttribute.GrpcClientAttributeValue;
+import de.itvsh.ozg.pluto.vorgang.GrpcEingang;
+import de.itvsh.ozg.pluto.vorgang.GrpcVorgangWithEingang;
+
+class GrpcVorgangWithEingangTestFactory {
+
+	static GrpcVorgangWithEingang create() {
+		return createBuilder().build();
+	}
+
+	static GrpcVorgangWithEingang createWithEingang(GrpcEingang eingang) {
+		return createBuilder().setEingang(eingang).build();
+	}
+
+	static GrpcVorgangWithEingang.Builder createBuilder() {
+		return GrpcVorgangWithEingang.newBuilder()
+				.setStatus(STATUS.name())
+				.setNummer(NUMMER)
+				.setAktenzeichen(AKTENZEICHEN)
+				.setAssignedTo(ASSIGNED_TO)
+				.setCreatedAt(CREATED_AT_STR)
+				.clearClientAttributes()
+				.addClientAttributes(
+						GrpcClientAttributeTestFactory.createBuilder()
+								.setAttributeName(ClientAttributeService.HAS_NEW_POSTFACH_NACHRICHT_ATTRIBUTE_NAME)
+								.setValue(GrpcClientAttributeValue.newBuilder().setBoolValue(true)).build())
+				.setEingang(GrpcEingangTestFactory.create());
+	}
+}
\ No newline at end of file
diff --git a/goofy-server/src/test/java/de/itvsh/goofy/vorgang/GrpcZustaendigeStelleTestFactory.java b/goofy-server/src/test/java/de/itvsh/goofy/vorgang/GrpcZustaendigeStelleTestFactory.java
new file mode 100644
index 0000000000000000000000000000000000000000..908cf76516023d2e64162c607ec19e860268d557
--- /dev/null
+++ b/goofy-server/src/test/java/de/itvsh/goofy/vorgang/GrpcZustaendigeStelleTestFactory.java
@@ -0,0 +1,41 @@
+/*
+ * 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.itvsh.goofy.vorgang;
+
+import de.itvsh.ozg.pluto.vorgang.GrpcZustaendigeStelle;
+
+public class GrpcZustaendigeStelleTestFactory {
+    private static final String EMAIL = ZustaendigeStelleTestFactory.EMAIL;
+    private static final String ORGANISATIONSEINHEITEN_ID = ZustaendigeStelleTestFactory.ORGANISATIONSEINHEITEN_ID;
+
+    public static GrpcZustaendigeStelle create() {
+        return createBuilder().build();
+    }
+
+    public static GrpcZustaendigeStelle.Builder createBuilder() {
+        return GrpcZustaendigeStelle.newBuilder()
+                .setEmail(EMAIL)
+                .setOrganisationseinheitenId(ORGANISATIONSEINHEITEN_ID);
+    }
+}
diff --git a/goofy-server/src/test/java/de/itvsh/goofy/vorgang/RedirectRequestTestFactory.java b/goofy-server/src/test/java/de/itvsh/goofy/vorgang/RedirectRequestTestFactory.java
new file mode 100644
index 0000000000000000000000000000000000000000..f734412eb1feb5814671ff84a5619cd45b1c5c59
--- /dev/null
+++ b/goofy-server/src/test/java/de/itvsh/goofy/vorgang/RedirectRequestTestFactory.java
@@ -0,0 +1,52 @@
+/*
+ * 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.itvsh.goofy.vorgang;
+
+import de.itvsh.goofy.common.command.CreateCommand;
+import de.itvsh.goofy.vorgang.forwarding.RedirectRequest;
+import de.itvsh.kop.common.test.TestUtils;
+
+public class RedirectRequestTestFactory {
+
+	public static final String EMAIL = "test@ozg-sh.de";
+	public static final String PASSWORD_AS_STR = "ValidesPassword";
+	public static final char[] PASSWORD = PASSWORD_AS_STR.toCharArray();
+
+	public static RedirectRequest create() {
+		return createBuilder().build();
+	}
+
+	public static RedirectRequest.RedirectRequestBuilder createBuilder() {
+		return RedirectRequest.builder()
+				.email(EMAIL)
+				.password(PASSWORD);
+	}
+
+	public static String createRedirectRequestContent(CreateCommand command) {
+		return TestUtils.loadTextFile("jsonTemplates/command/createCommandWithRedirectRequest.json.tmpl",
+				command.getOrder().name(),
+				TestUtils.addQuote(command.getRedirectRequest().getEmail()),
+				TestUtils.addQuote(String.valueOf(command.getRedirectRequest().getPassword())));
+	}
+}
\ No newline at end of file
diff --git a/goofy-server/src/test/java/de/itvsh/goofy/vorgang/VorgangAuthorizationServiceTest.java b/goofy-server/src/test/java/de/itvsh/goofy/vorgang/VorgangAuthorizationServiceTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..b5bed86a7e7c01305ac474171fe7ebb5ea16d1eb
--- /dev/null
+++ b/goofy-server/src/test/java/de/itvsh/goofy/vorgang/VorgangAuthorizationServiceTest.java
@@ -0,0 +1,98 @@
+/*
+ * 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.itvsh.goofy.vorgang;
+
+import static org.assertj.core.api.Assertions.*;
+import static org.mockito.Mockito.*;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.EnumSource;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+
+import de.itvsh.goofy.common.user.CurrentUserService;
+import de.itvsh.goofy.common.user.UserRole;
+import de.itvsh.goofy.vorgang.Vorgang.VorgangStatus;
+
+class VorgangAuthorizationServiceTest {
+
+	@InjectMocks
+	private VorgangAuthorizationService service;
+	@Mock
+	private CurrentUserService userService;
+
+	@Nested
+	class TestVerifyByUserRole {
+
+		@Nested
+		class TestForVerwaltungPoststelle {
+
+			@BeforeEach
+			void mockUserRole() {
+				when(userService.hasRole(UserRole.VERWALTUNG_POSTSTELLE)).thenReturn(true);
+			}
+
+			@DisplayName("should return true by vorgangstatus 'Neu'")
+			@Test
+			void shouldReturnTrue() {
+				var authorized = service.verifyByUserRole(VorgangWithEingangTestFactory.createBuilder().status(VorgangStatus.NEU).build());
+
+				assertThat(authorized).isTrue();
+			}
+
+			@DisplayName("should return false by other vorgangstatus then 'Neu'")
+			@ParameterizedTest
+			@EnumSource(value = VorgangStatus.class, names = { "ANGENOMMEN", "VERWORFEN", "IN_BEARBEITUNG", "BESCHIEDEN", "ABGESCHLOSSEN",
+					"WEITERGELEITET" })
+			void shouldReturnFalse(VorgangStatus status) {
+				var authorized = service.verifyByUserRole(VorgangWithEingangTestFactory.createBuilder().status(status).build());
+
+				assertThat(authorized).isFalse();
+			}
+		}
+
+		@Nested
+		class TestForOtherRoles {
+
+			@BeforeEach
+			void mockUserRole() {
+				when(userService.hasRole(UserRole.VERWALTUNG_POSTSTELLE)).thenReturn(false);
+			}
+
+			@DisplayName("should return true on role 'VERWALTUNG_USER' independent which vorgangstatus")
+			@ParameterizedTest
+			@EnumSource(value = VorgangStatus.class, names = { "NEU", "ANGENOMMEN", "VERWORFEN", "IN_BEARBEITUNG", "BESCHIEDEN", "ABGESCHLOSSEN",
+					"WEITERGELEITET" })
+			void shouldReturnTrue(VorgangStatus status) {
+				var authorized = service.verifyByUserRole(VorgangWithEingangTestFactory.createBuilder().status(status).build());
+
+				assertThat(authorized).isTrue();
+			}
+		}
+	}
+}
\ No newline at end of file
diff --git a/goofy-server/src/test/java/de/itvsh/goofy/vorgang/VorgangControllerITCase.java b/goofy-server/src/test/java/de/itvsh/goofy/vorgang/VorgangControllerITCase.java
new file mode 100644
index 0000000000000000000000000000000000000000..5102a5f41f730d982776c7c511bc5fc8c055c0e8
--- /dev/null
+++ b/goofy-server/src/test/java/de/itvsh/goofy/vorgang/VorgangControllerITCase.java
@@ -0,0 +1,184 @@
+/*
+ * 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.itvsh.goofy.vorgang;
+
+import static org.mockito.ArgumentMatchers.*;
+import static org.mockito.Mockito.*;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
+
+import java.util.List;
+
+import org.apache.commons.lang3.StringUtils;
+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.servlet.AutoConfigureMockMvc;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.boot.test.mock.mockito.MockBean;
+import org.springframework.http.MediaType;
+import org.springframework.security.test.context.support.WithMockUser;
+import org.springframework.test.web.servlet.MockMvc;
+import org.springframework.test.web.servlet.ResultActions;
+
+import de.itvsh.goofy.common.clientattribute.ClientAttributeService;
+import de.itvsh.goofy.common.command.CommandController;
+import de.itvsh.goofy.common.errorhandling.ResourceNotFoundException;
+import de.itvsh.goofy.common.user.UserProfileTestFactory;
+import de.itvsh.goofy.postfach.PostfachMailController;
+import de.itvsh.goofy.vorgang.forwarding.ForwardingController;
+import de.itvsh.goofy.wiedervorlage.WiedervorlageTestFactory;
+
+@AutoConfigureMockMvc
+@SpringBootTest
+@WithMockUser
+class VorgangControllerITCase {
+
+	@MockBean
+	private VorgangRemoteService remoteService;
+	@MockBean
+	private CommandController commandController;
+	@MockBean
+	private ForwardingController forwardingController;
+	@MockBean
+	private PostfachMailController postfachMailController;
+	@MockBean
+	private ClientAttributeService clientAttributeService;
+
+	@Autowired
+	private MockMvc mockMvc;
+
+	@WithMockUser
+	@Nested
+	class TestVorgangListByPage {
+
+		private final String PATH = VorgangController.PATH + "/";
+
+		private final VorgaengeHeaderResponse response = VorgaengeHeaderResponse.builder().vorgaengeHeader(List.of(
+				VorgangHeaderTestFactory.createBuilder().nextFrist(WiedervorlageTestFactory.FRIST).build())).build();
+
+		@BeforeEach
+		void mockRemoteService() {
+			when(remoteService.findVorgaengeHeader(any())).thenReturn(response);
+		}
+
+		@Test
+		void shouldReturnResult() throws Exception {
+			doRequest().andExpect(jsonPath("$._embedded.vorgangHeaderList[0].nextFrist").value(WiedervorlageTestFactory.FRIST_STR));
+		}
+
+		@Test
+		void shouldReturnStatusOk() throws Exception {
+			doRequest().andExpect(status().isOk());
+		}
+
+		private ResultActions doRequest() throws Exception {
+			return mockMvc.perform(get(PATH, VorgangHeaderTestFactory.ID));
+		}
+	}
+
+	@WithMockUser
+	@Nested
+	class TestGetSingleVorgang {
+
+		public static final String PATH = VorgangController.PATH + "/{id}";
+
+		@Test
+		void shouldReturnNotFound() throws Exception {
+			when(remoteService.findVorgangWithEingang(anyString())).thenThrow(new ResourceNotFoundException(Vorgang.class, StringUtils.EMPTY));
+
+			doRequest().andExpect(status().isNotFound());
+		}
+
+		@Test
+		void shouldFormatDateTime() throws Exception {
+			when(remoteService.findVorgangWithEingang(anyString())).thenReturn(VorgangWithEingangTestFactory.create());
+
+			doRequest().andExpect(status().isOk()).andExpect(jsonPath("$.createdAt").value(VorgangHeaderTestFactory.CREATED_AT_STR));
+		}
+
+		@Test
+		void shouldHaveAnnehmenLink() throws Exception {
+			when(remoteService.findVorgangWithEingang(anyString())).thenReturn(VorgangWithEingangTestFactory.create());
+
+			doRequest().andExpect(jsonPath("$._links.annehmen.href")
+					.value("http://localhost/api/vorgangs/" + VorgangHeaderTestFactory.ID + "/relations/" + VorgangHeaderTestFactory.ID + "/"
+							+ VorgangHeaderTestFactory.VERSION + "/commands"));
+		}
+
+		@WithMockUser
+		@Nested
+		class TestLinkToAssignedUser {
+
+			@Test
+			void shouldReturnStatusOk() throws Exception {
+				when(remoteService.findVorgangWithEingang(anyString())).thenReturn(VorgangWithEingangTestFactory.create());
+
+				doRequest().andExpect(status().is2xxSuccessful());
+			}
+
+			@Test
+			void shouldBePresentIfAssigned() throws Exception {
+				when(remoteService.findVorgangWithEingang(anyString())).thenReturn(VorgangWithEingangTestFactory.create());
+
+				doRequest()
+						.andExpect(jsonPath("$._links.assignedTo.href").value("http://localhost:9092/api/userProfiles/" + UserProfileTestFactory.ID));
+			}
+
+			@Test
+			void shouldNOTBePresentIfNOSTAssigned() throws Exception {
+				when(remoteService.findVorgangWithEingang(anyString()))
+						.thenReturn(VorgangWithEingangTestFactory.createBuilder().assignedTo(null).build());
+
+				doRequest().andExpect(jsonPath("$._links.assignedTo.href").doesNotExist());
+			}
+		}
+
+		private ResultActions doRequest() throws Exception {
+			return mockMvc.perform(get(PATH, VorgangHeaderTestFactory.ID));
+		}
+	}
+
+	@Nested
+	class TestClientAttribute {
+		@Test
+		void shouldReturnNoContent() throws Exception {
+			mockMvc
+					.perform(put("http://localhost/api/vorgangs/{0}/hasNewPostfachNachricht", VorgangHeaderTestFactory.ID)
+							.contentType(MediaType.APPLICATION_JSON)
+							.content("{\"hasNewPostfachNachricht\":false}"))
+					.andExpect(status().isNoContent());
+		}
+
+		@Test
+		void shouldReturnUnprocessableEntity() throws Exception {
+			mockMvc
+					.perform(put("http://localhost/api/vorgangs/{0}/hasNewPostfachNachricht", VorgangHeaderTestFactory.ID)
+							.contentType(MediaType.APPLICATION_JSON)
+							.content("{\"hasNewPostfachNachricht\":true}"))
+					.andExpect(status().isUnprocessableEntity());
+		}
+	}
+}
\ No newline at end of file
diff --git a/goofy-server/src/test/java/de/itvsh/goofy/vorgang/VorgangControllerTest.java b/goofy-server/src/test/java/de/itvsh/goofy/vorgang/VorgangControllerTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..9d0dceec2de8dd13d837097fca8dd23ce53c28f7
--- /dev/null
+++ b/goofy-server/src/test/java/de/itvsh/goofy/vorgang/VorgangControllerTest.java
@@ -0,0 +1,310 @@
+/*
+ * 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.itvsh.goofy.vorgang;
+
+import static de.itvsh.goofy.vorgang.EingangTestFactory.*;
+import static org.assertj.core.api.Assertions.*;
+import static org.mockito.ArgumentMatchers.*;
+import static org.mockito.Mockito.*;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
+
+import java.util.Optional;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.Spy;
+import org.springframework.context.ApplicationContext;
+import org.springframework.core.env.Environment;
+import org.springframework.hateoas.EntityModel;
+import org.springframework.http.MediaType;
+import org.springframework.test.web.servlet.MockMvc;
+import org.springframework.test.web.servlet.ResultActions;
+import org.springframework.test.web.servlet.setup.MockMvcBuilders;
+
+import de.itvsh.goofy.common.UserProfileUrlProvider;
+import de.itvsh.goofy.common.clientattribute.ClientAttributeService;
+import de.itvsh.goofy.common.user.UserId;
+import de.itvsh.goofy.common.user.UserProfileTestFactory;
+
+class VorgangControllerTest {
+
+	private final String RESET_POSTFACH_NACHRICHT_PATH = VorgangController.PATH + "/{0}/hasNewPostfachNachricht";
+	private final String PATH = VorgangController.PATH + "/";
+
+	@Spy
+	@InjectMocks
+	private VorgangController controller;
+
+	@Mock
+	private ClientAttributeService clientAttributeService;
+	@Mock
+	private VorgangService vorgangService;
+	@Mock
+	private VorgangModelAssembler modelAssembler;
+
+	private UserProfileUrlProvider urlProvider = new UserProfileUrlProvider();
+
+	private MockMvc mockMvc;
+
+	@BeforeEach
+	void initTest() {
+		mockMvc = MockMvcBuilders.standaloneSetup(controller).build();
+		ApplicationContext context = mock(ApplicationContext.class);
+		urlProvider.setApplicationContext(context);
+	}
+
+	@Nested
+	class TestVorgangListByPage {
+
+		final int PAGE = 5;
+
+		final int LIMIT = 7;
+
+		@BeforeEach
+		void initTest() {
+			when(vorgangService.findVorgaengeHeader(any(FindVorgaengeHeaderRequestCriteria.class)))
+					.thenReturn(VorgangListResponseTestFactory.create());
+		}
+
+		@Test
+		void shouldCallServiceWithDefaultPage() throws Exception {
+			mockMvc.perform(get(PATH)).andExpect(status().isOk());
+
+			verify(controller).buildFindVorgaengeRequestCriteria(0, Optional.empty(), Optional.empty(), Optional.empty());
+		}
+
+		@Test
+		void shouldCallServiceWithParameter() throws Exception {
+			callEndpointWithParamsPageSearchAndLimit();
+
+			verify(controller).buildFindVorgaengeRequestCriteria(PAGE, Optional.of("test"), Optional.of(7),
+					Optional.of(UserProfileTestFactory.ID));
+		}
+
+		@Test
+		void shouldCallService() throws Exception {
+			callEndpointWithParam();
+
+			verify(controller).buildFindVorgaengeRequestCriteria(PAGE, Optional.empty(), Optional.empty(), Optional.empty());
+		}
+
+		@Test
+		void shouldCallModelAssembler() throws Exception {
+			callEndpointWithParam();
+
+			verify(modelAssembler).toCollectionModel(any(), any(), any());
+		}
+
+		private void callEndpointWithParam() throws Exception {
+			mockMvc.perform(get(PATH).param(VorgangController.PARAM_PAGE, Integer.toString(PAGE))).andExpect(status().isOk());
+		}
+
+		private void callEndpointWithParamsPageSearchAndLimit() throws Exception {
+			mockMvc.perform(get(PATH)
+					.param(VorgangController.PARAM_PAGE, Integer.toString(PAGE))
+					.param(VorgangController.PARAM_SEARCH, "test")
+					.param(VorgangController.PARAM_LIMIT, Integer.toString(LIMIT))
+					.param(VorgangController.PARAM_ASSIGNED_TO, UserProfileTestFactory.ID.toString()))
+					.andExpect(status().isOk());
+		}
+	}
+
+	@Nested
+	class TestBuildFindVorgaengeRequestCriteria {
+
+		private final static Integer PAGE = 1;
+		private final static Optional<String> SEARCH_BY = Optional.of("SuchBegriff");
+		private final static Optional<Integer> LIMIT = Optional.of(5);
+		private final static Optional<UserId> ASSIGNED_TO = Optional.of(UserProfileTestFactory.ID);
+
+		@Test
+		void shouldHaveSetPage() {
+			var requestCriteria = buildRequestCriteria();
+
+			assertThat(requestCriteria.getPage()).isEqualTo(PAGE);
+		}
+
+		@Test
+		void shouldHaveSetSearchBy() {
+			var requestCriteria = buildRequestCriteria();
+
+			assertThat(requestCriteria.getSearchBy()).isEqualTo(SEARCH_BY);
+		}
+
+		@Test
+		void shouldHaveSetRequestLimit() {
+			var requestCriteria = buildRequestCriteria();
+
+			assertThat(requestCriteria.getRequestLimit()).isEqualTo(LIMIT);
+		}
+
+		@Test
+		void shouldHaveSetAssignedTo() {
+			var requestCriteria = buildRequestCriteria();
+
+			assertThat(requestCriteria.getAssignedTo()).isEqualTo(ASSIGNED_TO);
+		}
+
+		@Test
+		void shouldHaveCalculatedOffset() {
+			var requestCriteria = buildRequestCriteria();
+
+			assertThat(requestCriteria.getOffset()).isEqualTo(5);
+		}
+
+		@Test
+		void shouldHaveSetLimit() {
+			var requestCriteria = buildRequestCriteria();
+
+			assertThat(requestCriteria.getLimit()).isEqualTo(5);
+		}
+
+		private FindVorgaengeHeaderRequestCriteria buildRequestCriteria() {
+			return controller.buildFindVorgaengeRequestCriteria(PAGE, SEARCH_BY, LIMIT, ASSIGNED_TO);
+		}
+	}
+
+	@Nested
+	class TestSearchVorgangList {
+
+		static final String SEARCH_STRING = "Looking for freedom";
+
+		@BeforeEach
+		void initTest() {
+			when(vorgangService.findVorgaengeHeader(any(FindVorgaengeHeaderRequestCriteria.class)))
+					.thenReturn(VorgangListResponseTestFactory.create());
+		}
+
+		@Test
+		void shouldReturnOk() throws Exception {
+			doRequest().andExpect(status().isOk());
+		}
+
+		@Test
+		void shouldCallService() throws Exception {
+			doRequest();
+
+			verify(vorgangService).findVorgaengeHeader(any(FindVorgaengeHeaderRequestCriteria.class));
+		}
+
+		private ResultActions doRequest() throws Exception {
+			return mockMvc.perform(get(PATH).param(VorgangController.PARAM_SEARCH, SEARCH_STRING))
+					.andExpect(status().is2xxSuccessful());
+		}
+	}
+
+	@Nested
+	class TestVorgangWithEingang {
+
+		final int page = 0;
+
+		@BeforeEach
+		void initTest() {
+			when(vorgangService.findVorgangWithEingang(any())).thenReturn(VorgangWithEingangTestFactory.create());
+			when(modelAssembler.toModel(any())).then(i -> EntityModel.of(i.getArgument(0)));
+
+			ApplicationContext context = mock(ApplicationContext.class);
+			Environment environment = mock(Environment.class);
+			when(environment.getProperty(anyString())).thenReturn("test/");
+			when(context.getEnvironment()).thenReturn(environment);
+			urlProvider.setApplicationContext(context);
+		}
+
+		@Test
+		void shouldCallService() throws Exception {
+			performRequest();
+
+			verify(vorgangService).findVorgangWithEingang(VorgangHeaderTestFactory.ID);
+		}
+
+		@Test
+		void shouldCallModelAssembler() throws Exception {
+			performRequest();
+
+			verify(modelAssembler).toModel(any());
+		}
+
+		@Nested
+		class TestDataMapping {
+
+			@DisplayName("Single Field should be in json first level")
+			@Test
+			void singleField() throws Exception {
+				performRequest().andExpect(jsonPath("$.eingang.formData." + SINGLE_FIELD_NAME).value(SINGLE_FIELD_VALUE));
+			}
+
+			@DisplayName("Numeric field should be numeric in json")
+			@Test
+			void numericField() throws Exception {
+				performRequest().andExpect(jsonPath("$.eingang.formData." + NUMERIC_FIELD_NAME).isNumber());
+				performRequest().andExpect(jsonPath("$.eingang.formData." + NUMERIC_FIELD_NAME).value(NUMERIC_FIELD_VALUE));
+			}
+
+			@DisplayName("SubForm should have name as key")
+			@Test
+			void keyOfsubForm() throws Exception {
+				performRequest().andExpect(jsonPath("$.eingang.formData." + SUBFORM_NAME).isMap());
+			}
+
+			@DisplayName("SubForm-Field should be present")
+			@Test
+			void subFormField() throws Exception {
+				performRequest().andExpect(jsonPath("$.eingang.formData." + SUBFORM_NAME + "." + SUBFORM_FIELD_NAME).value(SUBFORM_FIELD_VALUE));
+			}
+
+		}
+
+		private ResultActions performRequest() throws Exception {
+			return mockMvc.perform(get(PATH + "/" + VorgangHeaderTestFactory.ID)).andExpect(status().isOk());
+		}
+	}
+
+	@Nested
+	class TestClientAttribute {
+		@Test
+		void resetNewPostfachNachricht() throws Exception {
+			performRequest("{\"hasNewPostfachNachricht\":false}").andExpect(status().isNoContent());
+
+			verify(clientAttributeService).resetPostfachNachricht(VorgangHeaderTestFactory.ID);
+		}
+
+		@Test
+		void shouldReturnUnprocessableEntity() throws Exception {
+			performRequest("{\"hasNewPostfachNachricht\":true}").andExpect(status().isUnprocessableEntity());
+
+			verify(clientAttributeService, never()).resetPostfachNachricht(VorgangHeaderTestFactory.ID);
+		}
+
+		private ResultActions performRequest(String body) throws Exception {
+			return mockMvc
+					.perform(put(RESET_POSTFACH_NACHRICHT_PATH, VorgangHeaderTestFactory.ID).contentType(MediaType.APPLICATION_JSON_VALUE)
+							.content(body));
+		}
+	}
+}
\ No newline at end of file
diff --git a/goofy-server/src/test/java/de/itvsh/goofy/vorgang/VorgangHeaderMapperTest.java b/goofy-server/src/test/java/de/itvsh/goofy/vorgang/VorgangHeaderMapperTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..80652a8ee60859d6b20e5a124a3b395e290ce183
--- /dev/null
+++ b/goofy-server/src/test/java/de/itvsh/goofy/vorgang/VorgangHeaderMapperTest.java
@@ -0,0 +1,167 @@
+/*
+ * 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.itvsh.goofy.vorgang;
+
+import static de.itvsh.goofy.vorgang.VorgangHeaderTestFactory.*;
+import static org.assertj.core.api.Assertions.*;
+
+import java.time.format.DateTimeFormatter;
+
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.mapstruct.factory.Mappers;
+import org.mockito.InjectMocks;
+import org.mockito.Spy;
+
+import de.itvsh.goofy.common.user.UserId;
+import de.itvsh.goofy.common.user.UserIdMapper;
+import de.itvsh.goofy.vorgang.Vorgang.VorgangStatus;
+import de.itvsh.goofy.wiedervorlage.WiedervorlageTestFactory;
+import de.itvsh.ozg.pluto.grpc.clientAttribute.GrpcClientAttribute;
+import de.itvsh.ozg.pluto.grpc.clientAttribute.GrpcClientAttributeValue;
+
+class VorgangHeaderMapperTest {
+
+	@Spy
+	@InjectMocks
+	private VorgangHeaderMapper mapper = Mappers.getMapper(VorgangHeaderMapper.class);
+
+	@Spy
+	private UserIdMapper userIdMapper = Mappers.getMapper(UserIdMapper.class);
+
+	@Nested
+	class TestToVorgangHeader {
+
+		@Test
+		void shouldMapCreatedAt() {
+			var header = callMapper();
+
+			assertThat(header.getCreatedAt()).isEqualTo(CREATED_AT);
+		}
+
+		@Test
+		void shouldMapNummer() {
+			var header = callMapper();
+
+			assertThat(header.getNummer()).isEqualTo(NUMMER);
+		}
+
+		@Test
+		void shouldMapAktenzeichen() {
+			var header = callMapper();
+
+			assertThat(header.getAktenzeichen()).isEqualTo(AKTENZEICHEN);
+		}
+
+		@Test
+		void shouldMapStatus() {
+			var header = callMapper();
+
+			assertThat(header.getStatus()).isEqualTo(VorgangStatus.NEU);
+		}
+
+		@Test
+		void shouldMapAssignedTo() {
+			var header = callMapper();
+
+			assertThat(header.getAssignedTo()).isEqualTo(UserId.from(ASSIGNED_TO));
+		}
+
+		@Nested
+		class NextFrist {
+
+			@Test
+			void shouldSetEmptyOptionalIfNotExist() {
+				var vorgangHeaderWithEmptyClientAttributes = GrpcVorgangHeaderTestFactory.createBuilder().clearClientAttributes().build();
+
+				var header = mapper.toVorgangHeader(vorgangHeaderWithEmptyClientAttributes);
+
+				assertThat(header.getNextFrist()).isNull();
+			}
+
+			@Test
+			void shouldSetIfExists() {
+				var nextWiedervorlageFristClientAttribute = GrpcClientAttribute.newBuilder()
+						.setAttributeName(VorgangHeaderMapper.WIEDERVORLAGE_NEXT_FRIST_ATTRIBUTE_NAME)
+						.setValue(GrpcClientAttributeValue.newBuilder()
+								.setStringValue(WiedervorlageTestFactory.FRIST.format(DateTimeFormatter.ISO_DATE)).build())
+						.build();
+				var vorgangHeaderWithNextWiedervorlageFristAsClientAttribute = GrpcVorgangHeaderTestFactory.createBuilder()
+						.addClientAttributes(nextWiedervorlageFristClientAttribute).build();
+
+				var header = mapper.toVorgangHeader(vorgangHeaderWithNextWiedervorlageFristAsClientAttribute);
+
+				assertThat(header.getNextFrist()).isEqualTo(WiedervorlageTestFactory.FRIST);
+			}
+		}
+
+		@Nested
+		class HasPostfachNachricht {
+			@Test
+			void shouldSetFalseIfNotExists() {
+				var vorgangHeader = GrpcVorgangHeaderTestFactory.createBuilder().clearClientAttributes().build();
+
+				var mapped = mapper.toVorgangHeader(vorgangHeader);
+
+				assertThat(mapped.isHasPostfachNachricht()).isFalse();
+			}
+
+			@Test
+			void shouldBeTrue() {
+				var clientAttribute = GrpcClientAttributeTestFactory.createWith("hasPostfachNachricht", true);
+				var vorgangHeader = GrpcVorgangHeaderTestFactory.createBuilder().addClientAttributes(clientAttribute).build();
+
+				var mapped = mapper.toVorgangHeader(vorgangHeader);
+
+				assertThat(mapped.isHasPostfachNachricht()).isTrue();
+			}
+		}
+
+		@Nested
+		class HasNewPostfachNachricht {
+			@Test
+			void shouldSetFalseIfNotExists() {
+				var vorgangHeader = GrpcVorgangHeaderTestFactory.createBuilder().clearClientAttributes().build();
+
+				var mapped = mapper.toVorgangHeader(vorgangHeader);
+
+				assertThat(mapped.isHasNewPostfachNachricht()).isFalse();
+			}
+
+			@Test
+			void shouldBeTrue() {
+				var clientAttribute = GrpcClientAttributeTestFactory.createWith("hasNewPostfachNachricht", true);
+				var vorgangHeader = GrpcVorgangHeaderTestFactory.createBuilder().addClientAttributes(clientAttribute).build();
+
+				var mapped = mapper.toVorgangHeader(vorgangHeader);
+
+				assertThat(mapped.isHasNewPostfachNachricht()).isTrue();
+			}
+		}
+
+		private VorgangHeader callMapper() {
+			return mapper.toVorgangHeader(GrpcVorgangHeaderTestFactory.create());
+		}
+	}
+}
diff --git a/goofy-server/src/test/java/de/itvsh/goofy/vorgang/VorgangHeaderTestFactory.java b/goofy-server/src/test/java/de/itvsh/goofy/vorgang/VorgangHeaderTestFactory.java
new file mode 100644
index 0000000000000000000000000000000000000000..36b9060f5a3523d607a6300edaae1938db886b2d
--- /dev/null
+++ b/goofy-server/src/test/java/de/itvsh/goofy/vorgang/VorgangHeaderTestFactory.java
@@ -0,0 +1,60 @@
+/*
+ * 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.itvsh.goofy.vorgang;
+
+import java.time.ZonedDateTime;
+import java.util.UUID;
+
+import com.thedeanda.lorem.LoremIpsum;
+
+import de.itvsh.goofy.common.user.UserProfileTestFactory;
+import de.itvsh.goofy.vorgang.Vorgang.VorgangStatus;
+
+public class VorgangHeaderTestFactory {
+
+	public static final String ID = UUID.randomUUID().toString();
+	public static final long VERSION = 42L;
+	public static final String NAME = LoremIpsum.getInstance().getWords(10);
+	public static final VorgangStatus STATUS = VorgangStatus.NEU;
+	public static final String CREATED_AT_STR = "2021-01-10T10:30:00Z";
+	public static final ZonedDateTime CREATED_AT = ZonedDateTime.parse(CREATED_AT_STR);
+	public static final String NUMMER = "VorgangNummerTest123";
+	public static final String AKTENZEICHEN = "0123456789XY";
+	public static final String ASSIGNED_TO = "paul";
+
+	public static VorgangHeader create() {
+		return createBuilder().build();
+	}
+
+	public static VorgangHeader.VorgangHeaderBuilder createBuilder() {
+		return VorgangHeader.builder()
+				.id(ID)
+				.version(VERSION)
+				.name(NAME)
+				.nummer(NUMMER)
+				.status(STATUS)
+				.createdAt(CREATED_AT)
+				.assignedTo(UserProfileTestFactory.ID);
+	}
+}
\ No newline at end of file
diff --git a/goofy-server/src/test/java/de/itvsh/goofy/vorgang/VorgangITCase.java b/goofy-server/src/test/java/de/itvsh/goofy/vorgang/VorgangITCase.java
new file mode 100644
index 0000000000000000000000000000000000000000..efc638ec94b067d5d6ce196cca05279649993fd0
--- /dev/null
+++ b/goofy-server/src/test/java/de/itvsh/goofy/vorgang/VorgangITCase.java
@@ -0,0 +1,157 @@
+/*
+ * 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.itvsh.goofy.vorgang;
+
+import static org.mockito.ArgumentMatchers.*;
+import static org.mockito.Mockito.*;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.EnumSource;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.boot.test.mock.mockito.MockBean;
+import org.springframework.security.test.context.support.WithMockUser;
+import org.springframework.test.web.servlet.MockMvc;
+import org.springframework.test.web.servlet.ResultActions;
+
+import de.itvsh.goofy.common.command.CommandController;
+import de.itvsh.goofy.common.user.CurrentUserService;
+import de.itvsh.goofy.common.user.UserProfileTestFactory;
+import de.itvsh.goofy.common.user.UserRole;
+import de.itvsh.goofy.postfach.PostfachMailController;
+import de.itvsh.goofy.vorgang.Vorgang.VorgangStatus;
+
+@AutoConfigureMockMvc
+@SpringBootTest
+@WithMockUser
+class VorgangITCase {
+
+	@MockBean
+	private VorgangRemoteService remoteService;
+
+	@MockBean
+	private CommandController commandController;
+
+	@MockBean
+	private CurrentUserService userService;
+
+	@MockBean
+	private PostfachMailController postfachController;
+
+	@Autowired
+	private MockMvc mockMvc;
+
+	@WithMockUser
+	@Nested
+	class TestGetVorgangWithEingang {
+
+		static final String SINGLE_PATH = VorgangController.PATH + "/{id}";
+
+		@BeforeEach
+		void mockStub() {
+			when(postfachController.isPostfachConfigured()).thenReturn(true);
+			when(remoteService.findVorgangWithEingang(anyString())).thenReturn(VorgangWithEingangTestFactory.create());
+		}
+
+		@Test
+		void shouldReturnVorgangOnMatchingOrganisationseinheitId() throws Exception {
+			when(userService.getUser()).thenReturn(
+					UserProfileTestFactory.createBuilder().clearOrganisationseinheitIds()
+							.organisationseinheitId(ZustaendigeStelleTestFactory.ORGANISATIONSEINHEITEN_ID).build());
+
+			doRequest().andExpect(status().isOk());
+		}
+
+		@Test
+		void shouldReturnVorgangOnEmptyUserOrganisationseinheitIdList() throws Exception {
+			when(userService.getUser()).thenReturn(UserProfileTestFactory.createBuilder().clearOrganisationseinheitIds().build());
+
+			doRequest().andExpect(status().isOk());
+		}
+
+		@Nested
+		class TestAuthorizationByRole {
+
+			@Nested
+			class TestForVerwaltungPoststelle {
+
+				@BeforeEach
+				void mockUserService() {
+					when(userService.hasRole(UserRole.VERWALTUNG_POSTSTELLE)).thenReturn(true);
+				}
+
+				@DisplayName("should return http forbidden status if vorgangstatus is different to 'Neu'")
+				@ParameterizedTest
+				@EnumSource(value = VorgangStatus.class, names = { "ANGENOMMEN", "VERWORFEN", "IN_BEARBEITUNG", "BESCHIEDEN", "ABGESCHLOSSEN",
+						"WEITERGELEITET" })
+				void shouldReturnForbiddenStatus(VorgangStatus status) throws Exception {
+					when(remoteService.findVorgangWithEingang(anyString()))
+							.thenReturn(VorgangWithEingangTestFactory.createBuilder().status(status).build());
+
+					doRequest().andExpect(status().isForbidden());
+				}
+
+				@DisplayName("should return http ok status if vorgangstatus is equal 'Neu'")
+				@Test
+				void shouldReturnOkStatus() throws Exception {
+					when(remoteService.findVorgangWithEingang(anyString()))
+							.thenReturn(VorgangWithEingangTestFactory.createBuilder().status(VorgangStatus.NEU).build());
+
+					doRequest().andExpect(status().isOk());
+				}
+			}
+
+			@Nested
+			class TestForOtherRoles {
+
+				@BeforeEach
+				void mockUserRole() {
+					when(userService.hasRole(UserRole.VERWALTUNG_POSTSTELLE)).thenReturn(false);
+				}
+
+				@DisplayName("should return true on role 'VERWALTUNG_USER' independent which vorgangstatus")
+				@ParameterizedTest
+				@EnumSource(value = VorgangStatus.class, names = { "NEU", "ANGENOMMEN", "VERWORFEN", "IN_BEARBEITUNG", "BESCHIEDEN", "ABGESCHLOSSEN",
+						"WEITERGELEITET" })
+				void shouldReturnTrue(VorgangStatus status) throws Exception {
+					when(remoteService.findVorgangWithEingang(anyString()))
+							.thenReturn(VorgangWithEingangTestFactory.createBuilder().status(status).build());
+
+					doRequest().andExpect(status().isOk());
+				}
+			}
+		}
+
+		private ResultActions doRequest() throws Exception {
+			return mockMvc.perform(get(SINGLE_PATH, VorgangHeaderTestFactory.ID));
+		}
+	}
+}
\ No newline at end of file
diff --git a/goofy-server/src/test/java/de/itvsh/goofy/vorgang/VorgangListResponseTestFactory.java b/goofy-server/src/test/java/de/itvsh/goofy/vorgang/VorgangListResponseTestFactory.java
new file mode 100644
index 0000000000000000000000000000000000000000..1baea8b849ddd81f97b12ecc49082d9ab3a883b0
--- /dev/null
+++ b/goofy-server/src/test/java/de/itvsh/goofy/vorgang/VorgangListResponseTestFactory.java
@@ -0,0 +1,51 @@
+/*
+ * 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.itvsh.goofy.vorgang;
+
+import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+
+import com.thedeanda.lorem.LoremIpsum;
+
+public class VorgangListResponseTestFactory {
+
+	public static final int TOTAL = 300;
+
+	public static VorgaengeHeaderResponse create() {
+		return createBuilder().build();
+	}
+
+	public static VorgaengeHeaderResponse.VorgaengeHeaderResponseBuilder createBuilder() {
+		return VorgaengeHeaderResponse.builder()
+				.total(TOTAL)
+				.vorgaengeHeader(createList());
+	}
+
+	public static List<VorgangHeader> createList() {
+		return IntStream.range(0, 10)
+				.mapToObj(i -> VorgangHeaderTestFactory.createBuilder().name(i + LoremIpsum.getInstance().getName()).build())
+				.collect(Collectors.toList());
+	}
+}
\ No newline at end of file
diff --git a/goofy-server/src/test/java/de/itvsh/goofy/vorgang/VorgangModelAssemblerTest.java b/goofy-server/src/test/java/de/itvsh/goofy/vorgang/VorgangModelAssemblerTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..e7cbe0567a9411b9705362c259cd356000dafbf9
--- /dev/null
+++ b/goofy-server/src/test/java/de/itvsh/goofy/vorgang/VorgangModelAssemblerTest.java
@@ -0,0 +1,271 @@
+/*
+ * 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.itvsh.goofy.vorgang;
+
+import static de.itvsh.goofy.common.UserProfileUrlProviderTestFactory.*;
+import static org.assertj.core.api.Assertions.*;
+import static org.mockito.Mockito.*;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Optional;
+import java.util.stream.Stream;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.springframework.hateoas.CollectionModel;
+import org.springframework.hateoas.EntityModel;
+import org.springframework.hateoas.IanaLinkRelations;
+import org.springframework.hateoas.Link;
+
+import de.itvsh.goofy.common.UserProfileUrlProvider;
+import de.itvsh.goofy.common.user.CurrentUserService;
+import de.itvsh.goofy.common.user.UserRole;
+import de.itvsh.goofy.common.user.UserProfileTestFactory;
+
+class VorgangModelAssemblerTest {
+
+	private final static String BASE_PATH = VorgangController.PATH;
+
+	@InjectMocks
+	private VorgangModelAssembler modelAssembler;
+	@Mock
+	private CurrentUserService userService;
+
+	private UserProfileUrlProvider urlProvider = new UserProfileUrlProvider();
+
+	@Nested
+	class TestCollectionModel {
+
+		private FindVorgaengeHeaderRequestCriteria.FindVorgaengeHeaderRequestCriteriaBuilder requestCriteriaBuilder = FindVorgaengeHeaderRequestCriteria
+				.builder()
+				.requestLimit(Optional.empty())
+				.limit(0)
+				.searchBy(Optional.empty())
+				.assignedTo(Optional.empty());
+
+		private VorgaengeHeaderResponse response = VorgaengeHeaderResponse.builder()
+				.vorgaengeHeader(Collections.emptyList())
+				.total(VorgangController.VORGANG_PAGE_SIZE * 2)
+				.build();
+
+		@Test
+		void shouldHaveSize() throws Exception {
+			initUserProfileUrlProvider(urlProvider);
+
+			var requestCriteria = requestCriteriaBuilder.page(1).requestLimit(Optional.of(1)).build();
+			var response = VorgaengeHeaderResponse.builder().vorgaengeHeader(List.of()).total(VorgangController.VORGANG_PAGE_SIZE).build();
+
+			var model = toCollectionModel(requestCriteria, response);
+
+			assertThat(model.getContent()).hasSize(1);
+		}
+
+		@Nested
+		class TestNextLink {
+
+			@BeforeEach
+			void prepareBuilder() {
+				requestCriteriaBuilder.page(1);
+
+				initUserProfileUrlProvider(urlProvider);
+			}
+
+			@Test
+			void shouldContainsLimiParameter() {
+				var requestCriteria = requestCriteriaBuilder.requestLimit(Optional.of(7)).build();
+
+				var link = getNextLinkByRequest(requestCriteria);
+
+				assertThat(link).isPresent().get().extracting(Link::getHref).isEqualTo(BASE_PATH + "?page=2&limit=7");
+			}
+
+			@Test
+			void shouldContainsSearchByParameter() {
+				var requestCriteria = requestCriteriaBuilder.searchBy(Optional.of("test")).build();
+
+				var link = getNextLinkByRequest(requestCriteria);
+
+				assertThat(link).isPresent().get().extracting(Link::getHref).isEqualTo(BASE_PATH + "?page=2&searchBy=test");
+			}
+
+			@Test
+			void shouldContainsAssignedToParameter() {
+				var requestCriteria = requestCriteriaBuilder.assignedTo(Optional.of(UserProfileTestFactory.ID)).build();
+
+				var link = getNextLinkByRequest(requestCriteria);
+
+				assertThat(link).isPresent().get().extracting(Link::getHref)
+						.isEqualTo(BASE_PATH + "?page=2&assignedTo=" + UserProfileTestFactory.ID.toString());
+			}
+
+			private Optional<Link> getNextLinkByRequest(FindVorgaengeHeaderRequestCriteria requestCriteria) {
+				return toCollectionModel(requestCriteria, response).getLink(VorgangModelAssembler.REL_NEXT);
+			}
+		}
+
+		@Nested
+		class TestPreviousLink {
+
+			@BeforeEach
+			void prepareBuilder() {
+				requestCriteriaBuilder.page(2);
+			}
+
+			@Test
+			void shouldHavePreviousLink() {
+				var requestCriteria = requestCriteriaBuilder.requestLimit(Optional.empty()).build();
+
+				var link = getPrevLinkByRequest(requestCriteria);
+
+				assertThat(link).isPresent().get().extracting(Link::getHref).isEqualTo(BASE_PATH + "?page=1");
+			}
+
+			@Test
+			void shouldContainsLimiParameter() {
+				var requestCriteria = requestCriteriaBuilder.requestLimit(Optional.of(7)).build();
+
+				var link = getPrevLinkByRequest(requestCriteria);
+
+				assertThat(link).isPresent().get().extracting(Link::getHref).isEqualTo(BASE_PATH + "?page=1&limit=7");
+			}
+
+			@Test
+			void shouldContainsSearchByParameter() {
+				var requestCriteria = requestCriteriaBuilder.searchBy(Optional.of("test")).build();
+
+				var link = getPrevLinkByRequest(requestCriteria);
+
+				assertThat(link).isPresent().get().extracting(Link::getHref).isEqualTo(BASE_PATH + "?page=1&searchBy=test");
+			}
+
+			@Test
+			void shouldContainsAssignedToParameter() {
+				var requestCriteria = requestCriteriaBuilder.assignedTo(Optional.of(UserProfileTestFactory.ID)).build();
+
+				var link = getPrevLinkByRequest(requestCriteria);
+
+				assertThat(link).isPresent().get().extracting(Link::getHref).isEqualTo(BASE_PATH + "?page=1&assignedTo=" + UserProfileTestFactory.ID);
+			}
+
+			private Optional<Link> getPrevLinkByRequest(FindVorgaengeHeaderRequestCriteria requestCriteria) {
+				return toCollectionModel(requestCriteria, response).getLink(VorgangModelAssembler.REL_PREVIOUS);
+			}
+		}
+
+		private CollectionModel<EntityModel<Vorgang>> toCollectionModel(FindVorgaengeHeaderRequestCriteria requestCriteria,
+				VorgaengeHeaderResponse response) {
+			return modelAssembler.toCollectionModel(Stream.of(VorgangHeaderTestFactory.create()), response, requestCriteria);
+		}
+	}
+
+	@Nested
+	class TestLinksOnModel {
+
+		@Test
+		void shouldHaveSelfLink() {
+			var link = toModel().getLink(IanaLinkRelations.SELF_VALUE);
+
+			assertThat(link).isPresent().get().extracting(Link::getHref).isEqualTo(BASE_PATH + "/" + VorgangHeaderTestFactory.ID);
+		}
+
+		@Test
+		void shouldHaveVorgangMitEingangLink() {
+			var link = toModel().getLink(VorgangModelAssembler.REL_VORGANG_MIT_EINGANG);
+
+			assertThat(link).isPresent().get().extracting(Link::getHref).isEqualTo(BASE_PATH + "/" + VorgangHeaderTestFactory.ID);
+		}
+
+		@DisplayName("Without any roles")
+		@Nested
+		class WithoutRoles {
+
+			@Test
+			void shouldNotHaveWiedervorlagenLink() {
+				var link = toModel().getLink(VorgangModelAssembler.REL_WIEDERVORLAGEN);
+
+				assertThat(link).isEmpty();
+			}
+
+			@DisplayName("Should not have Vorgang-assign-links")
+			@Test
+			void shouldNotHaveAssignLink() {
+				var link = toModel().getLink(VorgangModelAssembler.REL_VORGANG_ASSIGN);
+
+				assertThat(link).isEmpty();
+			}
+		}
+
+		@Nested
+		class ForRoleVerwaltungPoststelle {
+
+			@Test
+			void shouldHaveAssignLink() {
+				when(userService.hasRole(UserRole.VERWALTUNG_POSTSTELLE)).thenReturn(true);
+				when(userService.hasRole(UserRole.VERWALTUNG_USER)).thenReturn(false);
+
+				var link = toModel().getLink(VorgangModelAssembler.REL_VORGANG_ASSIGN);
+
+				assertThat(link).isPresent().get().extracting(Link::getHref).isEqualTo(buildCommandLink());
+			}
+		}
+
+		@Nested
+		class ForRoleVerwaltungUser {
+
+			@BeforeEach
+			void mockUserService() {
+				when(userService.hasRole(UserRole.VERWALTUNG_USER)).thenReturn(true);
+			}
+
+			@Test
+			void shouldHaveWiedervorlagenLink() {
+				var link = toModel().getLink(VorgangModelAssembler.REL_WIEDERVORLAGEN);
+
+				assertThat(link).isPresent().get().extracting(Link::getHref)
+						.isEqualTo("/api/wiedervorlages?vorgangId=" + VorgangHeaderTestFactory.ID);
+			}
+
+			@Test
+			void shouldHaveAssignLink() {
+				var link = toModel().getLink(VorgangModelAssembler.REL_VORGANG_ASSIGN);
+
+				assertThat(link).isPresent().get().extracting(Link::getHref).isEqualTo(buildCommandLink());
+			}
+		}
+
+		private String buildCommandLink() {
+			return String.format("/api/vorgangs/%s/relations/%s/%s/commands", VorgangHeaderTestFactory.ID,
+					VorgangHeaderTestFactory.ID, VorgangHeaderTestFactory.VERSION);
+		}
+
+		private EntityModel<Vorgang> toModel() {
+			return modelAssembler.toModel(VorgangWithEingangTestFactory.create());
+		}
+	}
+}
\ No newline at end of file
diff --git a/goofy-server/src/test/java/de/itvsh/goofy/vorgang/VorgangRemoteServiceTest.java b/goofy-server/src/test/java/de/itvsh/goofy/vorgang/VorgangRemoteServiceTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..5b8926d629a2ecb3b75661683924db961ab100a1
--- /dev/null
+++ b/goofy-server/src/test/java/de/itvsh/goofy/vorgang/VorgangRemoteServiceTest.java
@@ -0,0 +1,380 @@
+/*
+ * 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.itvsh.goofy.vorgang;
+
+import static org.assertj.core.api.Assertions.*;
+import static org.mockito.ArgumentMatchers.*;
+import static org.mockito.Mockito.*;
+
+import java.util.Optional;
+
+import org.apache.commons.lang3.StringUtils;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.Spy;
+
+import de.itvsh.goofy.common.user.CurrentUserService;
+import de.itvsh.goofy.common.user.UserProfileTestFactory;
+import de.itvsh.goofy.common.user.UserId;
+import de.itvsh.goofy.common.user.UserRole;
+import de.itvsh.goofy.common.user.UserProfileTestFactory;
+import de.itvsh.goofy.vorgang.Vorgang.VorgangStatus;
+import de.itvsh.ozg.pluto.vorgang.GrpcFilterBy;
+import de.itvsh.ozg.pluto.vorgang.GrpcFindVorgangRequest;
+import de.itvsh.ozg.pluto.vorgang.GrpcFindVorgangResponse;
+import de.itvsh.ozg.pluto.vorgang.VorgangServiceGrpc.VorgangServiceBlockingStub;
+
+class VorgangRemoteServiceTest {
+
+	@Spy
+	@InjectMocks // NOSONAR
+	private VorgangRemoteService service;
+
+	@Mock
+	private VorgangServiceBlockingStub serviceStub;
+	@Mock
+	private VorgangHeaderMapper vorgangHeaderMapper;
+	@Mock
+	private VorgangWithEingangMapper vorgangWithEingangMapper;
+	@Mock
+	private CurrentUserService userService;
+
+	private final GrpcFilterBy filterBy = GrpcFilterByTestFactory.create();
+
+	@Nested
+	class TestFindVorgaengeHeader {
+
+		@Nested
+		class TestCalls {
+
+			private final FindVorgaengeHeaderRequestCriteria requestCriteria = FindVorgaengeRequestCriteriaTestFactory.create();
+
+			@BeforeEach
+			void initMockReturnValues() {
+				doReturn(filterBy).when(service).createFilterBy(any());
+				doReturn(VorgangListResponseTestFactory.create()).when(service).buildVorgaengeHeaderResponse(any());
+			}
+
+			@Test
+			void shouldCallBuildRequest() {
+				callService();
+
+				verify(service).buildFindVorgangRequest(requestCriteria);
+			}
+
+			@Test
+			void shouldCallCreateFilterBy() {
+				callService();
+
+				verify(service).createFilterBy(requestCriteria.getAssignedTo());
+			}
+
+			@Test
+			void shouldCallStub() {
+				callService();
+
+				verify(serviceStub).findVorgang(any());
+			}
+
+			private VorgaengeHeaderResponse callService() {
+				return service.findVorgaengeHeader(requestCriteria);
+			}
+		}
+
+		@Nested
+		class TestBuildFindVorgangRequest {
+
+			@BeforeEach
+			void initMockReturnValues() {
+				doReturn(filterBy).when(service).createFilterBy(any());
+			}
+
+			@Test
+			void shouldSetLimit() {
+				var request = callService();
+
+				assertThat(request.getLimit()).isEqualTo(FindVorgaengeRequestCriteriaTestFactory.LIMIT);
+			}
+
+			@Test
+			void shouldSetOffset() {
+				var request = callService();
+
+				assertThat(request.getOffset()).isEqualTo(FindVorgaengeRequestCriteriaTestFactory.OFFSET);
+			}
+
+			@Test
+			void shouldSetFilterBy() {
+				var request = callService();
+
+				assertThat(request.getFilterBy()).isEqualTo(filterBy);
+			}
+
+			@Test
+			void shouldSetOrderBy() {
+				var request = callService();
+
+				assertThat(request.getOrderBy().name()).isEqualTo(FindVorgaengeRequestCriteriaTestFactory.ORDER_BY.name());
+			}
+
+			private GrpcFindVorgangRequest callService() {
+				return service.buildFindVorgangRequest(FindVorgaengeRequestCriteriaTestFactory.create());
+			}
+		}
+
+		@Nested
+		class TestResponse {
+
+			private final GrpcFindVorgangResponse findVorgangRespone = GrpcVorgangTestFactory.createVorgangResponse();
+			private final VorgangHeader vorgangHeader = VorgangHeaderTestFactory.create();
+
+			@BeforeEach
+			void initMockReturnValues() {
+				when(vorgangHeaderMapper.toVorgangHeader(any())).thenReturn(vorgangHeader);
+			}
+
+			@Test
+			void shouldReturnMappedVorgangHeader() {
+				var vorgangResponse = callService();
+
+				assertThat(vorgangResponse.getVorgaengeHeader()).hasSize(1);
+				assertThat(vorgangResponse.getVorgaengeHeader().get(0)).isEqualTo(vorgangHeader);
+			}
+
+			@Test
+			void shouldReturnTotal() {
+				var vorgangResponse = callService();
+
+				assertThat(vorgangResponse.getTotal()).isEqualTo(findVorgangRespone.getTotal());
+			}
+
+			@Test
+			void shouldMapVorgangHeader() {
+				callService();
+
+				verify(vorgangHeaderMapper).toVorgangHeader(any());
+			}
+
+			private VorgaengeHeaderResponse callService() {
+				return service.buildVorgaengeHeaderResponse(findVorgangRespone);
+			}
+		}
+	}
+
+	@Nested
+	class TestCreateFilterBy {
+
+		@Nested
+		class TestAssignedTo {
+
+			@BeforeEach
+			void mockUserService() {
+				when(userService.hasRole(any())).thenReturn(false);
+				when(userService.getUser()).thenReturn(UserProfileTestFactory.create());
+			}
+
+			@Test
+			void shouldBeSetIfExists() {
+				var filterCriteria = callService(Optional.of(UserProfileTestFactory.ID));
+
+				assertThat(filterCriteria.getAssignedTo()).isEqualTo(UserProfileTestFactory.ID.toString());
+			}
+
+			@Test
+			void shouldNotBeSetIfNotExist() {
+				var filterCriteria = callService(Optional.empty());
+
+				assertThat(filterCriteria.getAssignedTo()).isEqualTo(StringUtils.EMPTY);
+
+			}
+		}
+
+		@Nested
+		class TestForPoststelle {
+
+			@BeforeEach
+			void mockUser() {
+				when(userService.hasRole(UserRole.VERWALTUNG_POSTSTELLE)).thenReturn(true);
+			}
+
+			@Test
+			void shouldFillFilterByOrganisationseinheiten() {
+				var filterBy = callService();
+
+				assertThat(filterBy.getFilterByOrganisationseinheitenId()).isFalse();
+			}
+
+			@Test
+			void shouldFillStatus() {
+				var filterBy = callService();
+
+				assertThat(filterBy.getStatus(0)).isEqualTo(VorgangStatus.NEU.name());
+			}
+		}
+
+		@Nested
+		class TestForEa {
+
+			@BeforeEach
+			void mockUser() {
+				when(userService.hasRole(UserRole.VERWALTUNG_POSTSTELLE)).thenReturn(false);
+				when(userService.hasRole(UserRole.EINHEITLICHER_ANSPRECHPARTNER)).thenReturn(true);
+			}
+
+			@Test
+			void shouldFillFilterByOrganisationseinheiten() {
+				var filterBy = callService();
+
+				assertThat(filterBy.getFilterByOrganisationseinheitenId()).isFalse();
+			}
+		}
+
+		@Nested
+		class TestCreateForOtherRoles {
+
+			@BeforeEach
+			void mockUser() {
+				when(userService.hasRole(anyString())).thenReturn(false);
+			}
+
+			@Test
+			void shouldCallUserService() {
+				when(userService.getUser()).thenReturn(UserProfileTestFactory.create());
+
+				callService();
+
+				verify(userService).getUser();
+			}
+
+			@Test
+			void shouldFillFilterByOrganisationseinheitenId() {
+				when(userService.getUser()).thenReturn(UserProfileTestFactory.create());
+
+				var filterBy = callService();
+
+				assertThat(filterBy.getFilterByOrganisationseinheitenId()).isTrue();
+			}
+
+			@Test
+			void shouldFillOrganisationseinheitenId() {
+				when(userService.getUser()).thenReturn(UserProfileTestFactory.create());
+
+				var filterBy = callService();
+
+				assertThat(filterBy.getOrganisationseinheitIdList()).containsExactly(ZustaendigeStelleTestFactory.ORGANISATIONSEINHEITEN_ID);
+			}
+		}
+
+		private GrpcFilterBy callService() {
+			return callService(Optional.empty());
+		}
+
+		private GrpcFilterBy callService(Optional<UserId> assignedTo) {
+			return service.createFilterBy(assignedTo);
+		}
+	}
+
+	@Nested
+	class TestFindVorgangWithEingang {
+
+		@Nested
+		class TestCalls {
+
+			private final VorgangWithEingang vorgangWithEingang = VorgangWithEingangTestFactory.create();
+
+			@BeforeEach
+			void mockReturnValue() {
+				when(serviceStub.findVorgangWithEingang(any()))
+						.thenReturn(GrpcVorgangWithEingangResponseTestFactory.createVorgangWithEingangResponse());
+				when(vorgangWithEingangMapper.toVorgangWithEingang(any())).thenReturn(vorgangWithEingang);
+				doReturn(filterBy).when(service).createFilterBy(any());
+			}
+
+			@Test
+			void shouldCreateFilterBy() {
+				callService();
+
+				verify(service).createFilterBy(any());
+			}
+
+			@Test
+			void shouldCallBuildRequest() {
+				callService();
+
+				verify(service).buildFindVorgangWithEingangRequest(VorgangHeaderTestFactory.ID);
+			}
+
+			@Test
+			void shouldCallStub() {
+				callService();
+
+				verify(serviceStub).findVorgangWithEingang(any());
+			}
+
+			@Test
+			void shouldCallMapper() {
+				callService();
+
+				verify(vorgangWithEingangMapper).toVorgangWithEingang(any());
+			}
+
+			@Test
+			void shouldReturn() {
+				var result = callService();
+
+				assertThat(result).isEqualTo(vorgangWithEingang);
+			}
+		}
+
+		@Nested
+		class TestBuildRequest {
+
+			@BeforeEach
+			void mockReturnValue() {
+				doReturn(filterBy).when(service).createFilterBy(any());
+			}
+
+			@Test
+			void shouldHaveFilter() {
+				var filter = service.buildFindVorgangWithEingangRequest(VorgangHeaderTestFactory.ID);
+
+				assertThat(filter.getFilterBy()).isNotNull();
+			}
+
+			@Test
+			void shouldContainsVorgangId() {
+				var filter = service.buildFindVorgangWithEingangRequest(VorgangHeaderTestFactory.ID);
+
+				assertThat(filter.getId()).isEqualTo(VorgangHeaderTestFactory.ID);
+			}
+		}
+
+		private VorgangWithEingang callService() {
+			return service.findVorgangWithEingang(VorgangHeaderTestFactory.ID);
+		}
+	}
+}
\ No newline at end of file
diff --git a/goofy-server/src/test/java/de/itvsh/goofy/vorgang/VorgangServiceTest.java b/goofy-server/src/test/java/de/itvsh/goofy/vorgang/VorgangServiceTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..08da2a3ac1a5e187451a014cedc3d5c21db014bb
--- /dev/null
+++ b/goofy-server/src/test/java/de/itvsh/goofy/vorgang/VorgangServiceTest.java
@@ -0,0 +1,97 @@
+/*
+ * 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.itvsh.goofy.vorgang;
+
+import static org.assertj.core.api.Assertions.*;
+import static org.mockito.ArgumentMatchers.*;
+import static org.mockito.Mockito.*;
+
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.Spy;
+
+import de.itvsh.goofy.common.user.CurrentUserService;
+import de.itvsh.goofy.common.user.UserRole;
+
+class VorgangServiceTest {
+
+	@Spy
+	@InjectMocks // NOSONAR
+	private VorgangService service;
+
+	@Mock
+	private VorgangRemoteService remoteService;
+	@Mock
+	private CurrentUserService userService;
+
+	@Nested
+	class TestFindVorgaengeHeader {
+
+		private final FindVorgaengeHeaderRequestCriteria requestCriteria = FindVorgaengeRequestCriteriaTestFactory.create();
+
+		@Test
+		void shouldCallRemoteService() {
+			doReturn(requestCriteria).when(service).setOrderBy(any());
+
+			service.findVorgaengeHeader(requestCriteria);
+
+			verify(remoteService).findVorgaengeHeader(requestCriteria);
+		}
+
+		@Nested
+		class TestOrderBy {
+
+			@Test
+			void shouldSetPriority() {
+				when(userService.hasRole(UserRole.EINHEITLICHER_ANSPRECHPARTNER)).thenReturn(false);
+
+				var request = service.setOrderBy(FindVorgaengeRequestCriteriaTestFactory.create());
+
+				assertThat(request.getOrderBy()).isEqualTo(OrderBy.PRIORITY);
+			}
+
+			@Test
+			void shouldSetEaPriority() {
+				when(userService.hasRole(UserRole.EINHEITLICHER_ANSPRECHPARTNER)).thenReturn(true);
+
+				var request = service.setOrderBy(FindVorgaengeRequestCriteriaTestFactory.create());
+
+				assertThat(request.getOrderBy()).isEqualTo(OrderBy.EA_PRIORITY);
+			}
+		}
+	}
+
+	@Nested
+	class TestFindVorgangWithEingang {
+
+		@Test
+		void shouldCallRemoteService() {
+			service.findVorgangWithEingang(VorgangHeaderTestFactory.ID);
+
+			verify(remoteService).findVorgangWithEingang(VorgangHeaderTestFactory.ID);
+		}
+	}
+}
\ No newline at end of file
diff --git a/goofy-server/src/test/java/de/itvsh/goofy/vorgang/VorgangWithEingangMapperITCase.java b/goofy-server/src/test/java/de/itvsh/goofy/vorgang/VorgangWithEingangMapperITCase.java
new file mode 100644
index 0000000000000000000000000000000000000000..0e0d676516192edc0238457f3b3ce7368f7f4b68
--- /dev/null
+++ b/goofy-server/src/test/java/de/itvsh/goofy/vorgang/VorgangWithEingangMapperITCase.java
@@ -0,0 +1,69 @@
+/*
+ * 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.itvsh.goofy.vorgang;
+
+import static org.assertj.core.api.Assertions.*;
+
+import java.util.List;
+
+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 de.itvsh.ozg.pluto.vorgang.GrpcEingang;
+import de.itvsh.ozg.pluto.vorgang.GrpcFormData;
+import de.itvsh.ozg.pluto.vorgang.GrpcFormField;
+
+@SpringBootTest
+class VorgangWithEingangMapperITCase {
+
+	@Autowired
+	private VorgangWithEingangMapper mapper;
+
+	@Nested
+	class TestToVorgangWithEingang {
+
+		@Nested
+		class TestMappingFormData {
+
+			@Test
+			void shouldMapListOfStrings() {
+				var vorgang = mapper.toVorgangWithEingang(GrpcVorgangWithEingangTestFactory.createWithEingang(buildEingang()));
+
+				assertThat(vorgang.getEingang().getFormData()).containsEntry("TEST", List.of("VALUE1", "VALUE2"));
+			}
+
+			private GrpcEingang buildEingang() {
+				return GrpcEingangTestFactory.createBuilder().setFormData(GrpcFormData.newBuilder()
+						.addField(GrpcFormField.newBuilder().setName("TEST").setValue("VALUE1").build())
+						.addField(GrpcFormField.newBuilder().setName("TEST").setValue("VALUE2").build()))
+						.build();
+			}
+
+		}
+
+	}
+
+}
diff --git a/goofy-server/src/test/java/de/itvsh/goofy/vorgang/VorgangWithEingangMapperTest.java b/goofy-server/src/test/java/de/itvsh/goofy/vorgang/VorgangWithEingangMapperTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..371f6c1b110a2d375d83129d3a3bec0e5b2729da
--- /dev/null
+++ b/goofy-server/src/test/java/de/itvsh/goofy/vorgang/VorgangWithEingangMapperTest.java
@@ -0,0 +1,104 @@
+/*
+ * 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.itvsh.goofy.vorgang;
+
+import static de.itvsh.goofy.vorgang.VorgangHeaderTestFactory.*;
+import static org.assertj.core.api.Assertions.*;
+
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.mapstruct.factory.Mappers;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.Spy;
+
+import de.itvsh.goofy.common.BaseTypesMapper;
+import de.itvsh.goofy.common.user.UserId;
+import de.itvsh.goofy.common.user.UserIdMapper;
+import de.itvsh.goofy.vorgang.Vorgang.VorgangStatus;
+
+class VorgangWithEingangMapperTest {
+
+	@Spy
+	@InjectMocks
+	private VorgangWithEingangMapper mapper = Mappers.getMapper(VorgangWithEingangMapper.class);
+
+	@Spy
+	private UserIdMapper userIdMapper = Mappers.getMapper(UserIdMapper.class);
+
+	@Spy
+	private BaseTypesMapper baseTypesMapper = Mappers.getMapper(BaseTypesMapper.class);
+
+	@Mock
+	private EingangMapper eingangMapper;
+
+	@Nested
+	class TestGrpcVorgangWithEingangToGoofyVorgangWithEingang {
+		@Test
+		void shouldMapCreatedAt() {
+			var vorgang = callMapper();
+
+			assertThat(vorgang.getCreatedAt()).isEqualTo(CREATED_AT);
+		}
+
+		@Test
+		void shouldMapNummer() {
+			var vorgang = callMapper();
+
+			assertThat(vorgang.getNummer()).isEqualTo(NUMMER);
+		}
+
+		@Test
+		void shouldMapAktenzeichen() {
+			var vorgang = callMapper();
+
+			assertThat(vorgang.getAktenzeichen()).isEqualTo(AKTENZEICHEN);
+		}
+
+		@Test
+		void shouldMapStatus() {
+			var vorgang = callMapper();
+
+			assertThat(vorgang.getStatus()).isEqualTo(VorgangStatus.NEU);
+		}
+
+		@Test
+		void shouldMapAssignedTo() {
+			var vorgang = callMapper();
+
+			assertThat(vorgang.getAssignedTo()).isEqualTo(UserId.from(ASSIGNED_TO));
+		}
+
+		@Test
+		void shouldMapClientAttributeHasNewPostfachNachricht() {
+			var vorgang = callMapper();
+
+			assertThat(vorgang.isHasNewPostfachNachricht()).isTrue();
+		}
+
+		private VorgangWithEingang callMapper() {
+			return mapper.toVorgangWithEingang(GrpcVorgangWithEingangTestFactory.create());
+		}
+	}
+}
diff --git a/goofy-server/src/test/java/de/itvsh/goofy/vorgang/VorgangWithEingangProzessorTest.java b/goofy-server/src/test/java/de/itvsh/goofy/vorgang/VorgangWithEingangProzessorTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..0553ea33ebe08897b49b45f4f034622a501183a4
--- /dev/null
+++ b/goofy-server/src/test/java/de/itvsh/goofy/vorgang/VorgangWithEingangProzessorTest.java
@@ -0,0 +1,207 @@
+/*
+ * 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.itvsh.goofy.vorgang;
+
+import static de.itvsh.goofy.common.UserProfileUrlProviderTestFactory.*;
+import static org.assertj.core.api.Assertions.*;
+import static org.mockito.Mockito.*;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.EnumSource;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.springframework.hateoas.EntityModel;
+import org.springframework.hateoas.Link;
+import org.springframework.hateoas.LinkRelation;
+
+import de.itvsh.goofy.common.UserProfileUrlProvider;
+import de.itvsh.goofy.common.user.CurrentUserService;
+import de.itvsh.goofy.common.user.UserRole;
+import de.itvsh.goofy.postfach.PostfachMailController;
+import de.itvsh.goofy.vorgang.Vorgang.VorgangStatus;
+
+class VorgangWithEingangProzessorTest {
+
+	@InjectMocks
+	private VorgangWithEingangProzessor processor;
+	@Mock
+	private PostfachMailController postfachMailController;
+	@Mock
+	private CurrentUserService userService;
+
+	private UserProfileUrlProvider urlProvider = new UserProfileUrlProvider();
+
+	@BeforeEach
+	void init() {
+		initUserProfileUrlProvider(urlProvider);
+	}
+
+	@Nested
+	class TestAttachmentsLink {
+
+		private final LinkRelation linkRel = VorgangWithEingangProzessor.REL_ATTACHMENTS;
+		private final String PATH = "/api/attachments?eingangId=" + EingangTestFactory.ID;
+
+		@DisplayName("should be present on numberOfAttachments > 0")
+		@Test
+		void shouldBePresentByAttachments() {
+			var link = processor.process(buildModelWithAttachments(1)).getLink(linkRel);
+
+			assertThat(link).isPresent().get().extracting(Link::getHref).isEqualTo(PATH);
+		}
+
+		@Test
+		void shouldNotBePresent() {
+			var link = processor.process(buildModelWithAttachments(0)).getLink(linkRel);
+
+			assertThat(link).isNotPresent();
+		}
+
+		private EntityModel<VorgangWithEingang> buildModelWithAttachments(int numberOfAttachments) {
+			return buildModelWithEingang(EingangTestFactory.createBuilder()
+					.numberOfAttachments(numberOfAttachments)
+					.build());
+		}
+
+		private EntityModel<VorgangWithEingang> buildModelWithEingang(Eingang eingang) {
+			return EntityModel.of(VorgangWithEingangTestFactory.createBuilder().eingang(eingang).build());
+		}
+	}
+
+	@Nested
+	class TestRepresentationsLink {
+
+		private final LinkRelation linkRel = VorgangWithEingangProzessor.REL_REPRESENTATIONS;
+		private final String PATH = "/api/representations?eingangId=" + EingangTestFactory.ID;
+
+		@DisplayName("should be present on numberOfRepresentations > 0")
+		@Test
+		void shouldBePresentByRepresentations() {
+			var link = processor.process(buildModelWithRepresentation(1)).getLink(linkRel);
+
+			assertThat(link).isPresent().get().extracting(Link::getHref).isEqualTo(PATH);
+		}
+
+		@Test
+		void shouldNotBePresent() {
+			var link = processor.process(buildModelWithRepresentation(0)).getLink(linkRel);
+
+			assertThat(link).isNotPresent();
+		}
+
+		private EntityModel<VorgangWithEingang> buildModelWithRepresentation(int numberOfRepresentations) {
+			return buildModelWithEingang(EingangTestFactory.createBuilder()
+					.numberOfRepresentations(numberOfRepresentations)
+					.build());
+		}
+
+		private EntityModel<VorgangWithEingang> buildModelWithEingang(Eingang eingang) {
+			return EntityModel.of(VorgangWithEingangTestFactory.createBuilder().eingang(eingang).build());
+		}
+	}
+
+	@Nested
+	class TestPostfachMailsLink {
+
+		private final EntityModel<VorgangWithEingang> model = EntityModel.of(VorgangWithEingangTestFactory.create());
+
+		@Test
+		void shouldCallPostfachMailController() {
+			processor.process(model).getLink(VorgangWithEingangProzessor.REL_POSTFACH_MAILS);
+
+			verify(postfachMailController).isPostfachConfigured();
+		}
+
+		@Test
+		void shouldBePresentIfConfigured() {
+			when(postfachMailController.isPostfachConfigured()).thenReturn(true);
+
+			var link = processor.process(model).getLink(VorgangWithEingangProzessor.REL_POSTFACH_MAILS);
+
+			assertThat(link).isPresent().get().extracting(Link::getHref).isEqualTo("/api/postfachMails?vorgangId=" + VorgangHeaderTestFactory.ID);
+		}
+
+		@Test
+		void shouldNotBePresentIfNotConfigured() {
+			when(postfachMailController.isPostfachConfigured()).thenReturn(false);
+
+			var link = processor.process(model).getLink(VorgangWithEingangProzessor.REL_POSTFACH_MAILS);
+
+			assertThat(link).isEmpty();
+		}
+	}
+
+	@Nested
+	class TestForwardingLink {
+
+		@DisplayName("should NOT be present in other Role then EA")
+		@ParameterizedTest
+		@EnumSource
+		void shouldNotBePresentWithoutRole(Vorgang.VorgangStatus status) {
+			when(userService.hasRole(UserRole.EINHEITLICHER_ANSPRECHPARTNER)).thenReturn(false);
+
+			var link = processor.process(buildVorgangInStatus(status)).getLink(VorgangWithEingangProzessor.REL_VORGANG_FORWARDING);
+
+			assertThat(link).isEmpty();
+		}
+
+		@DisplayName("with role EinheitlicherAnsprechpartner")
+		@Nested
+		class TestWithRoleEinheitlicherAnsprechpartner {
+			@BeforeEach
+			void init() {
+				when(userService.hasRole(UserRole.EINHEITLICHER_ANSPRECHPARTNER)).thenReturn(true);
+			}
+
+			@DisplayName("should be present in any Status")
+			@ParameterizedTest
+			@EnumSource()
+			void shouldBePresent(VorgangStatus status) {
+				var link = processor.process(buildVorgangInStatus(status)).getLink(VorgangWithEingangProzessor.REL_VORGANG_FORWARDING);
+
+				assertThat(link).isPresent().get().extracting(Link::getHref).isEqualTo("/api/forwardings?vorgangId=" + VorgangHeaderTestFactory.ID);
+			}
+		}
+
+		private EntityModel<VorgangWithEingang> buildVorgangInStatus(VorgangStatus status) {
+			return EntityModel.of(VorgangWithEingangTestFactory.createBuilder().status(status).build());
+		}
+	}
+
+	@DisplayName("Historie Link")
+	@Nested
+	class TestHistorieLink {
+
+		@Test
+		void shouldBePresent() {
+			var link = processor.process(EntityModel.of(VorgangWithEingangTestFactory.create())).getLink(VorgangWithEingangProzessor.REL_HISTORIE);
+
+			assertThat(link).isPresent().get().extracting(Link::getHref).isEqualTo("/api/histories?vorgangId=" + VorgangHeaderTestFactory.ID);
+		}
+	}
+}
\ No newline at end of file
diff --git a/goofy-server/src/test/java/de/itvsh/goofy/vorgang/VorgangWithEingangTestFactory.java b/goofy-server/src/test/java/de/itvsh/goofy/vorgang/VorgangWithEingangTestFactory.java
new file mode 100644
index 0000000000000000000000000000000000000000..ff4cf21e4522a3fdcbdfddd554b700192e891278
--- /dev/null
+++ b/goofy-server/src/test/java/de/itvsh/goofy/vorgang/VorgangWithEingangTestFactory.java
@@ -0,0 +1,48 @@
+/*
+ * 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.itvsh.goofy.vorgang;
+
+import static de.itvsh.goofy.vorgang.VorgangHeaderTestFactory.*;
+
+import de.itvsh.goofy.common.user.UserProfileTestFactory;
+
+public class VorgangWithEingangTestFactory {
+
+	public static VorgangWithEingang create() {
+		return createBuilder().build();
+	}
+
+	public static VorgangWithEingang.VorgangWithEingangBuilder createBuilder() {
+		return VorgangWithEingang.builder()
+				.id(ID)
+				.version(VERSION)
+				.assignedTo(UserProfileTestFactory.ID)
+				.name(NAME)
+				.status(STATUS)
+				.nummer(NUMMER)
+				.createdAt(CREATED_AT)
+				.hasNewPostfachNachricht(true)
+				.eingang(EingangTestFactory.create());
+	}
+}
\ No newline at end of file
diff --git a/goofy-server/src/test/java/de/itvsh/goofy/vorgang/ZustaendigeStelleMapperTest.java b/goofy-server/src/test/java/de/itvsh/goofy/vorgang/ZustaendigeStelleMapperTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..febe1054c00f0ce5cd976bd40bcfedc73d92e4c5
--- /dev/null
+++ b/goofy-server/src/test/java/de/itvsh/goofy/vorgang/ZustaendigeStelleMapperTest.java
@@ -0,0 +1,44 @@
+/*
+ * 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.itvsh.goofy.vorgang;
+
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.mapstruct.factory.Mappers;
+
+import static org.assertj.core.api.Assertions.*;
+
+public class ZustaendigeStelleMapperTest {
+    private final ZustaendigeStelleMapper mapper = Mappers.getMapper(ZustaendigeStelleMapper.class);
+
+    @Nested
+    class TestMapToZustaendigeStelle {
+
+        @Test
+        void shouldMap() {
+            var zustaendigeStelle = mapper.mapToZustaendigeStelle(GrpcZustaendigeStelleTestFactory.create());
+            assertThat(zustaendigeStelle).usingRecursiveComparison().isEqualTo(ZustaendigeStelleTestFactory.create());
+        }
+    }
+}
diff --git a/goofy-server/src/test/java/de/itvsh/goofy/vorgang/ZustaendigeStelleTestFactory.java b/goofy-server/src/test/java/de/itvsh/goofy/vorgang/ZustaendigeStelleTestFactory.java
new file mode 100644
index 0000000000000000000000000000000000000000..54638b39f020d1584fa73b90371d24c89c4e17af
--- /dev/null
+++ b/goofy-server/src/test/java/de/itvsh/goofy/vorgang/ZustaendigeStelleTestFactory.java
@@ -0,0 +1,39 @@
+/*
+ * 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.itvsh.goofy.vorgang;
+
+public class ZustaendigeStelleTestFactory {
+    public static final String EMAIL = "test@test.de";
+    public static final String ORGANISATIONSEINHEITEN_ID = "123456";
+
+    public static ZustaendigeStelle create() {
+        return createBuilder().build();
+    }
+
+    public static ZustaendigeStelle.ZustaendigeStelleBuilder createBuilder() {
+        return ZustaendigeStelle.builder()
+                .email(EMAIL)
+                .organisationseinheitenId(ORGANISATIONSEINHEITEN_ID);
+    }
+}
diff --git a/goofy-server/src/test/java/de/itvsh/goofy/vorgang/command/VorgangCommandProzessorTest.java b/goofy-server/src/test/java/de/itvsh/goofy/vorgang/command/VorgangCommandProzessorTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..87346990ae04e57b666bed06a22bf087d409d7de
--- /dev/null
+++ b/goofy-server/src/test/java/de/itvsh/goofy/vorgang/command/VorgangCommandProzessorTest.java
@@ -0,0 +1,368 @@
+/*
+ * 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.itvsh.goofy.vorgang.command;
+
+import static org.assertj.core.api.Assertions.*;
+import static org.mockito.Mockito.*;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.EnumSource;
+import org.junit.jupiter.params.provider.EnumSource.Mode;
+import org.junit.jupiter.params.provider.ValueSource;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.Spy;
+import org.springframework.hateoas.EntityModel;
+import org.springframework.hateoas.Link;
+import org.springframework.hateoas.LinkRelation;
+
+import de.itvsh.goofy.common.command.CommandTestFactory;
+import de.itvsh.goofy.common.user.CurrentUserService;
+import de.itvsh.goofy.common.user.UserRole;
+import de.itvsh.goofy.vorgang.Vorgang;
+import de.itvsh.goofy.vorgang.Vorgang.VorgangStatus;
+import de.itvsh.goofy.vorgang.VorgangHeaderTestFactory;
+
+class VorgangCommandProzessorTest {
+
+	private static final String EXPECTED_COMMAND_LINK_TEMPL = "/api/vorgangs/%s/relations/%s/%d/commands";
+	private static final String EXPECTED_COMMAND_LINK = String.format(EXPECTED_COMMAND_LINK_TEMPL, CommandTestFactory.VORGANG_ID,
+			VorgangHeaderTestFactory.ID, VorgangHeaderTestFactory.VERSION);
+
+	@Spy
+	@InjectMocks
+	private VorgangCommandProzessor processor;
+	@Mock
+	private CurrentUserService userService;
+
+	@Nested
+	@DisplayName("add 'annehmen' link")
+	class TestAnnehmenLink {
+
+		private final LinkRelation linkRel = VorgangCommandProzessor.REL_VORGANG_ANNEHMEN;
+
+		@Test
+		void shouldBePresent() {
+			var processed = processor.process(buildVorgangInStatus(VorgangStatus.NEU));
+
+			assertThat(processed.getLink(linkRel)).isPresent().get()
+					.extracting(Link::getHref).isEqualTo(EXPECTED_COMMAND_LINK);
+		}
+
+		@ParameterizedTest
+		@EnumSource(mode = Mode.EXCLUDE, names = { "NEU" })
+		void shouldNOTbePresentbyStatus(VorgangStatus status) {
+			var processed = processor.process(buildVorgangInStatus(status));
+
+			assertThat(processed.getLink(linkRel)).isEmpty();
+		}
+	}
+
+	@Nested
+	@DisplayName("add 'verwerfen' link")
+	class TestVerwerfenLink {
+
+		private final LinkRelation linkRel = VorgangCommandProzessor.REL_VORGANG_VERWERFEN;
+
+		@ParameterizedTest
+		@ValueSource(strings = { "EINHEITLICHER_ANSPRECHPARTNER", "VERWALTUNG_USER" })
+		void shouldBePresentAtStatusAndRole(String userRole) {
+			doReturn(userRole).when(processor).getUserRole();
+
+			var processed = processor.process(buildVorgangInStatus(VorgangStatus.NEU));
+
+			assertThat(processed.getLink(linkRel)).isPresent().get()
+					.extracting(Link::getHref).isEqualTo(EXPECTED_COMMAND_LINK);
+		}
+
+		@ParameterizedTest
+		@EnumSource(mode = Mode.EXCLUDE, names = { "NEU" })
+		void shouldNOTbePresentAtStatusAndRoleEa(VorgangStatus status) {
+			doReturn(UserRole.EINHEITLICHER_ANSPRECHPARTNER).when(processor).getUserRole();
+
+			var processed = processor.process(buildVorgangInStatus(status));
+
+			assertThat(processed.getLink(linkRel)).isEmpty();
+		}
+
+		@ParameterizedTest
+		@EnumSource(mode = Mode.EXCLUDE, names = { "NEU" })
+		void shouldNOTbePresentAtStatusAndRoleVerwaltungUser(VorgangStatus status) {
+			doReturn(UserRole.VERWALTUNG_USER).when(processor).getUserRole();
+
+			var processed = processor.process(buildVorgangInStatus(status));
+
+			assertThat(processed.getLink(linkRel)).isEmpty();
+		}
+	}
+
+	@Nested
+	@DisplayName("add 'zurueckholen' link")
+	class TestZurueckholenLink {
+
+		private final LinkRelation linkRel = VorgangCommandProzessor.REL_VORGANG_ZURUECKHOLEN;
+
+		@ParameterizedTest
+		@ValueSource(strings = { "EINHEITLICHER_ANSPRECHPARTNER", "VERWALTUNG_USER" })
+		void shouldBePresentAtStatusAndRole(String userRole) {
+			var processed = processor.process(buildVorgangInStatus(VorgangStatus.VERWORFEN));
+
+			assertThat(processed.getLink(linkRel)).isPresent().get()
+					.extracting(Link::getHref).isEqualTo(EXPECTED_COMMAND_LINK);
+		}
+
+		@ParameterizedTest
+		@EnumSource(names = { "NEU", "ANGENOMMEN" })
+		void shouldNOTbePresentAtStatusAndRoleVerwaltungUser(VorgangStatus status) {
+			doReturn(UserRole.VERWALTUNG_USER).when(processor).getUserRole();
+
+			var processed = processor.process(buildVorgangInStatus(status));
+
+			assertThat(processed.getLink(linkRel)).isEmpty();
+		}
+
+		@ParameterizedTest
+		@EnumSource(names = { "NEU", "ANGENOMMEN" })
+		void shouldNOTbePresentAtStatusAndRoleEa(VorgangStatus status) {
+			doReturn(UserRole.EINHEITLICHER_ANSPRECHPARTNER).when(processor).getUserRole();
+
+			var processed = processor.process(buildVorgangInStatus(status));
+
+			assertThat(processed.getLink(linkRel)).isEmpty();
+		}
+	}
+
+	@Nested
+	@DisplayName("add 'bearbeiten' link")
+	class TestBearbeitenLink {
+
+		private final LinkRelation linkRel = VorgangCommandProzessor.REL_VORGANG_BEARBEITEN;
+
+		@Test
+		void shouldBePresent() {
+			var processed = processor.process(buildVorgangInStatus(VorgangStatus.ANGENOMMEN));
+
+			assertThat(processed.getLink(linkRel)).isPresent().get()
+					.extracting(Link::getHref)
+					.isEqualTo(EXPECTED_COMMAND_LINK);
+		}
+
+		@ParameterizedTest
+		@EnumSource(mode = Mode.EXCLUDE, names = { "ANGENOMMEN", "ABGESCHLOSSEN" })
+		void shouldNOTbePresentbyStatus(VorgangStatus status) {
+			var processed = processor.process(buildVorgangInStatus(status));
+
+			assertThat(processed.getLink(linkRel)).isEmpty();
+		}
+	}
+
+	@Nested
+	@DisplayName("add 'bescheiden' link")
+	class TestBescheidenLink {
+
+		private final LinkRelation linkRel = VorgangCommandProzessor.REL_VORGANG_BESCHEIDEN;
+
+		@Test
+		void shouldBePresent() {
+			var processed = processor.process(buildVorgangInStatus(VorgangStatus.IN_BEARBEITUNG));
+
+			assertThat(processed.getLink(linkRel)).isPresent().get()
+					.extracting(Link::getHref)
+					.isEqualTo(EXPECTED_COMMAND_LINK);
+		}
+
+		@ParameterizedTest
+		@EnumSource(mode = Mode.EXCLUDE, names = { "IN_BEARBEITUNG" })
+		void shouldNOTbePresentbyStatus(VorgangStatus status) {
+			var processed = processor.process(buildVorgangInStatus(status));
+
+			assertThat(processed.getLink(linkRel)).isEmpty();
+		}
+	}
+
+	@Nested
+	@DisplayName("add 'zurueckstellen' link")
+	class TestZurueckstellenLink {
+
+		private final LinkRelation linkRel = VorgangCommandProzessor.REL_VORGANG_ZURUECKSTELLEN;
+
+		@Test
+		void shouldBePresent() {
+			var processed = processor.process(buildVorgangInStatus(VorgangStatus.IN_BEARBEITUNG));
+
+			assertThat(processed.getLink(linkRel)).isPresent().get()
+					.extracting(Link::getHref).isEqualTo(EXPECTED_COMMAND_LINK);
+		}
+
+		@ParameterizedTest
+		@EnumSource(mode = Mode.EXCLUDE, names = { "IN_BEARBEITUNG" })
+		void shouldNOTbePresentbyStatus(VorgangStatus status) {
+			var processed = processor.process(buildVorgangInStatus(status));
+
+			assertThat(processed.getLink(linkRel)).isEmpty();
+		}
+	}
+
+	@Nested
+	@DisplayName("add 'abschliessen' link")
+	class TestAbschliessenLink {
+
+		private final LinkRelation linkRel = VorgangCommandProzessor.REL_VORGANG_ABSCHLIESSEN;
+
+		@Nested
+		class TestOnVerwaltungUser {
+
+			@BeforeEach
+			void mockUserRole() {
+				doReturn(UserRole.VERWALTUNG_USER).when(processor).getUserRole();
+			}
+
+			@Test
+			void shouldBePresent() {
+				var processed = processor.process(buildVorgangInStatus(VorgangStatus.BESCHIEDEN));
+
+				assertThat(processed.getLink(linkRel)).isPresent().get()
+						.extracting(Link::getHref)
+						.isEqualTo(EXPECTED_COMMAND_LINK);
+			}
+
+			@ParameterizedTest
+			@EnumSource(mode = Mode.EXCLUDE, names = { "BESCHIEDEN" })
+			void shouldNOTbePresentbyStatus(VorgangStatus status) {
+				var processed = processor.process(buildVorgangInStatus(status));
+
+				assertThat(processed.getLink(linkRel)).isEmpty();
+			}
+		}
+
+		@Nested
+		class TestOnEinheitlicherAnsprechpartner {
+
+			@BeforeEach
+			void mockUserRole() {
+				doReturn(UserRole.EINHEITLICHER_ANSPRECHPARTNER).when(processor).getUserRole();
+			}
+
+			@Test
+			void shouldBePresent() {
+				var processed = processor.process(buildVorgangInStatus(VorgangStatus.IN_BEARBEITUNG));
+
+				assertThat(processed.getLink(linkRel)).isPresent().get()
+						.extracting(Link::getHref)
+						.isEqualTo(EXPECTED_COMMAND_LINK);
+			}
+
+			@ParameterizedTest
+			@EnumSource(mode = Mode.EXCLUDE, names = { "IN_BEARBEITUNG" })
+			void shouldNOTbePresentbyStatus(VorgangStatus status) {
+				var processed = processor.process(buildVorgangInStatus(status));
+
+				assertThat(processed.getLink(linkRel)).isEmpty();
+			}
+		}
+	}
+
+	@Nested
+	@DisplayName("add 'wiedereroeffnen' link")
+	class TestWiedereroeffnenLink {
+
+		private final LinkRelation linkRel = VorgangCommandProzessor.REL_VORGANG_WIEDEREROEFFNEN;
+
+		@DisplayName("on Vorgang with Status Abgeschlossen")
+		@ParameterizedTest
+		@ValueSource(strings = { "EINHEITLICHER_ANSPRECHPARTNER", "VERWALTUNG_USER" })
+		void shouldBePresentOnStatusAbgeschlossen(String userRole) {
+			doReturn(userRole).when(processor).getUserRole();
+
+			var processed = processor.process(buildVorgangInStatus(VorgangStatus.ABGESCHLOSSEN));
+
+			assertThat(processed.getLink(linkRel)).isPresent().get()
+					.extracting(Link::getHref).isEqualTo(EXPECTED_COMMAND_LINK);
+		}
+
+		@Test
+		@DisplayName("on Vorgang with Status Beschieden and Role VerwaltungUser")
+		void shouldBePresentOnStatusBeschieden() {
+			var processed = processor.process(buildVorgangInStatus(VorgangStatus.BESCHIEDEN));
+
+			assertThat(processed.getLink(linkRel)).isPresent().get()
+					.extracting(Link::getHref).isEqualTo(EXPECTED_COMMAND_LINK);
+		}
+
+		@Test
+		@DisplayName("on Vorgang with Status Beschieden and Role EA")
+		void shouldBeNotPresentOnStatusBeschiedenAndRoleEA() {
+			doReturn(UserRole.EINHEITLICHER_ANSPRECHPARTNER).when(processor).getUserRole();
+
+			var processed = processor.process(buildVorgangInStatus(VorgangStatus.BESCHIEDEN));
+
+			assertThat(processed.getLink(linkRel)).isNotPresent();
+		}
+
+		@ParameterizedTest
+		@EnumSource(mode = Mode.EXCLUDE, names = { "ABGESCHLOSSEN", "BESCHIEDEN" })
+		void shouldNOTbePresentbyStatusAndRoleVerwaltungUser(VorgangStatus status) {
+			doReturn(UserRole.VERWALTUNG_USER).when(processor).getUserRole();
+
+			var processed = processor.process(buildVorgangInStatus(status));
+
+			assertThat(processed.getLink(linkRel)).isEmpty();
+		}
+
+		@ParameterizedTest
+		@EnumSource(mode = Mode.EXCLUDE, names = { "ABGESCHLOSSEN", "BESCHIEDEN" })
+		void shouldNOTbePresentbyStatusAndRoleEA(VorgangStatus status) {
+			doReturn(UserRole.EINHEITLICHER_ANSPRECHPARTNER).when(processor).getUserRole();
+
+			var processed = processor.process(buildVorgangInStatus(status));
+
+			assertThat(processed.getLink(linkRel)).isEmpty();
+		}
+	}
+
+	@Nested
+	class TestRoleVerwaltungPoststelle {
+		@BeforeEach
+		void init() {
+			when(userService.hasRole(UserRole.EINHEITLICHER_ANSPRECHPARTNER)).thenReturn(Boolean.FALSE);
+			when(userService.hasRole(UserRole.VERWALTUNG_POSTSTELLE)).thenReturn(Boolean.TRUE);
+		}
+
+		@ParameterizedTest
+		@EnumSource()
+		void shouldNotBePresent(VorgangStatus status) {
+			var processed = processor.process(buildVorgangInStatus(status));
+
+			assertThat(processed.getLinks()).isEmpty();
+		}
+	}
+
+	private EntityModel<Vorgang> buildVorgangInStatus(VorgangStatus status) {
+		return EntityModel.of(VorgangHeaderTestFactory.createBuilder().status(status).build());
+	}
+}
\ No newline at end of file
diff --git a/goofy-server/src/test/java/de/itvsh/goofy/vorgang/command/VorgangWithEingangCommandProzessorTest.java b/goofy-server/src/test/java/de/itvsh/goofy/vorgang/command/VorgangWithEingangCommandProzessorTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..e3f275c75f4e379a1b8a6a2536430a7b112bd5e1
--- /dev/null
+++ b/goofy-server/src/test/java/de/itvsh/goofy/vorgang/command/VorgangWithEingangCommandProzessorTest.java
@@ -0,0 +1,228 @@
+/*
+ * 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.itvsh.goofy.vorgang.command;
+
+import static de.itvsh.goofy.common.UserProfileUrlProviderTestFactory.*;
+import static org.assertj.core.api.Assertions.*;
+import static org.mockito.ArgumentMatchers.*;
+import static org.mockito.Mockito.*;
+
+import java.util.Collections;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.EnumSource;
+import org.junit.jupiter.params.provider.EnumSource.Mode;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.Spy;
+import org.springframework.hateoas.EntityModel;
+import org.springframework.hateoas.Link;
+import org.springframework.hateoas.LinkRelation;
+import org.springframework.security.test.context.support.WithMockUser;
+
+import de.itvsh.goofy.common.UserProfileUrlProvider;
+import de.itvsh.goofy.common.command.CommandController;
+import de.itvsh.goofy.common.user.CurrentUserService;
+import de.itvsh.goofy.common.user.UserRole;
+import de.itvsh.goofy.vorgang.Vorgang.VorgangStatus;
+import de.itvsh.goofy.vorgang.VorgangHeaderTestFactory;
+import de.itvsh.goofy.vorgang.VorgangWithEingang;
+import de.itvsh.goofy.vorgang.VorgangWithEingangTestFactory;
+import de.itvsh.goofy.vorgang.forwarding.ForwardingController;
+import de.itvsh.goofy.vorgang.forwarding.ForwardingTestFactory;
+
+class VorgangWithEingangCommandProzessorTest {
+
+	@Spy // NOSONAR
+	@InjectMocks
+	private VorgangWithEingangCommandProzessor processor;
+	@Mock
+	private CurrentUserService userService;
+	@Mock
+	private CommandController commandController;
+	@Mock
+	private ForwardingController forwardingController;
+
+	private VorgangWithEingang vorgang = VorgangWithEingangTestFactory.create();
+	private EntityModel<VorgangWithEingang> vorgangEntityModel = EntityModel.of(vorgang);
+
+	private UserProfileUrlProvider urlProvider = new UserProfileUrlProvider();
+
+	@Test
+	void shouldReturnEntityModel() {
+		initUserProfileUrlProvider(urlProvider);
+
+		var result = processor.process(vorgangEntityModel);
+
+		assertThat(result).isNotNull();
+	}
+
+	@Nested
+	class TestAddForwardLink {
+
+		private VorgangWithEingang vorgang = VorgangWithEingangTestFactory.createBuilder().status(VorgangStatus.IN_BEARBEITUNG).build();
+
+		@BeforeEach
+		void init() {
+			when(userService.hasRole(UserRole.EINHEITLICHER_ANSPRECHPARTNER)).thenReturn(true);
+		}
+
+		@DisplayName("should be true for Role EINHEITLICHER_ANSPRECHPARTNER, Status IN_BEARBEITUNG and Forwarding with Error")
+		@Test
+		void shouldBeTrue() {
+			when(forwardingController.findFailedForwardings(anyString())).thenReturn(Collections.singletonList(ForwardingTestFactory.create()));
+
+			var addLink = processor.isForwardingAllowed(vorgang);
+
+			assertThat(addLink).isTrue();
+		}
+
+		@Test
+		void shouldBeTrueInStatusNeu() {
+			var vorgang = VorgangWithEingangTestFactory.createBuilder().status(VorgangStatus.NEU).build();
+
+			var addLink = processor.isForwardingAllowed(vorgang);
+
+			assertThat(addLink).isTrue();
+		}
+
+		@Test
+		void shouldBeFalseForRoleNotEA() {
+			when(userService.hasRole(UserRole.EINHEITLICHER_ANSPRECHPARTNER)).thenReturn(false);
+
+			var addLink = processor.isForwardingAllowed(vorgang);
+
+			assertThat(addLink).isFalse();
+		}
+
+		@ParameterizedTest
+		@EnumSource(mode = Mode.EXCLUDE, names = { "NEU", "IN_BEARBEITUNG" })
+		void shouldBeFalseForVorgangInOtherStatus(VorgangStatus status) {
+			var vorgang = VorgangWithEingangTestFactory.createBuilder().status(status).build();
+
+			var addLink = processor.isForwardingAllowed(vorgang);
+
+			assertThat(addLink).isFalse();
+		}
+
+		@Test
+		void shouldBeFalseIfNoFailedForwarding() {
+			when(forwardingController.findFailedForwardings(anyString())).thenReturn(Collections.emptyList());
+
+			var addLink = processor.isForwardingAllowed(vorgang);
+
+			assertThat(addLink).isFalse();
+		}
+
+		@Test
+		void shouldBeFalseOnStatusWeitergeleitet() {
+			VorgangWithEingang vorgang = VorgangWithEingangTestFactory.createBuilder().status(VorgangStatus.WEITERGELEITET).build();
+
+			var addLink = processor.isForwardingAllowed(vorgang);
+
+			assertThat(addLink).isFalse();
+		}
+
+	}
+
+	@Nested
+	@DisplayName("add 'forward' link")
+	class TestForwardLink {
+
+		private final LinkRelation linkRel = VorgangWithEingangCommandProzessor.REL_VORGANG_FORWARD;
+
+		@BeforeEach
+		void init() {
+			initUserProfileUrlProvider(urlProvider);
+		}
+
+		@Test
+		void shouldNotBePresent() {
+			doReturn(false).when(processor).isForwardingAllowed(any());
+
+			var processed = processor.process(EntityModel.of(VorgangWithEingangTestFactory.create()));
+
+			assertThat(processed.getLink(linkRel)).isEmpty();
+		}
+
+		@Test
+		void shouldBePresent() {
+			doReturn(true).when(processor).isForwardingAllowed(any());
+
+			var processed = processor.process(EntityModel.of(VorgangWithEingangTestFactory.create()));
+
+			assertThat(processed.getLink(linkRel)).isPresent().get()
+					.extracting(Link::getHref)
+					.isEqualTo("/api/vorgangs/" + VorgangHeaderTestFactory.ID + "/relations/" + VorgangHeaderTestFactory.ID + "/"
+							+ VorgangHeaderTestFactory.VERSION + "/commands");
+		}
+
+	}
+
+	@Nested
+	class TestPendingCommandsLink {
+
+		@BeforeEach
+		void init() {
+			initUserProfileUrlProvider(urlProvider);
+		}
+
+		@Test
+		void shouldExists() {
+			when(commandController.existsPendingCommands(any())).thenReturn(true);
+
+			var link = processor.process(EntityModel.of(VorgangWithEingangTestFactory.create()))
+					.getLink(VorgangWithEingangCommandProzessor.REL_PENDING_COMMANDS);
+
+			assertThat(link).isPresent();
+			assertThat(link.get().getHref()).isEqualTo("/api/commands?pending=true&vorgangId=" + VorgangHeaderTestFactory.ID);
+		}
+
+		@Test
+		void shouldNotExists() {
+			when(commandController.existsPendingCommands(any())).thenReturn(false);
+
+			var link = processor.process(EntityModel.of(VorgangWithEingangTestFactory.create()))
+					.getLink(VorgangWithEingangCommandProzessor.REL_PENDING_COMMANDS);
+
+			assertThat(link).isNotPresent();
+		}
+
+		@DisplayName("should have link with user role " + UserRole.EINHEITLICHER_ANSPRECHPARTNER)
+		@WithMockUser(roles = UserRole.EINHEITLICHER_ANSPRECHPARTNER)
+		void shouldExistsByRoleEA() {
+			when(commandController.existsPendingCommands(any())).thenReturn(false);
+
+			var link = processor.process(EntityModel.of(VorgangWithEingangTestFactory.create()))
+					.getLink(VorgangWithEingangCommandProzessor.REL_PENDING_COMMANDS);
+
+			assertThat(link).isPresent().get().extracting(Link::getHref)
+					.isEqualTo("/api/commands?pending=true&vorgangId=" + VorgangHeaderTestFactory.ID);
+		}
+	}
+}
\ No newline at end of file
diff --git a/goofy-server/src/test/java/de/itvsh/goofy/vorgang/forwarding/ForwardingControllerTest.java b/goofy-server/src/test/java/de/itvsh/goofy/vorgang/forwarding/ForwardingControllerTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..eefe10cdf97f3acefa57e233e420bccfc377aa03
--- /dev/null
+++ b/goofy-server/src/test/java/de/itvsh/goofy/vorgang/forwarding/ForwardingControllerTest.java
@@ -0,0 +1,86 @@
+/*
+ * 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.itvsh.goofy.vorgang.forwarding;
+
+import static org.mockito.Mockito.*;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
+
+import java.util.stream.Stream;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.springframework.test.web.servlet.MockMvc;
+import org.springframework.test.web.servlet.ResultActions;
+import org.springframework.test.web.servlet.setup.MockMvcBuilders;
+
+import de.itvsh.goofy.vorgang.VorgangHeaderTestFactory;
+
+class ForwardingControllerTest {
+
+	@InjectMocks
+	private ForwardingController controller;
+	@Mock
+	private ForwardingService service;
+	@Mock
+	private ForwardingModelAssembler modelAssembler;
+
+	private MockMvc mockMvc;
+
+	@BeforeEach
+	void init() {
+		mockMvc = MockMvcBuilders.standaloneSetup(controller).build();
+	}
+
+	@Nested
+	class TestFindByVorgang {
+		@Test
+		void shouldReturn200() throws Exception {
+			doRequest().andExpect(status().isOk());
+		}
+
+		@Test
+		void shouldCallService() throws Exception {
+			doRequest();
+
+			verify(service).findByVorgang(VorgangHeaderTestFactory.ID);
+		}
+
+		@Test
+		void shouldCallModelAssembler() throws Exception {
+			doRequest();
+
+			verify(modelAssembler).toCollectionModel(Mockito.<Stream<Forwarding>>any());
+		}
+
+		private ResultActions doRequest() throws Exception {
+			return mockMvc.perform(get(ForwardingController.LIST_PATH).param(ForwardingController.PARAM_VORGANG_ID, VorgangHeaderTestFactory.ID));
+		}
+	}
+
+}
diff --git a/goofy-server/src/test/java/de/itvsh/goofy/vorgang/forwarding/ForwardingLandesnetzInfoServiceTest.java b/goofy-server/src/test/java/de/itvsh/goofy/vorgang/forwarding/ForwardingLandesnetzInfoServiceTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..9e6057c84c9e515d2253a5c72879112f8a155ad2
--- /dev/null
+++ b/goofy-server/src/test/java/de/itvsh/goofy/vorgang/forwarding/ForwardingLandesnetzInfoServiceTest.java
@@ -0,0 +1,108 @@
+/*
+ * 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.itvsh.goofy.vorgang.forwarding;
+
+import java.util.Arrays;
+import java.util.HashSet;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.Spy;
+import org.springframework.core.io.ResourceLoader;
+import org.springframework.test.util.ReflectionTestUtils;
+
+import static org.assertj.core.api.Assertions.*;
+
+class ForwardingLandesnetzInfoServiceTest {
+
+	@Spy
+	@InjectMocks
+	private ForwardingLandesnetzInfoService service;
+	@Mock
+	private ResourceLoader resourceLoader;
+
+	@DisplayName("Is email in landesnetz")
+	@Nested
+	class TestIstInLandesnetz {
+
+		private final String EMAIL_NAME = "testEmailName@";
+
+		private final String IN_LANDESNETZ = "itvsh.de";
+		private final String IN_LANDESNETZ_WITH_DOTS = ".itvsh.old.de";
+
+		@BeforeEach
+		void initMap() throws Exception {
+			ReflectionTestUtils.setField(service, "landesnetzInfo", new HashSet<>(Arrays.asList(IN_LANDESNETZ, IN_LANDESNETZ_WITH_DOTS)));
+		}
+
+		@DisplayName("validate with email in Landesnetz")
+		@Nested
+		class TestWithMatchingEmail {
+
+			@Test
+			void shouldReturnTrueWithSingleDot() {
+				var result = service.isEmailInLandesnetz(EMAIL_NAME + IN_LANDESNETZ);
+
+				assertThat(result).isTrue();
+			}
+
+			@Test
+			void shouldReturnTrueWithMultipleDots() {
+				var result = service.isEmailInLandesnetz(EMAIL_NAME + IN_LANDESNETZ_WITH_DOTS);
+
+				assertThat(result).isTrue();
+			}
+
+			@Test
+			void shouldReturnTrueByConsideringJustTheEnd() {
+				var result = service.isEmailInLandesnetz(EMAIL_NAME + "me" + IN_LANDESNETZ_WITH_DOTS);
+
+				assertThat(result).isTrue();
+			}
+		}
+
+		@DisplayName("validate with email NOT in Landesnetz")
+		@Nested
+		class TestEndsWithMatching {
+
+			@Test
+			void shouldReturnFalseOnWrongDomain() {
+				var result = service.isEmailInLandesnetz(EMAIL_NAME + "me.gmx.de");
+
+				assertThat(result).isFalse();
+			}
+
+			@Test
+			void shouldReturnFalseOnConsideringJustTheEnd() {
+				var result = service.isEmailInLandesnetz(EMAIL_NAME + "me" + IN_LANDESNETZ_WITH_DOTS + ".nicht.gueltig.de");
+
+				assertThat(result).isFalse();
+			}
+		}
+	}
+}
\ No newline at end of file
diff --git a/goofy-server/src/test/java/de/itvsh/goofy/vorgang/forwarding/ForwardingModelAssemblerTest.java b/goofy-server/src/test/java/de/itvsh/goofy/vorgang/forwarding/ForwardingModelAssemblerTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..e07d7cc132be135d9d8acc1a4ed63a4a4feb37bd
--- /dev/null
+++ b/goofy-server/src/test/java/de/itvsh/goofy/vorgang/forwarding/ForwardingModelAssemblerTest.java
@@ -0,0 +1,197 @@
+/*
+ * 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.itvsh.goofy.vorgang.forwarding;
+
+import static org.assertj.core.api.Assertions.*;
+import static org.mockito.ArgumentMatchers.*;
+import static org.mockito.Mockito.*;
+
+import java.util.stream.Stream;
+
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.EnumSource;
+import org.junit.jupiter.params.provider.EnumSource.Mode;
+import org.mockito.InjectMocks;
+import org.mockito.Spy;
+import org.springframework.hateoas.IanaLinkRelations;
+import org.springframework.hateoas.Link;
+import org.springframework.stereotype.Component;
+
+import de.itvsh.goofy.vorgang.VorgangHeaderTestFactory;
+import de.itvsh.goofy.vorgang.forwarding.Forwarding.Status;
+
+@Component
+class ForwardingModelAssemblerTest {
+
+	@Spy // NOSONAR
+	@InjectMocks
+	private ForwardingModelAssembler modelAssembler;
+
+	@Nested
+	class TestToModel {
+
+		@Test
+		void shouldReturnEntityModel() {
+			var model = modelAssembler.toModel(ForwardingTestFactory.create());
+
+			assertThat(model).isNotNull();
+		}
+
+		@Test
+		void shouldHaveSelfLink() {
+			var model = modelAssembler.toModel(ForwardingTestFactory.create());
+
+			assertThat(model.getLink(IanaLinkRelations.SELF)).isPresent().get()
+					.extracting(Link::getHref).isEqualTo("/api/forwardings/" + ForwardingTestFactory.ID);
+		}
+	}
+
+	@Nested
+	class TestToCollectionModel {
+		@Test
+		void shouldCallToModel() {
+			modelAssembler.toCollectionModel(Stream.of(ForwardingTestFactory.create()));
+
+			verify(modelAssembler).toModel(any());
+		}
+
+		@Test
+		void shouldHaveSelfLink() {
+			var model = modelAssembler.toCollectionModel(Stream.of(ForwardingTestFactory.create()));
+
+			assertThat(model.getLink(IanaLinkRelations.SELF)).isPresent().get()
+					.extracting(Link::getHref).isEqualTo("/api/forwardings");
+		}
+	}
+
+	@Nested
+	class TestLinkOnModel {
+
+		private final String expectedLinkHref = "/api/vorgangs/" + VorgangHeaderTestFactory.ID + "/relations/" + ForwardingTestFactory.ID
+				+ "/-1/commands";
+
+		@Nested
+		class TestMarkAsSuccessLink {
+
+			@Test
+			void shouldBePresentInStatusSENT() {
+				var model = modelAssembler.toModel(ForwardingTestFactory.createBuilder().status(Status.SENT).build());
+
+				assertThat(model.getLink(ForwardingModelAssembler.REL_MARK_AS_SUCCESS)).isPresent().get()
+						.extracting(Link::getHref).isEqualTo(expectedLinkHref);
+			}
+
+			@ParameterizedTest
+			@EnumSource(mode = Mode.EXCLUDE, names = { "SENT" })
+			void shouldNOTBePresentInAnyOtherStatus(Status status) {
+				var model = modelAssembler.toModel(ForwardingTestFactory.createBuilder().status(status).build());
+
+				assertThat(model.getLink(ForwardingModelAssembler.REL_MARK_AS_SUCCESS)).isEmpty();
+			}
+		}
+
+		@Nested
+		class TestMarkAsFailLink {
+
+			@ParameterizedTest
+			@EnumSource(names = { "SENT", "SUCCESSFULL" })
+			void shouldBePresentInStatusSENT(Status status) {
+				var model = modelAssembler.toModel(ForwardingTestFactory.createBuilder().status(status).build());
+
+				assertThat(model.getLink(ForwardingModelAssembler.REL_MARK_AS_FAIL)).isPresent().get()
+						.extracting(Link::getHref).isEqualTo(expectedLinkHref);
+			}
+
+			@ParameterizedTest
+			@EnumSource(mode = Mode.EXCLUDE, names = { "SENT", "SUCCESSFULL" })
+			void shouldNOTBePresentInAnyOtherStatus(Status status) {
+				var model = modelAssembler.toModel(ForwardingTestFactory.createBuilder().status(status).build());
+
+				assertThat(model.getLink(ForwardingModelAssembler.REL_MARK_AS_FAIL)).isEmpty();
+			}
+		}
+
+		@Nested
+		class TestFailedLink {
+
+			@Test
+			void shouldBePresentInStatusFAILED() {
+				var model = modelAssembler.toModel(ForwardingTestFactory.createBuilder().status(Status.FAILED).build());
+
+				assertThat(model.getLink(ForwardingModelAssembler.REL_FAILED)).isPresent().get()
+						.extracting(Link::getHref).isEqualTo("/api/forwardings/" + ForwardingTestFactory.ID);
+			}
+
+			@ParameterizedTest
+			@EnumSource(mode = Mode.EXCLUDE, names = { "FAILED" })
+			void shouldNOTBePresentInAnyOtherStatus(Status status) {
+				var model = modelAssembler.toModel(ForwardingTestFactory.createBuilder().status(status).build());
+
+				assertThat(model.getLink(ForwardingModelAssembler.REL_FAILED)).isEmpty();
+			}
+		}
+
+		@Nested
+		class TestSuccessfullLink {
+
+			@Test
+			void shouldBePresentInStatusSUCCESSFULL() {
+				var model = modelAssembler.toModel(ForwardingTestFactory.createBuilder().status(Status.SUCCESSFULL).build());
+
+				assertThat(model.getLink(ForwardingModelAssembler.REL_SUCCESSFULL)).isPresent().get()
+						.extracting(Link::getHref).isEqualTo("/api/forwardings/" + ForwardingTestFactory.ID);
+			}
+
+			@ParameterizedTest
+			@EnumSource(mode = Mode.EXCLUDE, names = { "SUCCESSFULL" })
+			void shouldNOTBePresentInAnyOtherStatus(Status status) {
+				var model = modelAssembler.toModel(ForwardingTestFactory.createBuilder().status(status).build());
+
+				assertThat(model.getLink(ForwardingModelAssembler.REL_SUCCESSFULL)).isEmpty();
+			}
+		}
+
+		@Nested
+		class TestSendErrorLink {
+
+			@Test
+			void shouldBePresentInStatusSEND_ERROR() {
+				var model = modelAssembler.toModel(ForwardingTestFactory.createBuilder().status(Status.SEND_ERROR).build());
+
+				assertThat(model.getLink(ForwardingModelAssembler.REL_ERROR)).isPresent().get()
+						.extracting(Link::getHref).isEqualTo("/api/forwardings/" + ForwardingTestFactory.ID);
+			}
+
+			@ParameterizedTest
+			@EnumSource(mode = Mode.EXCLUDE, names = { "SEND_ERROR" })
+			void shouldNOTBePresentInAnyOtherStatus(Status status) {
+				var model = modelAssembler.toModel(ForwardingTestFactory.createBuilder().status(status).build());
+
+				assertThat(model.getLink(ForwardingModelAssembler.REL_ERROR)).isEmpty();
+			}
+		}
+	}
+}
\ No newline at end of file
diff --git a/goofy-server/src/test/java/de/itvsh/goofy/vorgang/forwarding/ForwardingPasswordValidatorTest.java b/goofy-server/src/test/java/de/itvsh/goofy/vorgang/forwarding/ForwardingPasswordValidatorTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..83ee22aa9d566a7f4913aa5b8d1e382f4c766801
--- /dev/null
+++ b/goofy-server/src/test/java/de/itvsh/goofy/vorgang/forwarding/ForwardingPasswordValidatorTest.java
@@ -0,0 +1,146 @@
+/*
+ * 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.itvsh.goofy.vorgang.forwarding;
+
+import static org.mockito.ArgumentMatchers.*;
+import static org.mockito.Mockito.*;
+
+import javax.validation.ConstraintValidatorContext;
+import javax.validation.ConstraintValidatorContext.ConstraintViolationBuilder;
+
+import org.apache.commons.lang3.RandomStringUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.Spy;
+
+import static org.assertj.core.api.Assertions.*;
+
+import de.itvsh.goofy.vorgang.RedirectRequestTestFactory;
+
+class ForwardingPasswordValidatorTest {
+
+	@Spy
+	@InjectMocks
+	private ForwardingPasswordValidator validator = new ForwardingPasswordValidator();
+	@Mock
+	private ConstraintValidatorContext context;
+	@Mock
+	private ForwardingLandesnetzInfoService landesnetzInfoService;
+
+	@DisplayName("Is valid")
+	@Nested
+	class TestIsValid {
+
+		@DisplayName("with null/empty")
+		@Nested
+		class TestOnNullOrEmptyEmail {
+
+			@Test
+			void shouldReturnTrue() {
+				var isValid = validator.isValid(RedirectRequestTestFactory.createBuilder().email(StringUtils.EMPTY).build(), context);
+
+				assertThat(isValid).isTrue();
+			}
+		}
+
+		@DisplayName("for a doman in landesnetz")
+		@Nested
+		class TestOnIsInLandesnetz {
+
+			@BeforeEach
+			void mock() {
+				when(landesnetzInfoService.isEmailInLandesnetz(anyString())).thenReturn(true);
+			}
+
+			@Test
+			void shouldNotDoPasswordValidation() {
+				validator.isValid(RedirectRequestTestFactory.create(), context);
+
+				verify(validator, never()).isValidPassword(any());
+			}
+		}
+
+		@DisplayName("for domain NOT in landesnetz")
+		@Nested
+		class TestOnIfNOTInLandesnetz {
+
+			@Mock
+			private ConstraintViolationBuilder contraintBuilder;
+
+			@BeforeEach
+			void mock() {
+				when(landesnetzInfoService.isEmailInLandesnetz(anyString())).thenReturn(false);
+				doNothing().when(validator).prepareConstraint(any());
+			}
+
+			@Test
+			void shouldDoPasswordValidation() {
+				validator.isValid(RedirectRequestTestFactory.create(), context);
+
+				verify(validator).isValidPassword(any());
+			}
+		}
+
+		@DisplayName("password")
+		@Nested
+		class TestPasswordValidation {
+			@Test
+			void shouldReturnFalseOnLowerSize() {
+				var isValid = isValid("notEnou".toCharArray());
+
+				assertThat(isValid).isFalse();
+			}
+
+			@Test
+			void shouldReturnFalseOnLargerSize() {
+				var isValid = isValid(RandomStringUtils.randomAlphabetic(41).toCharArray());
+
+				assertThat(isValid).isFalse();
+			}
+
+			@Test
+			void shouldReturnFalseOnNull() {
+				var isValid = isValid(null);
+
+				assertThat(isValid).isFalse();
+			}
+
+			@Test
+			void shouldReturnTrueOnValidSize() {
+				var isValid = isValid(RedirectRequestTestFactory.PASSWORD);
+
+				assertThat(isValid).isTrue();
+			}
+
+			private boolean isValid(char[] password) {
+				return validator.isValidPassword(RedirectRequest.builder().password(password).build());
+			}
+		}
+	}
+}
\ No newline at end of file
diff --git a/goofy-server/src/test/java/de/itvsh/goofy/vorgang/forwarding/ForwardingRemoteServiceTest.java b/goofy-server/src/test/java/de/itvsh/goofy/vorgang/forwarding/ForwardingRemoteServiceTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..ccc5cb9e94369ba7e22e31271c4e96ee650e9cab
--- /dev/null
+++ b/goofy-server/src/test/java/de/itvsh/goofy/vorgang/forwarding/ForwardingRemoteServiceTest.java
@@ -0,0 +1,111 @@
+/*
+ * 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.itvsh.goofy.vorgang.forwarding;
+
+import static org.assertj.core.api.Assertions.*;
+import static org.mockito.ArgumentMatchers.*;
+import static org.mockito.Mockito.*;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+
+import de.itvsh.goofy.common.callcontext.ContextService;
+import de.itvsh.goofy.vorgang.VorgangHeaderTestFactory;
+import de.itvsh.ozg.pluto.forwarding.ForwardingServiceGrpc.ForwardingServiceBlockingStub;
+import de.itvsh.ozg.pluto.forwarding.GrpcFindForwardingsRequest;
+import de.itvsh.ozg.pluto.forwarding.GrpcForwarding;
+import de.itvsh.ozg.pluto.forwarding.GrpcForwardingsResponse;
+import de.itvsh.ozg.pluto.grpc.command.GrpcCallContext;
+
+class ForwardingRemoteServiceTest {
+
+	@InjectMocks // NOSONAR
+	private ForwardingRemoteService service;
+
+	@Mock
+	private ForwardingServiceBlockingStub serviceStub;
+	@Mock
+	private ForwardingMapper mapper;
+
+	@Mock
+	private ContextService contextService;
+
+	@Captor
+	private ArgumentCaptor<GrpcFindForwardingsRequest> requestCaptor;
+
+	private GrpcCallContext callContext = GrpcCallContext.newBuilder().build();
+
+	@BeforeEach
+	void initContextService() {
+		when(contextService.createCallContext()).thenReturn(callContext);
+	}
+
+	@Nested
+	class TestFindForwardings {
+		private GrpcForwarding grpcForwarding = GrpcForwarding.newBuilder().build();
+		private GrpcForwardingsResponse response = GrpcForwardingsResponse.newBuilder().addForwardings(grpcForwarding).build();
+
+		@BeforeEach
+		void initStub() {
+			when(serviceStub.findForwardings(any())).thenReturn(response);
+		}
+
+		@Test
+		void shouldCallStub() {
+			service.findForwardings(VorgangHeaderTestFactory.ID);
+
+			verify(serviceStub).findForwardings(any());
+		}
+
+		@Test
+		void shouldFillCallContext() {
+			service.findForwardings(VorgangHeaderTestFactory.ID);
+
+			verify(serviceStub).findForwardings(requestCaptor.capture());
+			assertThat(requestCaptor.getValue().getContext()).isSameAs(callContext);
+		}
+
+		@Test
+		void shouldHaveVorgangId() {
+			service.findForwardings(VorgangHeaderTestFactory.ID);
+
+			verify(serviceStub).findForwardings(requestCaptor.capture());
+			assertThat(requestCaptor.getValue().getVorgangId()).isEqualTo(VorgangHeaderTestFactory.ID);
+		}
+
+		@Test
+		void shouldCallMapper() {
+			service.findForwardings(VorgangHeaderTestFactory.ID).forEach(v -> {
+			});
+
+			verify(mapper).fromGrpc(grpcForwarding);
+		}
+	}
+
+}
diff --git a/goofy-server/src/test/java/de/itvsh/goofy/vorgang/forwarding/ForwardingTestFactory.java b/goofy-server/src/test/java/de/itvsh/goofy/vorgang/forwarding/ForwardingTestFactory.java
new file mode 100644
index 0000000000000000000000000000000000000000..58e30fc98f46a66918b1c586ebf6b46b57ed2889
--- /dev/null
+++ b/goofy-server/src/test/java/de/itvsh/goofy/vorgang/forwarding/ForwardingTestFactory.java
@@ -0,0 +1,46 @@
+/*
+ * 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.itvsh.goofy.vorgang.forwarding;
+
+import java.util.UUID;
+
+import de.itvsh.goofy.vorgang.VorgangHeaderTestFactory;
+
+public class ForwardingTestFactory {
+
+	public static final String ID = UUID.randomUUID().toString();
+	public static final String VORGANG_ID = VorgangHeaderTestFactory.ID;
+
+	public static Forwarding create() {
+		return createBuilder().build();
+	}
+
+	public static Forwarding.ForwardingBuilder createBuilder() {
+		return Forwarding.builder()
+				.id(ID)
+				.vorgangId(VORGANG_ID);
+
+	}
+
+}
diff --git a/goofy-server/src/test/java/de/itvsh/goofy/vorgang/forwarding/LandesnetzInfoReadServiceTest.java b/goofy-server/src/test/java/de/itvsh/goofy/vorgang/forwarding/LandesnetzInfoReadServiceTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..359629b552a79d538152fbdb3d0a1fbc42943dc7
--- /dev/null
+++ b/goofy-server/src/test/java/de/itvsh/goofy/vorgang/forwarding/LandesnetzInfoReadServiceTest.java
@@ -0,0 +1,69 @@
+/*
+ * 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.itvsh.goofy.vorgang.forwarding;
+
+import org.jsoup.Jsoup;
+import org.jsoup.nodes.Document;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.*;
+
+import de.itvsh.kop.common.test.TestUtils;
+
+class LandesnetzInfoReadServiceTest {
+
+	private LandesnetzInfoReadService service = new LandesnetzInfoReadService();
+
+	@Nested
+	class TestParseTable {
+
+		private String HTML_VALUE;
+
+		@BeforeEach
+		void initResource() {
+			HTML_VALUE = TestUtils.loadTextFile("files/LandesnetzInfo.html");
+		}
+
+		@Test
+		void shouldReadFileIfExist() {
+			var parsedDomainList = service.parseTableEntries(createDocument(HTML_VALUE));
+
+			assertThat(parsedDomainList).isNotNull().hasSize(6).containsExactlyInAnyOrder(".itvsh.de", "itvsh.de", ".itvsh.old.de", "itvsh.old.de",
+					"dataport.de", "ozg-sh.de");
+		}
+
+		@Test
+		void shouldReturnEmptyListIfFileNotExist() {
+			var parsedDomainList = service.parseTableEntries(createDocument("NOT_EXIST.html"));
+
+			assertThat(parsedDomainList).isNotNull().isEmpty();
+		}
+
+		private Document createDocument(String source) {
+			return Jsoup.parse(source);
+		}
+	}
+}
diff --git a/goofy-server/src/test/java/de/itvsh/goofy/wiedervorlage/AttachmentsByWiedervorlageControllerTest.java b/goofy-server/src/test/java/de/itvsh/goofy/wiedervorlage/AttachmentsByWiedervorlageControllerTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..5aa0ba47d600f2cc56ba80be77d2ef41a767a957
--- /dev/null
+++ b/goofy-server/src/test/java/de/itvsh/goofy/wiedervorlage/AttachmentsByWiedervorlageControllerTest.java
@@ -0,0 +1,98 @@
+/*
+ * 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.itvsh.goofy.wiedervorlage;
+
+import static org.mockito.ArgumentMatchers.*;
+import static org.mockito.Mockito.*;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
+
+import java.util.List;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.Spy;
+import org.springframework.test.web.servlet.MockMvc;
+import org.springframework.test.web.servlet.ResultActions;
+import org.springframework.test.web.servlet.setup.MockMvcBuilders;
+
+import de.itvsh.goofy.common.binaryfile.BinaryFileController;
+import de.itvsh.goofy.common.binaryfile.BinaryFileTestFactory;
+import de.itvsh.goofy.wiedervorlage.WiedervorlageController.AttachmentsByWiedervorlageController;
+
+class AttachmentsByWiedervorlageControllerTest {
+
+	private final static String PATH = AttachmentsByWiedervorlageController.PATH.replace("{wiedervorlageId}", WiedervorlageTestFactory.ID)
+			+ "/attachments";
+
+	@Spy
+	@InjectMocks
+	private AttachmentsByWiedervorlageController controller;
+	@Mock
+	private WiedervorlageService service;
+	@Mock
+	private BinaryFileController binaryFileController;
+
+	private MockMvc mockMvc;
+
+	@BeforeEach
+	void initTest() {
+		mockMvc = MockMvcBuilders.standaloneSetup(controller).build();
+	}
+
+	@Nested
+	class TestGetAttachments {
+
+		@BeforeEach
+		void mockService() {
+			when(service.getById(any())).thenReturn(WiedervorlageTestFactory.create());
+		}
+
+		@Test
+		void shouldReturnStatusOk() throws Exception {
+			callEndpoint().andExpect(status().isOk());
+		}
+
+		@Test
+		void shouldCallService() throws Exception {
+			callEndpoint();
+
+			verify(service).getById(WiedervorlageTestFactory.ID);
+		}
+
+		@Test
+		void shouldCallBinaryFileController() throws Exception {
+			callEndpoint();
+
+			verify(binaryFileController).getFiles(List.of(BinaryFileTestFactory.FILE_ID));
+		}
+
+		private ResultActions callEndpoint() throws Exception {
+			return mockMvc.perform(get(PATH));
+		}
+	}
+}
\ No newline at end of file
diff --git a/goofy-server/src/test/java/de/itvsh/goofy/wiedervorlage/WiedervorlageCommandByVorgangControllerTest.java b/goofy-server/src/test/java/de/itvsh/goofy/wiedervorlage/WiedervorlageCommandByVorgangControllerTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..83d2fd9b1e35c3d5045e9eac647b8ad9d53df00b
--- /dev/null
+++ b/goofy-server/src/test/java/de/itvsh/goofy/wiedervorlage/WiedervorlageCommandByVorgangControllerTest.java
@@ -0,0 +1,120 @@
+/*
+ * 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.itvsh.goofy.wiedervorlage;
+
+import static org.assertj.core.api.Assertions.*;
+import static org.mockito.ArgumentMatchers.*;
+import static org.mockito.Mockito.*;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.Spy;
+import org.springframework.http.MediaType;
+import org.springframework.test.web.servlet.MockMvc;
+import org.springframework.test.web.servlet.ResultActions;
+import org.springframework.test.web.servlet.setup.MockMvcBuilders;
+
+import de.itvsh.goofy.common.binaryfile.BinaryFileTestFactory;
+import de.itvsh.goofy.common.command.CommandController.CommandByRelationController;
+import de.itvsh.goofy.common.command.CommandTestFactory;
+import de.itvsh.goofy.common.command.CreateCommand;
+import de.itvsh.goofy.common.user.CurrentUserService;
+import de.itvsh.goofy.vorgang.VorgangHeaderTestFactory;
+import de.itvsh.goofy.wiedervorlage.WiedervorlageCommandController.WiedervorlageCommandByVorgangController;
+
+class WiedervorlageCommandByVorgangControllerTest {
+
+	@Spy
+	@InjectMocks
+	private WiedervorlageCommandByVorgangController controller;
+	@Mock
+	private CommandByRelationController commandByRelationController;
+	@Mock
+	private CurrentUserService userService;
+	@Mock
+	private WiedervorlageService service;
+
+	@Captor
+	private ArgumentCaptor<CreateCommand> createCommandCaptor;
+	private MockMvc mockMvc;
+
+	@BeforeEach
+	void init() {
+		mockMvc = MockMvcBuilders.standaloneSetup(controller).build();
+	}
+
+	@DisplayName("Create command")
+	@Nested
+	class TestCreateCommand {
+
+		@Captor
+		private ArgumentCaptor<Wiedervorlage> wiedervorlageCaptor;
+
+		@BeforeEach
+		void mockUserService() {
+			when(service.createWiedervorlage(any(), any())).thenReturn(CommandTestFactory.create());
+		}
+
+		@Nested
+		class ServiceMethod {
+
+			@Test
+			void shouldCallService() throws Exception {
+				doRequest();
+
+				verify(service).createWiedervorlage(wiedervorlageCaptor.capture(), eq(VorgangHeaderTestFactory.ID));
+				assertThat(wiedervorlageCaptor.getValue().getAttachments().get(0)).isEqualTo(BinaryFileTestFactory.FILE_ID);
+			}
+
+			@Test
+			void shouldCallServiceToUpdateNextFrist() throws Exception {
+				doRequest();
+
+				verify(service).updateNextFrist(VorgangHeaderTestFactory.ID);
+			}
+
+			@Test
+			void returnReturnCreated() throws Exception {
+				doRequest().andExpect(status().isCreated());
+			}
+		}
+	}
+
+	private ResultActions doRequest() throws Exception {
+		return mockMvc.perform(
+				post(WiedervorlageCommandByVorgangController.WIEDERVORLAGE_COMMANDS_BY_VORGANG,
+						VorgangHeaderTestFactory.ID)
+								.content(WiedervorlageCommandTestFactory.createValidRequestContent())
+								.contentType(MediaType.APPLICATION_JSON))
+				.andExpect(status().is2xxSuccessful());
+	}
+}
\ No newline at end of file
diff --git a/goofy-server/src/test/java/de/itvsh/goofy/wiedervorlage/WiedervorlageCommandControllerTest.java b/goofy-server/src/test/java/de/itvsh/goofy/wiedervorlage/WiedervorlageCommandControllerTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..00265de8617c99b82b7afb6ddf1d4ba14c9a3d0b
--- /dev/null
+++ b/goofy-server/src/test/java/de/itvsh/goofy/wiedervorlage/WiedervorlageCommandControllerTest.java
@@ -0,0 +1,247 @@
+/*
+ * 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.itvsh.goofy.wiedervorlage;
+
+import static org.assertj.core.api.Assertions.*;
+import static org.mockito.ArgumentMatchers.*;
+import static org.mockito.Mockito.*;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
+
+import java.time.LocalDate;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.Spy;
+import org.springframework.http.MediaType;
+import org.springframework.test.web.servlet.MockMvc;
+import org.springframework.test.web.servlet.ResultActions;
+import org.springframework.test.web.servlet.setup.MockMvcBuilders;
+
+import de.itvsh.goofy.common.binaryfile.FileId;
+import de.itvsh.goofy.common.command.Command;
+import de.itvsh.goofy.common.command.CommandOrder;
+import de.itvsh.goofy.common.command.CommandService;
+import de.itvsh.goofy.common.command.CommandTestFactory;
+import de.itvsh.goofy.common.command.CreateCommand;
+import de.itvsh.goofy.vorgang.VorgangHeaderTestFactory;
+import de.itvsh.kop.common.errorhandling.TechnicalException;
+
+class WiedervorlageCommandControllerTest {
+
+	@Spy
+	@InjectMocks
+	private WiedervorlageCommandController controller;
+	@Mock
+	private CommandService commandService;
+	@Mock
+	private WiedervorlageService service;
+	@Captor
+	private ArgumentCaptor<WiedervorlageCommand> commandCaptor;
+	@Captor
+	private ArgumentCaptor<CreateCommand> createCommandCaptor;
+
+	private MockMvc mockMvc;
+
+	@BeforeEach
+	void init() {
+		mockMvc = MockMvcBuilders.standaloneSetup(controller).build();
+	}
+
+	@DisplayName("Update wiedervorlage")
+	@Nested
+	class TestUpdateWiedervorlage {
+
+		private static final String RESPONSE_HEADER = "http://localhost/api/commands/" + WiedervorlageCommandTestFactory.ID;
+
+		@Nested
+		class ControllerMethods {
+
+			@BeforeEach
+			void init() {
+				when(service.getById(any())).thenReturn(WiedervorlageTestFactory.create());
+				when(service.editWiedervorlage(any(), any(), anyLong())).thenReturn(CommandTestFactory.createBuilder()
+						.order(CommandOrder.UPDATE_ATTACHED_ITEM)
+						.body(WiedervorlageTestFactory.createAsMap()).build());
+			}
+
+			@Test
+			void shouldCallServiceGetById() throws Exception {
+				doRequest();
+
+				verify(service).getById(WiedervorlageTestFactory.ID);
+			}
+
+			@Test
+			void shouldCallServiceUpdateNextFrist() throws Exception {
+				doRequest();
+
+				verify(service).updateNextFrist(VorgangHeaderTestFactory.ID);
+			}
+
+			@Test
+			void shouldReturnCreatedStatus() throws Exception {
+				doRequest().andExpect(status().isCreated());
+			}
+
+			@Test
+			void shouldResponseWithLocationHeader() throws Exception {
+				doRequest().andExpect(header().string("Location", RESPONSE_HEADER));
+			}
+
+			@Test
+			void shouldCreateCommand() throws Exception {
+				doRequest();
+
+				verify(controller).createCommand(any(Wiedervorlage.class), any(WiedervorlageCommand.class));
+			}
+
+			private ResultActions doRequest() throws Exception {
+				return mockMvc.perform(
+						post(WiedervorlageCommandController.WIEDERVORLAGE_COMMANDS, WiedervorlageTestFactory.ID, WiedervorlageTestFactory.VERSION)
+								.content(WiedervorlageCommandTestFactory.createValidRequestContent()).contentType(MediaType.APPLICATION_JSON))
+						.andExpect(status().is2xxSuccessful());
+			}
+		}
+
+		@DisplayName("create command")
+		@Nested
+		class TestCreateCommand {
+
+			@DisplayName("for order 'erledigen'")
+			@Nested
+			class TestOnErledigenOrder {
+
+				@Test
+				void shouldCallService() {
+					callCreateCommand(CommandOrder.WIEDERVORLAGE_ERLEDIGEN);
+
+					verify(service).erledigen(any(Wiedervorlage.class));
+				}
+			}
+
+			@DisplayName("for order 'wiedereroeffnen'")
+			@Nested
+			class TestOnWiedereroeffnenOrder {
+
+				@Test
+				void shouldCallService() {
+					callCreateCommand(CommandOrder.WIEDERVORLAGE_WIEDEREROEFFNEN);
+
+					verify(service).wiedereroeffnen(any(Wiedervorlage.class));
+				}
+			}
+
+			@DisplayName("for order 'edit'")
+			@Nested
+			class TestOnEditOrder {
+
+				@Test
+				void shouldCallService() {
+					callCreateCommand(CommandOrder.EDIT_WIEDERVORLAGE);
+
+					verify(service).editWiedervorlage(any(Wiedervorlage.class), eq(WiedervorlageTestFactory.ID),
+							eq(WiedervorlageTestFactory.VERSION));
+				}
+
+				@DisplayName("update wiedervorlage by given command")
+				@Nested
+				class TestUpdateWiedervorlageByCommand {
+
+					private final FileId ATTACHMENT_ID = FileId.from("73");
+					private final String BETREFF = "Neuer Betreff";
+					private final String BESCHREIBUNG = "Neuer Beschreibung";
+					private final LocalDate FRIST = LocalDate.now();
+
+					@Test
+					void shouldHaveSetAttachments() {
+						var updatedWiedervorlage = update();
+
+						assertThat(updatedWiedervorlage.getAttachments()).hasSize(1);
+						assertThat(updatedWiedervorlage.getAttachments().get(0)).isEqualTo(ATTACHMENT_ID);
+					}
+
+					@Test
+					void shouldHaveSetBetreff() {
+						var updatedWiedervorlage = update();
+
+						assertThat(updatedWiedervorlage.getBetreff()).isEqualTo(BETREFF);
+					}
+
+					@Test
+					void shouldHaveSetBeschreibung() {
+						var updatedWiedervorlage = update();
+
+						assertThat(updatedWiedervorlage.getBeschreibung()).isEqualTo(BESCHREIBUNG);
+					}
+
+					@Test
+					void shouldHaveSetFrist() {
+						var updatedWiedervorlage = update();
+
+						assertThat(updatedWiedervorlage.getFrist()).isEqualTo(FRIST);
+					}
+
+					private Wiedervorlage update() {
+						return controller.updateWiedervorlageByCommand(WiedervorlageTestFactory.create(), buildWiedervorlageCommand());
+					}
+
+					private WiedervorlageCommand buildWiedervorlageCommand() {
+						var commandWiedervorlage = WiedervorlageTestFactory.createBuilder()
+								.clearAttachments().attachment(ATTACHMENT_ID)
+								.betreff(BETREFF)
+								.beschreibung(BESCHREIBUNG)
+								.frist(FRIST)
+								.build();
+						return WiedervorlageCommandTestFactory.createBuilder().wiedervorlage(commandWiedervorlage).build();
+					}
+				}
+			}
+
+			@Nested
+			class OnUnsupportedOrder {
+
+				@Test
+				void shouldThrowException() {
+					var wiedervorlage = WiedervorlageTestFactory.create();
+					var command = WiedervorlageCommandTestFactory.createBuilder().order(CommandOrder.CREATE_ATTACHED_ITEM).build();
+
+					assertThatExceptionOfType(TechnicalException.class)
+							.isThrownBy(() -> controller.createCommand(wiedervorlage, command));
+				}
+			}
+
+			private Command callCreateCommand(CommandOrder order) {
+				return controller.createCommand(WiedervorlageTestFactory.create(),
+						WiedervorlageCommandTestFactory.createBuilder().order(order).build());
+			}
+		}
+	}
+}
\ No newline at end of file
diff --git a/goofy-server/src/test/java/de/itvsh/goofy/wiedervorlage/WiedervorlageCommandITCase.java b/goofy-server/src/test/java/de/itvsh/goofy/wiedervorlage/WiedervorlageCommandITCase.java
new file mode 100644
index 0000000000000000000000000000000000000000..ac16b37821ec9d63a0ddfed102ed8bb119ec391b
--- /dev/null
+++ b/goofy-server/src/test/java/de/itvsh/goofy/wiedervorlage/WiedervorlageCommandITCase.java
@@ -0,0 +1,266 @@
+/*
+ * 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.itvsh.goofy.wiedervorlage;
+
+import static de.itvsh.goofy.wiedervorlage.WiedervorlageCommandTestFactory.*;
+import static org.mockito.ArgumentMatchers.*;
+import static org.mockito.Mockito.*;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
+
+import java.time.LocalDate;
+
+import org.assertj.core.internal.bytebuddy.utility.RandomString;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.DisplayName;
+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.servlet.AutoConfigureMockMvc;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.boot.test.mock.mockito.MockBean;
+import org.springframework.http.MediaType;
+import org.springframework.security.test.context.support.WithMockUser;
+import org.springframework.test.web.servlet.MockMvc;
+import org.springframework.test.web.servlet.ResultActions;
+
+import de.itvsh.goofy.common.ValidationMessageCodes;
+import de.itvsh.goofy.common.command.CommandOrder;
+import de.itvsh.goofy.common.command.CommandRemoteService;
+import de.itvsh.goofy.common.command.CommandTestFactory;
+import de.itvsh.goofy.vorgang.VorgangHeaderTestFactory;
+import de.itvsh.goofy.wiedervorlage.WiedervorlageCommandController.WiedervorlageCommandByVorgangController;
+
+@AutoConfigureMockMvc
+@SpringBootTest
+@WithMockUser
+class WiedervorlageCommandITCase {
+
+	@Autowired
+	private MockMvc mockMvc;
+	@MockBean
+	private CommandRemoteService commandRemoteService;
+	@MockBean
+	private WiedervorlageRemoteService wiedervorlageRemoteService;
+
+	@DisplayName("Create edit command")
+	@WithMockUser
+	@Nested
+	class TestCreateEditCommand {
+
+		@BeforeEach
+		void initTest() {
+			when(wiedervorlageRemoteService.getById(anyString())).thenReturn(WiedervorlageTestFactory.create());
+			when(commandRemoteService.createCommand(any())).thenReturn(CommandTestFactory.create());
+		}
+
+		@Test
+		void shouldCreateCommand() throws Exception {
+			doRequest(createValidRequestContent()).andExpect(status().isCreated());
+
+			verify(commandRemoteService).createCommand(any());
+		}
+
+		@WithMockUser
+		@DisplayName("should return validation error")
+		@Nested
+		class TestValidation {
+
+			@DisplayName("for null Betreff")
+			@Test
+			void createCommandWithInvalidBetreff() throws Exception {
+				String content = buildContentWithBetreff(null);
+
+				doRequest(content).andExpect(status().isUnprocessableEntity())
+						.andExpect(jsonPath("$.issues.length()").value(1))
+						.andExpect(jsonPath("$.issues.[0].field").value("wiedervorlage.betreff"))
+						.andExpect(jsonPath("$.issues.[0].messageCode").value(ValidationMessageCodes.FIELD_IS_EMPTY));
+			}
+
+			@DisplayName("for only 1 character in Betreff")
+			@Test
+			void createcommandWithShortBetreff() throws Exception {
+				String content = buildContentWithBetreff("a");
+
+				doRequest(content).andExpect(status().isUnprocessableEntity())
+						.andExpect(jsonPath("$.issues.[0].field").value("wiedervorlage.betreff"));
+
+			}
+
+			@DisplayName("for invalid betreff should have paramter")
+			@Test
+			void minMaxParameter() throws Exception {
+				String content = buildContentWithBetreff("a");
+
+				doRequest(content).andExpect(status().isUnprocessableEntity()).andExpect(jsonPath("$.issues[0].parameters.length()").value(2));
+			}
+
+			@DisplayName("for 41 character in Betreff")
+			@Test
+			void createCommandWithLongBetreff() throws Exception {
+				var content = buildContentWithBetreff(RandomString.make(41));
+
+				doRequest(content).andExpect(status().isUnprocessableEntity());
+			}
+
+			private String buildContentWithBetreff(String betreff) {
+				return createEditContent(WiedervorlageTestFactory.createBuilder().betreff(betreff).build());
+			}
+
+			@DisplayName("for frist in past")
+			@Test
+			void createCommandWithInvalidDate() throws Exception {
+				String content = createEditContent(WiedervorlageTestFactory.createBuilder().frist(LocalDate.parse("2020-01-01")).build());
+
+				doRequest(content).andExpect(status().isUnprocessableEntity())
+						.andExpect(jsonPath("$.issues.length()").value(1))
+						.andExpect(jsonPath("$.issues.[0].field").value("wiedervorlage.frist"))
+						.andExpect(jsonPath("$.issues.[0].messageCode").value(ValidationMessageCodes.FIELD_DATE_PAST));
+			}
+
+			@DisplayName("for empty frist")
+			@Test
+			void createCommandWithoutDate() throws Exception {
+				String content = createEditContent(WiedervorlageTestFactory.createBuilder().frist(null).build());
+
+				doRequest(content).andExpect(status().isUnprocessableEntity())
+						.andExpect(jsonPath("$.issues.length()").value(1))
+						.andExpect(jsonPath("$.issues.[0].field").value("wiedervorlage.frist"))
+						.andExpect(jsonPath("$.issues.[0].messageCode").value(ValidationMessageCodes.FIELD_IS_EMPTY));
+			}
+
+			private String createEditContent(Wiedervorlage wiedervorlage) {
+				return createRequestContent(createWithWiedervorlage(wiedervorlage).toBuilder().order(CommandOrder.EDIT_WIEDERVORLAGE).build());
+			}
+
+		}
+
+		private ResultActions doRequest(String content) throws Exception {
+			return mockMvc.perform(
+					post(WiedervorlageCommandController.WIEDERVORLAGE_COMMANDS, WiedervorlageTestFactory.ID, WiedervorlageTestFactory.VERSION)
+							.contentType(MediaType.APPLICATION_JSON)
+							.content(content));
+		}
+	}
+
+	@DisplayName("Create create command")
+	@WithMockUser
+	@Nested
+	class TestCreateCreateCommand {
+
+		private static final long RELATION_ID_ON_CREATE = -1;
+
+		@BeforeEach
+		void initTest() {
+			when(commandRemoteService.createCommand(any())).thenReturn(CommandTestFactory.create());
+			when(wiedervorlageRemoteService.getById(anyString())).thenReturn(WiedervorlageTestFactory.create());
+		}
+
+		@Test
+		void shouldCreateCommand() throws Exception {
+			doRequest(createValidRequestContent()).andExpect(status().isCreated());
+
+			verify(commandRemoteService).createCommand(any());
+		}
+
+		@WithMockUser
+		@DisplayName("should return validation error")
+		@Nested
+		class TestValidation {
+
+			@DisplayName("for null Betreff")
+			@Test
+			void createCommandWithInvalidBetreff() throws Exception {
+				String content = buildContentWithBetreff(null);
+
+				doRequest(content).andExpect(status().isUnprocessableEntity())
+						.andExpect(jsonPath("$.issues.length()").value(1))
+						.andExpect(jsonPath("$.issues.[0].field").value("wiedervorlage.betreff"))
+						.andExpect(jsonPath("$.issues.[0].messageCode").value(ValidationMessageCodes.FIELD_IS_EMPTY));
+			}
+
+			@DisplayName("for only 1 character in Betreff")
+			@Test
+			void createcommandWithShortBetreff() throws Exception {
+				String content = buildContentWithBetreff("a");
+
+				doRequest(content).andExpect(status().isUnprocessableEntity())
+						.andExpect(jsonPath("$.issues.[0].field").value("wiedervorlage.betreff"));
+
+			}
+
+			@DisplayName("for invalid betreff should have paramter")
+			@Test
+			void minMaxParameter() throws Exception {
+				String content = buildContentWithBetreff("a");
+
+				doRequest(content).andExpect(status().isUnprocessableEntity()).andExpect(jsonPath("$.issues[0].parameters.length()").value(2));
+			}
+
+			@DisplayName("for 41 character in Betreff")
+			@Test
+			void createCommandWithLongBetreff() throws Exception {
+				var content = buildContentWithBetreff(RandomString.make(41));
+
+				doRequest(content).andExpect(status().isUnprocessableEntity());
+			}
+
+			private String buildContentWithBetreff(String betreff) {
+				return createRequestContent(createWithWiedervorlage(WiedervorlageTestFactory.createBuilder().betreff(betreff).build()));
+			}
+
+			@DisplayName("for frist in past")
+			@Test
+			void createCommandWithInvalidDate() throws Exception {
+				String content = createRequestContent(
+						createWithWiedervorlage(WiedervorlageTestFactory.createBuilder().frist(LocalDate.parse("2020-01-01")).build()));
+
+				doRequest(content).andExpect(status().isUnprocessableEntity())
+						.andExpect(jsonPath("$.issues.length()").value(1))
+						.andExpect(jsonPath("$.issues.[0].field").value("wiedervorlage.frist"))
+						.andExpect(jsonPath("$.issues.[0].messageCode").value(ValidationMessageCodes.FIELD_DATE_PAST));
+			}
+
+			@DisplayName("for empty frist")
+			@Test
+			void createCommandWithoutDate() throws Exception {
+				String content = createRequestContent(
+						createWithWiedervorlage(WiedervorlageTestFactory.createBuilder().frist(null).build()));
+
+				doRequest(content).andExpect(status().isUnprocessableEntity())
+						.andExpect(jsonPath("$.issues.length()").value(1))
+						.andExpect(jsonPath("$.issues.[0].field").value("wiedervorlage.frist"))
+						.andExpect(jsonPath("$.issues.[0].messageCode").value(ValidationMessageCodes.FIELD_IS_EMPTY));
+			}
+
+		}
+
+		private ResultActions doRequest(String content) throws Exception {
+			return mockMvc.perform(post(WiedervorlageCommandByVorgangController.WIEDERVORLAGE_COMMANDS_BY_VORGANG, VorgangHeaderTestFactory.ID,
+					RELATION_ID_ON_CREATE)
+							.contentType(MediaType.APPLICATION_JSON)
+							.content(content));
+		}
+	}
+}
\ No newline at end of file
diff --git a/goofy-server/src/test/java/de/itvsh/goofy/wiedervorlage/WiedervorlageCommandTestFactory.java b/goofy-server/src/test/java/de/itvsh/goofy/wiedervorlage/WiedervorlageCommandTestFactory.java
new file mode 100644
index 0000000000000000000000000000000000000000..d541b42b0103dd2883f328597b67096e006468c9
--- /dev/null
+++ b/goofy-server/src/test/java/de/itvsh/goofy/wiedervorlage/WiedervorlageCommandTestFactory.java
@@ -0,0 +1,76 @@
+/*
+ * 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.itvsh.goofy.wiedervorlage;
+
+import java.util.Objects;
+import java.util.Optional;
+
+import org.apache.commons.lang3.StringUtils;
+
+import de.itvsh.goofy.common.binaryfile.BinaryFileTestFactory;
+import de.itvsh.goofy.common.command.CommandOrder;
+import de.itvsh.goofy.common.command.CommandTestFactory;
+import de.itvsh.kop.common.test.TestUtils;
+
+public class WiedervorlageCommandTestFactory {
+
+	public static final String ID = CommandTestFactory.ID;
+	public static final CommandOrder ORDER = CommandOrder.EDIT_WIEDERVORLAGE;
+
+	public static WiedervorlageCommand create() {
+		return createBuilder().build();
+	}
+
+	public static WiedervorlageCommand createWithWiedervorlage(Wiedervorlage wiedervorlage) {
+		return createBuilder().wiedervorlage(wiedervorlage).build();
+	}
+
+	public static WiedervorlageCommand.WiedervorlageCommandBuilder createBuilder() {
+		return WiedervorlageCommand.builder()
+				.id(ID)
+				.order(ORDER)
+				.wiedervorlage(WiedervorlageTestFactory.create());
+	}
+
+	public static String createValidRequestContent() {
+		return createRequestContent(create());
+	}
+
+	public static String createRequestContent(WiedervorlageCommand command) {
+		return TestUtils.loadTextFile("jsonTemplates/command/createCommandWithWiedervorlage.json.tmpl",
+				command.getOrder().name(),
+				addTuedelchen(command.getWiedervorlage().getBetreff()),
+				command.getWiedervorlage().getBeschreibung(),
+				Optional.ofNullable(command.getWiedervorlage().getFrist()).map(Object::toString).orElse(StringUtils.EMPTY),
+				"/api/binaryFiles/" + BinaryFileTestFactory.FILE_ID);
+	}
+
+	private static String addTuedelchen(String str) {
+		return Objects.isNull(str) ? null : String.format("\"%s\"", str);
+	}
+
+	public static String createRequestContent(CommandOrder order) {
+		return TestUtils.loadTextFile("jsonTemplates/command/createWiedervorlageOrderCommand.json.tmpl", order.name());
+	}
+}
diff --git a/goofy-server/src/test/java/de/itvsh/goofy/wiedervorlage/WiedervorlageControllerITCase.java b/goofy-server/src/test/java/de/itvsh/goofy/wiedervorlage/WiedervorlageControllerITCase.java
new file mode 100644
index 0000000000000000000000000000000000000000..47c171995394c3471843df41f480a7df73684209
--- /dev/null
+++ b/goofy-server/src/test/java/de/itvsh/goofy/wiedervorlage/WiedervorlageControllerITCase.java
@@ -0,0 +1,105 @@
+/*
+ * 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.itvsh.goofy.wiedervorlage;
+
+import static org.mockito.ArgumentMatchers.*;
+import static org.mockito.Mockito.*;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
+
+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.servlet.AutoConfigureMockMvc;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.boot.test.mock.mockito.MockBean;
+import org.springframework.security.test.context.support.WithMockUser;
+import org.springframework.test.web.servlet.MockMvc;
+import org.springframework.test.web.servlet.ResultActions;
+
+import de.itvsh.goofy.common.user.UserRole;
+
+@WithMockUser
+@AutoConfigureMockMvc
+@SpringBootTest
+public class WiedervorlageControllerITCase {
+
+	@MockBean
+	private WiedervorlageService service;
+	@Autowired
+	private MockMvc mockMvc;
+
+	@WithMockUser
+	@Nested
+	class TestGetById {
+
+		@WithMockUser(roles = { UserRole.EINHEITLICHER_ANSPRECHPARTNER })
+		@Nested
+		class OnRoleEinheitlicherAnsprechpartner {
+
+			@Test
+			void shouldReturnForbidden() throws Exception {
+				callEndpoint().andExpect(status().isForbidden());
+			}
+		}
+
+		@WithMockUser(roles = UserRole.VERWALTUNG_POSTSTELLE)
+		@Nested
+		class OnRoleVerwaltungPoststelle {
+
+			@Test
+			void shouldReturnForbidden() throws Exception {
+				callEndpoint().andExpect(status().isForbidden());
+			}
+		}
+
+		@WithMockUser(roles = UserRole.VERWALTUNG_USER)
+		@Nested
+		class OnRoleVerwaltungUser {
+
+			@BeforeEach
+			void mockController() {
+				when(service.getById(any())).thenReturn(WiedervorlageTestFactory.create());
+			}
+
+			@Test
+			void shouldReturnStatusOk() throws Exception {
+				callEndpoint().andExpect(status().isOk());
+			}
+
+			@Test
+			void shouldReturnResult() throws Exception {
+				callEndpoint().andExpect(jsonPath("$._links.self").exists())
+						.andExpect(jsonPath("$._links.createdBy").exists())
+						.andExpect(jsonPath("$.createdAt").value(WiedervorlageTestFactory.CREATED_AT_STR))
+						.andExpect(jsonPath("$.frist").value(WiedervorlageTestFactory.FRIST_STR));
+			}
+		}
+
+		private ResultActions callEndpoint() throws Exception {
+			return mockMvc.perform(get(WiedervorlageController.WIEDERVORLAGE_PATH + "/" + WiedervorlageTestFactory.ID));
+		}
+	}
+}
\ No newline at end of file
diff --git a/goofy-server/src/test/java/de/itvsh/goofy/wiedervorlage/WiedervorlageControllerTest.java b/goofy-server/src/test/java/de/itvsh/goofy/wiedervorlage/WiedervorlageControllerTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..7f1a801f4e28b8c0b43d203e2f98d39598a76be7
--- /dev/null
+++ b/goofy-server/src/test/java/de/itvsh/goofy/wiedervorlage/WiedervorlageControllerTest.java
@@ -0,0 +1,137 @@
+/*
+ * 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.itvsh.goofy.wiedervorlage;
+
+import static org.mockito.ArgumentMatchers.*;
+import static org.mockito.Mockito.*;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
+
+import java.util.stream.Stream;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.mockito.ArgumentMatchers;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.Spy;
+import org.springframework.test.web.servlet.MockMvc;
+import org.springframework.test.web.servlet.ResultActions;
+import org.springframework.test.web.servlet.setup.MockMvcBuilders;
+
+import de.itvsh.goofy.common.binaryfile.BinaryFileController;
+import de.itvsh.goofy.vorgang.VorgangHeaderTestFactory;
+
+class WiedervorlageControllerTest {
+
+	private final String PATH = WiedervorlageController.WIEDERVORLAGE_PATH + "/";
+
+	@Spy
+	@InjectMocks
+	private WiedervorlageController controller;
+	@Mock
+	private WiedervorlageService service;
+	@Mock
+	private WiedervorlageModelAssembler modelAssembler;
+	@Mock
+	private BinaryFileController binaryFileController;
+	@Mock
+	private WiedervorlageMapper mapper;
+
+	private MockMvc mockMvc;
+
+	@BeforeEach
+	void initTest() {
+		mockMvc = MockMvcBuilders.standaloneSetup(controller).build();
+	}
+
+	@Nested
+	class TestGetById {
+
+		private Wiedervorlage wiedervorlage = WiedervorlageTestFactory.create();
+
+		@BeforeEach
+		void mockService() {
+			when(service.getById(any())).thenReturn(wiedervorlage);
+		}
+
+		@Test
+		void shouldReturnStatusOk() throws Exception {
+			callEndpoint().andExpect(status().isOk());
+		}
+
+		@Test
+		void shouldCallService() throws Exception {
+			callEndpoint();
+
+			verify(service).getById(WiedervorlageTestFactory.ID);
+		}
+
+		@Test
+		void shouldCallModelAssembler() throws Exception {
+			callEndpoint();
+
+			verify(modelAssembler).toModel(wiedervorlage);
+		}
+
+		private ResultActions callEndpoint() throws Exception {
+			return mockMvc.perform(get(PATH + WiedervorlageTestFactory.ID));
+		}
+	}
+
+	@Nested
+	class TestFindByVorgang {
+
+		private Wiedervorlage wiedervorlage = WiedervorlageTestFactory.create();
+
+		@BeforeEach
+		void mockService() {
+			when(service.findByVorgangId(any())).thenReturn(Stream.of(wiedervorlage));
+		}
+
+		@Test
+		void shouldReturnStatusOk() throws Exception {
+			callEndpoint().andExpect(status().isOk());
+		}
+
+		@Test
+		void shouldCallService() throws Exception {
+			callEndpoint();
+
+			verify(service).findByVorgangId(VorgangHeaderTestFactory.ID);
+		}
+
+		@Test
+		void shouldCallModelAssembler() throws Exception {
+			callEndpoint();
+
+			verify(modelAssembler).toCollectionModel(ArgumentMatchers.<Stream<Wiedervorlage>>any(), eq(VorgangHeaderTestFactory.ID));
+		}
+
+		private ResultActions callEndpoint() throws Exception {
+			return mockMvc.perform(get(PATH).param(WiedervorlageController.PARAM_VORGANG_ID, VorgangHeaderTestFactory.ID));
+		}
+	}
+}
\ No newline at end of file
diff --git a/goofy-server/src/test/java/de/itvsh/goofy/wiedervorlage/WiedervorlageMapperTest.java b/goofy-server/src/test/java/de/itvsh/goofy/wiedervorlage/WiedervorlageMapperTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..a755d9f95a6bf3761f30cfb313cc64ed62cc5c85
--- /dev/null
+++ b/goofy-server/src/test/java/de/itvsh/goofy/wiedervorlage/WiedervorlageMapperTest.java
@@ -0,0 +1,184 @@
+/*
+ * 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.itvsh.goofy.wiedervorlage;
+
+import static org.mockito.ArgumentMatchers.*;
+import static org.mockito.Mockito.*;
+
+import java.util.HashMap;
+import java.util.List;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.mapstruct.factory.Mappers;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.Spy;
+
+import static org.assertj.core.api.Assertions.*;
+
+import de.itvsh.goofy.common.binaryfile.BinaryFileTestFactory;
+import de.itvsh.goofy.common.binaryfile.FileIdMapper;
+import de.itvsh.goofy.vorgang.GrpcVorgangAttachedItemTestFactory;
+import de.itvsh.kop.pluto.common.grpc.GrpcObjectMapper;
+
+class WiedervorlageMapperTest {
+
+	@Spy
+	@InjectMocks
+	private WiedervorlageMapper mapper = Mappers.getMapper(WiedervorlageMapper.class);
+	@Spy
+	private FileIdMapper fileIdMapper = Mappers.getMapper(FileIdMapper.class);
+	@Mock
+	private GrpcObjectMapper grpcObjectMapper;
+
+	@DisplayName("Map from item")
+	@Nested
+	class TestFromItem {
+
+		@BeforeEach
+		void mockMapper() {
+			when(grpcObjectMapper.mapFromGrpc(any())).thenReturn(WiedervorlageTestFactory.createAsMap());
+		}
+
+		@Test
+		void shouldCallGrpcObjectMapper() {
+			mapper.fromItem(GrpcVorgangAttachedItemTestFactory.create());
+
+			verify(grpcObjectMapper).mapFromGrpc(GrpcVorgangAttachedItemTestFactory.ITEM);
+		}
+
+		@Test
+		void shouldCallMapperFromItemMap() {
+			mapper.fromItem(GrpcVorgangAttachedItemTestFactory.create());
+
+			verify(mapper).fromItemMap(WiedervorlageTestFactory.createAsMap(), GrpcVorgangAttachedItemTestFactory.VERSION);
+		}
+	}
+
+	@DisplayName("Map from item map")
+	@Nested
+	class TestFromItemMap {
+
+		@Test
+		void shouldMapId() {
+			var wiedervorlage = map();
+
+			assertThat(wiedervorlage.getId()).isEqualTo(WiedervorlageTestFactory.ID);
+		}
+
+		@Test
+		void shouldMapVersion() {
+			var wiedervorlage = map();
+
+			assertThat(wiedervorlage.getVersion()).isEqualTo(WiedervorlageTestFactory.VERSION);
+		}
+
+		@Test
+		void shouldMapDone() {
+			var wiedervorlage = map();
+
+			assertThat(wiedervorlage.isDone()).isEqualTo(WiedervorlageTestFactory.DONE);
+		}
+
+		@Test
+		void shouldMapCreatedBy() {
+			var wiedervorlage = map();
+
+			assertThat(wiedervorlage.getCreatedBy()).isEqualTo(WiedervorlageTestFactory.CREATED_BY);
+		}
+
+		@Test
+		void shouldMapCreatedAt() {
+			var wiedervorlage = map();
+
+			assertThat(wiedervorlage.getCreatedAt()).isEqualTo(WiedervorlageTestFactory.CREATED_AT);
+		}
+
+		@Test
+		void shouldMapBetreff() {
+			var wiedervorlage = map();
+
+			assertThat(wiedervorlage.getBetreff()).isEqualTo(WiedervorlageTestFactory.BETREFF);
+		}
+
+		@Test
+		void shouldMapBeschreibung() {
+			var wiedervorlage = map();
+
+			assertThat(wiedervorlage.getBeschreibung()).isEqualTo(WiedervorlageTestFactory.BESCHREIBUNG);
+		}
+
+		@Test
+		void shouldMapFrist() {
+			var wiedervorlage = map();
+
+			assertThat(wiedervorlage.getFrist()).isEqualTo(WiedervorlageTestFactory.FRIST);
+		}
+
+		@Nested
+		class Attachments {
+
+			@Test
+			void shouldReturnEmptyListOnNull() {
+				var wiedervorlageWithoutAttachments = new HashMap<String, Object>();
+				wiedervorlageWithoutAttachments.putAll(WiedervorlageTestFactory.createAsMap());
+				wiedervorlageWithoutAttachments.remove(WiedervorlageMapper.ATTACHMENTS);
+
+				var wiedervorlage = mapper.fromItemMap(wiedervorlageWithoutAttachments, WiedervorlageTestFactory.VERSION);
+
+				assertThat(wiedervorlage.getAttachments()).isEmpty();
+			}
+
+			@Test
+			void shouldMapSingleAttachment() {
+				var wiedervorlage = map();
+
+				assertThat(wiedervorlage.getAttachments()).contains(BinaryFileTestFactory.FILE_ID);
+			}
+
+			@Test
+			void shouldMapAttachmentList() {
+				var wiedervorlage = mapper.fromItemMap(createWiedervorlageAsMapWithMultipleAttachments(), WiedervorlageTestFactory.VERSION);
+
+				assertThat(wiedervorlage.getAttachments()).hasSize(2);
+				assertThat(wiedervorlage.getAttachments().get(0)).isEqualTo(BinaryFileTestFactory.FILE_ID);
+				assertThat(wiedervorlage.getAttachments().get(0)).isEqualTo(BinaryFileTestFactory.FILE_ID);
+			}
+
+			private HashMap<String, Object> createWiedervorlageAsMapWithMultipleAttachments() {
+				var wiedervorlageItem = new HashMap<String, Object>();
+				wiedervorlageItem.putAll(WiedervorlageTestFactory.createAsMap());
+				wiedervorlageItem.put(WiedervorlageMapper.ATTACHMENTS, List.of(BinaryFileTestFactory.ID, BinaryFileTestFactory.ID));
+				return wiedervorlageItem;
+			}
+		}
+
+		private Wiedervorlage map() {
+			return mapper.fromItemMap(WiedervorlageTestFactory.createAsMap(), WiedervorlageTestFactory.VERSION);
+		}
+	}
+}
\ No newline at end of file
diff --git a/goofy-server/src/test/java/de/itvsh/goofy/wiedervorlage/WiedervorlageModelAssemblerTest.java b/goofy-server/src/test/java/de/itvsh/goofy/wiedervorlage/WiedervorlageModelAssemblerTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..2a7100860aae4eb760342af8dcd08c6b78fd7ace
--- /dev/null
+++ b/goofy-server/src/test/java/de/itvsh/goofy/wiedervorlage/WiedervorlageModelAssemblerTest.java
@@ -0,0 +1,188 @@
+/*
+ * 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.itvsh.goofy.wiedervorlage;
+
+import static org.assertj.core.api.Assertions.*;
+import static org.mockito.ArgumentMatchers.*;
+import static org.mockito.Mockito.*;
+
+import java.util.Collections;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.springframework.context.ApplicationContext;
+import org.springframework.core.env.Environment;
+import org.springframework.hateoas.EntityModel;
+import org.springframework.hateoas.IanaLinkRelations;
+import org.springframework.hateoas.server.EntityLinks;
+
+import de.itvsh.goofy.common.UserProfileUrlProvider;
+import de.itvsh.goofy.common.file.OzgFileTestFactory;
+import de.itvsh.goofy.vorgang.VorgangHeaderTestFactory;
+
+class WiedervorlageModelAssemblerTest {
+
+	private final String PATH = WiedervorlageController.WIEDERVORLAGE_PATH + "/";
+
+	@InjectMocks
+	private WiedervorlageModelAssembler modelAssembler;
+
+	@Mock
+	private EntityLinks entityLinks;
+
+	private UserProfileUrlProvider urlProvider = new UserProfileUrlProvider();
+
+	@BeforeEach
+	void initTest() {
+		var context = mock(ApplicationContext.class);
+		var environment = mock(Environment.class);
+		when(environment.getProperty(anyString())).thenReturn("test/");
+		when(context.getEnvironment()).thenReturn(environment);
+		urlProvider.setApplicationContext(context);
+	}
+
+	@Nested
+	class TestLinksOnModel {
+		private static final String COMMAND_BY_WIEDERVORLAGE_PATH = WiedervorlageCommandController.WIEDERVORLAGE_COMMANDS
+				.replace("{wiedervorlageId}", WiedervorlageTestFactory.ID)
+				.replace("{wiedervorlageVersion}", String.valueOf(WiedervorlageTestFactory.VERSION));
+
+		@Test
+		void shouldHaveSelfLink() {
+			var link = toModel().getLink(IanaLinkRelations.SELF);
+
+			assertThat(link).isPresent();
+			assertThat(link.get().getHref()).isEqualTo(PATH + WiedervorlageTestFactory.ID);
+		}
+
+		@Test
+		void shouldHaveEditLink() {
+			var link = toModel().getLink(WiedervorlageModelAssembler.REL_EDIT);
+
+			assertThat(link).isPresent();
+			assertThat(link.get().getHref()).isEqualTo(COMMAND_BY_WIEDERVORLAGE_PATH);
+		}
+
+		@Nested
+		class TestErledigenLink {
+
+			@Test
+			void shouldHaveLink() {
+				var link = toModel().getLink(WiedervorlageModelAssembler.REL_ERLEDIGEN);
+
+				assertThat(link).isPresent();
+				assertThat(link.get().getHref()).isEqualTo(COMMAND_BY_WIEDERVORLAGE_PATH);
+			}
+
+			@Test
+			void shouldNotHaveLink() {
+				EntityModel<Wiedervorlage> entityModel = modelAssembler.toModel(WiedervorlageTestFactory.createBuilder().done(true).build());
+
+				var link = entityModel.getLink(WiedervorlageModelAssembler.REL_ERLEDIGEN);
+
+				assertThat(link).isNotPresent();
+			}
+		}
+
+		@Nested
+		class TestWiedereroeffnenLink {
+			@Test
+			void shouldHaveLink() {
+				EntityModel<Wiedervorlage> entityModel = modelAssembler.toModel(WiedervorlageTestFactory.createBuilder().done(true).build());
+
+				var link = entityModel.getLink(WiedervorlageModelAssembler.REL_WIEDEREROEFFNEN);
+
+				assertThat(link).isPresent();
+				assertThat(link.get().getHref()).isEqualTo(COMMAND_BY_WIEDERVORLAGE_PATH);
+			}
+
+			@Test
+			void shouldNotHaveLink() {
+				EntityModel<Wiedervorlage> entityModel = modelAssembler.toModel(WiedervorlageTestFactory.create());
+
+				var link = entityModel.getLink(WiedervorlageModelAssembler.REL_WIEDEREROEFFNEN);
+
+				assertThat(link).isNotPresent();
+			}
+		}
+
+		@Nested
+		class TestAttachmentsLink {
+
+			@Test
+			void shouldHaveLink() {
+				EntityModel<Wiedervorlage> entityModel = modelAssembler
+						.toModel(WiedervorlageTestFactory.createBuilder().attachment(OzgFileTestFactory.ID).build());
+
+				var link = entityModel.getLink(WiedervorlageModelAssembler.REL_ATTACHMENTS);
+
+				assertThat(link).isPresent();
+				assertThat(link.get().getHref()).isEqualTo("/api/wiedervorlages/" + WiedervorlageTestFactory.ID + "/attachments");
+			}
+
+			@Test
+			void shouldNotHaveLink() {
+				EntityModel<Wiedervorlage> entityModel = modelAssembler
+						.toModel(WiedervorlageTestFactory.createBuilder().clearAttachments().build());
+
+				var link = entityModel.getLink(WiedervorlageModelAssembler.REL_ATTACHMENTS);
+
+				assertThat(link).isNotPresent();
+			}
+		}
+
+		private EntityModel<Wiedervorlage> toModel() {
+			return modelAssembler.toModel(WiedervorlageTestFactory.create());
+		}
+	}
+
+	@Nested
+	class TestLinksOnCollectionModel {
+
+		@Test
+		void shouldHaveCreateWiedervorlageLink() {
+			var collectionModel = modelAssembler.toCollectionModel(Collections.singleton(WiedervorlageTestFactory.create()).stream(),
+					VorgangHeaderTestFactory.ID);
+
+			var link = collectionModel.getLink(WiedervorlageModelAssembler.REL_CREATE);
+
+			assertThat(link).isPresent();
+			assertThat(link.get().getHref()).isEqualTo("/api/vorgangs/" + VorgangHeaderTestFactory.ID + "/wiedervorlageCommands");
+		}
+
+		@Test
+		void shouldHaveUploadFileLink() {
+			var collectionModel = modelAssembler.toCollectionModel(Collections.singleton(WiedervorlageTestFactory.create()).stream(),
+					VorgangHeaderTestFactory.ID);
+
+			var link = collectionModel.getLink(WiedervorlageModelAssembler.REL_UPLOAD_FILE);
+
+			assertThat(link).isPresent();
+			assertThat(link.get().getHref()).isEqualTo("/api/binaryFiles/" + VorgangHeaderTestFactory.ID + "/wiedervorlageAttachment/file");
+		}
+	}
+}
\ No newline at end of file
diff --git a/goofy-server/src/test/java/de/itvsh/goofy/wiedervorlage/WiedervorlageRemoteServiceTest.java b/goofy-server/src/test/java/de/itvsh/goofy/wiedervorlage/WiedervorlageRemoteServiceTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..e8ff6cfe61fb0e7a7069a957fd38fdccd3df783a
--- /dev/null
+++ b/goofy-server/src/test/java/de/itvsh/goofy/wiedervorlage/WiedervorlageRemoteServiceTest.java
@@ -0,0 +1,296 @@
+/*
+ * 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.itvsh.goofy.wiedervorlage;
+
+import static org.mockito.ArgumentMatchers.*;
+import static org.mockito.Mockito.*;
+
+import java.time.LocalDate;
+import java.time.format.DateTimeFormatter;
+import java.util.Optional;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.Spy;
+import org.springframework.context.ApplicationContext;
+
+import static org.assertj.core.api.Assertions.*;
+
+import de.itvsh.goofy.ApplicationTestFactory;
+import de.itvsh.goofy.vorgang.GrpcVorgangAttachedItemTestFactory;
+import de.itvsh.goofy.vorgang.VorgangHeaderTestFactory;
+import de.itvsh.ozg.pluto.grpc.clientAttribute.ClientAttributeServiceGrpc.ClientAttributeServiceBlockingStub;
+import de.itvsh.ozg.pluto.grpc.clientAttribute.GrpcAccessPermission;
+import de.itvsh.ozg.pluto.grpc.clientAttribute.GrpcDeleteClientAttributeRequest;
+import de.itvsh.ozg.pluto.grpc.clientAttribute.GrpcSetClientAttributeRequest;
+import de.itvsh.ozg.pluto.vorgangAttachedItem.GrpcFindVorgangAttachedItemRequest;
+import de.itvsh.ozg.pluto.vorgangAttachedItem.GrpcFindVorgangAttachedItemResponse;
+import de.itvsh.ozg.pluto.vorgangAttachedItem.GrpcVorgangAttachedItemRequest;
+import de.itvsh.ozg.pluto.vorgangAttachedItem.GrpcVorgangAttachedItemResponse;
+import de.itvsh.ozg.pluto.vorgangAttachedItem.VorgangAttachedItemServiceGrpc.VorgangAttachedItemServiceBlockingStub;
+
+class WiedervorlageRemoteServiceTest {
+
+	@Spy // NOSONAR
+	@InjectMocks
+	private WiedervorlageRemoteService service;
+
+	@Mock
+	private ClientAttributeServiceBlockingStub clientAttributeServiceStub;
+	@Mock
+	private VorgangAttachedItemServiceBlockingStub vorgangAttachedItemServiceStub;
+	@Mock
+	private WiedervorlageMapper mapper;
+	@Mock
+	private ApplicationContext applicationContext;
+
+	@Nested
+	class TestFindByVorgangId {
+
+		private final GrpcFindVorgangAttachedItemResponse response = GrpcFindVorgangAttachedItemResponse.newBuilder()
+				.addVorgangAttachedItems(GrpcVorgangAttachedItemTestFactory.create())
+				.build();
+
+		@Nested
+		class ServiceMethod {
+
+			@BeforeEach
+			void initMocks() {
+				when(vorgangAttachedItemServiceStub.find(any())).thenReturn(response);
+			}
+
+			@Test
+			void shouldCallVorgangAttachedItemStub() {
+				service.findByVorgangId(VorgangHeaderTestFactory.ID);
+
+				verify(vorgangAttachedItemServiceStub).find(any(GrpcFindVorgangAttachedItemRequest.class));
+			}
+
+			@Nested
+			class Mapper {
+
+				@BeforeEach
+				void mockGrpcMapper() {
+					when(mapper.fromItem(any())).thenReturn(WiedervorlageTestFactory.create());
+				}
+
+				@Test
+				void shouldCallForWiedervorlage() {
+					callFindById();
+
+					verify(mapper).fromItem(GrpcVorgangAttachedItemTestFactory.create());
+				}
+
+				private void callFindById() {
+					var result = service.findByVorgangId(VorgangHeaderTestFactory.ID);
+					collectStreamElementsToTriggerLazyStream(result);
+				}
+
+				private void collectStreamElementsToTriggerLazyStream(Stream<Wiedervorlage> stream) {
+					stream.collect(Collectors.toList());
+				}
+			}
+		}
+
+		@Nested
+		class BuildRequest {
+
+			@Test
+			void shouldSetVorgangId() {
+				var request = buildRequest();
+
+				assertThat(request.getVorgangId()).isEqualTo(VorgangHeaderTestFactory.ID);
+			}
+
+			@Test
+			void shouldSetItemName() {
+				var request = buildRequest();
+
+				assertThat(request.getItemName()).isEqualTo(WiedervorlageRemoteService.ITEM_NAME);
+			}
+
+			private GrpcFindVorgangAttachedItemRequest buildRequest() {
+				return service.buildFindRequest(VorgangHeaderTestFactory.ID);
+			}
+		}
+	}
+
+	@Nested
+	class TestGetById {
+
+		private final GrpcVorgangAttachedItemRequest request = GrpcVorgangAttachedItemRequest.newBuilder()
+				.setId(WiedervorlageTestFactory.ID).build();
+		private final GrpcVorgangAttachedItemResponse response = GrpcVorgangAttachedItemResponse.newBuilder()
+				.setVorgangAttachedItem(GrpcVorgangAttachedItemTestFactory.create())
+				.build();
+
+		@BeforeEach
+		void initMocks() {
+			when(vorgangAttachedItemServiceStub.getById(any())).thenReturn(response);
+			when(mapper.fromItem(any())).thenReturn(WiedervorlageTestFactory.create());
+		}
+
+		@Test
+		void shouldCallStub() {
+			service.getById(WiedervorlageTestFactory.ID);
+
+			verify(vorgangAttachedItemServiceStub).getById(request);
+		}
+
+		@Test
+		void shouldCallWiedervorlageMapper() {
+			service.getById(WiedervorlageTestFactory.ID);
+
+			verify(mapper).fromItem(GrpcVorgangAttachedItemTestFactory.create());
+		}
+
+		@Test
+		void shouldSetVorgangId() {
+			var wiedervorlage = service.getById(WiedervorlageTestFactory.ID);
+
+			assertThat(wiedervorlage.getVorgangId()).isEqualTo(VorgangHeaderTestFactory.ID);
+		}
+	}
+
+	@Nested
+	class TestUpdateNextFrist {
+
+		private static final LocalDate NEXT_FRIST = LocalDate.now();
+
+		@Test
+		void shouldSetOnExistingFrist() {
+			service.updateNextFrist(VorgangHeaderTestFactory.ID, Optional.of(NEXT_FRIST));
+
+			verify(service).setNextFrist(VorgangHeaderTestFactory.ID, NEXT_FRIST);
+		}
+
+		@Test
+		void shouldDeleteOnNull() {
+			doNothing().when(service).deleteNextFrist(any());
+
+			service.updateNextFrist(VorgangHeaderTestFactory.ID, Optional.empty());
+
+			verify(service).deleteNextFrist(VorgangHeaderTestFactory.ID);
+		}
+
+		@Nested
+		class OnExisting {
+
+			@Captor
+			private ArgumentCaptor<GrpcSetClientAttributeRequest> requestCaptor;
+
+			@Test
+			void shouldCallSetServiceStub() {
+				setNextFrist();
+
+				verify(clientAttributeServiceStub).set(any(GrpcSetClientAttributeRequest.class));
+			}
+
+			@Test
+			void shouldSetAccessPermissionToReadOnly() {
+				setNextFrist();
+
+				verify(clientAttributeServiceStub).set(requestCaptor.capture());
+				assertThat(requestCaptor.getValue().getAttribute().getAccess()).isEqualTo(GrpcAccessPermission.READ_ONLY);
+			}
+
+			@Test
+			void shouldSetAttributeName() {
+				setNextFrist();
+
+				verify(clientAttributeServiceStub).set(requestCaptor.capture());
+				assertThat(requestCaptor.getValue().getAttribute().getAttributeName())
+						.isEqualTo(WiedervorlageRemoteService.CLIENT_ATTRIBUTE_NEXT_WIEDERVORLAGE_FRIST);
+			}
+
+			@Test
+			void shouldSetAttributeValue() {
+				setNextFrist();
+
+				verify(clientAttributeServiceStub).set(requestCaptor.capture());
+				assertThat(requestCaptor.getValue().getAttribute().getValue().getStringValue())
+						.isEqualTo(NEXT_FRIST.format(DateTimeFormatter.ISO_DATE));
+			}
+
+			private void setNextFrist() {
+				service.setNextFrist(VorgangHeaderTestFactory.ID, NEXT_FRIST);
+			}
+		}
+
+		@Nested
+		class OnNonExisting {
+
+			@Captor
+			private ArgumentCaptor<GrpcDeleteClientAttributeRequest> requestCaptor;
+
+			@BeforeEach
+			void init() {
+				when(applicationContext.getId()).thenReturn(ApplicationTestFactory.CONTEXT_ID);
+			}
+
+			@Test
+			void shouldCallDeleteOnServiceStub() {
+				deleteNextFrist();
+
+				verify(clientAttributeServiceStub).delete(any(GrpcDeleteClientAttributeRequest.class));
+			}
+
+			@Test
+			void shouldCallWithAttributeName() {
+				deleteNextFrist();
+
+				verify(clientAttributeServiceStub).delete(requestCaptor.capture());
+				assertThat(requestCaptor.getValue().getAttributeName())
+						.isEqualTo(WiedervorlageRemoteService.CLIENT_ATTRIBUTE_NEXT_WIEDERVORLAGE_FRIST);
+			}
+
+			@Test
+			void shouldCallWithVorgangId() {
+				deleteNextFrist();
+
+				verify(clientAttributeServiceStub).delete(requestCaptor.capture());
+				assertThat(requestCaptor.getValue().getVorgangId()).isEqualTo(VorgangHeaderTestFactory.ID);
+			}
+
+			@Test
+			void shouldCallWithClient() {
+				deleteNextFrist();
+
+				verify(clientAttributeServiceStub).delete(requestCaptor.capture());
+				assertThat(requestCaptor.getValue().getClientName()).isEqualTo(ApplicationTestFactory.CONTEXT_ID);
+			}
+
+			private void deleteNextFrist() {
+				service.deleteNextFrist(VorgangHeaderTestFactory.ID);
+			}
+		}
+	}
+}
\ No newline at end of file
diff --git a/goofy-server/src/test/java/de/itvsh/goofy/wiedervorlage/WiedervorlageServiceTest.java b/goofy-server/src/test/java/de/itvsh/goofy/wiedervorlage/WiedervorlageServiceTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..dd8f9730232d4211ecfab2011de19d158ab36fda
--- /dev/null
+++ b/goofy-server/src/test/java/de/itvsh/goofy/wiedervorlage/WiedervorlageServiceTest.java
@@ -0,0 +1,257 @@
+/*
+ * 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.itvsh.goofy.wiedervorlage;
+
+import static org.assertj.core.api.Assertions.*;
+import static org.mockito.ArgumentMatchers.*;
+import static org.mockito.Mockito.*;
+
+import java.time.LocalDate;
+import java.time.ZonedDateTime;
+import java.time.temporal.ChronoUnit;
+import java.util.Optional;
+import java.util.stream.Stream;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.ArgumentMatchers;
+import org.mockito.Captor;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.Spy;
+
+import de.itvsh.goofy.common.attacheditem.VorgangAttachedItemService;
+import de.itvsh.goofy.common.command.Command;
+import de.itvsh.goofy.common.command.CommandService;
+import de.itvsh.goofy.common.command.CommandTestFactory;
+import de.itvsh.goofy.common.user.CurrentUserService;
+import de.itvsh.goofy.common.user.UserProfileTestFactory;
+import de.itvsh.goofy.vorgang.VorgangHeaderTestFactory;
+
+class WiedervorlageServiceTest {
+
+	@Spy
+	@InjectMocks
+	private WiedervorlageService service;
+	@Mock
+	private WiedervorlageRemoteService remoteService;
+	@Mock
+	private VorgangAttachedItemService vorgangAttachedItemService;
+	@Mock
+	private CommandService commandService;
+	@Mock
+	private CurrentUserService currentUserService;
+
+	@DisplayName("Create wiedervorlage")
+	@Nested
+	class TestCreateWiedervorlage {
+
+		@DisplayName("should")
+		@Nested
+		class TestCalls {
+
+			@BeforeEach
+			void mockServices() {
+				doReturn(WiedervorlageTestFactory.create()).when(service).addCreated(any());
+				when(vorgangAttachedItemService.createNewWiedervorlage(any(), any())).thenReturn(CommandTestFactory.create());
+			}
+
+			@DisplayName("add created")
+			@Test
+			void shouldAddCreated() {
+				callCreateWiedervorlage();
+
+				verify(service).addCreated(any(Wiedervorlage.class));
+			}
+
+			@DisplayName("call vorgangattacheditem service")
+			@Test
+			void shouldCallVorgangAttachedItemService() {
+				callCreateWiedervorlage();
+
+				verify(vorgangAttachedItemService).createNewWiedervorlage(any(Wiedervorlage.class), eq(VorgangHeaderTestFactory.ID));
+			}
+		}
+
+		@DisplayName("Add created")
+		@Nested
+		class TestAddCreated {
+
+			@BeforeEach
+			void mockServices() {
+				when(currentUserService.getUserId()).thenReturn(UserProfileTestFactory.ID);
+			}
+
+			@Test
+			void shouldSetCreatedAt() throws Exception {
+				var wiedervorlage = callAddCreated();
+
+				assertThat(wiedervorlage.getCreatedAt()).isNotNull().isCloseTo(ZonedDateTime.now(), within(2, ChronoUnit.SECONDS));
+			}
+
+			@Test
+			void shouldSetCreatedBy() throws Exception {
+				var wiedervorlage = callAddCreated();
+
+				assertThat(wiedervorlage.getCreatedBy()).isEqualTo(UserProfileTestFactory.ID.toString());
+			}
+
+			private Wiedervorlage callAddCreated() {
+				return service.addCreated(WiedervorlageTestFactory.createBuilder().createdAt(null).createdBy(null).build());
+			}
+		}
+
+		private Command callCreateWiedervorlage() {
+			return service.createWiedervorlage(WiedervorlageTestFactory.create(), VorgangHeaderTestFactory.ID);
+		}
+	}
+
+	@DisplayName("Edit wiedervorlage")
+	@Nested
+	class TestEditWiedervorlage {
+
+		@Captor
+		private ArgumentCaptor<Wiedervorlage> wiedervorlageCaptor;
+
+		@Test
+		void shouldCallVorgangAttachedItemService() {
+			callEditWiedervorlage();
+
+			verify(vorgangAttachedItemService).editWiedervorlage(any(Wiedervorlage.class), any(), anyLong());
+		}
+
+		private Command callEditWiedervorlage() {
+			return service.editWiedervorlage(WiedervorlageTestFactory.create(), WiedervorlageTestFactory.ID, WiedervorlageTestFactory.VERSION);
+		}
+	}
+
+	@Nested
+	class TestGetById {
+
+		@Test
+		void shouldCallRemoteService() {
+			service.getById(WiedervorlageTestFactory.ID);
+
+			verify(remoteService).getById(WiedervorlageTestFactory.ID);
+		}
+	}
+
+	@Nested
+	class TestFindByVorgangId {
+
+		@Test
+		void shouldCallRemoteService() {
+			service.findByVorgangId(VorgangHeaderTestFactory.ID);
+
+			verify(remoteService).findByVorgangId(VorgangHeaderTestFactory.ID);
+		}
+	}
+
+	@Nested
+	class TestUpdateNextFrist {
+
+		@Nested
+		class ServiceMethod {
+
+			@BeforeEach
+			void mockService() {
+				when(remoteService.findByVorgangId(any())).thenReturn(Stream.of(WiedervorlageTestFactory.create()));
+			}
+
+			@Test
+			void shoulDoCalculation() {
+				callUpdateNextFrist();
+
+				verify(service).calculateNextFrist(ArgumentMatchers.<Stream<Wiedervorlage>>any());
+			}
+
+			@Test
+			void shouldCallFindByVorgangId() {
+				callUpdateNextFrist();
+
+				verify(service).findByVorgangId(VorgangHeaderTestFactory.ID);
+			}
+
+			@Test
+			void shouldCallRemoteService() {
+				doReturn(Optional.of(WiedervorlageTestFactory.FRIST)).when(service).calculateNextFrist(any());
+
+				callUpdateNextFrist();
+
+				verify(remoteService).updateNextFrist(VorgangHeaderTestFactory.ID, Optional.of(WiedervorlageTestFactory.FRIST));
+			}
+
+			private void callUpdateNextFrist() {
+				service.updateNextFrist(VorgangHeaderTestFactory.ID);
+			}
+		}
+
+		@Nested
+		class Calculation {
+
+			@Test
+			void shouldReturnNullOnAllDone() {
+				var wiedervorlage = WiedervorlageTestFactory.createBuilder().frist(LocalDate.now().plus(1, ChronoUnit.DAYS)).done(true).build();
+
+				var nextFrist = calculateNextFrist(Stream.of(wiedervorlage));
+
+				assertThat(nextFrist).isEmpty();
+			}
+
+			@Test
+			void shouldReturnEarliestFrist() {
+				var fristPast2Days = WiedervorlageTestFactory.createBuilder().frist(LocalDate.now().minus(2, ChronoUnit.DAYS)).done(false)
+						.build();
+				var fristPast1Day = WiedervorlageTestFactory.createBuilder().frist(LocalDate.now().minus(1, ChronoUnit.DAYS)).done(false).build();
+				var fristFuture1Day = WiedervorlageTestFactory.createBuilder().frist(LocalDate.now().plus(1, ChronoUnit.DAYS)).done(false)
+						.build();
+				var fristFuture2Days = WiedervorlageTestFactory.createBuilder().frist(LocalDate.now().plus(2, ChronoUnit.DAYS)).done(false)
+						.build();
+
+				var nextFrist = calculateNextFrist(Stream.of(fristPast2Days, fristPast1Day, fristFuture1Day, fristFuture2Days));
+
+				assertThat(nextFrist).contains(LocalDate.now().minus(2, ChronoUnit.DAYS));
+			}
+
+			@Test
+			void shouldReturnFristIgnoringDone() {
+				var fristPast1DayNotDone = WiedervorlageTestFactory.createBuilder().frist(LocalDate.now().plus(1, ChronoUnit.DAYS)).done(false)
+						.build();
+				var fristPast1DayDone = WiedervorlageTestFactory.createBuilder().frist(LocalDate.now().minus(1, ChronoUnit.DAYS)).done(true)
+						.build();
+
+				var nextFrist = calculateNextFrist(Stream.of(fristPast1DayNotDone, fristPast1DayDone));
+
+				assertThat(nextFrist).contains(LocalDate.now().plus(1, ChronoUnit.DAYS));
+			}
+
+			private Optional<LocalDate> calculateNextFrist(Stream<Wiedervorlage> wiedervorlagen) {
+				return service.calculateNextFrist(wiedervorlagen);
+			}
+		}
+	}
+}
\ No newline at end of file
diff --git a/goofy-server/src/test/java/de/itvsh/goofy/wiedervorlage/WiedervorlageTestFactory.java b/goofy-server/src/test/java/de/itvsh/goofy/wiedervorlage/WiedervorlageTestFactory.java
new file mode 100644
index 0000000000000000000000000000000000000000..e10fc170578d1f79c8bcf1e20af2f4eb36cf4944
--- /dev/null
+++ b/goofy-server/src/test/java/de/itvsh/goofy/wiedervorlage/WiedervorlageTestFactory.java
@@ -0,0 +1,81 @@
+/*
+ * 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.itvsh.goofy.wiedervorlage;
+
+import java.time.LocalDate;
+import java.time.ZonedDateTime;
+import java.util.Map;
+import java.util.UUID;
+
+import com.thedeanda.lorem.LoremIpsum;
+
+import de.itvsh.goofy.common.binaryfile.BinaryFileTestFactory;
+import de.itvsh.goofy.vorgang.VorgangHeaderTestFactory;
+
+public class WiedervorlageTestFactory {
+
+	public static final String ID = UUID.randomUUID().toString();
+	public static final long VERSION = 73;
+	public static final boolean DONE = false;
+	public static final String CREATED_BY = UUID.randomUUID().toString();
+	public static final String CREATED_BY_NAME = LoremIpsum.getInstance().getName();
+
+	public static final String BETREFF = "Lorem Ipsum";
+	public static final String BESCHREIBUNG = "Lorem Ipsum dolores est";
+	public static final String FRIST_STR = "2121-01-04";
+	public static final LocalDate FRIST = LocalDate.parse(FRIST_STR);
+	public static final String CREATED_AT_STR = "2021-01-10T10:30:00Z";
+	public static final ZonedDateTime CREATED_AT = ZonedDateTime.parse(CREATED_AT_STR);
+
+	public static Wiedervorlage create() {
+		return createBuilder().build();
+	}
+
+	public static Wiedervorlage.WiedervorlageBuilder createBuilder() {
+		return Wiedervorlage.builder()
+				.id(ID)
+				.version(VERSION)
+				.done(DONE)
+				.betreff(BETREFF)
+				.beschreibung(BESCHREIBUNG)
+				.createdBy(CREATED_BY)
+				.createdAt(CREATED_AT)
+				.frist(FRIST)
+				.vorgangId(VorgangHeaderTestFactory.ID)
+				.attachment(BinaryFileTestFactory.FILE_ID);
+	}
+
+	public static Map<String, Object> createAsMap() {
+		return Map.of(
+				WiedervorlageMapper.ID, ID,
+				WiedervorlageMapper.DONE, String.valueOf(DONE),
+				WiedervorlageMapper.CREATED_BY, CREATED_BY,
+				WiedervorlageMapper.CREATED_BY_NAME, CREATED_BY_NAME,
+				WiedervorlageMapper.BETREFF, BETREFF,
+				WiedervorlageMapper.BESCHREIBUNG, BESCHREIBUNG,
+				WiedervorlageMapper.FRIST, FRIST_STR,
+				WiedervorlageMapper.CREATED_AT, CREATED_AT_STR,
+				WiedervorlageMapper.ATTACHMENTS, BinaryFileTestFactory.ID);
+	}
+}
\ No newline at end of file
diff --git a/goofy-server/src/test/resources/META-INF/services/org.junit.jupiter.api.extension.Extension b/goofy-server/src/test/resources/META-INF/services/org.junit.jupiter.api.extension.Extension
new file mode 100644
index 0000000000000000000000000000000000000000..79b126e6cdb86bec1f4f08c205de8961bde1934a
--- /dev/null
+++ b/goofy-server/src/test/resources/META-INF/services/org.junit.jupiter.api.extension.Extension
@@ -0,0 +1 @@
+org.mockito.junit.jupiter.MockitoExtension
\ No newline at end of file
diff --git a/goofy-server/src/test/resources/application-itcase.yml b/goofy-server/src/test/resources/application-itcase.yml
new file mode 100644
index 0000000000000000000000000000000000000000..9d197cc5d6fe6d4eed19ff1b6619c47ac3437ad2
--- /dev/null
+++ b/goofy-server/src/test/resources/application-itcase.yml
@@ -0,0 +1,13 @@
+logging:
+  level:
+    ROOT: ERROR
+    
+kop:
+  user-manager:
+    url: https://localhost
+    internalurl: http://localhost:8080
+    search-template: /api/userProfiles/?searchBy={searchBy}
+    profile-template: /api/userProfiles/%s
+  upload:
+    maxFileSize:
+      postfachNachrichtAttachment: 3MB
diff --git a/goofy-server/src/test/resources/application.yml b/goofy-server/src/test/resources/application.yml
new file mode 100644
index 0000000000000000000000000000000000000000..21d2eb21d9d2215ef3ee7cd635fde3ccf446e47e
--- /dev/null
+++ b/goofy-server/src/test/resources/application.yml
@@ -0,0 +1,22 @@
+spring:
+  profiles:
+    active:
+    - itcase
+    - local
+  mvc:
+    pathmatch:
+      matching-strategy: ant-path-matcher
+      
+kop:
+  auth:
+    token:
+      secret: quatsch
+      validity: 60000
+  user-manager:
+    url: https://localhost
+    internalurl: http://localhost:8080
+    search-template: /api/userProfiles/?searchBy={searchBy}
+    profile-template: /api/userProfiles/%s
+      
+goofy:
+  production: false
\ No newline at end of file
diff --git a/goofy-server/src/test/resources/jsonTemplates/command/commandAssignedToBody b/goofy-server/src/test/resources/jsonTemplates/command/commandAssignedToBody
new file mode 100644
index 0000000000000000000000000000000000000000..4858e4f247bfbbc6de0a37a048982fb0551893c0
--- /dev/null
+++ b/goofy-server/src/test/resources/jsonTemplates/command/commandAssignedToBody
@@ -0,0 +1,3 @@
+ {
+ 	"assignedTo": %s
+ }
\ No newline at end of file
diff --git a/goofy-server/src/test/resources/jsonTemplates/command/createCommandWithBody.json.tmpl b/goofy-server/src/test/resources/jsonTemplates/command/createCommandWithBody.json.tmpl
new file mode 100644
index 0000000000000000000000000000000000000000..41b89955e8ec974e571d5ff8a6586ed49ca4c8c3
--- /dev/null
+++ b/goofy-server/src/test/resources/jsonTemplates/command/createCommandWithBody.json.tmpl
@@ -0,0 +1,4 @@
+{
+	"order": "%s",
+	"body": %s
+}
\ No newline at end of file
diff --git a/goofy-server/src/test/resources/jsonTemplates/command/createCommandWithKommentar.json.tmpl b/goofy-server/src/test/resources/jsonTemplates/command/createCommandWithKommentar.json.tmpl
new file mode 100644
index 0000000000000000000000000000000000000000..96999290ccd3538e3f396d603fbbdaa665f22f39
--- /dev/null
+++ b/goofy-server/src/test/resources/jsonTemplates/command/createCommandWithKommentar.json.tmpl
@@ -0,0 +1,6 @@
+{
+	"order": "%s",
+	"kommentar": {
+		"text": %s
+	}
+}
\ No newline at end of file
diff --git a/goofy-server/src/test/resources/jsonTemplates/command/createCommandWithPostfachMail.json.tmpl b/goofy-server/src/test/resources/jsonTemplates/command/createCommandWithPostfachMail.json.tmpl
new file mode 100644
index 0000000000000000000000000000000000000000..1481e0b8af4c3810e80c70855a01bab4351b59b8
--- /dev/null
+++ b/goofy-server/src/test/resources/jsonTemplates/command/createCommandWithPostfachMail.json.tmpl
@@ -0,0 +1,9 @@
+{
+	"order": "%s",
+	"body": {
+		"replyOption": "%s",
+		"subject": %s,
+		"mailBody": %s,
+		"attachments": ["%s"]
+	}
+}
\ No newline at end of file
diff --git a/goofy-server/src/test/resources/jsonTemplates/command/createCommandWithRedirectRequest.json.tmpl b/goofy-server/src/test/resources/jsonTemplates/command/createCommandWithRedirectRequest.json.tmpl
new file mode 100644
index 0000000000000000000000000000000000000000..0e1be8961d1f1bdc4010e188d7e4ebce2331d637
--- /dev/null
+++ b/goofy-server/src/test/resources/jsonTemplates/command/createCommandWithRedirectRequest.json.tmpl
@@ -0,0 +1,8 @@
+{
+	"order": "%s",
+	"redirectRequest": {
+		"email": %s,
+		"password": %s
+	},
+	"body": null
+}
\ No newline at end of file
diff --git a/goofy-server/src/test/resources/jsonTemplates/command/createCommandWithWiedervorlage.json.tmpl b/goofy-server/src/test/resources/jsonTemplates/command/createCommandWithWiedervorlage.json.tmpl
new file mode 100644
index 0000000000000000000000000000000000000000..fa1665155401142608bf6e6f08b1502a358ae8ca
--- /dev/null
+++ b/goofy-server/src/test/resources/jsonTemplates/command/createCommandWithWiedervorlage.json.tmpl
@@ -0,0 +1,9 @@
+{
+	"order": "%s",
+	"wiedervorlage": {
+		"betreff": %s,
+		"beschreibung": "%s",
+		"frist": "%s",
+		"attachments": ["%s"]
+	}
+}
\ No newline at end of file
diff --git a/goofy-server/src/test/resources/jsonTemplates/command/createVorgangCommand.json.tmpl b/goofy-server/src/test/resources/jsonTemplates/command/createVorgangCommand.json.tmpl
new file mode 100644
index 0000000000000000000000000000000000000000..deddf3fb6e23dd58606ad0c0bb0a7501449eb70b
--- /dev/null
+++ b/goofy-server/src/test/resources/jsonTemplates/command/createVorgangCommand.json.tmpl
@@ -0,0 +1,5 @@
+{
+	"order": "%s",
+	"body": null
+	
+}
\ No newline at end of file
diff --git a/goofy-server/src/test/resources/jsonTemplates/command/createWiedervorlageOrderCommand.json.tmpl b/goofy-server/src/test/resources/jsonTemplates/command/createWiedervorlageOrderCommand.json.tmpl
new file mode 100644
index 0000000000000000000000000000000000000000..64d27c8846dd5cd0654e7af502fded4231eb7b4e
--- /dev/null
+++ b/goofy-server/src/test/resources/jsonTemplates/command/createWiedervorlageOrderCommand.json.tmpl
@@ -0,0 +1,3 @@
+{
+	"order": "%s"
+}
\ No newline at end of file
diff --git a/goofy-server/src/test/resources/jsonTemplates/command/patchStatus.json b/goofy-server/src/test/resources/jsonTemplates/command/patchStatus.json
new file mode 100644
index 0000000000000000000000000000000000000000..20a5c9066e1becb0e595789f03b82da35a833aea
--- /dev/null
+++ b/goofy-server/src/test/resources/jsonTemplates/command/patchStatus.json
@@ -0,0 +1,3 @@
+{
+	"status": "%s"
+}
\ No newline at end of file
diff --git a/goofy-server/src/test/resources/jsonTemplates/downloadTokenRequest.json.tmpl b/goofy-server/src/test/resources/jsonTemplates/downloadTokenRequest.json.tmpl
new file mode 100644
index 0000000000000000000000000000000000000000..b7889317e3109cb467a78aa6c8a43f56b2bf10cd
--- /dev/null
+++ b/goofy-server/src/test/resources/jsonTemplates/downloadTokenRequest.json.tmpl
@@ -0,0 +1,3 @@
+{
+	"fileId":"%s"
+}
\ No newline at end of file
diff --git a/goofy-server/src/test/resources/jsonTemplates/vorgangWithEingang.json.tmpl b/goofy-server/src/test/resources/jsonTemplates/vorgangWithEingang.json.tmpl
new file mode 100644
index 0000000000000000000000000000000000000000..f549216198f886c64ef591c958b19fa7031946ba
--- /dev/null
+++ b/goofy-server/src/test/resources/jsonTemplates/vorgangWithEingang.json.tmpl
@@ -0,0 +1,9 @@
+{	
+	"name": "testName",
+	"status": "NEU",
+	"createdAt": "2012-04-23T18:25:43.511Z",
+	"aktenzeichen": "sadfjkhw7r3",
+	"nummer": "1234567788",
+	"hasNewPostfachNachricht": false,
+	"eingang": {}
+}
\ No newline at end of file
diff --git a/goofy-server/src/test/resources/junit-platform.properties b/goofy-server/src/test/resources/junit-platform.properties
new file mode 100644
index 0000000000000000000000000000000000000000..1cebb76d5a58ac034b2627d12411d82d1e85821e
--- /dev/null
+++ b/goofy-server/src/test/resources/junit-platform.properties
@@ -0,0 +1 @@
+junit.jupiter.extensions.autodetection.enabled = true
\ No newline at end of file
diff --git a/goofy-server/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker b/goofy-server/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker
new file mode 100644
index 0000000000000000000000000000000000000000..ca6ee9cea8ec189a088d50559325d4e84ff8ad09
--- /dev/null
+++ b/goofy-server/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker
@@ -0,0 +1 @@
+mock-maker-inline
\ No newline at end of file
diff --git a/lombok.config b/lombok.config
new file mode 100644
index 0000000000000000000000000000000000000000..4cbb865834f6128b8ddc16fc9b1966af99e52653
--- /dev/null
+++ b/lombok.config
@@ -0,0 +1,6 @@
+lombok.log.fieldName=LOG
+lombok.log.slf4j.flagUsage = ERROR
+lombok.log.log4j.flagUsage = ERROR
+lombok.data.flagUsage = ERROR
+lombok.nonNull.exceptionType = IllegalArgumentException
+lombok.addLombokGeneratedAnnotation = true
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000000000000000000000000000000000000..9e15abca833cf95193520f228b0a6c5bf0f681fe
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,131 @@
+<!--
+
+    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.
+
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
+	<modelVersion>4.0.0</modelVersion>
+
+	<groupId>de.itvsh.ozg</groupId>
+	<artifactId>goofy</artifactId>
+	<version>1.1.1</version>
+	<name>Goofy Parent</name>
+	<packaging>pom</packaging>
+
+	<parent>
+		<groupId>de.itvsh.kop.common</groupId>
+		<artifactId>kop-common-parent</artifactId>
+		<version>1.3.0</version>
+	</parent>
+
+	<modules>
+		<module>goofy-client</module>
+		<module>goofy-server</module>
+	</modules>
+
+	<properties>
+		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+		<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
+		
+		<kop.license.version>1.3.0-SNAPSHOT</kop.license.version>
+		<pluto.version>1.1.0</pluto.version>
+		<jsoup.version>1.15.1</jsoup.version>
+		<kop-common-pdf.version>1.3.0</kop-common-pdf.version>
+	</properties>
+	
+	<build>
+		<pluginManagement>
+		    <plugins>
+				<plugin>
+					<groupId>com.mycila</groupId>
+					<artifactId>license-maven-plugin</artifactId>
+					<version>4.1</version>
+					<configuration>
+						<mapping>
+							<ts>SLASHSTAR_STYLE</ts>
+						</mapping>
+						<licenseSets>
+							<licenseSet>
+								<header>license/eupl_v1_2_de/header.txt</header>
+								<excludes>
+									<exclude>**/README</exclude>
+									<exclude>src/test/resources/**</exclude>
+									<exclude>src/main/resources/**</exclude>
+									<exclude>**/goofy-e2e/reports/videos/**</exclude>
+									<exclude>**/.angular/cache/**</exclude>
+								</excludes>
+							</licenseSet>
+						</licenseSets>
+					</configuration>
+					<dependencies>
+						<dependency>
+							<groupId>de.itvsh.kop.common</groupId>
+							<artifactId>kop-common-license</artifactId>
+							<version>${kop.license.version}</version>
+						</dependency>
+					</dependencies>
+				</plugin>
+			</plugins>
+		</pluginManagement>
+	</build>
+
+	<dependencyManagement>
+		<dependencies>
+			<dependency>
+				<groupId>de.itvsh.ozg.pluto</groupId>
+				<artifactId>pluto-interface</artifactId>
+				<version>${pluto.version}</version>
+			</dependency>
+			<dependency>
+				<groupId>de.itvsh.ozg.pluto</groupId>
+				<artifactId>pluto-utils</artifactId>
+				<version>${pluto.version}</version>
+			</dependency>
+			<dependency>
+				<groupId>de.itvsh.kop.common</groupId>
+				<artifactId>kop-common-pdf</artifactId>
+				<version>${kop-common-pdf.version}</version>
+			</dependency>
+			<dependency>
+				<groupId>org.jsoup</groupId>
+				<artifactId>jsoup</artifactId>
+				<version>${jsoup.version}</version>
+			</dependency>
+		</dependencies>
+	</dependencyManagement>
+
+	<distributionManagement>
+		<repository>
+			<id>ozg-nexus</id>
+			<name>ozg-releases</name>
+			<url>https://nexus.ozg-sh.de/repository/ozg-releases/</url>
+		</repository>
+		<snapshotRepository>
+			<id>ozg-snapshots-nexus</id>
+			<name>ozg-snapshots</name>
+			<url>https://nexus.ozg-sh.de/repository/ozg-snapshots/</url>
+		</snapshotRepository>
+	</distributionManagement>
+</project>