diff --git a/src/main/java/de/ozgcloud/nachrichten/postfach/osiv2/transfer/Osi2QuarantineService.java b/src/main/java/de/ozgcloud/nachrichten/postfach/osiv2/transfer/Osi2QuarantineService.java
index 619c4083bf4d8947295422b044a3480fb745a610..2c5253ee2e934cf0ecb6d775a0bebf3aa953192c 100644
--- a/src/main/java/de/ozgcloud/nachrichten/postfach/osiv2/transfer/Osi2QuarantineService.java
+++ b/src/main/java/de/ozgcloud/nachrichten/postfach/osiv2/transfer/Osi2QuarantineService.java
@@ -12,11 +12,11 @@ import java.util.stream.Stream;
 
 import de.ozgcloud.apilib.file.OzgCloudFile;
 import de.ozgcloud.nachrichten.postfach.osiv2.ServiceIfOsi2Enabled;
+import de.ozgcloud.nachrichten.postfach.osiv2.attachment.Osi2AttachmentFileService;
 import de.ozgcloud.nachrichten.postfach.osiv2.exception.Osi2RuntimeException;
 import de.ozgcloud.nachrichten.postfach.osiv2.exception.Osi2UploadException;
 import de.ozgcloud.nachrichten.postfach.osiv2.model.FileChunkInfo;
 import de.ozgcloud.nachrichten.postfach.osiv2.model.Osi2FileUpload;
-import de.ozgcloud.nachrichten.postfach.osiv2.attachment.Osi2AttachmentFileService;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.log4j.Log4j2;
 
