diff --git a/Jenkinsfile b/Jenkinsfile
index 06c2b68cf5db6f76963578a7c2a01e69d5064c52..81dffd261fd106e1d2409174199cb7c31aa7ab2a 100644
--- a/Jenkinsfile
+++ b/Jenkinsfile
@@ -210,13 +210,18 @@ pipeline {
             }
             steps {
                 script {
-                    FAILED_STAGE = env.STAGE_NAME
-
-                    checkoutProvisioningRepo()
-
-                    setNewGoofyProvisioningVersion('dev')
-
-                    pushNewProvisioningVersion('dev')
+               	 	if(currentBuild.changeSets.size() > 0) {
+						FAILED_STAGE = env.STAGE_NAME
+	
+	                    checkoutProvisioningRepo()
+	
+	                    setNewGoofyProvisioningVersion('dev')
+	
+	                    pushNewProvisioningVersion('dev')
+					}
+					else {
+					     sh 'echo "no code changes found"'
+					}
                 }
             }
         }
@@ -234,7 +239,9 @@ pipeline {
 
                             def testResult = runTests(stageName, bezeichner, 'einheitlicher-ansprechpartner')
 
-                            deleteKopStack(bezeichner, stageName)
+							if (env.BRANCH_NAME != 'master') {
+                            	deleteKopStack(bezeichner, stageName)
+                            }
 
                             if(!testResult) {
                                 E2E_FAILED += "${stageName}, "
@@ -260,7 +267,9 @@ pipeline {
 
                             def testResult = runTests(stageName, bezeichner, 'main-tests')
 
-                            deleteKopStack(bezeichner, stageName)
+                            if (env.BRANCH_NAME != 'master') {
+                            	deleteKopStack(bezeichner, stageName)
+                            }
 
                             if(!testResult) {
                                 E2E_FAILED += "${stageName}, "
diff --git a/goofy-client/apps/goofy-e2e/README.md b/goofy-client/apps/goofy-e2e/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..91f6661fd994ba8a64809bc50a80a876fd8dcc42
--- /dev/null
+++ b/goofy-client/apps/goofy-e2e/README.md
@@ -0,0 +1,13 @@
+# E2E
+
+## Gegen ein bestehenden Namespace testen
+
+Beispiel Namespace: sh-mastere2emain-dev
+
+1. Umleiten der Datenbank: `kubectl port-forward pluto-database-0 27017:27017 -n sh-mastere2emain-dev`
+
+2. Umleiten des Elastic Search: `kubectl port-forward ozg-search-cluster-es-ozg-search-0 9200:9200 -n elastic-system`
+
+3. Host Eintrag ergänzen: In `/etc/hosts` folgenden Eintrag ergänzen: `127.0.0.1 pluto-database-0.pluto-database-svc.sh-mastere2emain-dev.svc.cluster.local`
+
+4. Cypress mit entsprechender config starten: `npm run cypress:open -- --config-file cypress-master-main.json`
diff --git a/goofy-client/apps/goofy-e2e/cypress-master-ea.json b/goofy-client/apps/goofy-e2e/cypress-master-ea.json
new file mode 100644
index 0000000000000000000000000000000000000000..336cd383bcbf1faa53a03a09f36e75a4fec1447a
--- /dev/null
+++ b/goofy-client/apps/goofy-e2e/cypress-master-ea.json
@@ -0,0 +1,40 @@
+{
+	"baseUrl": "https://mastere2eea.dev.ozg-sh.de/",
+	"env": {
+		"dbUrl": "mongodb://pluto-database-user:XnHhfznNWg65NNd@localhost/admin?ssl=false",
+		"database": "pluto-database",
+		"keycloakRealm": "sh-mastere2eea-dev",
+		"keycloakUrl": "https://sso.dev.ozg-sh.de/",
+		"keycloakClient": "sh-mastere2eea-dev-goofy",
+		"sabineUuid": "c2e95389-a86d-49b7-993d-12105b1a5408",
+		"search": {
+			"url": "https://localhost:9200",
+			"index": "sh-mastere2eea-dev",
+			"user": "sh-mastere2eea-dev",
+			"password": "VtMqdo0ctphHkDH"
+		},
+		"userManager": {
+			"dbUrl":"mongodb://user-manager-database-user:Er5CuXV6eBmYPy7@localhost/admin?ssl=false",
+	        "database":"user-manager-database"
+		}
+	},
+	"fileServerFolder": ".",
+	"fixturesFolder": "./src/fixtures",
+	"integrationFolder": "./src/integration",
+	"modifyObstructiveCode": false,
+	"pluginsFile": "./src/plugins/index",
+	"supportFile": "./src/support/index.ts",
+	"video": true,
+	"videosFolder": "./reports/videos",
+	"screenshotsFolder": "./reports/screenshots",
+	"chromeWebSecurity": false,
+	"reporter": "../../node_modules/cypress-mochawesome-reporter",
+	"defaultCommandTimeout": 10000,
+	"reporterOptions": {
+		"html": false,
+		"json": true,
+		"reportDir": "./reports/mochawesome-report",
+		"reportFilename": "report",
+		"overwrite": true
+	}
+}
\ No newline at end of file
diff --git a/goofy-client/apps/goofy-e2e/cypress-master-main.json b/goofy-client/apps/goofy-e2e/cypress-master-main.json
new file mode 100644
index 0000000000000000000000000000000000000000..6575029056e9d5d73fc2cfc395d6f64d60b6d186
--- /dev/null
+++ b/goofy-client/apps/goofy-e2e/cypress-master-main.json
@@ -0,0 +1,40 @@
+{
+	"baseUrl": "https://mastere2emain.dev.ozg-sh.de/",
+	"env": {
+		"dbUrl": "mongodb://pluto-database-user:XnHhfznNWg65NNd@localhost/admin?ssl=false",
+		"database": "pluto-database",
+		"keycloakRealm": "sh-mastere2emain-dev",
+		"keycloakUrl": "https://sso.dev.ozg-sh.de/",
+		"keycloakClient": "sh-mastere2emain-dev-goofy",
+		"sabineUuid": "5f2efd21-250a-4c0a-ab7d-f324764a5d69",
+		"search": {
+			"url": "https://localhost:9200",
+			"index": "sh-mastere2emain-dev",
+			"user": "sh-mastere2emain-dev",
+			"password": "bea5hCA2RTsJupP"
+		},
+		"userManager": {
+			"dbUrl":"mongodb://user-manager-database-user:7eDJTX397PytrOk@localhost/admin?ssl=false",
+	        "database":"user-manager-database"
+		}
+	},
+	"fileServerFolder": ".",
+	"fixturesFolder": "./src/fixtures",
+	"integrationFolder": "./src/integration",
+	"modifyObstructiveCode": false,
+	"pluginsFile": "./src/plugins/index",
+	"supportFile": "./src/support/index.ts",
+	"video": true,
+	"videosFolder": "./reports/videos",
+	"screenshotsFolder": "./reports/screenshots",
+	"chromeWebSecurity": false,
+	"reporter": "../../node_modules/cypress-mochawesome-reporter",
+	"defaultCommandTimeout": 10000,
+	"reporterOptions": {
+		"html": false,
+		"json": true,
+		"reportDir": "./reports/mochawesome-report",
+		"reportFilename": "report",
+		"overwrite": true
+	}
+}
\ No newline at end of file
diff --git a/goofy-client/apps/goofy-e2e/src/components/user-profile/current-user-profile.component.e2e.ts b/goofy-client/apps/goofy-e2e/src/components/user-profile/current-user-profile.component.e2e.ts
new file mode 100644
index 0000000000000000000000000000000000000000..f8576004728ef910895cefaa6807368eeb50756f
--- /dev/null
+++ b/goofy-client/apps/goofy-e2e/src/components/user-profile/current-user-profile.component.e2e.ts
@@ -0,0 +1,30 @@
+import { UserProfileE2EComponent } from './user-profile.component.e2e';
+
+export class CurrentUserProfileE2EComponent {
+
+	private readonly locatorUserIconButton: string = 'user-icon-button';
+	private readonly locatorLogoutButton: string = 'logout-button';
+
+	private readonly locatorRoot: string = 'current-user';
+
+	public getRoot() {
+		return cy.getTestElement(this.locatorRoot);
+	}
+
+	public getUserProfile(): UserProfileE2EComponent {
+		return new UserProfileE2EComponent(this.locatorRoot);
+	}
+
+	public logout(): void {
+		this.getUserIconButton().click();
+		this.getLogoutButton().click();
+	}
+
+	private getUserIconButton() {
+		return cy.getTestElement(this.locatorUserIconButton);
+	}
+
+	private getLogoutButton() {
+		return cy.getTestElement(this.locatorLogoutButton);
+	}
+}
\ No newline at end of file
diff --git a/goofy-client/apps/goofy-e2e/src/integration/einheitlicher-ansprechpartner/navigation/navigation.e2e-spec.ts b/goofy-client/apps/goofy-e2e/src/integration/einheitlicher-ansprechpartner/navigation/navigation.e2e-spec.ts
index c0fcd0535bd473b973157f8a0d997b311fcc572c..a55d8226aa0c071050d0393d7676bdf517c2454b 100644
--- a/goofy-client/apps/goofy-e2e/src/integration/einheitlicher-ansprechpartner/navigation/navigation.e2e-spec.ts
+++ b/goofy-client/apps/goofy-e2e/src/integration/einheitlicher-ansprechpartner/navigation/navigation.e2e-spec.ts
@@ -13,15 +13,20 @@ describe('Navigation', () => {
 
 	before(() => {
 		loginAsEmil();
-
-		waitForSpinnerToDisappear();
-		exist(vorgangList.getRoot());
 	})
 
 	after(() => {
 		dropCollections();
 	})
 
+	describe('after login', () => {
+
+		it('should show vorgangList', { defaultCommandTimeout: 30000 }, () => {
+			waitForSpinnerToDisappear();
+			exist(vorgangList.getRoot());
+		})
+	})
+
 	describe('navigation item myVorgaenge', () => {
 
 		it('should not exists', () => {
diff --git a/goofy-client/apps/goofy-e2e/src/integration/main-tests/app/buildinfo.e2e-spec.ts b/goofy-client/apps/goofy-e2e/src/integration/main-tests/app/buildinfo.e2e-spec.ts
index 2173435c27c08c68e83d7ef4d58916348b7bc964..a0cfd2fa7e995a0b6f247501830e46c8483b3d9a 100644
--- a/goofy-client/apps/goofy-e2e/src/integration/main-tests/app/buildinfo.e2e-spec.ts
+++ b/goofy-client/apps/goofy-e2e/src/integration/main-tests/app/buildinfo.e2e-spec.ts
@@ -12,15 +12,20 @@ describe('Buildinfo', () => {
 
 	before(() => {
 		loginAsSabine();
-
-		waitForSpinnerToDisappear();
-		exist(vorgangList.getRoot());
 	})
 
 	after(() => {
 		dropCollections();
 	})
 
+	describe('after login', () => {
+
+		it('should show vorgangList', { defaultCommandTimeout: 30000 }, () => {
+			waitForSpinnerToDisappear();
+			exist(vorgangList.getRoot());
+		})
+	})
+
 	describe('buildinfo', () => {
 
 		it('should show version', () => {
diff --git a/goofy-client/apps/goofy-e2e/src/integration/main-tests/app/login-logout.e2e-spec.ts b/goofy-client/apps/goofy-e2e/src/integration/main-tests/app/login-logout.e2e-spec.ts
index f3cca66e65178ddd12d7504fa2ffb8310649a7e3..0223e6b2cf65832b7027f9abf4d1b21c9fdff722 100644
--- a/goofy-client/apps/goofy-e2e/src/integration/main-tests/app/login-logout.e2e-spec.ts
+++ b/goofy-client/apps/goofy-e2e/src/integration/main-tests/app/login-logout.e2e-spec.ts
@@ -1,13 +1,16 @@
+import { App } from 'apps/goofy-e2e/src/model/app';
+import { getApp } from 'apps/goofy-e2e/src/support/app-util';
 import { UserE2E } from '../../../model/user';
 import { HeaderE2EComponent } from '../../../page-objects/header.po';
 import { MainPage } from '../../../page-objects/main.po';
 import { exist, haveText } from '../../../support/cypress.util';
 import { getUserSabine } from '../../../support/user-util';
 
-const mainFixture = require('../../../fixtures/main.json');
-const user: UserE2E = getUserSabine();
-
 describe('Login and Logout', () => {
+
+	const app: App = getApp();
+	const user: UserE2E = getUserSabine();
+
 	const mainPage: MainPage = new MainPage();
 	const header: HeaderE2EComponent = mainPage.getHeader();
 
@@ -24,11 +27,11 @@ describe('Login and Logout', () => {
 	})
 
 	it('should display Goofy', () => {
-		haveText(header.getTitle(), mainFixture.title);
+		haveText(header.getTitle(), app.title);
 	})
 
-	it.skip('FIXME(OZG-2950 UserManager) should logout', () => {
-		header.logout();
+	it('should logout', () => {
+		header.getCurrentUserProfile().logout();
 
 		exist(cy.get('#kc-login'));
 	})
diff --git a/goofy-client/apps/goofy-e2e/src/integration/main-tests/navigation/navigation.e2e-spec.ts b/goofy-client/apps/goofy-e2e/src/integration/main-tests/navigation/navigation.e2e-spec.ts
index bee58f155197c4b2e473ca055d281a15b85cc168..4862efcb5eedb309e308af3645f6a5972df5c791 100644
--- a/goofy-client/apps/goofy-e2e/src/integration/main-tests/navigation/navigation.e2e-spec.ts
+++ b/goofy-client/apps/goofy-e2e/src/integration/main-tests/navigation/navigation.e2e-spec.ts
@@ -5,7 +5,7 @@ import { VorgangE2E } from 'apps/goofy-e2e/src/model/vorgang';
 import { MainPage, waitForSpinnerToDisappear } from 'apps/goofy-e2e/src/page-objects/main.po';
 import { dropCollections } from 'apps/goofy-e2e/src/support/cypress-helper';
 import { CypressKeyboardActions, exist, notExist } from 'apps/goofy-e2e/src/support/cypress.util';
-import { getUserManagerUserEmil, getUserManagerUserPeter, getUserManagerUserSabine, initUsermanagerUsers, loginAsSabine } from 'apps/goofy-e2e/src/support/user-util';
+import { getUserManagerUserEmil, getUserManagerUserPeter, getUserManagerUserSabine, getUserSabineInternalId, initUsermanagerUsers, loginAsSabine } from 'apps/goofy-e2e/src/support/user-util';
 import { buildVorgang, createVorgang, initSearchIndex, initVorgaenge, objectIds } from 'apps/goofy-e2e/src/support/vorgang-util';
 
 describe('Navigation', () => {
@@ -18,8 +18,8 @@ describe('Navigation', () => {
 
 	const vorgang: VorgangE2E = createVorgang();
 	const vorgangNotBeFiltered: VorgangE2E = { ...buildVorgang(objectIds[0], 'vorgangNotBeFiltered') };
-	const vorgangAssigned: VorgangE2E = { ...buildVorgang(objectIds[1], 'vorgangAssigned'), assignedTo: usermanagerUserSabine.externalId };
-	const vorgangAssignedNotBeFiltered: VorgangE2E = { ...buildVorgang(objectIds[2], 'vorgangAssignedNotBeFiltered'), assignedTo: usermanagerUserSabine.externalId };
+	const vorgangAssigned: VorgangE2E = { ...buildVorgang(objectIds[1], 'vorgangAssigned'), assignedTo: getUserSabineInternalId() };
+	const vorgangAssignedNotBeFiltered: VorgangE2E = { ...buildVorgang(objectIds[2], 'vorgangAssignedNotBeFiltered'), assignedTo: getUserSabineInternalId() };
 
 	const searchString: string = 'NotBeFiltered';
 
diff --git a/goofy-client/apps/goofy-e2e/src/integration/main-tests/user-profile/user-profile-current-user-icon.e2e-spec.ts b/goofy-client/apps/goofy-e2e/src/integration/main-tests/user-profile/user-profile-current-user-icon.e2e-spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..a0d340d4fd447a77ae64b9ed69327d22376d8075
--- /dev/null
+++ b/goofy-client/apps/goofy-e2e/src/integration/main-tests/user-profile/user-profile-current-user-icon.e2e-spec.ts
@@ -0,0 +1,44 @@
+import { CurrentUserProfileE2EComponent } from "apps/goofy-e2e/src/components/user-profile/current-user-profile.component.e2e";
+import { UserE2E } from "apps/goofy-e2e/src/model/user";
+import { HeaderE2EComponent } from "apps/goofy-e2e/src/page-objects/header.po";
+import { MainPage, waitForSpinnerToDisappear } from "apps/goofy-e2e/src/page-objects/main.po";
+import { dropCollections } from "apps/goofy-e2e/src/support/cypress-helper";
+import { exist, haveText } from "apps/goofy-e2e/src/support/cypress.util";
+import { getUserManagerUserSabine, getUserSabine, initUsermanagerUsers, loginAsSabine } from "apps/goofy-e2e/src/support/user-util";
+
+describe('Current User Profile', () => {
+	const mainPage: MainPage = new MainPage();
+
+	const header: HeaderE2EComponent = mainPage.getHeader();
+	const currentUserProfile: CurrentUserProfileE2EComponent = header.getCurrentUserProfile();
+
+	const userSabine: UserE2E = getUserSabine();
+
+	describe('for sabine', () => {
+
+		before(() => {
+			initUsermanagerUsers([getUserManagerUserSabine()]);
+
+			loginAsSabine();
+
+			waitForSpinnerToDisappear();
+			exist(header.getRoot());
+		})
+
+		after(() => {
+			dropCollections();
+		})
+
+		it('should show current user profile', () => {
+			exist(currentUserProfile.getRoot());
+		})
+
+		it('should show assigned icon', () => {
+			exist(currentUserProfile.getUserProfile().getIconContainer().getAssignedIcon());
+		})
+
+		it('should show initials', () => {
+			haveText(currentUserProfile.getUserProfile().getIconContainer().getAssignedIcon(), userSabine.initials)
+		})
+	})
+})
\ No newline at end of file
diff --git a/goofy-client/apps/goofy-e2e/src/model/app.ts b/goofy-client/apps/goofy-e2e/src/model/app.ts
new file mode 100644
index 0000000000000000000000000000000000000000..5e0f1e90ea345f6b76080b78940354133b09f16e
--- /dev/null
+++ b/goofy-client/apps/goofy-e2e/src/model/app.ts
@@ -0,0 +1,3 @@
+export class App {
+	title: string
+}
\ No newline at end of file
diff --git a/goofy-client/apps/goofy-e2e/src/page-objects/header.po.ts b/goofy-client/apps/goofy-e2e/src/page-objects/header.po.ts
index b2527afde30ccc7d93e5b2680044509d7f24261d..96a927b8f6022e8610dc9bd0c885b1fc4f0ab839 100644
--- a/goofy-client/apps/goofy-e2e/src/page-objects/header.po.ts
+++ b/goofy-client/apps/goofy-e2e/src/page-objects/header.po.ts
@@ -1,31 +1,27 @@
+import { CurrentUserProfileE2EComponent } from "../components/user-profile/current-user-profile.component.e2e";
 import { UserSettingsE2EComponent } from "../components/user-settings/user-settings.component.e2e";
 
 export class HeaderE2EComponent {
 
-	private readonly locatorUserIconButton: string = 'user-icon-button';
-	private readonly locatorLogoutButton: string = 'logout-button';
 	private readonly locatorTitle: string = 'title';
+	private readonly locatorRoot: string = 'header';
 
 	private readonly userSettings: UserSettingsE2EComponent = new UserSettingsE2EComponent();
+	private readonly currentUserProfile: CurrentUserProfileE2EComponent = new CurrentUserProfileE2EComponent();
 
-	public getTitle() {
-		return cy.getTestElement(this.locatorTitle);
-	}
-
-	public logout(): void {
-		this.getUserIconButton().click();
-		this.getLogoutButton().click();
-	}
-
-	private getUserIconButton() {
-		return cy.getTestElement(this.locatorUserIconButton);
+	public getRoot() {
+		return cy.getTestElement(this.locatorRoot);
 	}
 
-	private getLogoutButton() {
-		return cy.getTestElement(this.locatorLogoutButton);
+	public getTitle() {
+		return cy.getTestElement(this.locatorTitle);
 	}
 
 	public getUserSettings(): UserSettingsE2EComponent {
 		return this.userSettings;
 	}
+
+	public getCurrentUserProfile(): CurrentUserProfileE2EComponent {
+		return this.currentUserProfile;
+	}
 }
\ No newline at end of file
diff --git a/goofy-client/apps/goofy-e2e/src/plugins/index.js b/goofy-client/apps/goofy-e2e/src/plugins/index.js
index 232ec66bc71746d07c117e46debd59c555aa59ad..0ca7f2013436776942e95db051a1e5d1576d229b 100644
--- a/goofy-client/apps/goofy-e2e/src/plugins/index.js
+++ b/goofy-client/apps/goofy-e2e/src/plugins/index.js
@@ -56,6 +56,11 @@ module.exports = (on, config) => {
 			console.log('dropCollections: ', collections);
 			dropCollectionsFromDatabase(config, collections);
 			return 0;
+		},
+		dropUserManagerCollections(collections) {
+			console.log('dropUserManagerCollections: ', collections);
+			dropUserManagerCollectionsFromDatabase(config, collections);
+			return 0;
 		}
 	});
 
@@ -215,11 +220,19 @@ function createNumberLong(numberValue){
 }
 
 function insertIntoDatabase(config, collection, data) {
-	MongoClient.connect(buildDatabaseUrl(config), (error, connection) => {
+	insert(getDatabaseUrl(config), getDatabase(config), collection, data);
+}
+
+function insertIntoUserManagerDatabase(config, collection, data){
+	insert(getUserManagerDatabaseUrl(config), getUserManagerDatabase(config), collection, data);
+}
+
+function insert(databaseUrl, databaseName, collection, data){
+	MongoClient.connect(databaseUrl, (error, connection) => {
 		console.log('connect to database...')
 		if (!error) {
 			console.log('success');
-			var db = connection.db(config.env.database);
+			var db = connection.db(databaseName);
 
 			db.collection(collection).drop(() => {
 				db.createCollection(collection, (error) => handleCreateCollection(db, connection, collection, data, error));
@@ -253,12 +266,37 @@ function handleInsertMany(connection, error) {
 }
 
 function dropCollectionsFromDatabase(config, collections) {
-	MongoClient.connect(buildDatabaseUrl(config), (error, connection) => {
+	dropCollections(getDatabaseUrl(config), getDatabase(config), collections);
+}
+
+function getDatabaseUrl(config){
+	return config.env.dbUrl;
+}
+
+function getDatabase(config){
+	return config.env.database
+}
+
+function dropUserManagerCollectionsFromDatabase(config, collections){
+	dropCollections(getUserManagerDatabaseUrl(config), getUserManagerDatabase(config), collections);
+}
+
+function getUserManagerDatabaseUrl(config){
+	return config.env.userManager.dbUrl;
+}
+
+function getUserManagerDatabase(config){
+	return config.env.userManager.database;
+}
+
+function dropCollections(databaseUrl, databaseName, collections){
+	MongoClient.connect(databaseUrl, (error, connection) => {
 		if (!error) {
-			var db = connection.db(config.env.database);
+			var db = connection.db(databaseName);
 			collections.forEach((oneCollection, index) => {
 				console.log('drop collection', oneCollection);
 				db.collection(oneCollection).drop(() => {
+					//CHECKME Ist die Abfrage notwendig?
 					if(index == collections.length){
 						console.log('close connection');
 						connection.close();
@@ -267,28 +305,4 @@ function dropCollectionsFromDatabase(config, collections) {
 			});
 		}
 	});
-}
-
-function buildDatabaseUrl(config) {
-	return config.env.dbUrl;
-}
-
-function insertIntoUserManagerDatabase(config, collection, data){
-	MongoClient.connect(buildUsermanagerDatabaseUrl(config), (error, connection) => {
-		console.log('connect to database...')
-		if (!error) {
-			console.log('success');
-			var db = connection.db(config.env.userManager.database);
-
-			db.collection(collection).drop(() => {
-				db.createCollection(collection, (error) => handleCreateCollection(db, connection, collection, data, error));
-			});
-		} else {
-			console.log('fail', error);
-		}
-	});
-}
-
-function buildUsermanagerDatabaseUrl(config){
-	return config.env.userManager.dbUrl;
 }
\ No newline at end of file
diff --git a/goofy-client/apps/goofy-e2e/src/support/app-util.ts b/goofy-client/apps/goofy-e2e/src/support/app-util.ts
new file mode 100644
index 0000000000000000000000000000000000000000..98758452698ecf8a460ebc892b31a3d6da4dc427
--- /dev/null
+++ b/goofy-client/apps/goofy-e2e/src/support/app-util.ts
@@ -0,0 +1,8 @@
+import { App } from '../model/app';
+
+//TODO main.json in app.json umbenennen
+const appFixture: App = require('../fixtures/main.json');
+
+export function getApp(): App {
+	return appFixture;
+}
\ No newline at end of file
diff --git a/goofy-client/apps/goofy-e2e/src/support/cypress-helper.ts b/goofy-client/apps/goofy-e2e/src/support/cypress-helper.ts
index 91b16ab54160f3518505c814c09cd44ebdf7ea50..20642b9371085595d0e1fdeb3acb8cff1ccc4d48 100644
--- a/goofy-client/apps/goofy-e2e/src/support/cypress-helper.ts
+++ b/goofy-client/apps/goofy-e2e/src/support/cypress-helper.ts
@@ -6,8 +6,8 @@ import { VorgangE2E } from '../model/vorgang';
 import { VorgangAttachedItemE2E } from '../model/vorgang-attached-item';
 
 enum CypressTasks {
-	DROP_COLLECTION = 'dropCollection',
 	DROP_COLLECTIONS = 'dropCollections',
+	DROP_USER_MANAGER_COLLECTIONS = 'dropUserManagerCollections',
 	INIT_COMMAND_DATA = 'initCommandData',
 	INIT_GRID_FS_FILE_DATA = 'initGridFsFileData',
 	INIT_GRID_FS_CHUNK_DATA = 'initGridFsChunkData',
@@ -80,7 +80,8 @@ export function initUsermanagerData(data: UsermanagerUserE2E[]): void {
 }
 
 export function dropCollections() {
-	cy.task(CypressTasks.DROP_COLLECTIONS, [MongoCollections.COMMAND, MongoCollections.VORGANG, MongoCollections.VORGANG_ATTACHED_ITEM, MongoCollections.FS_FILES, MongoCollections.FS_CHUNKS, MongoCollections.USER]);
+	cy.task(CypressTasks.DROP_COLLECTIONS, [MongoCollections.COMMAND, MongoCollections.VORGANG, MongoCollections.VORGANG_ATTACHED_ITEM, MongoCollections.FS_FILES, MongoCollections.FS_CHUNKS]);
+	cy.task(CypressTasks.DROP_USER_MANAGER_COLLECTIONS, [MongoCollections.USER]);
 }
 
 export function scrollToWindowBottom(): void {
diff --git a/goofy-client/apps/goofy-e2e/start-e2e-environment.sh b/goofy-client/apps/goofy-e2e/start-e2e-environment.sh
index cb35b1ea66542ef9e83406a11383b95847581ce6..7007869b39a5897d87749d22ec5439672935fcd0 100755
--- a/goofy-client/apps/goofy-e2e/start-e2e-environment.sh
+++ b/goofy-client/apps/goofy-e2e/start-e2e-environment.sh
@@ -13,7 +13,7 @@ echo
 
 if [ "$(expr substr $(uname -s) 1 5)" == "Linux" ];
 then
-	DOCKER_GATEWAY_HOST=172.17.0.1
+	export DOCKER_GATEWAY_HOST=172.17.0.1
 fi
 docker compose -f ${SCRIPT_DIR}/docker-compose.yml up -d ozg-mongodb ozg-usermanager ozg-elastic
 
diff --git a/goofy-client/libs/navigation/src/lib/header-container/header/header.component.html b/goofy-client/libs/navigation/src/lib/header-container/header/header.component.html
index 44beeb8de47346ca9eec1d8d6b1a7fcaf239c197..d1a44c1627d422a791c7aa862ab0be58cd7e2e97 100644
--- a/goofy-client/libs/navigation/src/lib/header-container/header/header.component.html
+++ b/goofy-client/libs/navigation/src/lib/header-container/header/header.component.html
@@ -1,4 +1,4 @@
-<header>
+<header data-test-id="header">
 	<div class="left">
 		<goofy-client-icon-button-with-spinner icon="menu" toolTip="Hauptmenü umschalten" (clickEmitter)="toggleMenuEvent.emit(!this.navigationCollapse)">
 		</goofy-client-icon-button-with-spinner>
@@ -16,7 +16,7 @@
 	<div class="right">
 
 		<goofy-client-user-settings-container data-test-id="user-settings"></goofy-client-user-settings-container>
-		<goofy-client-user-profile-in-header-container></goofy-client-user-profile-in-header-container>
+		<goofy-client-user-profile-in-header-container data-test-id="current-user"></goofy-client-user-profile-in-header-container>
 
 	</div>
 </header>
diff --git a/goofy-server/pom.xml b/goofy-server/pom.xml
index 448ed43101e28e88ac34279e06b115598f8da8f6..9c68b482b0988f999a7cb1385bee185836eb7e44 100644
--- a/goofy-server/pom.xml
+++ b/goofy-server/pom.xml
@@ -89,6 +89,10 @@
 			<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>
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
index 8e3b0f26ed6515492f4e0a3b51f7421eade65025..dff48916a6ec2ec09c17bf9c30582c2a322d481c 100644
--- 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
@@ -1,18 +1,13 @@
 package de.itvsh.goofy.common.binaryfile;
 
-import java.io.BufferedInputStream;
-import java.io.IOException;
-import java.io.InputStream;
 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.apache.commons.io.IOUtils;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 
@@ -30,7 +25,6 @@ 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 io.grpc.stub.CallStreamObserver;
 import net.devh.boot.grpc.client.inject.GrpcClient;
 
 @Service
@@ -69,23 +63,17 @@ class BinaryFileRemoteService {
 
 	public CompletableFuture<FileId> uploadFile(UploadBinaryFileRequest uploadBinaryFileRequest) {
 		var fileIdFuture = new CompletableFuture<FileId>();
-		var responseObserver = createUploadBinaryFileObserver(fileIdFuture);
+		var responseObserver = createUploadBinaryFileObserver(fileIdFuture, uploadBinaryFileRequest);
 
-		var requestObserver = (CallStreamObserver<GrpcUploadBinaryFileRequest>) asyncServiceStub.uploadBinaryFileAsStream(responseObserver);
-
-		sendBinaryFile(requestObserver, uploadBinaryFileRequest);
+		asyncServiceStub.uploadBinaryFileAsStream(responseObserver);
 
 		return fileIdFuture;
 	}
 
-	BinaryFileUploadStreamObserver createUploadBinaryFileObserver(CompletableFuture<FileId> fileIdFuture) {
-		return new BinaryFileUploadStreamObserver(fileIdFuture);
-	}
-
-	void sendBinaryFile(CallStreamObserver<GrpcUploadBinaryFileRequest> requestObserver, UploadBinaryFileRequest uploadBinaryFileRequest) {
-		requestObserver.onNext(buildMetaDataRequest(uploadBinaryFileRequest));
-		sendAsChunks(requestObserver, uploadBinaryFileRequest.getUploadStream());
-		requestObserver.onCompleted();
+	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) {
@@ -100,44 +88,6 @@ class BinaryFileRemoteService {
 				.build();
 	}
 
-	void sendAsChunks(CallStreamObserver<GrpcUploadBinaryFileRequest> requestObserver, InputStream uploadStream) {
-		try (var bufferUploadStream = getAsBufferedStream(uploadStream)) {
-			while (hasDataToRead(bufferUploadStream)) {
-				sendNextChunk(requestObserver, bufferUploadStream);
-			}
-		} catch (IOException e) {
-			throw new TechnicalException("Error on closing input file stream", e);
-		}
-	}
-
-	BufferedInputStream getAsBufferedStream(InputStream uploadStream) {
-		return IOUtils.buffer(uploadStream);
-	}
-
-	boolean hasDataToRead(BufferedInputStream uploadStream) throws IOException {
-		return uploadStream.available() > 0;
-	}
-
-	void sendNextChunk(CallStreamObserver<GrpcUploadBinaryFileRequest> requestObserver, BufferedInputStream uploadStream) {
-		if (requestObserver.isReady()) {
-			sendChunk(requestObserver, uploadStream);
-		}
-	}
-
-	void sendChunk(CallStreamObserver<GrpcUploadBinaryFileRequest> streamObserver, BufferedInputStream uploadStream) {
-		byte[] content = readFromStream(uploadStream, CHUNK_SIZE);
-
-		streamObserver.onNext(buildChunkRequest(content));
-	}
-
-	private byte[] readFromStream(BufferedInputStream uploadStream, int size) {
-		try {
-			return uploadStream.readNBytes(size);
-		} catch (IOException e) {
-			throw new TechnicalException("Error on sending a single chunk", e);
-		}
-	}
-
 	GrpcUploadBinaryFileRequest buildChunkRequest(byte[] bytes) {
 		return GrpcUploadBinaryFileRequest.newBuilder().setFileContent((ByteString.copyFrom(bytes))).build();
 	}
@@ -160,7 +110,7 @@ class BinaryFileRemoteService {
 	}
 
 	private List<String> mapFileIds(List<FileId> fileIds) {
-		return fileIds.stream().map(fileIdMapper::toString).collect(Collectors.toList());
+		return fileIds.stream().map(fileIdMapper::toString).toList();
 	}
 
 	public void writeFileContent(FileId fileId, OutputStream out) {
@@ -183,10 +133,6 @@ class BinaryFileRemoteService {
 		}
 	}
 
-	void throwTechnicalException(Exception e) {
-		throw new TechnicalException("Error completing download future on grpc", e);
-	}
-
 	BinaryFileDownloadStreamObserver createDownloadBinaryFileObserver(CompletableFuture<Boolean> streamFuture, OutputStream out) {
 		return new BinaryFileDownloadStreamObserver(streamFuture, out);
 	}
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
index cf13566064d7341b7f3bc86a5c5ab013f0044844..889056aba1da2ff7c02aecd31ad98056cf4fd45a 100644
--- 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
@@ -2,19 +2,30 @@ 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.StreamObserver;
+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 StreamObserver<GrpcUploadBinaryFileResponse> {
+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();
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..263948a45e2dc4b131c65d87b672a7f632e9bd4e
--- /dev/null
+++ b/goofy-server/src/main/java/de/itvsh/goofy/common/binaryfile/ChunkedFileSender.java
@@ -0,0 +1,61 @@
+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/postfach/PostfachMailController.java b/goofy-server/src/main/java/de/itvsh/goofy/postfach/PostfachMailController.java
index 16fffe84d057e6bcd731f2a7964a6f1d441b9796..b4a05600b45ab52eeb402efc62a70856490e5d49 100644
--- a/goofy-server/src/main/java/de/itvsh/goofy/postfach/PostfachMailController.java
+++ b/goofy-server/src/main/java/de/itvsh/goofy/postfach/PostfachMailController.java
@@ -2,6 +2,8 @@ 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;
 
@@ -9,6 +11,8 @@ 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;
@@ -17,6 +21,7 @@ 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;
@@ -35,6 +40,9 @@ public class PostfachMailController {
 
 	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
@@ -52,6 +60,28 @@ public class PostfachMailController {
 		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);
 	}
diff --git a/goofy-server/src/main/java/de/itvsh/goofy/postfach/PostfachMailPdfService.java b/goofy-server/src/main/java/de/itvsh/goofy/postfach/PostfachMailPdfService.java
new file mode 100644
index 0000000000000000000000000000000000000000..db7b191919a5658a4b2558d4336b00871cd7fda9
--- /dev/null
+++ b/goofy-server/src/main/java/de/itvsh/goofy/postfach/PostfachMailPdfService.java
@@ -0,0 +1,44 @@
+package de.itvsh.goofy.postfach;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+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.errorhandling.FunctionalException;
+import de.itvsh.goofy.vorgang.VorgangWithEingang;
+import de.itvsh.kop.common.pdf.PdfService;
+
+@Service
+class PostfachMailPdfService {
+
+	static final String PDF_TEMPLATE_PATH = "classpath:postfach_nachrichten_template.xsl";
+
+	@Autowired
+	private PdfService pdfService;
+
+	@Value(PostfachMailPdfService.PDF_TEMPLATE_PATH)
+	private Resource pdfTemplate;
+
+	public OutputStream getAllAsPdf(VorgangWithEingang vorgang, OutputStream out) {
+		return pdfService.createPdf(getTemplate(), out, buildModel(vorgang));
+	}
+
+	InputStream getTemplate() {
+		try {
+			return pdfTemplate.getInputStream();
+		} catch (IOException e) {
+			// TODO Exception definieren
+			throw new FunctionalException(() -> "Pdf Template for postfach nachrichten not found");
+		}
+	}
+
+	PostfachNachrichtenPdfModel buildModel(VorgangWithEingang vorgang) {
+		// TODO Setzen der Werte
+		return new PostfachNachrichtenPdfModel();
+	}
+}
\ 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
index 93dadb6cc66b19d0ef41318fb960a171e1152ba9..eca9c0c669d6d5dd651d82d1297803a926a681d3 100644
--- a/goofy-server/src/main/java/de/itvsh/goofy/postfach/PostfachMailService.java
+++ b/goofy-server/src/main/java/de/itvsh/goofy/postfach/PostfachMailService.java
@@ -1,5 +1,6 @@
 package de.itvsh.goofy.postfach;
 
+import java.io.OutputStream;
 import java.util.Objects;
 import java.util.stream.Stream;
 
@@ -7,6 +8,7 @@ import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 
 import de.itvsh.goofy.common.errorhandling.ResourceNotFoundException;
+import de.itvsh.goofy.vorgang.VorgangWithEingang;
 import lombok.extern.log4j.Log4j2;
 
 @Log4j2
@@ -15,6 +17,9 @@ class PostfachMailService {
 
 	private Boolean isPostfachConfigured = null;
 
+	@Autowired
+	private PostfachMailPdfService pdfService;
+
 	@Autowired
 	private PostfachMailRemoteService remoteService;
 
@@ -45,4 +50,8 @@ class PostfachMailService {
 		}
 		return isPostfachConfigured;
 	}
+
+	public OutputStream getAllAsPdf(VorgangWithEingang vorgang, OutputStream out) {
+		return pdfService.getAllAsPdf(vorgang, out);
+	}
 }
\ No newline at end of file
diff --git a/goofy-server/src/main/java/de/itvsh/goofy/postfach/PostfachNachrichtenPdfModel.java b/goofy-server/src/main/java/de/itvsh/goofy/postfach/PostfachNachrichtenPdfModel.java
new file mode 100644
index 0000000000000000000000000000000000000000..2dc015b3a66fbeb2d2771db1e1e0b9ce808c018f
--- /dev/null
+++ b/goofy-server/src/main/java/de/itvsh/goofy/postfach/PostfachNachrichtenPdfModel.java
@@ -0,0 +1,17 @@
+package de.itvsh.goofy.postfach;
+
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlRootElement;
+
+import lombok.Getter;
+
+@Getter
+@XmlRootElement
+class PostfachNachrichtenPdfModel {
+
+	@XmlElement
+	private String vorgangsTyp = "VorgangsTypTestValue";
+
+	@XmlElement
+	private String vorgangsNummer = "VorgangsNummerTestValue";
+}
\ No newline at end of file
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..3daed3ceb700089f666fd47c403464e99267b2e1
--- /dev/null
+++ b/goofy-server/src/main/resources/fop/postfach-nachrichten.xsl
@@ -0,0 +1,43 @@
+<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="100%"/>
+					<fo:table-body>
+						<fo:table-row>
+							<fo:table-cell>
+								<fo:block>Vorgangsname</fo:block>
+							</fo:table-cell>
+							<fo:table-cell>
+								<fo:block>Versammlungsanzeige</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>12345</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">Nachrichten</fo:block>
+				
+				<xsl:apply-templates select="nachricht"/>
+			</fo:block-container>
+		</fo:flow>
+	</xsl:template>
+	
+	<xsl:template name="nachricht">
+		<fo:block>nachricht</fo:block>
+	</xsl:template>
+</xsl:stylesheet>
\ No newline at end of file
diff --git a/goofy-server/src/main/resources/postfach_nachrichten_template.xsl b/goofy-server/src/main/resources/postfach_nachrichten_template.xsl
new file mode 100644
index 0000000000000000000000000000000000000000..c4517419babe8caf02a71fa8e4680d7ab2ab1948
--- /dev/null
+++ b/goofy-server/src/main/resources/postfach_nachrichten_template.xsl
@@ -0,0 +1,10 @@
+<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="/postfachNachrichtenPdfModel">
+		<fo:flow flow-name="xsl-region-body">
+			<fo:block>
+				<xsl:value-of select="postfachNachrichtenPdfModel/vorgangsTyp" />&#160;<xsl:value-of select="postfachNachrichtenPdfModel/vorgangsNummer" />
+			</fo:block>
+		</fo:flow>
+	</xsl:template>
+</xsl:stylesheet>
\ 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
index 866895e17ae1db2cb667a4b871289df5725f9003..27aaa54bc095e9d9c49ecfe31698ae07a02d445a 100644
--- a/goofy-server/src/test/java/de/itvsh/goofy/GoofyServerApplicationTest.java
+++ b/goofy-server/src/test/java/de/itvsh/goofy/GoofyServerApplicationTest.java
@@ -7,6 +7,6 @@ import org.springframework.boot.test.context.SpringBootTest;
 class GoofyServerApplicationTest {
 
 	@Test
-	void contextLoads() {
+	void contextLoads() { // NOSONAR
 	}
 }
\ 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
index 18a1c9dda77ff36ce45a22d7d65ee2b5e3f497bb..a15b2b4da9c50db348361a30ce8c5912db427f87 100644
--- 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
@@ -5,9 +5,6 @@ import static org.junit.jupiter.api.Assertions.*;
 import static org.mockito.ArgumentMatchers.*;
 import static org.mockito.Mockito.*;
 
-import java.io.BufferedInputStream;
-import java.io.IOException;
-import java.io.InputStream;
 import java.io.OutputStream;
 import java.util.List;
 import java.util.concurrent.CompletableFuture;
@@ -81,82 +78,30 @@ class BinaryFileRemoteServiceTest {
 		@BeforeEach
 		void initMocks() {
 			when(serviceAsyncStub.uploadBinaryFileAsStream(any())).thenReturn(requestObserver);
-			doNothing().when(remoteService).sendBinaryFile(any(), any());
 		}
 
 		@Test
 		void shouldCreateStreamObserver() {
 			callService();
 
-			verify(remoteService).createUploadBinaryFileObserver(ArgumentMatchers.<CompletableFuture<FileId>>any());
+			verify(remoteService).createUploadBinaryFileObserver(ArgumentMatchers.<CompletableFuture<FileId>>any(), any());
+			verify(remoteService).buildMetaDataRequest(uploadBinaryFile);
 		}
 
 		@Test
 		void shouldCallUploadBinaryFileOnServiceAsyncStub() {
-			doReturn(responseObserver).when(remoteService).createUploadBinaryFileObserver(any());
+			doReturn(responseObserver).when(remoteService).createUploadBinaryFileObserver(any(), any());
 
 			callService();
 
 			verify(serviceAsyncStub).uploadBinaryFileAsStream(responseObserver);
 		}
 
-		@Test
-		void shouldCallSendBinaryFile() {
-			callService();
-
-			verify(remoteService).sendBinaryFile(requestObserver, uploadBinaryFile);
-		}
-
 		private void callService() {
 			remoteService.uploadFile(uploadBinaryFile);
 		}
 	}
 
-	@Nested
-	class TestSendBinaryFile {
-
-		@Mock
-		private CallStreamObserver<GrpcUploadBinaryFileRequest> requestObserver;
-		private UploadBinaryFileRequest uploadBinaryFile = UploadBinaryFileTestFactory.create();
-
-		@BeforeEach
-		void mockSendAsChunks() {
-			doNothing().when(remoteService).sendAsChunks(any(), any());
-		}
-
-		@Test
-		void shouldBuildMetaData() {
-			callService();
-
-			verify(remoteService).buildMetaDataRequest(uploadBinaryFile);
-		}
-
-		@Test
-		void shouldCallNextWithMetaData() {
-			callService();
-
-			verify(requestObserver).onNext(any(GrpcUploadBinaryFileRequest.class));
-		}
-
-		@Test
-		void shouldSendChunks() {
-			callService();
-
-			verify(remoteService).sendAsChunks(requestObserver, UploadBinaryFileTestFactory.UPLOAD_STREAM);
-		}
-
-		@Test
-		void shouldCallOnComplete() {
-			callService();
-
-			verify(requestObserver).onCompleted();
-		}
-
-		private void callService() {
-			remoteService.sendBinaryFile(requestObserver, uploadBinaryFile);
-		}
-	}
-
 	@Nested
 	class TestBuildMetaDataRequest {
 
@@ -208,119 +153,6 @@ class BinaryFileRemoteServiceTest {
 		}
 	}
 
-	@Nested
-	class TestSendAsChunks {
-
-		@Mock
-		private CallStreamObserver<GrpcUploadBinaryFileRequest> requestObserver;
-		@Mock
-		private BufferedInputStream bufferedUploadStream;
-
-		@BeforeEach
-		void mock() {
-			doReturn(bufferedUploadStream).when(remoteService).getAsBufferedStream(any(InputStream.class));
-		}
-
-		@Test
-		void shouldCallHasDataToRead() throws IOException {
-			callService();
-
-			verify(remoteService).hasDataToRead(bufferedUploadStream);
-		}
-
-		@Test
-		void shouldSendNextChunk() throws IOException {
-			doNothing().when(remoteService).sendNextChunk(any(), any());
-			doReturn(true, false).when(remoteService).hasDataToRead(any());
-
-			callService();
-
-			verify(remoteService, atLeastOnce()).sendNextChunk(requestObserver, bufferedUploadStream);
-		}
-
-		@Test
-		void shouldThrowException() throws IOException {
-			doThrow(IOException.class).when(remoteService).hasDataToRead(any());
-
-			assertThrows(TechnicalException.class, () -> callService());
-		}
-
-		private void callService() {
-			remoteService.sendAsChunks(requestObserver, UploadBinaryFileTestFactory.UPLOAD_STREAM);
-		}
-	}
-
-	@Nested
-	class TestSendNextChunk {
-
-		@Mock
-		private CallStreamObserver<GrpcUploadBinaryFileRequest> requestObserver;
-		@Mock
-		private BufferedInputStream bufferedUploadStream;
-
-		@Test
-		void shouldSendNextChunk() {
-			doNothing().when(remoteService).sendChunk(any(), any());
-			when(requestObserver.isReady()).thenReturn(true);
-
-			callService();
-
-			verify(remoteService).sendChunk(requestObserver, bufferedUploadStream);
-		}
-
-		@Test
-		void shouldNotSendNextChunkWhenNotReady() {
-			when(requestObserver.isReady()).thenReturn(false);
-
-			callService();
-
-			verify(remoteService, never()).sendChunk(requestObserver, bufferedUploadStream);
-		}
-
-		private void callService() {
-			remoteService.sendNextChunk(requestObserver, bufferedUploadStream);
-		}
-	}
-
-	@Nested
-	class TestSendChunk {
-
-		@Mock
-		private BufferedInputStream uploadStream;
-		@Mock
-		private CallStreamObserver<GrpcUploadBinaryFileRequest> requestObserver;
-		private final byte[] chunkPart = BinaryFileTestFactory.DATA;
-
-		@Test
-		void shouldBuildChunk() throws IOException {
-			doReturn(chunkPart).when(uploadStream).readNBytes(anyInt());
-
-			callService();
-
-			verify(remoteService).buildChunkRequest(chunkPart);
-		}
-
-		@Test
-		void shouldCallOnNext() throws IOException {
-			doReturn(chunkPart).when(uploadStream).readNBytes(anyInt());
-
-			callService();
-
-			verify(requestObserver).onNext(any(GrpcUploadBinaryFileRequest.class));
-		}
-
-		@Test
-		void shouldThrowException() throws IOException {
-			doThrow(IOException.class).when(uploadStream).readNBytes(anyInt());
-
-			assertThrows(TechnicalException.class, () -> callService());
-		}
-
-		private void callService() {
-			remoteService.sendChunk(requestObserver, uploadStream);
-		}
-	}
-
 	@Nested
 	class TestBuildChunkRequest {
 
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..9fd462cb12bcbf9f9ea36582ce2488e282798d25
--- /dev/null
+++ b/goofy-server/src/test/java/de/itvsh/goofy/common/binaryfile/ChunkedFileSenderTest.java
@@ -0,0 +1,101 @@
+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/GrpcUploadBinaryFileMetaDataTestFactory.java b/goofy-server/src/test/java/de/itvsh/goofy/common/binaryfile/GrpcUploadBinaryFileMetaDataTestFactory.java
new file mode 100644
index 0000000000000000000000000000000000000000..f393e5880fd7bb6d87fe8ed969f871cc88edcc63
--- /dev/null
+++ b/goofy-server/src/test/java/de/itvsh/goofy/common/binaryfile/GrpcUploadBinaryFileMetaDataTestFactory.java
@@ -0,0 +1,20 @@
+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..6bac8903ba9d15f428626dd8ab67450857126e93
--- /dev/null
+++ b/goofy-server/src/test/java/de/itvsh/goofy/common/binaryfile/GrpcUploadBinaryFileRequestTestFactory.java
@@ -0,0 +1,20 @@
+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/postfach/PostfachMailControllerTest.java b/goofy-server/src/test/java/de/itvsh/goofy/postfach/PostfachMailControllerTest.java
index dde850aa32d006dfc55c10ed003d3524249058bf..4423d22786638e5c079cdb650624b02fb7e463e6 100644
--- a/goofy-server/src/test/java/de/itvsh/goofy/postfach/PostfachMailControllerTest.java
+++ b/goofy-server/src/test/java/de/itvsh/goofy/postfach/PostfachMailControllerTest.java
@@ -6,20 +6,27 @@ 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.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;
@@ -31,6 +38,7 @@ import de.itvsh.goofy.vorgang.VorgangWithEingangTestFactory;
 
 class PostfachMailControllerTest {
 
+	@Spy
 	@InjectMocks // NOSONAR
 	private PostfachMailController controller;
 	@Mock
@@ -49,8 +57,9 @@ class PostfachMailControllerTest {
 		mockMvc = MockMvcBuilders.standaloneSetup(controller).build();
 	}
 
+	@DisplayName("Get all")
 	@Nested
-	class TestPostfachMailGetAll {
+	class TestGetAll {
 
 		@BeforeEach
 		void mockVorgangController() {
@@ -79,15 +88,73 @@ class PostfachMailControllerTest {
 					ArgumentMatchers.<Optional<String>>any());
 		}
 
-		void doRequest() throws Exception {
+		private void doRequest() throws Exception {
 			mockMvc.perform(get(PostfachMailController.PATH + "?" + PostfachMailController.PARAM_VORGANG_ID + "=" + VorgangHeaderTestFactory.ID))
 					.andExpect(status().isOk());
 		}
 	}
 
+	@Disabled("FIXME OZG-2966")
+	@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() throws Exception {
+			doRequest();
+
+			verify(vorgangController).getVorgang(VorgangHeaderTestFactory.ID);
+		}
+
+		@Test
+		void shouldCallService() throws Exception {
+			doRequest();
+
+			verify(service).getAllAsPdf(eq(vorgang), any(OutputStream.class));
+		}
+
+		@Test
+		void shouldBuildResponseEntity() throws Exception {
+			doReturn(streamingBody).when(controller).createDownloadStreamingBody(vorgang);
+
+			doRequest();
+
+			verify(controller).buildResponseEntity(vorgang, streamingBody);
+		}
+
+		@Test
+		void shouldReturnResponse() throws Exception {
+			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));
+		}
+
+		private ResultActions doRequest() throws Exception {
+			return mockMvc
+					.perform(get(PostfachMailController.PATH + "?" + PostfachMailController.PARAM_VORGANG_ID + "=" + VorgangHeaderTestFactory.ID)
+							.accept(MediaType.APPLICATION_PDF_VALUE))
+					.andExpect(status().isOk());
+		}
+	}
+
+	@DisplayName("Get postfachId")
 	@Nested
 	class TestGetPostfachId {
 
+		@DisplayName("without antragsteller")
 		@Nested
 		class TestWithoutAntragsteller {
 
@@ -103,6 +170,7 @@ class PostfachMailControllerTest {
 			}
 		}
 
+		@DisplayName("with empty postfachId")
 		@Nested
 		class TestWithEmptyPostfachId {
 
@@ -124,6 +192,7 @@ class PostfachMailControllerTest {
 		}
 	}
 
+	@DisplayName("Find postfach attachments")
 	@Nested
 	class TestFindPostfachAttachments {
 
@@ -156,6 +225,7 @@ class PostfachMailControllerTest {
 		}
 	}
 
+	@DisplayName("Is postfach configured")
 	@Nested
 	class TestIsPostfachConfigured {
 
diff --git a/goofy-server/src/test/java/de/itvsh/goofy/postfach/PostfachMailPdfServiceTest.java b/goofy-server/src/test/java/de/itvsh/goofy/postfach/PostfachMailPdfServiceTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..70ed8e9febad707b40e49eee8d3d5177f75f692f
--- /dev/null
+++ b/goofy-server/src/test/java/de/itvsh/goofy/postfach/PostfachMailPdfServiceTest.java
@@ -0,0 +1,120 @@
+package de.itvsh.goofy.postfach;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.Assert.*;
+import static org.mockito.ArgumentMatchers.*;
+import static org.mockito.Mockito.*;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+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.errorhandling.FunctionalException;
+import de.itvsh.goofy.vorgang.VorgangWithEingangTestFactory;
+import de.itvsh.kop.common.pdf.PdfService;
+import lombok.SneakyThrows;
+
+class PostfachMailPdfServiceTest {
+
+	@Spy
+	@InjectMocks
+	private PostfachMailPdfService 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(PostfachMailPdfService.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);
+
+				assertThrows(FunctionalException.class, () -> service.getTemplate());
+			}
+		}
+
+		@DisplayName("build model")
+		@Nested
+		class TestBuildModel {
+
+			@Test
+			void shouldContainsVorgangsNummer() {
+				var model = buildModel();
+
+				// assertThat(model.getVorgangsNummer()).isEqualTo(VorgangHeaderTestFactory.NUMMER);
+				assertThat(model.getVorgangsNummer()).isEqualTo("VorgangsNummerTestValue");
+			}
+
+			@Test
+			void shouldContainsVorgangsTyp() {
+				var model = buildModel();
+
+				// assertThat(model.getVorgangsTyp()).isEqualTo(VorgangHeaderTestFactory.NAME);
+				assertThat(model.getVorgangsTyp()).isEqualTo("VorgangsTypTestValue");
+			}
+
+			private PostfachNachrichtenPdfModel buildModel() {
+				return service.buildModel(VorgangWithEingangTestFactory.create());
+			}
+		}
+
+		@Test
+		void shouldCallPdfService() {
+			doReturn(InputStream.nullInputStream()).when(service).getTemplate();
+
+			service.getAllAsPdf(VorgangWithEingangTestFactory.create(), output);
+
+			verify(pdfService).createPdf(any(InputStream.class), any(OutputStream.class), any(PostfachNachrichtenPdfModel.class));
+		}
+	}
+}
\ 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
index 4e14e58beb7119298c477c241417a81cf375a985..54198f4649c50df4264c9e03967a3663c99e4977 100644
--- a/goofy-server/src/test/java/de/itvsh/goofy/postfach/PostfachMailServiceTest.java
+++ b/goofy-server/src/test/java/de/itvsh/goofy/postfach/PostfachMailServiceTest.java
@@ -1,9 +1,11 @@
 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.Optional;
 
 import org.junit.jupiter.api.BeforeEach;
@@ -13,19 +15,22 @@ import org.junit.jupiter.api.Test;
 import org.mockito.InjectMocks;
 import org.mockito.Mock;
 
-import static org.assertj.core.api.Assertions.*;
-
 import de.itvsh.goofy.common.command.CommandTestFactory;
 import de.itvsh.goofy.common.errorhandling.ResourceNotFoundException;
 import de.itvsh.goofy.vorgang.VorgangHeaderTestFactory;
+import de.itvsh.goofy.vorgang.VorgangWithEingang;
+import de.itvsh.goofy.vorgang.VorgangWithEingangTestFactory;
 
 class PostfachMailServiceTest {
 
 	@InjectMocks // NOSONAR
 	private PostfachMailService service;
 	@Mock
+	private PostfachMailPdfService pdfService;
+	@Mock
 	private PostfachMailRemoteService remoteService;
 
+	@DisplayName("Get all")
 	@Nested
 	class TestGetAll {
 
@@ -37,6 +42,7 @@ class PostfachMailServiceTest {
 		}
 	}
 
+	@DisplayName("Find by id")
 	@Nested
 	class TestFindById {
 
@@ -69,6 +75,7 @@ class PostfachMailServiceTest {
 		}
 	}
 
+	@DisplayName("Resend postfach mail")
 	@Nested
 	class TestResendPostfachMail {
 
@@ -80,19 +87,19 @@ class PostfachMailServiceTest {
 		}
 	}
 
+	@DisplayName("Is postfach configured")
 	@Nested
 	class TestIsPostfachConfigured {
 
 		@Test
-		void shouldCallRemoteService() {
+		void shouldCallRemoteServiceOnMissingProperty() {
 			service.isPostfachConfigured();
 
 			verify(remoteService).isPostfachConfigured();
 		}
 
-		@DisplayName("if property is already set, no remoteservice call should be done")
 		@Test
-		void shouldNotCallRemoteService() throws Exception {
+		void shouldNotCallRemoteServiceOnExistingProperty() throws Exception {
 			setIsConfigured();
 
 			service.isPostfachConfigured();
@@ -106,4 +113,21 @@ class PostfachMailServiceTest {
 			isPostfachConfigured.set(service, true);
 		}
 	}
+
+	@DisplayName("Get all as pdf")
+	@Nested
+	class TestGetAllAsPdf {
+
+		@Mock
+		private OutputStream outputStream;
+
+		private final VorgangWithEingang vorgang = VorgangWithEingangTestFactory.create();
+
+		@Test
+		void shouldCallPdfService() {
+			service.getAllAsPdf(vorgang, outputStream);
+
+			verify(pdfService).getAllAsPdf(vorgang, outputStream);
+		}
+	}
 }
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
index 7d7e2bc18e7acd6d87a9149b7b061cf3f54d01d5..ce17122232f4c322c304d199cb1bbcaa0915c6a8 100644
--- a/pom.xml
+++ b/pom.xml
@@ -12,7 +12,7 @@
 	<parent>
 		<groupId>de.itvsh.kop.common</groupId>
 		<artifactId>kop-common-parent</artifactId>
-		<version>1.2.1</version>
+		<version>1.3.0-SNAPSHOT</version>
 	</parent>
 
 	<modules>
@@ -26,6 +26,7 @@
 
 		<pluto.version>1.1.0-SNAPSHOT</pluto.version>
 		<jsoup.version>1.15.1</jsoup.version>
+		<kop-common-pdf.version>1.3.0-SNAPSHOT</kop-common-pdf.version>
 	</properties>
 
 	<dependencyManagement>
@@ -40,6 +41,11 @@
 				<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>