diff --git a/src/app/core/cache/models/normalized-object-factory.ts b/src/app/core/cache/models/normalized-object-factory.ts
index 786b5b3de30ec2f2664b090182bdac12780185ec..3e0c805be8f01f5e232cebd00c280e422d815ac1 100644
--- a/src/app/core/cache/models/normalized-object-factory.ts
+++ b/src/app/core/cache/models/normalized-object-factory.ts
@@ -12,6 +12,8 @@ import { NormalizedWorkspaceItem } from '../../submission/models/normalized-work
 import { NormalizedEPerson } from '../../eperson/models/normalized-eperson.model';
 import { NormalizedGroup } from '../../eperson/models/normalized-group.model';
 import { NormalizedWorkflowItem } from '../../submission/models/normalized-workflowitem.model';
+import { NormalizedClaimedTask } from '../../tasks/models/normalized-claimed-task-object.model';
+import { NormalizedPoolTask } from '../../tasks/models/normalized-pool-task-object.model';
 import { NormalizedBitstreamFormat } from './normalized-bitstream-format.model';
 import { SubmissionDefinitionsModel } from '../../config/models/config-submission-definitions.model';
 import { SubmissionFormsModel } from '../../config/models/config-submission-forms.model';
@@ -63,6 +65,12 @@ export class NormalizedObjectFactory {
       case ResourceType.Workflowitem: {
         return NormalizedWorkflowItem
       }
+      case ResourceType.ClaimedTask: {
+        return NormalizedClaimedTask
+      }
+      case ResourceType.PoolTask: {
+        return NormalizedPoolTask
+      }
       case ResourceType.BitstreamFormat: {
         return NormalizedBitstreamFormat
       }
diff --git a/src/app/core/cache/response.models.ts b/src/app/core/cache/response.models.ts
index 5640ac54d2474181fa98e322f5007bb06bd4ea76..8034a904136a56035524f7a6e5c75b31b879e92b 100644
--- a/src/app/core/cache/response.models.ts
+++ b/src/app/core/cache/response.models.ts
@@ -253,4 +253,28 @@ export class EpersonSuccessResponse extends RestResponse {
   }
 }
 
+export class MessageResponse extends RestResponse {
+  public toCache = false;
+
+  constructor(
+    public statusCode: number,
+    public statusText: string,
+    public pageInfo?: PageInfo
+  ) {
+    super(true, statusCode, statusText);
+  }
+}
+
+export class TaskResponse extends RestResponse {
+  public toCache = false;
+
+  constructor(
+    public statusCode: number,
+    public statusText: string,
+    public pageInfo?: PageInfo
+  ) {
+    super(true, statusCode, statusText);
+  }
+}
+
 /* tslint:enable:max-classes-per-file */
diff --git a/src/app/core/core.module.ts b/src/app/core/core.module.ts
index 7106d28b3d3e914f6c608e66cc261f2c3eca6143..ce8e3397d22c646dcc473d7be712b1a34218fe21 100644
--- a/src/app/core/core.module.ts
+++ b/src/app/core/core.module.ts
@@ -77,6 +77,14 @@ import { MenuService } from '../shared/menu/menu.service';
 import { SubmissionJsonPatchOperationsService } from './submission/submission-json-patch-operations.service';
 import { NormalizedObjectBuildService } from './cache/builders/normalized-object-build.service';
 import { DSOChangeAnalyzer } from './data/dso-change-analyzer.service';