@@ -31,37 +31,52 @@ public class Osi2QuarantineService {
 	static final Duration POLLING_TIMEOUT = Duration.ofMinutes(5);
 
 	public List<Osi2FileUpload> uploadAttachments(List<String> attachmentIds) {
-		var ozgCloudFiles = binaryFileService.getFileMetadataOfIds(attachmentIds);
-		var uploadFiles = uploadFilesToQuarantineWithLargestFileFirst(ozgCloudFiles);
-		waitForVirusScan(uploadFiles);
-		return uploadFiles;
+		return uploadFiles(binaryFileService.getFileMetadataOfIds(attachmentIds));
+	}
+
+	List<Osi2FileUpload> uploadFiles(List<OzgCloudFile> ozgCloudFiles) {
+		var sortedUploadFiles = deriveSortedUploadFiles(ozgCloudFiles);
+		try {
+			tryUploadSortedFiles(sortedUploadFiles);
+			return sortedUploadFiles;
+		} catch (RuntimeException e) {
+			deleteAttachments(sortedUploadFiles);
+			throw e;
+		}
+	}
+
+	void tryUploadSortedFiles(List<Osi2FileUpload> sortedUploadFiles) {
+		uploadFilesToQuarantine(sortedUploadFiles);
+		waitForVirusScan(sortedUploadFiles);
 	}
 
-	List<Osi2FileUpload> uploadFilesToQuarantineWithLargestFileFirst(List<OzgCloudFile> ozgCloudFiles) {
+	List<Osi2FileUpload> deriveSortedUploadFiles(List<OzgCloudFile> ozgCloudFiles) {
 		return ozgCloudFiles.stream()
 				.sorted(Comparator.comparing(OzgCloudFile::getSize).reversed())
-				.map(this::uploadFileToQuarantine)
+				.map(Osi2FileUpload::from)
 				.toList();
 	}
 
-	Osi2FileUpload uploadFileToQuarantine(OzgCloudFile file) {
-		try (var fileInputStream = binaryFileService.downloadFileContent(file.getId().toString())) {
-			return uploadInputStreamToQuarantine(file, fileInputStream);
+	void uploadFilesToQuarantine(List<Osi2FileUpload> uploads) {
+		uploads.forEach(this::uploadFileToQuarantine);
+	}
+
+	void uploadFileToQuarantine(Osi2FileUpload upload) {
+		try (var fileInputStream = binaryFileService.downloadFileContent(upload.file().getId().toString())) {
+			uploadInputStreamToQuarantine(upload, fileInputStream);
 		} catch (IOException e) {
 			throw new Osi2RuntimeException("Failed to close upload input stream!", e);
 		}
 	}
 
-	Osi2FileUpload uploadInputStreamToQuarantine(OzgCloudFile file, InputStream fileInputStream) {
-		var fileUpload = Osi2FileUpload.from(file);
-		streamFileChunkInfos(fileUpload)
+	void uploadInputStreamToQuarantine(Osi2FileUpload upload, InputStream fileInputStream) {
+		streamFileChunkInfos(upload)
 				.forEachOrdered(chunkInfo ->
 						postfachApiFacadeService.uploadChunk(
 								chunkInfo,
 								chunkInfo.createUploadResource(fileInputStream)
 						)
 				);
-		return fileUpload;
 	}
 
 	Stream<FileChunkInfo> streamFileChunkInfos(Osi2FileUpload upload) {
@@ -95,8 +110,10 @@ public class Osi2QuarantineService {
 		}
 	}
 
-	public void deleteAttachments(List<Osi2FileUpload> osi2FileMetadata) {
-		// TODO delete on exception
+	void deleteAttachments(List<Osi2FileUpload> uploads) {
+		uploads.stream()
+				.map(Osi2FileUpload::guid)
+				.forEach(postfachApiFacadeService::deleteFileUpload);
 	}
 
 }
diff --git a/src/main/java/de/ozgcloud/nachrichten/postfach/osiv2/transfer/Osi2ResponseMapper.java b/src/main/java/de/ozgcloud/nachrichten/postfach/osiv2/transfer/Osi2ResponseMapper.java
index f64b1b1787337a5e95f309e0aea980ee6afd4b83..a5781050a74ae31f8bb090d1f19f0c750f02e516 100644
--- a/src/main/java/de/ozgcloud/nachrichten/postfach/osiv2/transfer/Osi2ResponseMapper.java
+++ b/src/main/java/de/ozgcloud/nachrichten/postfach/osiv2/transfer/Osi2ResponseMapper.java
@@ -21,6 +21,7 @@ import de.ozgcloud.nachrichten.postfach.osiv2.exception.Osi2RuntimeException;
 import de.ozgcloud.nachrichten.postfach.osiv2.exception.Osi2UploadException;
 import de.ozgcloud.nachrichten.postfach.osiv2.gen.model.MessageExchangeReceiveMessage;
 import de.ozgcloud.nachrichten.postfach.osiv2.gen.model.MessageExchangeReceiveMessagesResponse;
+import de.ozgcloud.nachrichten.postfach.osiv2.gen.model.QuarantineFileResult;
 import de.ozgcloud.nachrichten.postfach.osiv2.gen.model.QuarantineStatus;
 import de.ozgcloud.nachrichten.postfach.osiv2.gen.model.V1ReplyBehavior;
 import de.ozgcloud.nachrichten.postfach.osiv2.gen.model.V1ReplyMessage;
@@ -117,6 +118,13 @@ public interface Osi2ResponseMapper {
 		};
 	}
 
+	default void checkChunkUploadSuccess(QuarantineFileResult quarantineFileResult) {
+		if (!Boolean.TRUE.equals(quarantineFileResult.getSuccess())) {
+			throw new Osi2RuntimeException(
+					"Chunk-Upload of file %s failed: %s".formatted(quarantineFileResult.getFileUid(), quarantineFileResult.getError()), null);
+		}
+	}
+
 	@Builder
 	@Getter
 	class StringBasedIdentifier implements PostfachAddressIdentifier {
diff --git a/src/main/java/de/ozgcloud/nachrichten/postfach/osiv2/transfer/PostfachApiFacadeService.java b/src/main/java/de/ozgcloud/nachrichten/postfach/osiv2/transfer/PostfachApiFacadeService.java
index c27b95d8ee8d9bae0676ddfb80417cb2a76bc13a..c38e7300c12033e6d69028fc1bbe9d3dce0e80e5 100644
--- a/src/main/java/de/ozgcloud/nachrichten/postfach/osiv2/transfer/PostfachApiFacadeService.java
+++ b/src/main/java/de/ozgcloud/nachrichten/postfach/osiv2/transfer/PostfachApiFacadeService.java
@@ -37,12 +37,12 @@ public class PostfachApiFacadeService {
 	}
 
 	public <T extends AbstractResource> void uploadChunk(FileChunkInfo chunkInfo, T chunkResource) {
-		quarantineApi.uploadChunk(
+		responseMapper.checkChunkUploadSuccess(quarantineApi.uploadChunk(
 				requestMapper.mapDomainChunkMetaData(chunkInfo),
 				apiConfiguration.getTenant(),
 				apiConfiguration.getNameIdentifier(),
 				chunkResource
-		);
+		));
 	}
 
 	public boolean checkUploadSuccessful(final String messageId) throws Osi2UploadException {
@@ -65,4 +65,8 @@ public class PostfachApiFacadeService {
 	public void deleteMessage(final String messageId) {
 		messageExchangeApi.deleteMessage(UUID.fromString(messageId));
 	}
+
+	public void deleteFileUpload(String fileUploadId) {
+		quarantineApi.deleteUpload(UUID.fromString(fileUploadId));
+	}
 }
diff --git a/src/test/java/de/ozgcloud/nachrichten/postfach/osiv2/OsiPostfachRemoteServiceITCase.java b/src/test/java/de/ozgcloud/nachrichten/postfach/osiv2/OsiPostfachRemoteServiceITCase.java
index 34c70d821aefa1598a762406b138002ab4ed67c8..71a645f8feec2638bd44472479ee7348ea1b4cf2 100644
--- a/src/test/java/de/ozgcloud/nachrichten/postfach/osiv2/OsiPostfachRemoteServiceITCase.java
+++ b/src/test/java/de/ozgcloud/nachrichten/postfach/osiv2/OsiPostfachRemoteServiceITCase.java
@@ -1,17 +1,9 @@
 package de.ozgcloud.nachrichten.postfach.osiv2;
 
-import static com.github.tomakehurst.wiremock.client.WireMock.*;
 import static de.ozgcloud.nachrichten.NachrichtenManagerConfiguration.*;
 import static de.ozgcloud.nachrichten.postfach.osiv2.factory.JwtFactory.*;
-import static org.assertj.core.api.Assertions.*;
-
-import java.time.OffsetDateTime;
-import java.util.List;
-import java.util.UUID;
 
 import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.DisplayName;
-import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.extension.RegisterExtension;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.boot.test.context.SpringBootTest;
@@ -21,22 +13,10 @@ import org.springframework.test.context.DynamicPropertySource;
 import org.springframework.test.context.TestPropertySource;
 
 import com.github.tomakehurst.wiremock.WireMockServer;
-import com.github.tomakehurst.wiremock.client.ResponseDefinitionBuilder;
 
-import de.ozgcloud.nachrichten.postfach.PostfachMessageCode;
-import de.ozgcloud.nachrichten.postfach.osiv2.exception.Osi2PostfachException;
-import de.ozgcloud.nachrichten.postfach.osiv2.extension.AttachmentExampleUploadUtil;
-import de.ozgcloud.nachrichten.postfach.osiv2.extension.Jwt;
+import de.ozgcloud.nachrichten.postfach.osiv2.attachment.Osi2AttachmentFileService;
 import de.ozgcloud.nachrichten.postfach.osiv2.extension.OsiMockServerExtension;
 import de.ozgcloud.nachrichten.postfach.osiv2.extension.VorgangManagerServerExtension;
-import de.ozgcloud.nachrichten.postfach.osiv2.factory.JsonUtil;
-import de.ozgcloud.nachrichten.postfach.osiv2.factory.MessageExchangeReceiveMessagesResponseTestFactory;
-import de.ozgcloud.nachrichten.postfach.osiv2.factory.MessageExchangeSendMessageResponseTestFactory;
-import de.ozgcloud.nachrichten.postfach.osiv2.factory.PostfachNachrichtTestFactory;
-import de.ozgcloud.nachrichten.postfach.osiv2.factory.V1ReplyMessageTestFactory;
-import de.ozgcloud.nachrichten.postfach.osiv2.gen.model.QuarantineFileResult;
-import de.ozgcloud.nachrichten.postfach.osiv2.gen.model.QuarantineStatus;
-import de.ozgcloud.nachrichten.postfach.osiv2.attachment.Osi2AttachmentFileService;
 import lombok.SneakyThrows;
 
 @SpringBootTest(classes = TestApplication.class)
@@ -53,9 +33,9 @@ class OsiPostfachRemoteServiceITCase {
 	static final VorgangManagerServerExtension VORGANG_MANAGER_SERVER_EXTENSION = new VorgangManagerServerExtension();
 
 	@Autowired
-	private OsiPostfachRemoteService osiPostfachRemoteService;
+	protected OsiPostfachRemoteService osiPostfachRemoteService;
 	@Autowired
-	private Osi2AttachmentFileService osi2AttachmentFileService;
+	protected Osi2AttachmentFileService osi2AttachmentFileService;
 
 	@DynamicPropertySource
 	static void dynamicProperties(DynamicPropertyRegistry registry) {
@@ -68,202 +48,11 @@ class OsiPostfachRemoteServiceITCase {
 		registry.add("grpc.client." + GRPC_FILE_MANAGER_NAME + ".negotiationType", () -> "PLAINTEXT");
 	}
 
-	private WireMockServer postfachFacadeMockServer;
+	protected WireMockServer postfachFacadeMockServer;
 
 	@BeforeEach
 	@SneakyThrows
 	public void setup() {
 		postfachFacadeMockServer = OSI_MOCK_SERVER_EXTENSION.getPostfachFacadeMockServer();
 	}
-
-	@DisplayName("should send request with jwt")
-	@Test
-	@SneakyThrows
-	void shouldSendRequestWithJwt() {
-		var postfachNachricht = PostfachNachrichtTestFactory.create();
-		mockSendResponse();
-
-		osiPostfachRemoteService.sendMessage(postfachNachricht);
-
-		var requests = postfachFacadeMockServer.findAll(
-				postRequestedFor(urlPathTemplate("/MessageExchange/v1/Send/{mailboxId}")));
-		assertThat(requests).hasSize(1);
-		var jwt = Jwt.parseAuthHeaderValue(requests.getFirst().getHeader("Authorization"));
-		assertThat(jwt.body().read("$.client_id", String.class)).isEqualTo(CLIENT_ID);
-		assertThat(jwt.body().read("$.aud", String.class)).isEqualTo(RESOURCE_URN);
-	}
-
-	@DisplayName("should throw postfach exception with connection error code")
-	@Test
-	void shouldThrowPostfachExceptionWithConnectionErrorCode() {
-		var postfachNachricht = PostfachNachrichtTestFactory.create();
-		postfachFacadeMockServer.stop();
-
-		assertThatThrownBy(() -> osiPostfachRemoteService.sendMessage(postfachNachricht))
-				.isInstanceOf(Osi2PostfachException.class)
-				.hasFieldOrPropertyWithValue("messageCode", PostfachMessageCode.SERVER_CONNECTION_FAILED_MESSAGE_CODE);
-	}
-
-	@DisplayName("should send message with attachment")
-	@Test
-	void shouldSendMessageWithAttachment() {
-		var textFileId = AttachmentExampleUploadUtil.uploadTextFile(osi2AttachmentFileService);
-		postfachFacadeMockServer.stubFor(post(urlPathTemplate("/Quarantine/v1/Upload/Chunked"))
-				.willReturn(okJsonObj(QuarantineFileResult.builder()
-						.success(true)
-						.build()))
-		);
-		postfachFacadeMockServer.stubFor(get(urlPathTemplate("/Quarantine/v1/Upload/{guid}"))
-				.willReturn(okJsonObj(QuarantineStatus.SAFE))
-		);
-		var postfachNachrichtWithAttachment = PostfachNachrichtTestFactory.createBuilder()
-				.attachments(List.of(textFileId))
-				.build();
-		mockSendResponse();
-
-		osiPostfachRemoteService.sendMessage(postfachNachrichtWithAttachment);
-
-		postfachFacadeMockServer.verify(
-				exactly(1),
-				postRequestedFor(urlPathTemplate("/Quarantine/v1/Upload/Chunked"))
-		);
-		postfachFacadeMockServer.verify(
-				exactly(1),
-				postRequestedFor(urlPathTemplate("/Quarantine/v1/Upload/{guid}"))
-		);
-		postfachFacadeMockServer.verify(
-				exactly(1),
-				postRequestedFor(urlPathTemplate("/MessageExchange/v1/Send/{mailboxId}"))
-		);
-	}
-
-	@DisplayName("should delete attachments on upload exception")
-	@Test
-	void shouldDeleteAttachmentsOnUploadException() {
-		var textFileId = AttachmentExampleUploadUtil.uploadTextFile(osi2AttachmentFileService);
-		postfachFacadeMockServer.stubFor(post(urlPathTemplate("/Quarantine/v1/Upload/Chunked"))
-				.willReturn(okJsonObj(QuarantineFileResult.builder()
-						.success(false)
-						.error("Upload failure")
-						.build()))
-		);
-		postfachFacadeMockServer.stubFor(delete(urlPathTemplate("/Quarantine/v1/Upload/{guid}"))
-				.willReturn(ok())
-		);
-		var postfachNachrichtWithAttachment = PostfachNachrichtTestFactory.createBuilder()
-				.attachments(List.of(textFileId))
-				.build();
-
-		osiPostfachRemoteService.sendMessage(postfachNachrichtWithAttachment);
-
-		postfachFacadeMockServer.verify(
-				exactly(1),
-				deleteRequestedFor(urlPathTemplate("/Quarantine/v1/Upload/{guid}"))
-		);
-	}
-
-	@DisplayName("should delete attachments on scan exception")
-	@Test
-	void shouldDeleteAttachmentsOnScanException() {
-		var textFileId = AttachmentExampleUploadUtil.uploadTextFile(osi2AttachmentFileService);
-		postfachFacadeMockServer.stubFor(post(urlPathTemplate("/Quarantine/v1/Upload/Chunked"))
-				.willReturn(okJsonObj(QuarantineFileResult.builder()
-						.success(true)
-						.build()))
-		);
-		postfachFacadeMockServer.stubFor(get(urlPathTemplate("/Quarantine/v1/Upload/{guid}"))
-				.willReturn(okJsonObj(QuarantineStatus.UNSAFE))
-		);
-		postfachFacadeMockServer.stubFor(delete(urlPathTemplate("/Quarantine/v1/Upload/{guid}"))
-				.willReturn(ok())
-		);
-		var postfachNachrichtWithAttachment = PostfachNachrichtTestFactory.createBuilder()
-				.attachments(List.of(textFileId))
-				.build();
-
-		osiPostfachRemoteService.sendMessage(postfachNachrichtWithAttachment);
-
-		postfachFacadeMockServer.verify(
-				exactly(1),
-				deleteRequestedFor(urlPathTemplate("/Quarantine/v1/Upload/{guid}"))
-		);
-	}
-
-	private void mockSendResponse() {
-		// Stub message send response (MessageExchangeApi::sendMessage)
-		postfachFacadeMockServer.stubFor(post(urlPathTemplate("/MessageExchange/v1/Send/{mailboxId}"))
-				.willReturn(
-						okJsonObj(
-								MessageExchangeSendMessageResponseTestFactory.create()
-										.messageId(UUID.randomUUID())
-						)
-				)
-		);
-	}
-
-	@DisplayName("should receive one messages")
-	@Test
-	@SneakyThrows
-	void shouldReceiveMessages() {
-		mockPostfachMessageAndResponse("00000000-0000-0000-0000-000000000000");
-
-		var messageList = osiPostfachRemoteService.getAllMessages().toList();
-
-		assertThat(messageList).hasSize(1);
-	}
-
-	@DisplayName("should receive two messages")
-	@Test
-	@SneakyThrows
-	void shouldReceiveTwoMessages() {
-		mockPostfachMessageAndResponse("00000000-0000-0000-0000-000000000000", "00000000-0000-0000-0000-000000000001");
-
-		var messageList = osiPostfachRemoteService.getAllMessages().toList();
-
-		assertThat(messageList).hasSize(2);
-	}
-
-	private void mockPostfachMessageAndResponse(final String... uuids) {
-		// Stub message listing response (MessageExchangeApi::receiveMessages)
-		postfachFacadeMockServer.stubFor(get(urlPathEqualTo("/MessageExchange/v1/Receive"))
-				.withQueryParam("take", equalTo("100"))
-				.withQueryParam("skip", equalTo("0"))
-				.willReturn(okJsonObj(MessageExchangeReceiveMessagesResponseTestFactory.create(uuids)))
-		);
-		for (String uuid : uuids) {
-			// Stub individual response for message (MessageExchangeApi::getMessage)
-			postfachFacadeMockServer.stubFor(get(urlPathTemplate("/MessageExchange/v1/Receive/{messageId}"))
-					.withPathParam("messageId", equalTo(uuid))
-					.willReturn(okJsonObj(
-							V1ReplyMessageTestFactory.create()
-									.messageBox(UUID.fromString(uuid))
-									.responseTime(OffsetDateTime.now())
-					))
-			);
-		}
-	}
-
-	private ResponseDefinitionBuilder okJsonObj(final Object body) {
-		return okJson(JsonUtil.toJson(body));
-	}
-
-	@DisplayName("should delete message")
-	@Test
-	@SneakyThrows
-	void shouldDeleteMessage() {
-		var messageId = "00000000-0000-0000-0000-000000000000";
-		// Stub delete message response (MessageExchangeApi::deleteMessage)
-		postfachFacadeMockServer.stubFor(delete(urlPathTemplate("/MessageExchange/v1/Delete/{messageId}"))
-				.willReturn(ok())
-		);
-
-		osiPostfachRemoteService.deleteMessage(messageId);
-
-		postfachFacadeMockServer.verify(
-				exactly(1),
-				deleteRequestedFor(urlPathTemplate("/MessageExchange/v1/Delete/{messageId}"))
-						.withPathParam("messageId", equalTo(messageId))
-		);
-	}
-
 }
diff --git a/src/test/java/de/ozgcloud/nachrichten/postfach/osiv2/OsiPostfachRemoteServicePrimaryITCase.java b/src/test/java/de/ozgcloud/nachrichten/postfach/osiv2/OsiPostfachRemoteServicePrimaryITCase.java
new file mode 100644
index 0000000000000000000000000000000000000000..0b9b9b5280346c8a21f66b9964002257a9d9373d
--- /dev/null
+++ b/src/test/java/de/ozgcloud/nachrichten/postfach/osiv2/OsiPostfachRemoteServicePrimaryITCase.java
@@ -0,0 +1,216 @@
+package de.ozgcloud.nachrichten.postfach.osiv2;
+
+import static com.github.tomakehurst.wiremock.client.WireMock.*;
+import static de.ozgcloud.nachrichten.postfach.osiv2.factory.JwtFactory.*;
+import static org.assertj.core.api.Assertions.*;
+
+import java.time.OffsetDateTime;
+import java.util.List;
+import java.util.UUID;
+
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+
+import com.github.tomakehurst.wiremock.client.ResponseDefinitionBuilder;
+
+import de.ozgcloud.nachrichten.postfach.osiv2.exception.Osi2PostfachException;
+import de.ozgcloud.nachrichten.postfach.osiv2.extension.AttachmentExampleUploadUtil;
+import de.ozgcloud.nachrichten.postfach.osiv2.extension.Jwt;
+import de.ozgcloud.nachrichten.postfach.osiv2.factory.JsonUtil;
+import de.ozgcloud.nachrichten.postfach.osiv2.factory.MessageExchangeReceiveMessagesResponseTestFactory;
+import de.ozgcloud.nachrichten.postfach.osiv2.factory.MessageExchangeSendMessageResponseTestFactory;
+import de.ozgcloud.nachrichten.postfach.osiv2.factory.PostfachNachrichtTestFactory;
+import de.ozgcloud.nachrichten.postfach.osiv2.factory.V1ReplyMessageTestFactory;
+import de.ozgcloud.nachrichten.postfach.osiv2.gen.model.QuarantineFileResult;
+import de.ozgcloud.nachrichten.postfach.osiv2.gen.model.QuarantineStatus;
+import lombok.SneakyThrows;
+
+class OsiPostfachRemoteServicePrimaryITCase extends OsiPostfachRemoteServiceITCase {
+
+	@DisplayName("should send request with jwt")
+	@Test
+	@SneakyThrows
+	void shouldSendRequestWithJwt() {
+		var postfachNachricht = PostfachNachrichtTestFactory.create();
+		mockSendResponse();
+
+		osiPostfachRemoteService.sendMessage(postfachNachricht);
+
+		var requests = postfachFacadeMockServer.findAll(
+				postRequestedFor(urlPathTemplate("/MessageExchange/v1/Send/{mailboxId}")));
+		assertThat(requests).hasSize(1);
+		var jwt = Jwt.parseAuthHeaderValue(requests.getFirst().getHeader("Authorization"));
+		assertThat(jwt.body().read("$.client_id", String.class)).isEqualTo(CLIENT_ID);
+		assertThat(jwt.body().read("$.aud", String.class)).isEqualTo(RESOURCE_URN);
+	}
+
+	@DisplayName("should send message with attachment")
+	@Test
+	void shouldSendMessageWithAttachment() {
+		var textFileId = AttachmentExampleUploadUtil.uploadTextFile(osi2AttachmentFileService);
+		postfachFacadeMockServer.stubFor(post(urlPathTemplate("/Quarantine/v1/Upload/Chunked"))
+				.willReturn(okJsonObj(QuarantineFileResult.builder()
+						.success(true)
+						.build()))
+		);
+		postfachFacadeMockServer.stubFor(get(urlPathTemplate("/Quarantine/v1/Upload/{guid}"))
+				.willReturn(okJsonObj(QuarantineStatus.SAFE))
+		);
+		var postfachNachrichtWithAttachment = PostfachNachrichtTestFactory.createBuilder()
+				.attachments(List.of(textFileId))
+				.build();
+		mockSendResponse();
+
+		osiPostfachRemoteService.sendMessage(postfachNachrichtWithAttachment);
+
+		postfachFacadeMockServer.verify(
+				exactly(1),
+				postRequestedFor(urlPathTemplate("/Quarantine/v1/Upload/Chunked"))
+		);
+		postfachFacadeMockServer.verify(
+				exactly(1),
+				postRequestedFor(urlPathTemplate("/Quarantine/v1/Upload/{guid}"))
+		);
+		postfachFacadeMockServer.verify(
+				exactly(1),
+				postRequestedFor(urlPathTemplate("/MessageExchange/v1/Send/{mailboxId}"))
+		);
+	}
+
+	@DisplayName("should delete attachments on upload exception")
+	@Test
+	void shouldDeleteAttachmentsOnUploadException() {
+		var textFileId = AttachmentExampleUploadUtil.uploadTextFile(osi2AttachmentFileService);
+		postfachFacadeMockServer.stubFor(post(urlPathTemplate("/Quarantine/v1/Upload/Chunked"))
+				.willReturn(okJsonObj(QuarantineFileResult.builder()
+						.success(false)
+						.error("Upload failure")
+						.build()))
+		);
+		postfachFacadeMockServer.stubFor(delete(urlPathTemplate("/Quarantine/v1/Upload/{guid}"))
+				.willReturn(ok())
+		);
+		var postfachNachrichtWithAttachment = PostfachNachrichtTestFactory.createBuilder()
+				.attachments(List.of(textFileId))
+				.build();
+
+		try {
+			osiPostfachRemoteService.sendMessage(postfachNachrichtWithAttachment);
+		} catch (Osi2PostfachException e) {
+			// ignore
+		}
+
+		postfachFacadeMockServer.verify(
+				exactly(1),
+				deleteRequestedFor(urlPathTemplate("/Quarantine/v1/Upload/{guid}"))
+		);
+	}
+
+	@DisplayName("should delete attachments on scan exception")
+	@Test
+	void shouldDeleteAttachmentsOnScanException() {
+		var textFileId = AttachmentExampleUploadUtil.uploadTextFile(osi2AttachmentFileService);
+		postfachFacadeMockServer.stubFor(post(urlPathTemplate("/Quarantine/v1/Upload/Chunked"))
+				.willReturn(okJsonObj(QuarantineFileResult.builder()
+						.success(true)
+						.build()))
+		);
+		postfachFacadeMockServer.stubFor(get(urlPathTemplate("/Quarantine/v1/Upload/{guid}"))
+				.willReturn(okJsonObj(QuarantineStatus.UNSAFE))
+		);
+		postfachFacadeMockServer.stubFor(delete(urlPathTemplate("/Quarantine/v1/Upload/{guid}"))
+				.willReturn(ok())
+		);
+		var postfachNachrichtWithAttachment = PostfachNachrichtTestFactory.createBuilder()
+				.attachments(List.of(textFileId))
+				.build();
+
+		try {
+			osiPostfachRemoteService.sendMessage(postfachNachrichtWithAttachment);
+		} catch (Osi2PostfachException e) {
+			// ignore
+		}
+
+		postfachFacadeMockServer.verify(
+				exactly(1),
+				deleteRequestedFor(urlPathTemplate("/Quarantine/v1/Upload/{guid}"))
+		);
+	}
+
+	private void mockSendResponse() {
+		// Stub message send response (MessageExchangeApi::sendMessage)
+		postfachFacadeMockServer.stubFor(post(urlPathTemplate("/MessageExchange/v1/Send/{mailboxId}"))
+				.willReturn(
+						okJsonObj(
+								MessageExchangeSendMessageResponseTestFactory.create()
+										.messageId(UUID.randomUUID())
+						)
+				)
+		);
+	}
+
+	@DisplayName("should receive one messages")
+	@Test
+	@SneakyThrows
+	void shouldReceiveMessages() {
+		mockPostfachMessageAndResponse("00000000-0000-0000-0000-000000000000");
+
+		var messageList = osiPostfachRemoteService.getAllMessages().toList();
+
+		assertThat(messageList).hasSize(1);
+	}
+
+	@DisplayName("should receive two messages")
+	@Test
+	@SneakyThrows
+	void shouldReceiveTwoMessages() {
+		mockPostfachMessageAndResponse("00000000-0000-0000-0000-000000000000", "00000000-0000-0000-0000-000000000001");
+
+		var messageList = osiPostfachRemoteService.getAllMessages().toList();
+
+		assertThat(messageList).hasSize(2);
+	}
+
+	private void mockPostfachMessageAndResponse(final String... uuids) {
+		// Stub message listing response (MessageExchangeApi::receiveMessages)
+		postfachFacadeMockServer.stubFor(get(urlPathEqualTo("/MessageExchange/v1/Receive"))
+				.withQueryParam("take", equalTo("100"))
+				.withQueryParam("skip", equalTo("0"))
+				.willReturn(okJsonObj(MessageExchangeReceiveMessagesResponseTestFactory.create(uuids)))
+		);
+		for (String uuid : uuids) {
+			// Stub individual response for message (MessageExchangeApi::getMessage)
+			postfachFacadeMockServer.stubFor(get(urlPathTemplate("/MessageExchange/v1/Receive/{messageId}"))
+					.withPathParam("messageId", equalTo(uuid))
+					.willReturn(okJsonObj(
+							V1ReplyMessageTestFactory.create()
+									.messageBox(UUID.fromString(uuid))
+									.responseTime(OffsetDateTime.now())
+					))
+			);
+		}
+	}
+
+	private ResponseDefinitionBuilder okJsonObj(final Object body) {
+		return okJson(JsonUtil.toJson(body));
+	}
+
+	@DisplayName("should delete message")
+	@Test
+	@SneakyThrows
+	void shouldDeleteMessage() {
+		var messageId = "00000000-0000-0000-0000-000000000000";
+		// Stub delete message response (MessageExchangeApi::deleteMessage)
+		postfachFacadeMockServer.stubFor(delete(urlPathTemplate("/MessageExchange/v1/Delete/{messageId}"))
+				.willReturn(ok())
+		);
+
+		osiPostfachRemoteService.deleteMessage(messageId);
+
+		postfachFacadeMockServer.verify(
+				exactly(1),
+				deleteRequestedFor(urlPathTemplate("/MessageExchange/v1/Delete/{messageId}"))
+						.withPathParam("messageId", equalTo(messageId))
+		);
+	}
+}
diff --git a/src/test/java/de/ozgcloud/nachrichten/postfach/osiv2/OsiPostfachRemoteServiceSecondaryITCase.java b/src/test/java/de/ozgcloud/nachrichten/postfach/osiv2/OsiPostfachRemoteServiceSecondaryITCase.java
new file mode 100644
index 0000000000000000000000000000000000000000..33f22c4feb9b2da7e871d7aa2b01cbdbcbf83385
--- /dev/null
+++ b/src/test/java/de/ozgcloud/nachrichten/postfach/osiv2/OsiPostfachRemoteServiceSecondaryITCase.java
@@ -0,0 +1,31 @@
+package de.ozgcloud.nachrichten.postfach.osiv2;
+
+import static org.assertj.core.api.Assertions.*;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+
+import de.ozgcloud.nachrichten.postfach.PostfachMessageCode;
+import de.ozgcloud.nachrichten.postfach.osiv2.exception.Osi2PostfachException;
+import de.ozgcloud.nachrichten.postfach.osiv2.factory.PostfachNachrichtTestFactory;
+
+class OsiPostfachRemoteServiceSecondaryITCase extends OsiPostfachRemoteServiceITCase {
+
+	@BeforeEach
+	void mock() {
+		// Stopping the mock server
+		// Note that a restarting the server will configure a new unknown port.
+		postfachFacadeMockServer.stop();
+	}
+
+	@DisplayName("should throw postfach exception with connection error code")
+	@Test
+	void shouldThrowPostfachExceptionWithConnectionErrorCode() {
+		var postfachNachricht = PostfachNachrichtTestFactory.create();
+
+		assertThatThrownBy(() -> osiPostfachRemoteService.sendMessage(postfachNachricht))
+				.isInstanceOf(Osi2PostfachException.class)
+				.hasFieldOrPropertyWithValue("messageCode", PostfachMessageCode.SERVER_CONNECTION_FAILED_MESSAGE_CODE);
+	}
+}
diff --git a/src/test/java/de/ozgcloud/nachrichten/postfach/osiv2/storage/ConcatInputStreamTest.java b/src/test/java/de/ozgcloud/nachrichten/postfach/osiv2/attachment/ConcatInputStreamTest.java
similarity index 92%
rename from src/test/java/de/ozgcloud/nachrichten/postfach/osiv2/storage/ConcatInputStreamTest.java
rename to src/test/java/de/ozgcloud/nachrichten/postfach/osiv2/attachment/ConcatInputStreamTest.java
index 43509c4f41a84aee5c19887d496c00cefbf3842b..9340fe2ab0ae140e324cf7b1ccff1c2b60b65376 100644
--- a/src/test/java/de/ozgcloud/nachrichten/postfach/osiv2/storage/ConcatInputStreamTest.java
+++ b/src/test/java/de/ozgcloud/nachrichten/postfach/osiv2/attachment/ConcatInputStreamTest.java
@@ -1,4 +1,4 @@
-package de.ozgcloud.nachrichten.postfach.osiv2.storage;
+package de.ozgcloud.nachrichten.postfach.osiv2.attachment;
 
 import static org.assertj.core.api.Assertions.*;
 import static org.junit.jupiter.api.Assertions.*;
@@ -10,7 +10,6 @@ import org.apache.commons.io.IOUtils;
 import org.junit.jupiter.api.DisplayName;
 import org.junit.jupiter.api.Test;
 
-import de.ozgcloud.nachrichten.postfach.osiv2.attachment.ConcatInputStream;
 import lombok.SneakyThrows;
 
 class ConcatInputStreamTest {
@@ -70,7 +69,7 @@ class ConcatInputStreamTest {
 	}
 
 	private ConcatInputStream createExampleConcatInputStream() {
-		var streams = Stream.of("1234","567","", "89")
+		var streams = Stream.of("1234", "567", "", "89")
 				.map(s -> new ByteArrayInputStream(s.getBytes()))
 				.toList();
 		return new ConcatInputStream(streams.iterator());
diff --git a/src/test/java/de/ozgcloud/nachrichten/postfach/osiv2/storage/LimitedInputStreamTest.java b/src/test/java/de/ozgcloud/nachrichten/postfach/osiv2/attachment/LimitedInputStreamTest.java
similarity index 90%
rename from src/test/java/de/ozgcloud/nachrichten/postfach/osiv2/storage/LimitedInputStreamTest.java
rename to src/test/java/de/ozgcloud/nachrichten/postfach/osiv2/attachment/LimitedInputStreamTest.java
index b9eae5d2e6c1d14cfa7580a76f1fb891f4bcd0eb..880c800901bce17d30e5ab2cd38a21647b2e5206 100644
--- a/src/test/java/de/ozgcloud/nachrichten/postfach/osiv2/storage/LimitedInputStreamTest.java
+++ b/src/test/java/de/ozgcloud/nachrichten/postfach/osiv2/attachment/LimitedInputStreamTest.java
@@ -1,4 +1,4 @@
-package de.ozgcloud.nachrichten.postfach.osiv2.storage;
+package de.ozgcloud.nachrichten.postfach.osiv2.attachment;
 
 import static org.junit.jupiter.api.Assertions.*;
 
@@ -8,7 +8,6 @@ import org.apache.commons.io.IOUtils;
 import org.junit.jupiter.api.DisplayName;
 import org.junit.jupiter.api.Test;
 
-import de.ozgcloud.nachrichten.postfach.osiv2.attachment.LimitedInputStream;
 import lombok.SneakyThrows;
 
 class LimitedInputStreamTest {
diff --git a/src/test/java/de/ozgcloud/nachrichten/postfach/osiv2/factory/FileChunkInfoTestFactory.java b/src/test/java/de/ozgcloud/nachrichten/postfach/osiv2/factory/FileChunkInfoTestFactory.java
index 56d8f10addabdaf746ac3df4bfacf048b7d61de0..f720d9bf668a6cff0a7401c249142d7e23d10aac 100644
--- a/src/test/java/de/ozgcloud/nachrichten/postfach/osiv2/factory/FileChunkInfoTestFactory.java
+++ b/src/test/java/de/ozgcloud/nachrichten/postfach/osiv2/factory/FileChunkInfoTestFactory.java
@@ -1,7 +1,6 @@
 package de.ozgcloud.nachrichten.postfach.osiv2.factory;
 
 import de.ozgcloud.nachrichten.postfach.osiv2.model.FileChunkInfo;
-import de.ozgcloud.nachrichten.postfach.osiv2.transfer.Osi2FileUploadTestFactory;
 
 public class FileChunkInfoTestFactory {
 
diff --git a/src/test/java/de/ozgcloud/nachrichten/postfach/osiv2/transfer/Osi2FileUploadTestFactory.java b/src/test/java/de/ozgcloud/nachrichten/postfach/osiv2/factory/Osi2FileUploadTestFactory.java
similarity index 95%
rename from src/test/java/de/ozgcloud/nachrichten/postfach/osiv2/transfer/Osi2FileUploadTestFactory.java
rename to src/test/java/de/ozgcloud/nachrichten/postfach/osiv2/factory/Osi2FileUploadTestFactory.java
index b9cf5d26efd8c35da40219aeb089068087b838e7..195148874625ce607380e776832ddc1222e8e1bf 100644
--- a/src/test/java/de/ozgcloud/nachrichten/postfach/osiv2/transfer/Osi2FileUploadTestFactory.java
+++ b/src/test/java/de/ozgcloud/nachrichten/postfach/osiv2/factory/Osi2FileUploadTestFactory.java
@@ -1,4 +1,4 @@
-package de.ozgcloud.nachrichten.postfach.osiv2.transfer;
+package de.ozgcloud.nachrichten.postfach.osiv2.factory;
 
 import static de.ozgcloud.nachrichten.postfach.osiv2.model.Osi2FileUpload.*;
 
diff --git a/src/test/java/de/ozgcloud/nachrichten/postfach/osiv2/factory/QuarantineFileResultTestFactory.java b/src/test/java/de/ozgcloud/nachrichten/postfach/osiv2/factory/QuarantineFileResultTestFactory.java
new file mode 100644
index 0000000000000000000000000000000000000000..c50c8233f6224e68f54c3c50e68a52f0bcef05d4
--- /dev/null
+++ b/src/test/java/de/ozgcloud/nachrichten/postfach/osiv2/factory/QuarantineFileResultTestFactory.java
@@ -0,0 +1,19 @@
+package de.ozgcloud.nachrichten.postfach.osiv2.factory;
+
+import com.thedeanda.lorem.LoremIpsum;
+
+import de.ozgcloud.nachrichten.postfach.osiv2.gen.model.QuarantineFileResult;
+
+public class QuarantineFileResultTestFactory {
+
+	public static final String ERROR_STRING = LoremIpsum.getInstance().getWords(10);
+
+	public static QuarantineFileResult create() {
+		return createBuilder().build();
+	}
+
+	public static QuarantineFileResult.Builder createBuilder() {
+		return QuarantineFileResult.builder()
+				.success(true);
+	}
+}
diff --git a/src/test/java/de/ozgcloud/nachrichten/postfach/osiv2/transfer/Osi2PostfachServiceTest.java b/src/test/java/de/ozgcloud/nachrichten/postfach/osiv2/transfer/Osi2PostfachServiceTest.java
index a0784519b193ca1b972c03bbcae0a9ca18db279b..1a70dd959ab3e4ccfaf7aed2ed48f9fe5f2e3d2c 100644
--- a/src/test/java/de/ozgcloud/nachrichten/postfach/osiv2/transfer/Osi2PostfachServiceTest.java
+++ b/src/test/java/de/ozgcloud/nachrichten/postfach/osiv2/transfer/Osi2PostfachServiceTest.java
@@ -15,6 +15,7 @@ import org.mockito.InjectMocks;
 import org.mockito.Mock;
 
 import de.ozgcloud.nachrichten.postfach.PostfachNachricht;
+import de.ozgcloud.nachrichten.postfach.osiv2.factory.Osi2FileUploadTestFactory;
 import de.ozgcloud.nachrichten.postfach.osiv2.factory.PostfachNachrichtTestFactory;
 import de.ozgcloud.nachrichten.postfach.osiv2.model.Osi2FileUpload;
 
@@ -88,7 +89,6 @@ class Osi2PostfachServiceTest {
 		private Stream<PostfachNachricht> receiveMessages() {
 			return osi2PostfachService.receiveMessages();
 		}
-
 	}
 
 }
\ No newline at end of file
diff --git a/src/test/java/de/ozgcloud/nachrichten/postfach/osiv2/transfer/Osi2QuarantineServiceTest.java b/src/test/java/de/ozgcloud/nachrichten/postfach/osiv2/transfer/Osi2QuarantineServiceTest.java
index 37039f8817204326ad9cbdbb12c07191423d83b9..0b903983a12284bb6997ae44d7c16c233efa690a 100644
--- a/src/test/java/de/ozgcloud/nachrichten/postfach/osiv2/transfer/Osi2QuarantineServiceTest.java
+++ b/src/test/java/de/ozgcloud/nachrichten/postfach/osiv2/transfer/Osi2QuarantineServiceTest.java
@@ -1,7 +1,7 @@
 package de.ozgcloud.nachrichten.postfach.osiv2.transfer;
 
+import static de.ozgcloud.nachrichten.postfach.osiv2.factory.Osi2FileUploadTestFactory.*;
 import static de.ozgcloud.nachrichten.postfach.osiv2.model.Osi2FileUpload.*;
-import static de.ozgcloud.nachrichten.postfach.osiv2.transfer.Osi2FileUploadTestFactory.*;
 import static de.ozgcloud.nachrichten.postfach.osiv2.transfer.Osi2QuarantineService.*;
 import static org.assertj.core.api.Assertions.*;
 import static org.mockito.Mockito.*;
@@ -25,12 +25,13 @@ import org.mockito.Spy;
 import de.ozgcloud.apilib.file.OzgCloudFile;
 import de.ozgcloud.apilib.file.OzgCloudFileId;
 import de.ozgcloud.apilib.file.OzgCloudFileTestFactory;
+import de.ozgcloud.nachrichten.postfach.osiv2.attachment.Osi2AttachmentFileService;
 import de.ozgcloud.nachrichten.postfach.osiv2.exception.Osi2RuntimeException;
 import de.ozgcloud.nachrichten.postfach.osiv2.exception.Osi2UploadException;
+import de.ozgcloud.nachrichten.postfach.osiv2.factory.Osi2FileUploadTestFactory;
 import de.ozgcloud.nachrichten.postfach.osiv2.gen.model.QuarantineStatus;
 import de.ozgcloud.nachrichten.postfach.osiv2.model.FileChunkInfo;
 import de.ozgcloud.nachrichten.postfach.osiv2.model.Osi2FileUpload;
-import de.ozgcloud.nachrichten.postfach.osiv2.attachment.Osi2AttachmentFileService;
 import lombok.SneakyThrows;
 
 class Osi2QuarantineServiceTest {
@@ -45,18 +46,35 @@ class Osi2QuarantineServiceTest {
 	@Mock
 	private Osi2AttachmentFileService binaryFileService;
 
+	private final OzgCloudFile file1 = OzgCloudFileTestFactory.createBuilder()
+			.id(new OzgCloudFileId(UPLOAD_FILE_ID))
+			.size(1000L)
+			.build();
+	private final OzgCloudFile file2 = OzgCloudFileTestFactory.createBuilder()
+			.size(2000L)
+			.build();
+
+	private final Osi2FileUpload upload1 = Osi2FileUploadTestFactory.createBuilder()
+			.file(file1)
+			.build();
+	private final Osi2FileUpload upload2 = Osi2FileUploadTestFactory.createBuilder()
+			.guid(UUID.randomUUID().toString())
+			.file(file2)
+			.build();
+	private final Osi2FileUpload upload3 = Osi2FileUploadTestFactory.createBuilder()
+			.guid(UUID.randomUUID().toString())
+			.build();
+
+	private final List<Osi2FileUpload> uploads = List.of(upload1, upload2, upload3);
+
 	@DisplayName("upload attachments")
 	@Nested
 	class TestUploadAttachments {
 
-		private final OzgCloudFile file = OzgCloudFileTestFactory.create();
-		private final Osi2FileUpload uploadFile = Osi2FileUploadTestFactory.create();
-
 		@BeforeEach
 		void mock() {
-			when(binaryFileService.getFileMetadataOfIds(any())).thenReturn(List.of(file));
-			doReturn(List.of(uploadFile)).when(service).uploadFilesToQuarantineWithLargestFileFirst(any());
-			doNothing().when(service).waitForVirusScan(any());
+			when(binaryFileService.getFileMetadataOfIds(any())).thenReturn(List.of(file1));
+			doReturn(List.of(upload1)).when(service).uploadFiles(any());
 		}
 
 		@DisplayName("should call getFileMetadataOfIds")
@@ -67,20 +85,12 @@ class Osi2QuarantineServiceTest {
 			verify(binaryFileService).getFileMetadataOfIds(List.of(UPLOAD_FILE_ID));
 		}
 
-		@DisplayName("should call uploadFilesToQuarantineWithLargestFileFirst")
+		@DisplayName("should call uploadFiles")
 		@Test
 		void shouldCallUploadFilesToQuarantineWithLargestFileFirst() {
 			uploadAttachments();
 
-			verify(service).uploadFilesToQuarantineWithLargestFileFirst(List.of(file));
-		}
-
-		@DisplayName("should call waitForVirusScan")
-		@Test
-		void shouldCallWaitForVirusScan() {
-			uploadAttachments();
-
-			verify(service).waitForVirusScan(List.of(uploadFile));
+			verify(service).uploadFiles(List.of(file1));
 		}
 
 		@DisplayName("should return")
@@ -88,7 +98,7 @@ class Osi2QuarantineServiceTest {
 		void shouldReturn() {
 			var result = uploadAttachments();
 
-			assertThat(result).containsExactly(uploadFile);
+			assertThat(result).containsExactly(upload1);
 		}
 
 		List<Osi2FileUpload> uploadAttachments() {
@@ -96,36 +106,155 @@ class Osi2QuarantineServiceTest {
 		}
 	}
 
-	@DisplayName("upload files to quarantine with largest file first")
+	@DisplayName("upload files")
 	@Nested
-	class TestUploadFilesToQuarantineWithLargestFileFirst {
+	class TestUploadFiles {
 
-		private final OzgCloudFile file1 = OzgCloudFileTestFactory.createBuilder()
-				.id(new OzgCloudFileId(UPLOAD_FILE_ID))
-				.size(1000L)
-				.build();
-		private final OzgCloudFile file2 = OzgCloudFileTestFactory.createBuilder()
-				.size(2000L)
-				.build();
-		private final Osi2FileUpload uploadFile1 = Osi2FileUploadTestFactory.createBuilder().file(file1).build();
-		private final Osi2FileUpload uploadFile2 = Osi2FileUploadTestFactory.createBuilder().file(file2).build();
+		@BeforeEach
+		void mock() {
+			doReturn(uploads).when(service).deriveSortedUploadFiles(any());
+		}
+
+		@DisplayName("without exception")
+		@Nested
+		class TestWithoutException {
+			@BeforeEach
+			void mock() {
+				doNothing().when(service).tryUploadSortedFiles(any());
+			}
+
+			@DisplayName("should call deriveSortedUploadFiles")
+			@Test
+			void shouldCallDeriveSortedUploadFiles() {
+				uploadFiles();
+
+				verify(service).deriveSortedUploadFiles(List.of(file1));
+			}
+
+			@DisplayName("should call uploadFiles")
+			@Test
+			void shouldCallUploadFilesToQuarantineWithLargestFileFirst() {
+				uploadFiles();
+
+				verify(service).tryUploadSortedFiles(uploads);
+			}
+
+			@DisplayName("should return")
+			@Test
+			void shouldReturn() {
+				var result = uploadFiles();
+
+				assertThat(result).containsExactly(upload1, upload2, upload3);
+			}
+		}
+
+		@DisplayName("with exception")
+		@Nested
+		class TestWithException {
+
+			private final RuntimeException exception = new RuntimeException("test");
+
+			@BeforeEach
+			void mock() {
+				doThrow(exception).when(service).tryUploadSortedFiles(any());
+			}
+
+			@DisplayName("should delete attachments")
+			@Test
+			void shouldDeleteAttachments() {
+				try {
+					uploadFiles();
+				} catch (RuntimeException e) {
+					// ignore
+				}
+
+				verify(service).deleteAttachments(uploads);
+			}
+
+			@DisplayName("should rethrow exception")
+			@Test
+			void shouldRethrowException() {
+				assertThatThrownBy(TestUploadFiles.this::uploadFiles)
+						.isEqualTo(exception);
+			}
+		}
+
+		List<Osi2FileUpload> uploadFiles() {
+			return service.uploadFiles(List.of(file1));
+		}
+	}
+
+	@DisplayName("derive sorted upload files")
+	@Nested
+	class TestDeriveSortedUploadFiles {
+
+		@DisplayName("should return largest file first")
+		@Test
+		void shouldReturnLargestFileFirst() {
+			var result = service.deriveSortedUploadFiles(List.of(file1, file2));
+
+			assertThat(result)
+					.extracting(Osi2FileUpload::file)
+					.containsExactly(file2, file1);
+		}
+
+		@DisplayName("should have random upload guid")
+		@Test
+		void shouldHaveRandomUploadGuid() {
+			var result = service.deriveSortedUploadFiles(List.of(file1, file2));
+
+			assertThat(result)
+					.extracting(Osi2FileUpload::guid)
+					.extracting(String::length)
+					.containsExactly(36, 36);
+		}
+	}
+
+	@DisplayName("try upload sorted files")
+	@Nested
+	class TestTryUploadSortedFiles {
 
 		@BeforeEach
 		void mock() {
-			doReturn(uploadFile1).when(service).uploadFileToQuarantine(file1);
-			doReturn(uploadFile2).when(service).uploadFileToQuarantine(file2);
+			doNothing().when(service).uploadFilesToQuarantine(any());
+			doNothing().when(service).waitForVirusScan(any());
 		}
 
-		@DisplayName("should return")
+		@DisplayName("should call uploadFilesToQuarantine")
 		@Test
-		void shouldReturn() {
-			var result = uploadFilesToQuarantineWithLargestFileFirst();
+		void shouldCallUploadFilesToQuarantine() {
+			tryUploadSortedFiles();
 
-			assertThat(result).containsExactly(uploadFile2, uploadFile1);
+			verify(service).uploadFilesToQuarantine(uploads);
 		}
 
-		List<Osi2FileUpload> uploadFilesToQuarantineWithLargestFileFirst() {
-			return service.uploadFilesToQuarantineWithLargestFileFirst(List.of(file1, file2));
+		@DisplayName("should call waitForVirusScan")
+		@Test
+		void shouldCallWaitForVirusScan() {
+			tryUploadSortedFiles();
+
+			verify(service).waitForVirusScan(uploads);
+		}
+
+		void tryUploadSortedFiles() {
+			service.tryUploadSortedFiles(uploads);
+		}
+	}
+
+	@DisplayName("upload files to quarantine")
+	@Nested
+	class TestUploadFilesToQuarantine {
+
+		@DisplayName("should call uploadFileToQuarantine for each file")
+		@Test
+		void shouldCallUploadFileToQuarantineForEachFile() {
+			doNothing().when(service).uploadFileToQuarantine(any());
+
+			service.uploadFilesToQuarantine(uploads);
+
+			verify(service).uploadFileToQuarantine(upload1);
+			verify(service).uploadFileToQuarantine(upload2);
+			verify(service).uploadFileToQuarantine(upload3);
 		}
 	}
 
@@ -136,13 +265,10 @@ class Osi2QuarantineServiceTest {
 		@Mock
 		private InputStream fileInputStream;
 
-		private final OzgCloudFile file = OzgCloudFileTestFactory.create();
-		private final Osi2FileUpload uploadFile = Osi2FileUploadTestFactory.create();
-
 		@BeforeEach
 		void mock() {
 			when(binaryFileService.downloadFileContent(any())).thenReturn(fileInputStream);
-			doReturn(uploadFile).when(service).uploadInputStreamToQuarantine(any(), any());
+			doNothing().when(service).uploadInputStreamToQuarantine(any(), any());
 		}
 
 		@DisplayName("should call streamFileContent")
@@ -150,7 +276,7 @@ class Osi2QuarantineServiceTest {
 		void shouldCallStreamFileContent() {
 			uploadFileToQuarantine();
 
-			verify(binaryFileService).downloadFileContent(OzgCloudFileTestFactory.ID_STR);
+			verify(binaryFileService).downloadFileContent(UPLOAD_FILE_ID);
 		}
 
 		@DisplayName("should call uploadInputStreamToQuarantine")
@@ -158,15 +284,7 @@ class Osi2QuarantineServiceTest {
 		void shouldCallUploadInputStreamToQuarantine() {
 			uploadFileToQuarantine();
 
-			verify(service).uploadInputStreamToQuarantine(file, fileInputStream);
-		}
-
-		@DisplayName("should return")
-		@Test
-		void shouldReturn() {
-			var result = uploadFileToQuarantine();
-
-			assertThat(result).isEqualTo(uploadFile);
+			verify(service).uploadInputStreamToQuarantine(upload1, fileInputStream);
 		}
 
 		@DisplayName("should close inputStream")
@@ -188,8 +306,8 @@ class Osi2QuarantineServiceTest {
 					.isInstanceOf(Osi2RuntimeException.class);
 		}
 
-		Osi2FileUpload uploadFileToQuarantine() {
-			return service.uploadFileToQuarantine(file);
+		void uploadFileToQuarantine() {
+			service.uploadFileToQuarantine(upload1);
 		}
 	}
 
@@ -225,16 +343,6 @@ class Osi2QuarantineServiceTest {
 	@Nested
 	class TestCheckVirusScanCompleted {
 
-		private final Osi2FileUpload upload1 = Osi2FileUploadTestFactory.create();
-		private final Osi2FileUpload upload2 = Osi2FileUploadTestFactory.createBuilder()
-				.guid(UUID.randomUUID().toString())
-				.build();
-		private final Osi2FileUpload upload3 = Osi2FileUploadTestFactory.createBuilder()
-				.guid(UUID.randomUUID().toString())
-				.build();
-
-		private final List<Osi2FileUpload> uploads = List.of(upload1, upload2, upload3);
-
 		@DisplayName("should return true if all virus scans completed")
 		@Test
 		void shouldReturnTrueIfAllVirusScansCompleted() {
@@ -302,8 +410,6 @@ class Osi2QuarantineServiceTest {
 	@DisplayName("wait for virus scan")
 	@Nested
 	class TestWaitForVirusScan {
-		private final Osi2FileUpload upload1 = Osi2FileUploadTestFactory.create();
-		private final List<Osi2FileUpload> uploads = List.of(upload1);
 
 		@DisplayName("should call waitUntil")
 		@Test
@@ -339,4 +445,18 @@ class Osi2QuarantineServiceTest {
 		}
 	}
 
+	@DisplayName("delete attachments")
+	@Nested
+	class TestDeleteAttachments {
+		@DisplayName("should call deleteAttachment for uploads")
+		@Test
+		void shouldCallDeleteAttachmentForUploads() {
+			service.deleteAttachments(uploads);
+
+			verify(postfachApiFacadeService).deleteFileUpload(upload1.guid());
+			verify(postfachApiFacadeService).deleteFileUpload(upload2.guid());
+			verify(postfachApiFacadeService).deleteFileUpload(upload3.guid());
+		}
+	}
+
 }
\ No newline at end of file
diff --git a/src/test/java/de/ozgcloud/nachrichten/postfach/osiv2/transfer/Osi2RequestMapperTest.java b/src/test/java/de/ozgcloud/nachrichten/postfach/osiv2/transfer/Osi2RequestMapperTest.java
index eef22fd05e33f867fbdbabe63d6bfe4668e1e5de..b43843d0651a8236217cf6d99b7966a8b8c7a40a 100644
--- a/src/test/java/de/ozgcloud/nachrichten/postfach/osiv2/transfer/Osi2RequestMapperTest.java
+++ b/src/test/java/de/ozgcloud/nachrichten/postfach/osiv2/transfer/Osi2RequestMapperTest.java
@@ -1,8 +1,8 @@
 package de.ozgcloud.nachrichten.postfach.osiv2.transfer;
 
+import static de.ozgcloud.nachrichten.postfach.osiv2.factory.Osi2FileUploadTestFactory.*;
 import static de.ozgcloud.nachrichten.postfach.osiv2.factory.PostfachAddressTestFactory.*;
 import static de.ozgcloud.nachrichten.postfach.osiv2.factory.PostfachNachrichtTestFactory.*;
-import static de.ozgcloud.nachrichten.postfach.osiv2.transfer.Osi2FileUploadTestFactory.*;
 import static java.util.Collections.*;
 import static org.assertj.core.api.Assertions.*;
 
@@ -23,6 +23,7 @@ import de.ozgcloud.nachrichten.postfach.PostfachAddress;
 import de.ozgcloud.nachrichten.postfach.PostfachAddressIdentifier;
 import de.ozgcloud.nachrichten.postfach.PostfachNachricht;
 import de.ozgcloud.nachrichten.postfach.osiv2.factory.FileChunkInfoTestFactory;
+import de.ozgcloud.nachrichten.postfach.osiv2.factory.Osi2FileUploadTestFactory;
 import de.ozgcloud.nachrichten.postfach.osiv2.factory.PostfachAddressTestFactory;
 import de.ozgcloud.nachrichten.postfach.osiv2.factory.PostfachNachrichtTestFactory;
 import de.ozgcloud.nachrichten.postfach.osiv2.gen.model.DomainChunkMetaData;
@@ -303,7 +304,7 @@ class Osi2RequestMapperTest {
 		void shouldMapTotalChunks() {
 			var result = doMapping();
 
-			assertThat(result.getTotalChunks()).isEqualTo(NUMBER_OF_CHUNKS);
+			assertThat(result.getTotalChunks()).isEqualTo((int) NUMBER_OF_CHUNKS);
 		}
 
 		@DisplayName("should map total file size")
diff --git a/src/test/java/de/ozgcloud/nachrichten/postfach/osiv2/transfer/Osi2ResponseMapperTest.java b/src/test/java/de/ozgcloud/nachrichten/postfach/osiv2/transfer/Osi2ResponseMapperTest.java
index 0bc446ff315bc6e282b3c6d57e0445a216a70ecf..6001100c5a47497528e114ebc208432dbe5917ba 100644
--- a/src/test/java/de/ozgcloud/nachrichten/postfach/osiv2/transfer/Osi2ResponseMapperTest.java
+++ b/src/test/java/de/ozgcloud/nachrichten/postfach/osiv2/transfer/Osi2ResponseMapperTest.java
@@ -1,5 +1,6 @@
 package de.ozgcloud.nachrichten.postfach.osiv2.transfer;
 
+import static de.ozgcloud.nachrichten.postfach.osiv2.factory.QuarantineFileResultTestFactory.*;
 import static de.ozgcloud.nachrichten.postfach.osiv2.factory.V1ReplyMessageTestFactory.*;
 import static org.assertj.core.api.Assertions.*;
 
@@ -19,6 +20,8 @@ import de.ozgcloud.nachrichten.postfach.PostfachNachricht;
 import de.ozgcloud.nachrichten.postfach.osiv2.exception.Osi2RuntimeException;
 import de.ozgcloud.nachrichten.postfach.osiv2.exception.Osi2UploadException;
 import de.ozgcloud.nachrichten.postfach.osiv2.factory.MessageExchangeReceiveMessagesResponseTestFactory;
+import de.ozgcloud.nachrichten.postfach.osiv2.factory.Osi2FileUploadTestFactory;
+import de.ozgcloud.nachrichten.postfach.osiv2.factory.QuarantineFileResultTestFactory;
 import de.ozgcloud.nachrichten.postfach.osiv2.factory.V1ReplyMessageTestFactory;
 import de.ozgcloud.nachrichten.postfach.osiv2.gen.model.QuarantineStatus;
 import de.ozgcloud.nachrichten.postfach.osiv2.gen.model.V1ReplyBehavior;
@@ -230,4 +233,32 @@ class Osi2ResponseMapperTest {
 			assertThat(result).isTrue();
 		}
 	}
+
+	@DisplayName("check chunk upload success")
+	@Nested
+	class TestCheckChunkUploadSuccess {
+
+		@DisplayName("should return")
+		@Test
+		void shouldReturn() {
+			var quarantineFileResult = QuarantineFileResultTestFactory.create();
+
+			mapper.checkChunkUploadSuccess(quarantineFileResult);
+		}
+
+		@DisplayName("should throw on failed upload")
+		@Test
+		void shouldThrowOnFailedUpload() {
+			var quarantineFileResult = QuarantineFileResultTestFactory.createBuilder()
+					.success(false)
+					.error(ERROR_STRING)
+					.fileUid(Osi2FileUploadTestFactory.UPLOAD_GUID)
+					.build();
+
+			assertThatThrownBy(() -> mapper.checkChunkUploadSuccess(quarantineFileResult))
+					.isInstanceOf(Osi2RuntimeException.class)
+					.hasMessageContaining(ERROR_STRING)
+					.hasMessageContaining(Osi2FileUploadTestFactory.UPLOAD_GUID);
+		}
+	}
 }
diff --git a/src/test/java/de/ozgcloud/nachrichten/postfach/osiv2/transfer/PostfachApiFacadeServiceTest.java b/src/test/java/de/ozgcloud/nachrichten/postfach/osiv2/transfer/PostfachApiFacadeServiceTest.java
index 6117bc3e23f9702a6fee0fbcedfae106ec296654..95a4ab65500ecabd9dc1366528039fc368441080 100644
--- a/src/test/java/de/ozgcloud/nachrichten/postfach/osiv2/transfer/PostfachApiFacadeServiceTest.java
+++ b/src/test/java/de/ozgcloud/nachrichten/postfach/osiv2/transfer/PostfachApiFacadeServiceTest.java
@@ -22,6 +22,7 @@ import de.ozgcloud.nachrichten.postfach.PostfachNachricht;
 import de.ozgcloud.nachrichten.postfach.osiv2.config.Osi2PostfachProperties;
 import de.ozgcloud.nachrichten.postfach.osiv2.factory.FileChunkInfoTestFactory;
 import de.ozgcloud.nachrichten.postfach.osiv2.factory.MessageExchangeReceiveMessagesResponseTestFactory;
+import de.ozgcloud.nachrichten.postfach.osiv2.factory.Osi2FileUploadTestFactory;
 import de.ozgcloud.nachrichten.postfach.osiv2.factory.PostfachNachrichtTestFactory;
 import de.ozgcloud.nachrichten.postfach.osiv2.gen.api.MessageExchangeApi;
 import de.ozgcloud.nachrichten.postfach.osiv2.gen.api.QuarantineApi;
@@ -306,4 +307,16 @@ class PostfachApiFacadeServiceTest {
 		}
 	}
 
+	@DisplayName("delete file upload")
+	@Nested
+	class TestDeleteFileUpload {
+		@DisplayName("should call deleteUpload")
+		@Test
+		void shouldCallDeleteUpload() {
+			service.deleteFileUpload(MESSAGE_ID_1);
+
+			verify(quarantineApi).deleteUpload(UUID.fromString(MESSAGE_ID_1));
+		}
+	}
+
 }
\ No newline at end of file