+import { RoleService } from './roles/role.service';
+import { MyDSpaceGuard } from '../+my-dspace-page/my-dspace.guard';
+import { MyDSpaceResponseParsingService } from './data/mydspace-response-parsing.service';
+import { MessageService } from './message/message.service';
+import { ClaimedTaskDataService } from './tasks/claimed-task-data.service';
+import { PoolTaskDataService } from './tasks/pool-task-data.service';
+import { TaskResponseParsingService } from './tasks/task-response-parsing.service';
+import { MessageResponseParsingService } from './message/message-response-parsing.service';
 
 const IMPORTS = [
   CommonModule,
@@ -128,6 +136,7 @@ const PROVIDERS = [
   RegistryBitstreamformatsResponseParsingService,
   DebugResponseParsingService,
   SearchResponseParsingService,
+  MyDSpaceResponseParsingService,
   ServerResponseService,
   BrowseResponseParsingService,
   BrowseEntriesResponseParsingService,
@@ -156,6 +165,13 @@ const PROVIDERS = [
   DSOChangeAnalyzer,
   CSSVariableService,
   MenuService,
+  MyDSpaceGuard,
+  RoleService,
+  MessageResponseParsingService,
+  MessageService,
+  TaskResponseParsingService,
+  ClaimedTaskDataService,
+  PoolTaskDataService,
   // register AuthInterceptor as HttpInterceptor
   {
     provide: HTTP_INTERCEPTORS,
@@ -166,15 +182,20 @@ const PROVIDERS = [
   { provide: NativeWindowService, useFactory: NativeWindowFactory }
 ];
 
+const DIRECTIVES = [
+];
+
 @NgModule({
   imports: [
     ...IMPORTS
   ],
   declarations: [
-    ...DECLARATIONS
+    ...DECLARATIONS,
+    ...DIRECTIVES
   ],
   exports: [
-    ...EXPORTS
+    ...EXPORTS,
+    ...DIRECTIVES
   ],
   providers: [
     ...PROVIDERS
diff --git a/src/app/core/data/base-response-parsing.service.ts b/src/app/core/data/base-response-parsing.service.ts
index 6102f930b0833dec39fca3d20f49f4417f0d4489..57cd8c2cb9772721b5807ffa5396ac00e804d9e4 100644
--- a/src/app/core/data/base-response-parsing.service.ts
+++ b/src/app/core/data/base-response-parsing.service.ts
@@ -160,4 +160,10 @@ export abstract class BaseResponseParsingService {
     }
     return obj;
   }
+
+  protected isSuccessStatus(statusCode: number) {
+    return (statusCode === 201
+      || statusCode === 200
+      || statusCode === 204)
+  }
 }
diff --git a/src/app/core/data/collection-data.service.ts b/src/app/core/data/collection-data.service.ts
index 55642a181c3119ba9aaf20466ce936a63c28ecaf..d02e42ce79d2dfd70c2f39ffbc531af43fce26df 100644
--- a/src/app/core/data/collection-data.service.ts
+++ b/src/app/core/data/collection-data.service.ts
@@ -1,5 +1,8 @@
 import { Injectable } from '@angular/core';
+
+import { filter, map, take } from 'rxjs/operators';
 import { Store } from '@ngrx/store';
+
 import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
 import { NormalizedCollection } from '../cache/models/normalized-collection.model';
 import { ObjectCacheService } from '../cache/object-cache.service';
@@ -13,6 +16,10 @@ import { NotificationsService } from '../../shared/notifications/notifications.s
 import { HttpClient } from '@angular/common/http';
 import { NormalizedObjectBuildService } from '../cache/builders/normalized-object-build.service';
 import { DSOChangeAnalyzer } from './dso-change-analyzer.service';
+import { Observable } from 'rxjs/internal/Observable';
+import { FindAllOptions } from './request.models';
+import { RemoteData } from './remote-data';
+import { PaginatedList } from './paginated-list';
 
 @Injectable()
 export class CollectionDataService extends ComColDataService<NormalizedCollection, Collection> {
@@ -34,4 +41,21 @@ export class CollectionDataService extends ComColDataService<NormalizedCollectio
     super();
   }
 
+  /**
+   * Find whether there is a collection whom user has authorization to submit to
+   *
+   * @return boolean
+   *    true if the user has at least one collection to submit to
+   */
+  hasAuthorizedCollection(): Observable<boolean> {
+    const searchHref = 'findAuthorized';
+    const options = new FindAllOptions();
+    options.elementsPerPage = 1;
+
+    return this.searchBy(searchHref, options).pipe(
+      filter((collections: RemoteData<PaginatedList<Collection>>) => !collections.isResponsePending),
+      take(1),
+      map((collections: RemoteData<PaginatedList<Collection>>) => collections.payload.totalElements > 0)
+    );
+  }
 }
diff --git a/src/app/core/data/mydspace-response-parsing.service.ts b/src/app/core/data/mydspace-response-parsing.service.ts
new file mode 100644
index 0000000000000000000000000000000000000000..a99661faed44d8eeee8295d1723e3f2f1d567617
--- /dev/null
+++ b/src/app/core/data/mydspace-response-parsing.service.ts
@@ -0,0 +1,59 @@
+import { Injectable } from '@angular/core';
+import { RestResponse, SearchSuccessResponse } from '../cache/response.models';
+import { DSOResponseParsingService } from './dso-response-parsing.service';
+import { ResponseParsingService } from './parsing.service';
+import { RestRequest } from './request.models';
+import { DSpaceRESTV2Response } from '../dspace-rest-v2/dspace-rest-v2-response.model';
+import { DSpaceRESTv2Serializer } from '../dspace-rest-v2/dspace-rest-v2.serializer';
+import { hasValue } from '../../shared/empty.util';
+import { SearchQueryResponse } from '../../+search-page/search-service/search-query-response.model';
+import { MetadataMap, MetadataValue } from '../shared/metadata.interfaces';
+
+@Injectable()
+export class MyDSpaceResponseParsingService implements ResponseParsingService {
+  constructor(private dsoParser: DSOResponseParsingService) {
+  }
+
+  parse(request: RestRequest, data: DSpaceRESTV2Response): RestResponse {
+    // fallback for unexpected empty response
+    const emptyPayload = {
+      _embedded : {
+        objects: []
+      }
+    };
+    const payload = data.payload._embedded.searchResult || emptyPayload;
+    const hitHighlights: MetadataMap[] = payload._embedded.objects
+      .map((object) => object.hitHighlights)
+      .map((hhObject) => {
+        const mdMap: MetadataMap = {};
+        if (hhObject) {
+          for (const key of Object.keys(hhObject)) {
+            const value: MetadataValue = { value: hhObject[key].join('...'), language: null };
+            mdMap[key] = [ value ];
+          }
+        }
+        return mdMap;
+      });
+
+    const dsoSelfLinks = payload._embedded.objects
+      .filter((object) => hasValue(object._embedded))
+      .map((object) => object._embedded.rObject)
+      .map((dso) => this.dsoParser.parse(request, {
+        payload: dso,
+        statusCode: data.statusCode,
+        statusText: data.statusText
+      }))
+      .map((obj) => obj.resourceSelfLinks)
+      .reduce((combined, thisElement) => [...combined, ...thisElement], []);
+
+    const objects = payload._embedded.objects
+      .filter((object) => hasValue(object._embedded))
+      .map((object, index) => Object.assign({}, object, {
+        rObject: dsoSelfLinks[index],
+        hitHighlights: hitHighlights[index]
+      }));
+    payload.objects = objects;
+    const deserialized = new DSpaceRESTv2Serializer(SearchQueryResponse).deserialize(payload);
+    return new SearchSuccessResponse(deserialized, data.statusCode, data.statusText, this.dsoParser.processPageInfo(payload));
+  }
+}
diff --git a/src/app/core/data/paginated-list.ts b/src/app/core/data/paginated-list.ts
index 8efdccd75d55a77cd15ef531976b87d1ce1dd3f8..e1c1b22569b752593030e4f28a45f25f6323ac7a 100644
--- a/src/app/core/data/paginated-list.ts
+++ b/src/app/core/data/paginated-list.ts
@@ -11,7 +11,7 @@ export class PaginatedList<T> {
     if (hasValue(this.pageInfo) && hasValue(this.pageInfo.elementsPerPage)) {
       return this.pageInfo.elementsPerPage;
     }
-    return this.page.length;
+    return this.getPageLength();
   }
 
   set elementsPerPage(value: number) {
@@ -22,7 +22,7 @@ export class PaginatedList<T> {
     if (hasValue(this.pageInfo) && hasValue(this.pageInfo.totalElements)) {
       return this.pageInfo.totalElements;
     }
-    return this.page.length;
+    return this.getPageLength();
   }
 
   set totalElements(value: number) {
@@ -89,4 +89,8 @@ export class PaginatedList<T> {
   set self(self: string) {
     this.pageInfo.self = self;
   }
+
+  protected getPageLength() {
+    return (Array.isArray(this.page)) ? this.page.length : 0;
+  }
 }
diff --git a/src/app/core/data/request.models.ts b/src/app/core/data/request.models.ts
index 1afd24962c43c9e87276ed81026386cf36c5473c..40680c9a377f95e3a0e29dea96e1a846ef6b2799 100644
--- a/src/app/core/data/request.models.ts
+++ b/src/app/core/data/request.models.ts
@@ -17,6 +17,8 @@ import { BrowseItemsResponseParsingService } from './browse-items-response-parsi
 import { RegistryMetadataschemasResponseParsingService } from './registry-metadataschemas-response-parsing.service';
 import { MetadataschemaParsingService } from './metadataschema-parsing.service';
 import { MetadatafieldParsingService } from './metadatafield-parsing.service';
+import { TaskResponseParsingService } from '../tasks/task-response-parsing.service';
+import { MessageResponseParsingService } from '../message/message-response-parsing.service';
 
 /* tslint:disable:max-classes-per-file */
 
@@ -370,6 +372,46 @@ export class DeleteByIDRequest extends DeleteRequest {
   }
 }
 
+export class MessagePostRequest extends PostRequest {
+  constructor(uuid: string, href: string, public body?: any, public options?: HttpOptions) {
+    super(uuid, href, body, options);
+  }
+
+  getResponseParser(): GenericConstructor<ResponseParsingService> {
+    return MessageResponseParsingService;
+  }
+}
+
+export class MessageGetRequest extends GetRequest {
+  constructor(uuid: string, href: string, public options?: HttpOptions) {
+    super(uuid, href, null, options);
+  }
+
+  getResponseParser(): GenericConstructor<ResponseParsingService> {
+    return MessageResponseParsingService;
+  }
+}
+
+export class TaskPostRequest extends PostRequest {
+  constructor(uuid: string, href: string, public body?: any, public options?: HttpOptions) {
+    super(uuid, href, body, options);
+  }
+
+  getResponseParser(): GenericConstructor<ResponseParsingService> {
+    return TaskResponseParsingService;
+  }
+}
+
+export class TaskDeleteRequest extends DeleteRequest {
+  constructor(uuid: string, href: string, public body?: any, public options?: HttpOptions) {
+    super(uuid, href, body, options);
+  }
+
+  getResponseParser(): GenericConstructor<ResponseParsingService> {
+    return TaskResponseParsingService;
+  }
+}
+
 export class RequestError extends Error {
   statusCode: number;
   statusText: string;
diff --git a/src/app/core/data/request.service.ts b/src/app/core/data/request.service.ts
index 5c5f0880e0af12d36912ceddcd99ae2829e68649..da1857b1c0d5e5c7f8d4ebccde04aeb5ec8bc5df 100644
--- a/src/app/core/data/request.service.ts
+++ b/src/app/core/data/request.service.ts
@@ -4,7 +4,7 @@ import { createSelector, MemoizedSelector, select, Store } from '@ngrx/store';
 import { merge as observableMerge, Observable, of as observableOf, race as observableRace } from 'rxjs';
 import { filter, map, mergeMap, switchMap, take } from 'rxjs/operators';
 
-import { hasNoValue, hasValue, isNotEmpty } from '../../shared/empty.util';
+import { hasNoValue, hasValue, isEmpty, isNotEmpty } from '../../shared/empty.util';
 import { CacheableObject } from '../cache/object-cache.reducer';
 import { ObjectCacheService } from '../cache/object-cache.service';
 import { DSOSuccessResponse, RestResponse } from '../cache/response.models';
@@ -123,9 +123,9 @@ export class RequestService {
   // TODO to review "forceBypassCache" param when https://github.com/DSpace/dspace-angular/issues/217 will be fixed
   configure<T extends CacheableObject>(request: RestRequest, forceBypassCache: boolean = false): void {
     const isGetRequest = request.method === RestRequestMethod.GET;
-    if (!isGetRequest || !this.isCachedOrPending(request) || forceBypassCache) {
+    if (!isGetRequest || !this.isCachedOrPending(request) || (forceBypassCache && !this.isPending(request))) {
       this.dispatchRequest(request);
-      if (isGetRequest && !forceBypassCache) {
+      if (isGetRequest) {
         this.trackRequestsOnTheirWayToTheStore(request);
       }
     } else {
@@ -139,6 +139,29 @@ export class RequestService {
     }
   }
 
+  /**
+   * Convert request Payload to a URL-encoded string
+   *
+   * e.g.  prepareBody({param: value, param1: value1})
+   * returns: param=value&param1=value1
+   *
+   * @param body
+   *    The request Payload to convert
+   * @return string
+   *    URL-encoded string
+   */
+  public prepareBody(body: any) {
+    let queryParams = '';
+    if (isNotEmpty(body) && typeof body === 'object') {
+      Object.keys(body)
+        .forEach((param) => {
+          const paramValue = `${param}=${body[param]}`;
+          queryParams = isEmpty(queryParams) ? queryParams.concat(paramValue) : queryParams.concat('&', paramValue);
+        })
+    }
+    return encodeURI(queryParams);
+  }
+
   /**
    * Remove all request cache providing (part of) the href
    * This also includes href-to-uuid index cache
diff --git a/src/app/core/data/search-response-parsing.service.ts b/src/app/core/data/search-response-parsing.service.ts
index 0e6ac0fd05731ae38e78ae3c0f913c50af3d91fe..fe9a3a241ea22f201a2f94d1f87ae97c57ffd36d 100644
--- a/src/app/core/data/search-response-parsing.service.ts
+++ b/src/app/core/data/search-response-parsing.service.ts
@@ -15,7 +15,13 @@ export class SearchResponseParsingService implements ResponseParsingService {
   }
 
   parse(request: RestRequest, data: DSpaceRESTV2Response): RestResponse {
-    const payload = data.payload._embedded.searchResult;
+    // fallback for unexpected empty response
+    const emptyPayload = {
+      _embedded : {
+        objects: []
+      }
+    };
+    const payload = data.payload._embedded.searchResult || emptyPayload;
     const hitHighlights: MetadataMap[] = payload._embedded.objects
       .map((object) => object.hitHighlights)
       .map((hhObject) => {
@@ -31,7 +37,7 @@ export class SearchResponseParsingService implements ResponseParsingService {
 
     const dsoSelfLinks = payload._embedded.objects
       .filter((object) => hasValue(object._embedded))
-      .map((object) => object._embedded.dspaceObject)
+      .map((object) => object._embedded.rObject)
       // we don't need embedded collections, bitstreamformats, etc for search results.
       // And parsing them all takes up a lot of time. Throw them away to improve performance
       // until objs until partial results are supported by the rest api
@@ -47,7 +53,7 @@ export class SearchResponseParsingService implements ResponseParsingService {
     const objects = payload._embedded.objects
       .filter((object) => hasValue(object._embedded))
       .map((object, index) => Object.assign({}, object, {
-        dspaceObject: dsoSelfLinks[index],
+        rObject: dsoSelfLinks[index],
         hitHighlights: hitHighlights[index],
         // we don't need embedded collections, bitstreamformats, etc for search results.
         // And parsing them all takes up a lot of time. Throw them away to improve performance
diff --git a/src/app/core/message/message-data-response.ts b/src/app/core/message/message-data-response.ts
new file mode 100644
index 0000000000000000000000000000000000000000..2c7432b18c2e9a48443a1d53fabffa5cf932e7d9
--- /dev/null
+++ b/src/app/core/message/message-data-response.ts
@@ -0,0 +1,17 @@
+import { RemoteDataError } from '../data/remote-data-error';
+
+/**
+ * A class to represent the data retrieved by after processing a message
+ */
+export class MessageDataResponse {
+  constructor(
+    private isSuccessful: boolean,
+    public error?: RemoteDataError,
+    public payload?: any
+  ) {
+  }
+
+  get hasSucceeded(): boolean {
+    return this.isSuccessful;
+  }
+}
diff --git a/src/app/core/message/message-response-parsing.service.ts b/src/app/core/message/message-response-parsing.service.ts
new file mode 100644
index 0000000000000000000000000000000000000000..1a08ab481d4b18c77883590e82a15cb83c30c0dc
--- /dev/null
+++ b/src/app/core/message/message-response-parsing.service.ts
@@ -0,0 +1,37 @@
+import { Inject, Injectable } from '@angular/core';
+
+import { ResponseParsingService } from '../data/parsing.service';
+import { RestRequest } from '../data/request.models';
+import { DSpaceRESTV2Response } from '../dspace-rest-v2/dspace-rest-v2-response.model';
+import { BaseResponseParsingService } from '../data/base-response-parsing.service';
+import { GLOBAL_CONFIG } from '../../../config';
+import { GlobalConfig } from '../../../config/global-config.interface';
+import { ObjectCacheService } from '../cache/object-cache.service';
+import { NormalizedSubmissionObjectFactory } from '../submission/normalized-submission-object-factory';
+import { ErrorResponse, MessageResponse, RestResponse } from '../cache/response.models';
+
+@Injectable()
+export class MessageResponseParsingService extends BaseResponseParsingService implements ResponseParsingService {
+
+  protected objectFactory = NormalizedSubmissionObjectFactory;
+  protected toCache = false;
+
+  constructor(@Inject(GLOBAL_CONFIG) protected EnvConfig: GlobalConfig,
+              protected objectCache: ObjectCacheService,) {
+    super();
+  }
+
+  parse(request: RestRequest, data: DSpaceRESTV2Response): RestResponse {
+    if (this.isSuccessStatus(data.statusCode)) {
+      return new MessageResponse( data.statusCode, data.statusText);
+    } else {
+      return new ErrorResponse(
+        Object.assign(
+          new Error('Unexpected response from server'),
+          { statusCode: data.statusCode, statusText: data.statusText }
+        )
+      );
+    }
+  }
+
+}
diff --git a/src/app/core/message/message.service.ts b/src/app/core/message/message.service.ts
new file mode 100644
index 0000000000000000000000000000000000000000..bbe761a443d573d40195533f97bbe5b9a8b48fd8
--- /dev/null
+++ b/src/app/core/message/message.service.ts
@@ -0,0 +1,107 @@
+import { Injectable } from '@angular/core';
+import { HttpHeaders } from '@angular/common/http';
+
+import { merge as observableMerge, Observable, of as observableOf } from 'rxjs';
+import { catchError, distinctUntilChanged, filter, flatMap, map, mergeMap, tap } from 'rxjs/operators';
+
+import { HALEndpointService } from '../shared/hal-endpoint.service';
+import { RequestService } from '../data/request.service';
+import { isNotEmpty } from '../../shared/empty.util';
+import { MessageGetRequest, MessagePostRequest, PostRequest, RestRequest } from '../data/request.models';
+import { DSpaceRESTv2Service, HttpOptions } from '../dspace-rest-v2/dspace-rest-v2.service';
+import { MessageDataResponse } from './message-data-response';
+import { RemoteDataError } from '../data/remote-data-error';
+import { getResponseFromEntry } from '../shared/operators';
+import { ErrorResponse, MessageResponse, RestResponse } from '../cache/response.models';
+import { RestRequestMethod } from '../data/rest-request-method';
+
+@Injectable()
+export class MessageService {
+  protected linkPath = 'messages';
+
+  constructor(protected http: DSpaceRESTv2Service,
+              protected requestService: RequestService,
+              protected halService: HALEndpointService) {
+  }
+
+  protected fetchRequest(requestId: string): Observable<MessageDataResponse> {
+    const responses = this.requestService.getByUUID(requestId).pipe(
+      getResponseFromEntry()
+    );
+    const errorResponses = responses.pipe(
+      filter((response: RestResponse) => !response.isSuccessful),
+      mergeMap((response: ErrorResponse) => observableOf(
+        new MessageDataResponse(
+          response.isSuccessful,
+          new RemoteDataError(response.statusCode, response.statusText, response.errorMessage)
+      ))
+    ));
+    const successResponses = responses.pipe(
+      filter((response: RestResponse) => response.isSuccessful),
+      map((response: MessageResponse) =>  new MessageDataResponse(response.isSuccessful)),
+      distinctUntilChanged()
+    );
+    return observableMerge(errorResponses, successResponses);
+  }
+
+  protected getEndpointByMethod(endpoint: string, method: string): string {
+    return isNotEmpty(method) ? `${endpoint}/${method}` : `${endpoint}`;
+  }
+
+  public postToEndpoint(method: string, body: any, options?: HttpOptions): Observable<MessageDataResponse> {
+    const requestId = this.requestService.generateRequestId();
+    return this.halService.getEndpoint(this.linkPath).pipe(
+      filter((href: string) => isNotEmpty(href)),
+      map((endpointURL: string) => this.getEndpointByMethod(endpointURL, method)),
+      distinctUntilChanged(),
+      map((endpointURL: string) => new MessagePostRequest(requestId, endpointURL, body, options)),
+      tap((request: PostRequest) => this.requestService.configure(request)),
+      flatMap((request: PostRequest) => this.fetchRequest(requestId)),
+      distinctUntilChanged());
+  }
+
+  public getRequest(method: string, options?: HttpOptions): Observable<any> {
+    const requestId = this.requestService.generateRequestId();
+    return this.halService.getEndpoint(this.linkPath).pipe(
+      map((endpointURL: string) => this.getEndpointByMethod(endpointURL, method)),
+      filter((href: string) => isNotEmpty(href)),
+      distinctUntilChanged(),
+      map((endpointURL: string) => new MessageGetRequest(requestId, endpointURL)),
+      tap((request: RestRequest) => this.requestService.configure(request, true)),
+      flatMap((request: RestRequest) => this.fetchRequest(requestId)),
+      distinctUntilChanged());
+  }
+
+  public createMessage(body: any, options?: HttpOptions): Observable<MessageDataResponse> {
+    return this.postToEndpoint('', this.requestService.prepareBody(body), this.makeHttpOptions());
+  }
+
+  public markAsRead(body: any, options?: HttpOptions): Observable<MessageDataResponse> {
+    return this.postToEndpoint('read', this.requestService.prepareBody(body), this.makeHttpOptions());
+  }
+
+  public markAsUnread(body: any, options?: HttpOptions): Observable<MessageDataResponse> {
+    return this.postToEndpoint('unread', this.requestService.prepareBody(body), this.makeHttpOptions());
+  }
+
+  protected makeHttpOptions() {
+    const options: HttpOptions = Object.create({});
+    let headers = new HttpHeaders();
+    headers = headers.append('Content-Type', 'application/x-www-form-urlencoded');
+    options.headers = headers;
+    return options;
+  }
+
+  public getMessageContent(url: string): Observable<any> {
+    if (isNotEmpty(url)) {
+      const options: HttpOptions = Object.create({});
+      options.observe = 'response';
+      options.responseType = 'text';
+      return this.http.request(RestRequestMethod.GET, url, null, options).pipe(
+        map((res) => ({ payload: res.payload })),
+        catchError((err) => observableOf({ payload: '' })));
+    } else {
+      return observableOf({ payload: '' });
+    }
+  }
+}
diff --git a/src/app/core/roles/role-types.ts b/src/app/core/roles/role-types.ts
new file mode 100644
index 0000000000000000000000000000000000000000..b39d1205a6d8536917e445ec361cdb86fa22ff89
--- /dev/null
+++ b/src/app/core/roles/role-types.ts
@@ -0,0 +1,5 @@
+export enum RoleType {
+  Submitter = 'submitter',
+  Controller = 'controller',
+  Admin = 'admin'
+}
diff --git a/src/app/core/roles/role.service.ts b/src/app/core/roles/role.service.ts
new file mode 100644
index 0000000000000000000000000000000000000000..49c358d3f5778546e7716e5b59661bd476a35454
--- /dev/null
+++ b/src/app/core/roles/role.service.ts
@@ -0,0 +1,52 @@
+import { Injectable } from '@angular/core';
+import { AppState } from '../../app.reducer';
+
+import { Observable, of as observableOf } from 'rxjs';
+import { distinctUntilChanged } from 'rxjs/operators';
+import { Store } from '@ngrx/store';
+
+import { RoleType } from './role-types';
+import { CollectionDataService } from '../data/collection-data.service';
+
+@Injectable()
+export class RoleService {
+
+  constructor(
+    private collectionService: CollectionDataService,
+    private store: Store<AppState>) {
+
+  }
+
+  isSubmitter(): Observable<boolean> {
+    return this.collectionService.hasAuthorizedCollection().pipe(
+      distinctUntilChanged()
+    );
+  }
+
+  isController(): Observable<boolean> {
+    // TODO find a way to check if user is a controller
+    return observableOf(true);
+  }
+
+  isAdmin(): Observable<boolean> {
+    // TODO find a way to check if user is an admin
+    return observableOf(false);
+  }
+
+  checkRole(role: RoleType): Observable<boolean> {
+    let check: Observable<boolean>;
+    switch (role) {
+      case RoleType.Submitter:
+        check = this.isSubmitter();
+        break;
+      case RoleType.Controller:
+        check = this.isController();
+        break;
+      case RoleType.Admin:
+        check = this.isAdmin();
+        break;
+    }
+
+    return check;
+  }
+}
diff --git a/src/app/core/shared/resource-type.ts b/src/app/core/shared/resource-type.ts
index 484f1ea6e25c26c9ea58731d096747cd3d5bd875..8cbd255ae781f12e2b7c5fab736d8dc31d6e4e6c 100644
--- a/src/app/core/shared/resource-type.ts
+++ b/src/app/core/shared/resource-type.ts
@@ -20,4 +20,6 @@ export enum ResourceType {
   SubmissionForms = 'submissionforms',
   SubmissionSections = 'submissionsections',
   SubmissionSection = 'submissionsection',
+  ClaimedTask = 'claimedtask',
+  PoolTask = 'pooltask'
 }
diff --git a/src/app/core/shared/view-mode.model.ts b/src/app/core/shared/view-mode.model.ts
index b026d6843132dc1c72ebaa301d5ad87b0d1fbb5f..9c8d08609718d375c16f15d3a49fa8df849f0f67 100644
--- a/src/app/core/shared/view-mode.model.ts
+++ b/src/app/core/shared/view-mode.model.ts
@@ -4,5 +4,6 @@
 
 export enum ViewMode {
   List = 'list',
-  Grid = 'grid'
+  Grid = 'grid',
+  Detail = 'detail'
 }
diff --git a/src/app/core/tasks/claimed-task-data.service.ts b/src/app/core/tasks/claimed-task-data.service.ts
new file mode 100644
index 0000000000000000000000000000000000000000..f35c999ac5bc32be3061b1c2dbac4e312ea88569
--- /dev/null
+++ b/src/app/core/tasks/claimed-task-data.service.ts
@@ -0,0 +1,56 @@
+import { Injectable } from '@angular/core';
+import { HttpClient } from '@angular/common/http';
+
+import { Store } from '@ngrx/store';
+import { Observable } from 'rxjs';
+
+import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
+import { CoreState } from '../core.reducers';
+import { RequestService } from '../data/request.service';
+import { NormalizedClaimedTask } from './models/normalized-claimed-task-object.model';
+import { ClaimedTask } from './models/claimed-task-object.model';
+import { TasksService } from './tasks.service';
+import { HALEndpointService } from '../shared/hal-endpoint.service';
+import { NormalizedObjectBuildService } from '../cache/builders/normalized-object-build.service';
+import { ObjectCacheService } from '../cache/object-cache.service';
+import { NotificationsService } from '../../shared/notifications/notifications.service';
+import { DSOChangeAnalyzer } from '../data/dso-change-analyzer.service';
+
+@Injectable()
+export class ClaimedTaskDataService extends TasksService<NormalizedClaimedTask, ClaimedTask> {
+  protected linkPath = 'claimedtasks';
+  protected forceBypassCache = true;
+
+  constructor(
+    protected requestService: RequestService,
+    protected rdbService: RemoteDataBuildService,
+    protected dataBuildService: NormalizedObjectBuildService,
+    protected store: Store<CoreState>,
+    protected objectCache: ObjectCacheService,
+    protected halService: HALEndpointService,
+    protected notificationsService: NotificationsService,
+    protected http: HttpClient,
+    protected comparator: DSOChangeAnalyzer) {
+    super();
+  }
+
+  public approveTask(scopeId: string): Observable<any> {
+    const body = {
+      submit_approve: 'true'
+    };
+    return this.postToEndpoint(this.linkPath, this.requestService.prepareBody(body), scopeId, this.makeHttpOptions());
+  }
+
+  public rejectTask(reason: string, scopeId: string): Observable<any> {
+    const body = {
+      submit_reject: 'true',
+      reason
+    };
+    return this.postToEndpoint(this.linkPath, this.requestService.prepareBody(body), scopeId, this.makeHttpOptions());
+  }
+
+  public returnToPoolTask(scopeId: string): Observable<any> {
+    return this.deleteById(this.linkPath, scopeId, this.makeHttpOptions());
+  }
+
+}
diff --git a/src/app/core/tasks/models/claimed-task-object.model.ts b/src/app/core/tasks/models/claimed-task-object.model.ts
new file mode 100644
index 0000000000000000000000000000000000000000..d0474a1aa8fe35ddd1689dcb8ad108d474dc5078
--- /dev/null
+++ b/src/app/core/tasks/models/claimed-task-object.model.ts
@@ -0,0 +1,5 @@
+import { TaskObject } from './task-object.model';
+
+export class ClaimedTask extends TaskObject {
+
+}
diff --git a/src/app/core/tasks/models/normalized-claimed-task-object.model.ts b/src/app/core/tasks/models/normalized-claimed-task-object.model.ts
new file mode 100644
index 0000000000000000000000000000000000000000..5b8604ac42812b05f4fc2657d5f23a5cf67ad129
--- /dev/null
+++ b/src/app/core/tasks/models/normalized-claimed-task-object.model.ts
@@ -0,0 +1,39 @@
+import { NormalizedTaskObject } from './normalized-task-object.model';
+import { mapsTo, relationship } from '../../cache/builders/build-decorators';
+import { autoserialize, inheritSerialization } from 'cerialize';
+import { ClaimedTask } from './claimed-task-object.model';
+import { ResourceType } from '../../shared/resource-type';
+
+/**
+ * A model class for a NormalizedClaimedTaskObject.
+ */
+@mapsTo(ClaimedTask)
+@inheritSerialization(NormalizedTaskObject)
+export class NormalizedClaimedTask extends NormalizedTaskObject {
+
+  /**
+   * The task identifier
+   */
+  @autoserialize
+  id: string;
+
+  /**
+   * The workflow step
+   */
+  @autoserialize
+  step: string;
+
+  /**
+   * The task action type
+   */
+  @autoserialize
+  action: string;
+
+  /**
+   * The workflowitem object whom this task is related
+   */
+  @autoserialize
+  @relationship(ResourceType.Workflowitem, false)
+  workflowitem: string;
+
+}
diff --git a/src/app/core/tasks/models/normalized-pool-task-object.model.ts b/src/app/core/tasks/models/normalized-pool-task-object.model.ts
new file mode 100644
index 0000000000000000000000000000000000000000..15152b4f5a455114a25b9aac92b490ba472d65a5
--- /dev/null
+++ b/src/app/core/tasks/models/normalized-pool-task-object.model.ts
@@ -0,0 +1,38 @@
+import { NormalizedTaskObject } from './normalized-task-object.model';
+import { PoolTask } from './pool-task-object.model';
+import { autoserialize, inheritSerialization } from 'cerialize';
+import { mapsTo, relationship } from '../../cache/builders/build-decorators';
+import { ResourceType } from '../../shared/resource-type';
+
+/**
+ * A model class for a NormalizedPoolTaskObject.
+ */
+@mapsTo(PoolTask)
+@inheritSerialization(NormalizedTaskObject)
+export class NormalizedPoolTask extends NormalizedTaskObject {
+
+  /**
+   * The task identifier
+   */
+  @autoserialize
+  id: string;
+
+  /**
+   * The workflow step
+   */
+  @autoserialize
+  step: string;
+
+  /**
+   * The task action type
+   */
+  @autoserialize
+  action: string;
+
+  /**
+   * The workflowitem object whom this task is related
+   */
+  @autoserialize
+  @relationship(ResourceType.Workflowitem, false)
+  workflowitem: string;
+}
diff --git a/src/app/core/tasks/models/normalized-task-object.model.ts b/src/app/core/tasks/models/normalized-task-object.model.ts
new file mode 100644
index 0000000000000000000000000000000000000000..4a41ec983a6830513b2da68f8037e95a20652727
--- /dev/null
+++ b/src/app/core/tasks/models/normalized-task-object.model.ts
@@ -0,0 +1,38 @@
+import { autoserialize, inheritSerialization } from 'cerialize';
+import { mapsTo, relationship } from '../../cache/builders/build-decorators';
+import { ResourceType } from '../../shared/resource-type';
+import { NormalizedDSpaceObject } from '../../cache/models/normalized-dspace-object.model';
+import { TaskObject } from './task-object.model';
+
+/**
+ * An abstract model class for a DSpaceObject.
+ */
+@mapsTo(TaskObject)
+@inheritSerialization(NormalizedDSpaceObject)
+export abstract class NormalizedTaskObject extends NormalizedDSpaceObject {
+
+  /**
+   * The task identifier
+   */
+  @autoserialize
+  id: string;
+
+  /**
+   * The workflow step
+   */
+  @autoserialize
+  step: string;
+
+  /**
+   * The task action type
+   */
+  @autoserialize
+  action: string;
+
+  /**
+   * The workflowitem object whom this task is related
+   */
+  @autoserialize
+  @relationship(ResourceType.Workflowitem, false)
+  workflowitem: string;
+}
diff --git a/src/app/core/tasks/models/pool-task-object.model.ts b/src/app/core/tasks/models/pool-task-object.model.ts
new file mode 100644
index 0000000000000000000000000000000000000000..fcaf4309a13110565f329ece3e3c35ef0f91c6bf
--- /dev/null
+++ b/src/app/core/tasks/models/pool-task-object.model.ts
@@ -0,0 +1,5 @@
+import { TaskObject } from './task-object.model';
+
+export class PoolTask extends TaskObject {
+
+}
diff --git a/src/app/core/tasks/models/process-task-response.ts b/src/app/core/tasks/models/process-task-response.ts
new file mode 100644
index 0000000000000000000000000000000000000000..ca4bc9a0682dc6e20b7ec316cce154c300749885
--- /dev/null
+++ b/src/app/core/tasks/models/process-task-response.ts
@@ -0,0 +1,17 @@
+import { RemoteDataError } from '../../data/remote-data-error';
+
+/**
+ * A class to represent the data retrieved by after processing a task
+ */
+export class ProcessTaskResponse {
+  constructor(
+    private isSuccessful: boolean,
+    public error?: RemoteDataError,
+    public payload?: any
+  ) {
+  }
+
+  get hasSucceeded(): boolean {
+    return this.isSuccessful;
+  }
+}
diff --git a/src/app/core/tasks/models/task-object.model.ts b/src/app/core/tasks/models/task-object.model.ts
new file mode 100644
index 0000000000000000000000000000000000000000..1475bdb14ad01258f2a4e353f40b317c0bed5f2b
--- /dev/null
+++ b/src/app/core/tasks/models/task-object.model.ts
@@ -0,0 +1,30 @@
+import { Observable } from 'rxjs';
+
+import { CacheableObject } from '../../cache/object-cache.reducer';
+import { DSpaceObject } from '../../shared/dspace-object.model';
+import { ListableObject } from '../../../shared/object-collection/shared/listable-object.model';
+import { RemoteData } from '../../data/remote-data';
+import { Workflowitem } from '../../submission/models/workflowitem.model';
+
+export class TaskObject extends DSpaceObject implements CacheableObject, ListableObject {
+
+  /**
+   * The task identifier
+   */
+  id: string;
+
+  /**
+   * The workflow step
+   */
+  step: string;
+
+  /**
+   * The task action type
+   */
+  action: string;
+
+  /**
+   * The workflowitem object whom this task is related
+   */
+  workflowitem: Observable<RemoteData<Workflowitem>> | Workflowitem;
+}
diff --git a/src/app/core/tasks/pool-task-data.service.ts b/src/app/core/tasks/pool-task-data.service.ts
new file mode 100644
index 0000000000000000000000000000000000000000..df2c05fe6d2206499d336f82111e9f44d11ab136
--- /dev/null
+++ b/src/app/core/tasks/pool-task-data.service.ts
@@ -0,0 +1,40 @@
+import { Injectable } from '@angular/core';
+import { HttpClient } from '@angular/common/http';
+
+import { Observable } from 'rxjs';
+import { Store } from '@ngrx/store';
+
+import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
+import { CoreState } from '../core.reducers';
+import { RequestService } from '../data/request.service';
+import { NormalizedPoolTask } from './models/normalized-pool-task-object.model';
+import { PoolTask } from './models/pool-task-object.model';
+import { TasksService } from './tasks.service';
+import { HALEndpointService } from '../shared/hal-endpoint.service';
+import { NormalizedObjectBuildService } from '../cache/builders/normalized-object-build.service';
+import { ObjectCacheService } from '../cache/object-cache.service';
+import { NotificationsService } from '../../shared/notifications/notifications.service';
+import { DSOChangeAnalyzer } from '../data/dso-change-analyzer.service';
+
+@Injectable()
+export class PoolTaskDataService extends TasksService<NormalizedPoolTask, PoolTask> {
+  protected linkPath = 'pooltasks';
+  protected forceBypassCache = true;
+
+  constructor(
+    protected requestService: RequestService,
+    protected rdbService: RemoteDataBuildService,
+    protected dataBuildService: NormalizedObjectBuildService,
+    protected store: Store<CoreState>,
+    protected objectCache: ObjectCacheService,
+    protected halService: HALEndpointService,
+    protected notificationsService: NotificationsService,
+    protected http: HttpClient,
+    protected comparator: DSOChangeAnalyzer) {
+    super();
+  }
+
+  public claimTask(scopeId: string): Observable<any> {
+    return this.postToEndpoint(this.linkPath, {}, scopeId, this.makeHttpOptions());
+  }
+}
diff --git a/src/app/core/tasks/task-response-parsing.service.ts b/src/app/core/tasks/task-response-parsing.service.ts
new file mode 100644
index 0000000000000000000000000000000000000000..d12b1b22f8b97520ea619b5eafc1aee8b6493f43
--- /dev/null
+++ b/src/app/core/tasks/task-response-parsing.service.ts
@@ -0,0 +1,38 @@
+import { Inject, Injectable } from '@angular/core';
+
+import { ResponseParsingService } from '../data/parsing.service';
+import { RestRequest } from '../data/request.models';
+import { DSpaceRESTV2Response } from '../dspace-rest-v2/dspace-rest-v2-response.model';
+
+import { BaseResponseParsingService } from '../data/base-response-parsing.service';
+import { GLOBAL_CONFIG } from '../../../config';
+import { GlobalConfig } from '../../../config/global-config.interface';
+import { ObjectCacheService } from '../cache/object-cache.service';
+import { NormalizedObjectFactory } from '../cache/models/normalized-object-factory';
+import { ErrorResponse, RestResponse, TaskResponse } from '../cache/response.models';
+
+@Injectable()
+export class TaskResponseParsingService extends BaseResponseParsingService implements ResponseParsingService {
+
+  protected objectFactory = NormalizedObjectFactory;
+  protected toCache = false;
+
+  constructor(@Inject(GLOBAL_CONFIG) protected EnvConfig: GlobalConfig,
+              protected objectCache: ObjectCacheService,) {
+    super();
+  }
+
+  parse(request: RestRequest, data: DSpaceRESTV2Response): RestResponse {
+    if (this.isSuccessStatus(data.statusCode)) {
+      return new TaskResponse( data.statusCode, data.statusText);
+    } else {
+      return new ErrorResponse(
+        Object.assign(
+          new Error('Unexpected response from server'),
+          { statusCode: data.statusCode, statusText: data.statusText }
+        )
+      );
+    }
+  }
+
+}
diff --git a/src/app/core/tasks/tasks.service.ts b/src/app/core/tasks/tasks.service.ts
new file mode 100644
index 0000000000000000000000000000000000000000..7b42d950507d5603189c684afcf899d5a17b7674
--- /dev/null
+++ b/src/app/core/tasks/tasks.service.ts
@@ -0,0 +1,82 @@
+import { HttpHeaders } from '@angular/common/http';
+
+import { merge as observableMerge, Observable, of as observableOf } from 'rxjs';
+import { distinctUntilChanged, filter, flatMap, map, mergeMap, tap } from 'rxjs/operators';
+
+import { DataService } from '../data/data.service';
+import { DeleteRequest, FindAllOptions, PostRequest, TaskDeleteRequest, TaskPostRequest } from '../data/request.models';
+import { isNotEmpty } from '../../shared/empty.util';
+import { HttpOptions } from '../dspace-rest-v2/dspace-rest-v2.service';
+import { ProcessTaskResponse } from './models/process-task-response';
+import { RemoteDataError } from '../data/remote-data-error';
+import { NormalizedObject } from '../cache/models/normalized-object.model';
+import { getResponseFromEntry } from '../shared/operators';
+import { ErrorResponse, MessageResponse, RestResponse } from '../cache/response.models';
+import { CacheableObject } from '../cache/object-cache.reducer';
+
+export abstract class TasksService<TNormalized extends NormalizedObject, TDomain extends CacheableObject> extends DataService<TNormalized, TDomain> {
+
+  public getBrowseEndpoint(options: FindAllOptions): Observable<string> {
+    return this.halService.getEndpoint(this.linkPath);
+  }
+
+  protected fetchRequest(requestId: string): Observable<ProcessTaskResponse> {
+    const responses = this.requestService.getByUUID(requestId).pipe(
+      getResponseFromEntry()
+    );
+    const errorResponses = responses.pipe(
+      filter((response: RestResponse) => !response.isSuccessful),
+      mergeMap((response: ErrorResponse) => observableOf(
+        new ProcessTaskResponse(
+          response.isSuccessful,
+          new RemoteDataError(response.statusCode, response.statusText, response.errorMessage)
+        ))
+      ));
+    const successResponses = responses.pipe(
+      filter((response: RestResponse) => response.isSuccessful),
+      map((response: MessageResponse) =>  new ProcessTaskResponse(response.isSuccessful)),
+      distinctUntilChanged()
+    );
+    return observableMerge(errorResponses, successResponses);
+  }
+
+  protected getEndpointByIDHref(endpoint, resourceID): string {
+    return isNotEmpty(resourceID) ? `${endpoint}/${resourceID}` : `${endpoint}`;
+  }
+
+  protected getEndpointByMethod(endpoint: string, method: string): string {
+    return isNotEmpty(method) ? `${endpoint}/${method}` : `${endpoint}`;
+  }
+
+  public postToEndpoint(linkPath: string, body: any, scopeId?: string, options?: HttpOptions): Observable<ProcessTaskResponse> {
+    const requestId = this.requestService.generateRequestId();
+    return this.halService.getEndpoint(linkPath).pipe(
+      filter((href: string) => isNotEmpty(href)),
+      map((endpointURL: string) => this.getEndpointByIDHref(endpointURL, scopeId)),
+      distinctUntilChanged(),
+      map((endpointURL: string) => new TaskPostRequest(requestId, endpointURL, body, options)),
+      tap((request: PostRequest) => this.requestService.configure(request)),
+      flatMap((request: PostRequest) => this.fetchRequest(requestId)),
+      distinctUntilChanged());
+  }
+
+  public deleteById(linkName: string, scopeId: string, options?: HttpOptions): Observable<ProcessTaskResponse> {
+    const requestId = this.requestService.generateRequestId();
+    return this.halService.getEndpoint(linkName || this.linkPath).pipe(
+      filter((href: string) => isNotEmpty(href)),
+      distinctUntilChanged(),
+      map((endpointURL: string) => this.getEndpointByIDHref(endpointURL, scopeId)),
+      map((endpointURL: string) => new TaskDeleteRequest(requestId, endpointURL, null, options)),
+      tap((request: DeleteRequest) => this.requestService.configure(request)),
+      flatMap((request: DeleteRequest) => this.fetchRequest(requestId)),
+      distinctUntilChanged());
+  }
+
+  protected makeHttpOptions() {
+    const options: HttpOptions = Object.create({});
+    let headers = new HttpHeaders();
+    headers = headers.append('Content-Type', 'application/x-www-form-urlencoded');
+    options.headers = headers;
+    return options;
+  }
+}