diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 6627203b17d769b8719beda1b188da1b25198643..6a9870968e93df0c20c5b054fc6afb5fabe2c78e 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -1,213 +1,68 @@
-# variables:
-#   IMAGE_ORG: xpub
-#   IMAGE_NAME: xpub
-#   BASE_DOMAIN: gateway.xpub.semioticsquares.com
-#   CONFIGURATION_REPOSITORY: https://gitlab.coko.foundation/xpub/deployment-config.git
+variables:
+  IMAGE_ORG: bogdandev
+  IMAGE_NAME: xpub-faraday
+  REPO_URL: https://gitlab.coko.foundation/xpub/xpub-faraday
 
-# stages:
-#   - build
-#   - test
-#   - review
-#   - staging
-#   - production
-#   - demo
+stages:
+  - build
+  - test
+  - review
+  - docker
+  - demo
 
-# build:
-#   image: docker:latest
-#   stage: build
-#   script:
-#     - docker version
-#     - docker build -t $IMAGE_ORG/$IMAGE_NAME:$CI_COMMIT_SHA .
-#     - if [ -z "$DOCKERHUB_USERNAME" ] || [ -z "$DOCKERHUB_PASSWORD" ]; then echo "Not pushing" && exit 0; fi
-#     - docker login -u $DOCKERHUB_USERNAME -p $DOCKERHUB_PASSWORD
-#     - echo "Ignore warning! Cannot perform an interactive login from a non TTY device"
-#     - docker push $IMAGE_ORG/$IMAGE_NAME:$CI_COMMIT_SHA
+build:
+  image: docker:latest
+  stage: build
+  script:
+    - docker version
+    - docker build -t $IMAGE_ORG/$IMAGE_NAME:$CI_COMMIT_SHA .
+    - if [ -z "$DOCKERHUB_USERNAME" ] || [ -z "$DOCKERHUB_PASSWORD" ]; then echo "Not pushing" && exit 0; fi
+    - docker login -u $DOCKERHUB_USERNAME -p $DOCKERHUB_PASSWORD
+    - echo "Ignore warning! Cannot perform an interactive login from a non TTY device"
+    - docker push $IMAGE_ORG/$IMAGE_NAME:$CI_COMMIT_SHA
 
-# lint:
-#   image: $IMAGE_ORG/$IMAGE_NAME:$CI_COMMIT_SHA
-#   stage: test
-#   variables:
-#     GIT_STRATEGY: none
-#   script:
-#     - cd ${HOME}
-#     - npm run lint
+lint:
+  image: $IMAGE_ORG/$IMAGE_NAME:$CI_COMMIT_SHA
+  stage: test
+  variables:
+    GIT_STRATEGY: none
+  script:
+    - cd ${HOME}
+    - npm run lint
 
-# test:
-#   image: $IMAGE_ORG/$IMAGE_NAME:$CI_COMMIT_SHA
-#   stage: test
-#   variables:
-#     GIT_STRATEGY: none
-#   script:
-#     - cd ${HOME}
-#     - npm run test
+test:
+  image: $IMAGE_ORG/$IMAGE_NAME:$CI_COMMIT_SHA
+  stage: test
+  variables:
+    GIT_STRATEGY: none
+  script:
+    - cd ${HOME}
+    - npm run test
 
-# push:latest:
-#   image: docker:latest
-#   stage: staging
-#   script:
-#     - if [ -z "$DOCKERHUB_USERNAME" ] || [ -z "$DOCKERHUB_PASSWORD" ]; then echo "Not pushing" && exit 0; fi
-#     - docker login -u $DOCKERHUB_USERNAME -p $DOCKERHUB_PASSWORD
-#     - echo "Ignore warning! Cannot perform an interactive login from a non TTY device"
-#     - docker build -t $IMAGE_ORG/$IMAGE_NAME:latest --label COMMIT_SHA=$CI_COMMIT_SHA .
-#     - docker push $IMAGE_ORG/$IMAGE_NAME:latest
-#   only:
-#   - master
+push:latest:
+  image: docker:latest
+  stage: docker
+  script:
+    - if [ -z "$DOCKERHUB_USERNAME" ] || [ -z "$DOCKERHUB_PASSWORD" ]; then echo "Not pushing" && exit 0; fi
+    - docker login -u $DOCKERHUB_USERNAME -p $DOCKERHUB_PASSWORD
+    - echo "Ignore warning! Cannot perform an interactive login from a non TTY device"
+    - docker build -t $IMAGE_ORG/$IMAGE_NAME:latest --label COMMIT_SHA=$CI_COMMIT_SHA .
+    - docker push $IMAGE_ORG/$IMAGE_NAME:latest
+  only:
+  - master
 
-# # -----------------------------------------------
-# # xpub-collabra ---------------------------------
-# # -----------------------------------------------
-
-# review:xpub-collabra:
-#   image: pubsweet/deployer:latest
-#   stage: review
-#   variables:
-#     PACKAGE_NAME: xpub-collabra
-#     FORCE_FRESH_DB: "yes"
-#     REQUIRES_PROVISIONING: "yes"
-#   environment:
-#     name: $PACKAGE_NAME/review/$CI_COMMIT_REF_NAME
-#     # !! kube-lego will fail if domain > 64 chars
-#     url: "http://${CI_ENVIRONMENT_SLUG}.${BASE_DOMAIN}"
-#     on_stop: stop_review:xpub-collabra
-#   except:
-#   - master
-#   script:
-#     - source deploy.sh
-#     - create_deployment
-
-# stop_review:xpub-collabra:
-#   image: pubsweet/deployer:latest
-#   stage: review
-#   variables:
-#     PACKAGE_NAME: xpub-collabra
-#     REQUIRES_PROVISIONING: "yes"
-#     GIT_STRATEGY: none
-#   environment:
-#     name: $PACKAGE_NAME/review/$CI_COMMIT_REF_NAME
-#     action: stop
-#   when: manual
-#   except:
-#   - master
-#   script:
-#     - source deploy.sh
-#     - delete_deployment
-#     - delete_objects_in_environment pvc
-
-# staging:xpub-collabra:
-#   image: pubsweet/deployer:latest
-#   stage: staging
-#   variables:
-#     PACKAGE_NAME: xpub-collabra
-#   environment:
-#     name: $PACKAGE_NAME/staging
-#     url: "https://${CI_ENVIRONMENT_SLUG}.${BASE_DOMAIN}"
-#   only:
-#   - master
-#   script:
-#     - source deploy.sh
-#     - create_deployment
-
-# production:xpub-collabra:
-#   image: pubsweet/deployer:latest
-#   stage: production
-#   variables:
-#     PACKAGE_NAME: xpub-collabra
-#   environment:
-#     name: $PACKAGE_NAME/production
-#     url: "https://${CI_ENVIRONMENT_SLUG}.${BASE_DOMAIN}"
-#   when: manual
-#   only:
-#   - master
-#   script:
-#     - source deploy.sh
-#     - create_deployment
-
-# demo:xpub-collabra:
-#   image: pubsweet/deployer:latest
-#   stage: demo
-#   variables:
-#     PACKAGE_NAME: xpub-collabra
-#   environment:
-#     name: $PACKAGE_NAME/demo
-#     url: "https://${CI_ENVIRONMENT_SLUG}.${BASE_DOMAIN}"
-#   when: manual
-#   script:
-#     - source deploy.sh
-#     - create_deployment
-
-# # -----------------------------------------------
-# # xpub-ui ---------------------------------------
-# # -----------------------------------------------
-
-# #review:xpub-ui:
-# #  image: pubsweet/deployer:latest
-# #  stage: review
-# #  variables:
-# #    PACKAGE_NAME: xpub-ui
-# #  environment:
-# #    name: $PACKAGE_NAME/review/$CI_COMMIT_REF_NAME
-# #    # !! kube-lego will fail if domain > 63 chars
-# #    url: "http://${CI_ENVIRONMENT_SLUG}.${BASE_DOMAIN}"
-# #    on_stop: stop_review:xpub-ui
-# #  except:
-# #  - master
-# #  script:
-# #    - source deploy.sh
-# #    - create_deployment
-# #
-# #stop_review:xpub-ui:
-# #  image: pubsweet/deployer:latest
-# #  stage: review
-# #  variables:
-# #    PACKAGE_NAME: xpub-ui
-# #    GIT_STRATEGY: none
-# #  when: manual
-# #  environment:
-# #    name: $PACKAGE_NAME/review/$CI_COMMIT_REF_NAME
-# #    action: stop
-# #  script:
-# #    - source deploy.sh
-# #    - delete_deployment
-# #
-# #staging:xpub-ui:
-# #  image: pubsweet/deployer:latest
-# #  stage: staging
-# #  variables:
-# #    PACKAGE_NAME: xpub-ui
-# #  environment:
-# #    name: $PACKAGE_NAME/staging
-# #    url: "https://${CI_ENVIRONMENT_SLUG}.${BASE_DOMAIN}"
-# #  only:
-# #  - master
-# #  script:
-# #    - source deploy.sh
-# #    - create_deployment
-# #
-# #production:xpub-ui:
-# #  image: pubsweet/deployer:latest
-# #  stage: production
-# #  variables:
-# #    PACKAGE_NAME: xpub-ui
-# #  environment:
-# #    name: $PACKAGE_NAME/production
-# #    url: "https://${CI_ENVIRONMENT_SLUG}.${BASE_DOMAIN}"
-# #  when: manual
-# #  only:
-# #  - master
-# #  script:
-# #    - source deploy.sh
-# #    - create_deployment
-# #
-# #demo:xpub-ui:
-# #  image: pubsweet/deployer:latest
-# #  stage: demo
-# #  variables:
-# #    PACKAGE_NAME: xpub-ui
-# #  environment:
-# #    name: $PACKAGE_NAME/demo
-# #    url: "https://${CI_ENVIRONMENT_SLUG}.${BASE_DOMAIN}"
-# #  when: manual
-# #  script:
-# #    - source deploy.sh
-# #    - create_deployment
-# #
+demo:now:
+  image: $IMAGE_ORG/$IMAGE_NAME:latest
+  stage: demo
+  when: manual
+  variables:
+    PACKAGE_NAME: xpub-faraday
+  only:
+    - master
+  environment:
+    name: $PACKAGE_NAME/demo
+    url: $NOW_URL
+  script:
+    - npm i -g --unsafe-perm now
+    - cd ${HOME}
+    - npm run deploy_now
\ No newline at end of file
diff --git a/now/Dockerfile b/now/Dockerfile
new file mode 100644
index 0000000000000000000000000000000000000000..e202a9e52db54c0017a1b4cc372f3cf3b85701a6
--- /dev/null
+++ b/now/Dockerfile
@@ -0,0 +1 @@
+FROM bogdandev/xpub-faraday:latest
\ No newline at end of file
diff --git a/package.json b/package.json
index 079e1d213d174a5d15d3b0943eded9c29c5e2881..d3ba020bbae08ea43170d63411b295dfcbc9a6bd 100644
--- a/package.json
+++ b/package.json
@@ -36,7 +36,8 @@
     "lint:style": "stylelint packages/**/*.scss packages/**/*.css",
     "precommit": "lint-staged",
     "styleguide": "lerna run styleguide",
-    "test": "lerna run test"
+    "test": "lerna run test",
+    "deploy_now": "cd now && now --public --docker --token $NOW_TOKEN -e AWS_S3_ACCESS_KEY=$AWS_S3_ACCESS_KEY -e AWS_S3_SECRET_KEY=$AWS_S3_SECRET_KEY -e AWS_S3_REGION=$AWS_S3_REGION -e AWS_S3_BUCKET=$AWS_S3_BUCKET -e AWS_SES_SECRET_KEY=$AWS_SES_SECRET_KEY -e AWS_SES_ACCESS_KEY=$AWS_SES_ACCESS_KEY -e AWS_SES_REGION=$AWS_SES_REGION -e EMAIL_SENDER=$EMAIL_SENDER -e secret=$SECRET"
   },
   "lint-staged": {
     "*.js": [
diff --git a/packages/component-invite/src/HandleInvitation.js b/packages/component-invite/src/HandleInvitation.js
index 7f8c9b5bc7ad3ae280df88d21abbcaa99b322ab1..d3c5c37336388cfd5779fdd0c5f6a1fc812cdbe0 100644
--- a/packages/component-invite/src/HandleInvitation.js
+++ b/packages/component-invite/src/HandleInvitation.js
@@ -10,6 +10,11 @@ const HandleInvitation = app => {
     authBearer,
     require('./routes/postHandleInvitation')(app.locals.models),
   )
+  app.get(
+    '/api/collections/:collectionId/users',
+    authBearer,
+    require('./routes/getCollectionUsers')(app.locals.models),
+  )
 }
 
 module.exports = HandleInvitation
diff --git a/packages/component-invite/src/controllers/assignCollectionRole.js b/packages/component-invite/src/controllers/assignCollectionRole.js
index 1a87770deb0c43decea68ed059eb860c401585df..2dc68984601b5ab8ddf8a60ea8ae01db49434cb0 100644
--- a/packages/component-invite/src/controllers/assignCollectionRole.js
+++ b/packages/component-invite/src/controllers/assignCollectionRole.js
@@ -70,6 +70,8 @@ module.exports = async (
       role,
     )
 
+    // getting the updated user from the DB - creating a team also updates the user
+    user = await models.User.findByEmail(email)
     user = await teamHelper.setupInvitation(user, role, collectionId, team.id)
 
     try {
diff --git a/packages/component-invite/src/controllers/inviteGlobalRole.js b/packages/component-invite/src/controllers/inviteGlobalRole.js
index def996ead4d6451d31d814740ba927d417d8ed64..3858f0c87159f85ed95bacbde32b3a158565b3cb 100644
--- a/packages/component-invite/src/controllers/inviteGlobalRole.js
+++ b/packages/component-invite/src/controllers/inviteGlobalRole.js
@@ -17,7 +17,6 @@ module.exports = async (body, models, res, url) => {
 
   try {
     const user = await models.User.findByEmail(email)
-
     if (user) {
       logger.error(`admin tried to invite existing user: ${email}`)
       return res.status(400).json({ error: 'User already exists' })
diff --git a/packages/component-invite/src/helpers/Team.js b/packages/component-invite/src/helpers/Team.js
index 87c1a2dbe6a8d5b19275981e681f555f0b90db49..553b4cd75f9d84eceecca548b54a851a94c77331 100644
--- a/packages/component-invite/src/helpers/Team.js
+++ b/packages/component-invite/src/helpers/Team.js
@@ -1,5 +1,6 @@
 const logger = require('@pubsweet/logger')
 const config = require('config')
+const get = require('lodash/get')
 
 const configRoles = config.get('roles')
 
@@ -134,6 +135,37 @@ const removeTeamMember = async (teamId, userId, TeamModel) => {
   await TeamModel.updateProperties(team)
   await team.save()
 }
+
+const getTeamMembersByCollection = async (collectionId, role, TeamModel) => {
+  const teams = await TeamModel.all()
+  const members = get(
+    teams.find(
+      team =>
+        team.group === role &&
+        team.object.type === 'collection' &&
+        team.object.id === collectionId,
+    ),
+    'members',
+  )
+
+  return members
+}
+
+const getInviteData = (invitations, collectionId, role) => {
+  const matchingInvitation = invitations.find(
+    invite => invite.type === role && invite.collectionId === collectionId,
+  )
+  let status = 'pending'
+  if (matchingInvitation.isAccepted) {
+    status = 'accepted'
+  } else if (matchingInvitation.hasAnswer) {
+    status = 'refused'
+  }
+
+  const { timestamp } = matchingInvitation
+  return { timestamp, status }
+}
+
 module.exports = {
   createNewTeam,
   setupEiCTeams,
@@ -141,4 +173,6 @@ module.exports = {
   getMatchingTeams,
   setupInvitation,
   removeTeamMember,
+  getTeamMembersByCollection,
+  getInviteData,
 }
diff --git a/packages/component-invite/src/routes/getCollectionUsers.js b/packages/component-invite/src/routes/getCollectionUsers.js
new file mode 100644
index 0000000000000000000000000000000000000000..c1389b98c1e00722f03a65309d4263b9d95dd693
--- /dev/null
+++ b/packages/component-invite/src/routes/getCollectionUsers.js
@@ -0,0 +1,43 @@
+const helpers = require('../helpers/helpers')
+const teamHelper = require('../helpers/Team')
+
+module.exports = models => async (req, res) => {
+  const { role } = req.query
+  if (!helpers.checkForUndefinedParams(role)) {
+    res.status(400).json({ error: 'Role is required' })
+    return
+  }
+
+  const { collectionId } = req.params
+  try {
+    await models.Collection.find(collectionId)
+    const members = await teamHelper.getTeamMembersByCollection(
+      collectionId,
+      role,
+      models.Team,
+    )
+
+    const membersData = members.map(async member => {
+      const user = await models.User.find(member)
+      const { timestamp, status } = teamHelper.getInviteData(
+        user.invitations,
+        collectionId,
+        role,
+      )
+      return {
+        name: `${user.firstName} ${user.lastName}`,
+        timestamp,
+        email: user.email,
+        status,
+      }
+    })
+
+    const resBody = await Promise.all(membersData)
+    res.status(200).json(resBody)
+  } catch (e) {
+    const notFoundError = await helpers.handleNotFoundError(e, 'collection')
+    return res.status(notFoundError.status).json({
+      error: notFoundError.message,
+    })
+  }
+}
diff --git a/packages/component-invite/src/routes/postHandleInvitation.js b/packages/component-invite/src/routes/postHandleInvitation.js
index d9ff99d5025c8dea2c024bb38a6b46013430560d..85fd4151dcd1e929cc6f95b7cafd4435f958c482 100644
--- a/packages/component-invite/src/routes/postHandleInvitation.js
+++ b/packages/component-invite/src/routes/postHandleInvitation.js
@@ -18,25 +18,25 @@ module.exports = models => async (req, res) => {
     return
   }
   const { collectionId } = req.params
-  // console.log('UI', user.invitations)
-  const filteredInvitations = user.invitations.filter(
-    invitation =>
-      invitation.collectionId === collectionId && invitation.type === type,
-  )
 
-  if (filteredInvitations.length === 0) {
-    res.status(400).json({
-      error: `Request data does not match any user invitation`,
-    })
-    logger.error(
-      `Collection ${collectionId} and type '${type}' do not match any user invitation`,
-    )
-    return
-  }
-
-  const matchingInvitation = filteredInvitations[0]
   try {
     await models.Collection.find(collectionId)
+    const filteredInvitations = user.invitations.filter(
+      invitation =>
+        invitation.collectionId === collectionId && invitation.type === type,
+    )
+
+    if (filteredInvitations.length === 0) {
+      res.status(400).json({
+        error: `Request data does not match any user invitation`,
+      })
+      logger.error(
+        `Collection ${collectionId} and type '${type}' do not match any user invitation`,
+      )
+      return
+    }
+
+    const matchingInvitation = filteredInvitations[0]
     matchingInvitation.hasAnswer = true
     if (accept === true) {
       matchingInvitation.isAccepted = true
diff --git a/packages/component-invite/src/routes/postInvite.js b/packages/component-invite/src/routes/postInvite.js
index 5e9d312a99676492e75532fa8ece95018bc4bba9..6c8d4de3a92bccf49ad4f3a4a5de8335796dafb1 100644
--- a/packages/component-invite/src/routes/postInvite.js
+++ b/packages/component-invite/src/routes/postInvite.js
@@ -1,6 +1,10 @@
 const logger = require('@pubsweet/logger')
 const get = require('lodash/get')
 const helpers = require('../helpers/helpers')
+const config = require('config')
+const concat = require('lodash/concat')
+
+const configRoles = config.get('roles')
 
 module.exports = models => async (req, res) => {
   const { email, role } = req.body
@@ -11,7 +15,18 @@ module.exports = models => async (req, res) => {
     return
   }
 
+  const validRolesList = concat(configRoles.global, configRoles.collection)
+  if (!validRolesList.includes(role)) {
+    res.status(400).json({ error: `Role ${role} is invalid` })
+    logger.error(`invitation attempted on invalid role ${role}`)
+    return
+  }
   const reqUser = await models.User.find(req.user)
+  if (email === reqUser.email) {
+    res.status(400).json({ error: 'Cannot invite yourself' })
+    logger.error(`${reqUser.email} tried to invite his own email`)
+    return
+  }
   const collectionId = get(req, 'params.collectionId')
 
   const url = `${req.protocol}://${req.get('host')}`
@@ -19,7 +34,7 @@ module.exports = models => async (req, res) => {
     return require('../controllers/assignCollectionRole')(
       email,
       role,
-      req.user,
+      reqUser,
       res,
       collectionId,
       models,
diff --git a/packages/component-invite/src/tests/fixtures/teamIDs.js b/packages/component-invite/src/tests/fixtures/teamIDs.js
new file mode 100644
index 0000000000000000000000000000000000000000..f8e1c9f2285384a39ccb39de082abf773c7cfb02
--- /dev/null
+++ b/packages/component-invite/src/tests/fixtures/teamIDs.js
@@ -0,0 +1,10 @@
+const Chance = require('chance')
+
+const chance = new Chance()
+
+const teamIDs = {
+  heTeam: chance.guid(),
+  reviewerTeam: chance.guid(),
+}
+
+module.exports = { teamIDs }
diff --git a/packages/component-invite/src/tests/fixtures/teams.js b/packages/component-invite/src/tests/fixtures/teams.js
index bcc5690ed8b674c625d10a8098960f48a593621b..84de7cd6b6284772d1516308692709f0031a8f6d 100644
--- a/packages/component-invite/src/tests/fixtures/teams.js
+++ b/packages/component-invite/src/tests/fixtures/teams.js
@@ -1,8 +1,9 @@
 const users = require('./users')
 const collections = require('./collections')
+const { heTeam, reviewerTeam } = require('./teamIDs')
 
 const { standardCollection } = collections
-const { editorInChief, handlingEditor, reviewer } = users
+const { editorInChief, handlingEditor, reviewer, invitedHandlingEditor } = users
 const teams = {
   eicTeam: {
     teamType: {
@@ -30,10 +31,10 @@ const teams = {
       type: 'collection',
       id: standardCollection.id,
     },
-    members: [handlingEditor.id],
+    members: [handlingEditor.id, invitedHandlingEditor.id],
     save: jest.fn(() => teams.heTeam),
     updateProperties: jest.fn(() => teams.heTeam),
-    id: 'he123',
+    id: heTeam,
   },
   reviewerTeam: {
     teamType: {
@@ -49,6 +50,7 @@ const teams = {
     members: [reviewer.id],
     save: jest.fn(() => teams.reviewerTeam),
     updateProperties: jest.fn(() => teams.reviewerTeam),
+    id: reviewerTeam,
   },
 }
 module.exports = teams
diff --git a/packages/component-invite/src/tests/fixtures/users.js b/packages/component-invite/src/tests/fixtures/users.js
index 86f6c66be75d1fcc462d96b98861d003915f4e80..a04b7ff4c5e65b0b444f1a085b2c6fcc1254ed4e 100644
--- a/packages/component-invite/src/tests/fixtures/users.js
+++ b/packages/component-invite/src/tests/fixtures/users.js
@@ -1,5 +1,5 @@
-// const { heInvitation } = require('./invitations')
 const { standardCollection } = require('./collections')
+const { heTeam, reviewerTeam } = require('./teamIDs')
 
 const users = {
   admin: {
@@ -33,6 +33,8 @@ const users = {
     password: 'test',
     admin: false,
     id: 'handling123',
+    firstName: 'Handling',
+    lastName: 'Editor',
     invitations: [
       {
         type: 'handlingEditor',
@@ -40,9 +42,10 @@ const users = {
         isAccepted: false,
         collectionId: standardCollection.id,
         timestamp: Date.now(),
-        teamId: 'he123',
+        teamId: heTeam,
       },
     ],
+    teams: [heTeam],
     save: jest.fn(() => users.handlingEditor),
     editorInChief: false,
   },
@@ -75,7 +78,40 @@ const users = {
     title: 'Ms',
     save: jest.fn(() => users.reviewer),
     isConfirmed: false,
-    teams: [],
+    teams: [reviewerTeam],
+    invitations: [
+      {
+        type: 'reviewer',
+        hasAnswer: false,
+        isAccepted: false,
+        collectionId: '123',
+        timestamp: Date.now(),
+        teamId: reviewerTeam,
+      },
+    ],
+  },
+  invitedHandlingEditor: {
+    type: 'user',
+    username: 'handling',
+    email: 'handling@example.com',
+    password: 'test',
+    admin: false,
+    id: 'invitedHandling123',
+    firstName: 'Invited',
+    lastName: 'HE',
+    invitations: [
+      {
+        type: 'handlingEditor',
+        hasAnswer: true,
+        isAccepted: false,
+        collectionId: standardCollection.id,
+        timestamp: Date.now(),
+        teamId: heTeam,
+      },
+    ],
+    teams: [heTeam],
+    save: jest.fn(() => users.handlingEditor),
+    editorInChief: false,
   },
 }
 
diff --git a/packages/component-invite/src/tests/getCollectionUsers.test.js b/packages/component-invite/src/tests/getCollectionUsers.test.js
new file mode 100644
index 0000000000000000000000000000000000000000..2458b1044b8349bbc9f7f00fd6c8732be6cd1ba1
--- /dev/null
+++ b/packages/component-invite/src/tests/getCollectionUsers.test.js
@@ -0,0 +1,39 @@
+process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'
+process.env.SUPPRESS_NO_CONFIG_WARNING = true
+
+const httpMocks = require('node-mocks-http')
+const fixtures = require('./fixtures/fixtures')
+const Model = require('./helpers/Model')
+
+const user = fixtures.users.editorInChief
+const query = {
+  role: 'handlingEditor',
+}
+const getCollectionUsersPath = '../routes/getCollectionUsers'
+describe('Get collection users route handler', () => {
+  it('should return success when the role is correct, the collection exists and the request user is editorInChief ', async () => {
+    const req = httpMocks.createRequest()
+    req.query = query
+    req.params.collectionId = '2c4fb766-a798-4c32-b857-c5d21a2ab331'
+    const res = httpMocks.createResponse()
+    const models = Model.build()
+    await require(getCollectionUsersPath)(models)(req, res)
+
+    expect(res.statusCode).toBe(200)
+    const data = JSON.parse(res._getData())
+    expect(data).toHaveLength(2)
+  })
+  it('should return an error when the role parameter is missing', async () => {
+    delete query.role
+    const req = httpMocks.createRequest()
+    req.query = query
+
+    const res = httpMocks.createResponse()
+    const models = Model.build(user)
+    await require(getCollectionUsersPath)(models)(req, res)
+    expect(res.statusCode).toBe(400)
+    const data = JSON.parse(res._getData())
+    expect(data.error).toEqual('Role is required')
+    query.email = 'handlingEditor'
+  })
+})
diff --git a/packages/component-invite/src/tests/helpers/Model.js b/packages/component-invite/src/tests/helpers/Model.js
index 40fecc6e1812f276d9be4944e9cdef786b177d17..7ff2e90b59f218c2e37967701d0ea8b70d78c73b 100644
--- a/packages/component-invite/src/tests/helpers/Model.js
+++ b/packages/component-invite/src/tests/helpers/Model.js
@@ -3,45 +3,25 @@ const fixtures = require('../fixtures/fixtures')
 const UserMock = require('../mocks/User')
 const TeamMock = require('../mocks/Team')
 
-const build = (collection, findUser, emailUser, team) => {
+const notFoundError = new Error()
+notFoundError.name = 'NotFoundError'
+notFoundError.status = 404
+
+const build = () => {
   const models = {
     User: {},
     Collection: {
-      find: jest.fn(
-        () =>
-          collection instanceof Error
-            ? Promise.reject(collection)
-            : Promise.resolve(collection),
-      ),
+      find: jest.fn(id => findMock(id, 'collections')),
     },
     Team: {},
   }
-  UserMock.find = jest.fn(user => {
-    const foundUser = Object.values(fixtures.users).find(
-      fixUser => fixUser.id === user.id,
-    )
-
-    if (foundUser === undefined) {
-      return Promise.reject(findUser)
-    }
-
-    return Promise.resolve(findUser)
-  })
-
-  UserMock.findByEmail = jest.fn(
-    () =>
-      emailUser instanceof Error
-        ? Promise.reject(emailUser)
-        : Promise.resolve(emailUser),
-  )
+  UserMock.find = jest.fn(id => findMock(id, 'users'))
 
-  TeamMock.find = jest.fn(
-    () =>
-      team instanceof Error ? Promise.reject(team) : Promise.resolve(team),
-  )
-  TeamMock.updateProperties = jest.fn(
-    () =>
-      team instanceof Error ? Promise.reject(team) : Promise.resolve(team),
+  UserMock.findByEmail = jest.fn(email => findByEmailMock(email))
+
+  TeamMock.find = jest.fn(id => findMock(id, 'teams'))
+  TeamMock.updateProperties = jest.fn(team =>
+    updatePropertiesMock(team, 'teams'),
   )
   TeamMock.all = jest.fn(() => Object.values(fixtures.teams))
 
@@ -50,4 +30,30 @@ const build = (collection, findUser, emailUser, team) => {
   return models
 }
 
+const findMock = (id, type) => {
+  const foundObj = Object.values(fixtures[type]).find(
+    fixtureObj => fixtureObj.id === id,
+  )
+
+  if (foundObj === undefined) return Promise.reject(notFoundError)
+  return Promise.resolve(foundObj)
+}
+
+const findByEmailMock = email => {
+  const foundUser = Object.values(fixtures.users).find(
+    fixtureUser => fixtureUser.email === email,
+  )
+
+  if (foundUser === undefined) return Promise.reject(notFoundError)
+  return Promise.resolve(foundUser)
+}
+
+const updatePropertiesMock = (obj, type) => {
+  const foundObj = Object.values(fixtures[type]).find(
+    fixtureObj => fixtureObj === obj,
+  )
+
+  if (foundObj === undefined) return Promise.reject(notFoundError)
+  return Promise.resolve(foundObj)
+}
 module.exports = { build }
diff --git a/packages/component-invite/src/tests/postHandleInvitation.test.js b/packages/component-invite/src/tests/postHandleInvitation.test.js
index 795cdb4ea8180d1a29e458677a95f36c37a157ab..ffa1215761d1b0a017f09a71f939dacb2d2a1c3c 100644
--- a/packages/component-invite/src/tests/postHandleInvitation.test.js
+++ b/packages/component-invite/src/tests/postHandleInvitation.test.js
@@ -3,9 +3,9 @@ process.env.SUPPRESS_NO_CONFIG_WARNING = true
 
 const httpMocks = require('node-mocks-http')
 const fixtures = require('./fixtures/fixtures')
-const cloneDeep = require('lodash/cloneDeep')
 const Model = require('./helpers/Model')
 
+const models = Model.build()
 jest.mock('pubsweet-component-mail-service', () => ({
   setupAssignEmail: jest.fn(),
 }))
@@ -14,14 +14,17 @@ const notFoundError = new Error()
 notFoundError.name = 'NotFoundError'
 notFoundError.status = 404
 
-const { handlingEditor } = fixtures.users
+const {
+  handlingEditor,
+  invitedHandlingEditor,
+  author,
+  reviewer,
+} = fixtures.users
 const { standardCollection } = fixtures.collections
-const { heTeam } = fixtures.teams
 
 const postInvitationPath = '../routes/postHandleInvitation'
 describe('Post handle invitation route handler', () => {
   it('should return success when the handling editor accepts work on a collection', async () => {
-    const acceptingHE = cloneDeep(handlingEditor)
     const body = {
       type: 'handlingEditor',
       accept: true,
@@ -29,17 +32,15 @@ describe('Post handle invitation route handler', () => {
     const req = httpMocks.createRequest({
       body,
     })
-    req.user = acceptingHE
+    req.user = handlingEditor.id
     req.params.collectionId = standardCollection.id
     const res = httpMocks.createResponse()
-    const models = Model.build(standardCollection, acceptingHE, null, heTeam)
     await require(postInvitationPath)(models)(req, res)
     expect(res.statusCode).toBe(204)
-    expect(acceptingHE.invitations[0].hasAnswer).toBeTruthy()
-    expect(acceptingHE.invitations[0].isAccepted).toBeTruthy()
+    expect(handlingEditor.invitations[0].hasAnswer).toBeTruthy()
+    expect(handlingEditor.invitations[0].isAccepted).toBeTruthy()
   })
   it('should return success when the handling editor refuses work on a collection', async () => {
-    const refusingHE = cloneDeep(handlingEditor)
     const body = {
       type: 'handlingEditor',
       accept: false,
@@ -47,15 +48,14 @@ describe('Post handle invitation route handler', () => {
     const req = httpMocks.createRequest({
       body,
     })
-    req.user = refusingHE
+    req.user = invitedHandlingEditor.id
     req.params.collectionId = standardCollection.id
     const res = httpMocks.createResponse()
-    const models = Model.build(standardCollection, refusingHE, null, heTeam)
     await require(postInvitationPath)(models)(req, res)
 
     expect(res.statusCode).toBe(204)
-    expect(refusingHE.invitations[0].hasAnswer).toBeTruthy()
-    expect(refusingHE.invitations[0].isAccepted).toBeFalsy()
+    expect(invitedHandlingEditor.invitations[0].hasAnswer).toBeTruthy()
+    expect(invitedHandlingEditor.invitations[0].isAccepted).toBeFalsy()
   })
   it('should return an error params are missing', async () => {
     const body = {
@@ -64,17 +64,16 @@ describe('Post handle invitation route handler', () => {
     const req = httpMocks.createRequest({
       body,
     })
-    req.user = handlingEditor
+    req.user = handlingEditor.id
     req.params.collectionId = standardCollection.id
     const res = httpMocks.createResponse()
-    const models = Model.build(standardCollection, handlingEditor)
     await require(postInvitationPath)(models)(req, res)
 
     expect(res.statusCode).toBe(400)
     const data = JSON.parse(res._getData())
     expect(data.error).toEqual('Type and accept are required')
   })
-  it('should return an error if the collection id does not exists', async () => {
+  it('should return an error if the collection does not exists', async () => {
     const body = {
       type: 'handlingEditor',
       accept: false,
@@ -82,10 +81,9 @@ describe('Post handle invitation route handler', () => {
     const req = httpMocks.createRequest({
       body,
     })
-    req.user = handlingEditor
-    req.params.collectionId = standardCollection.id
+    req.user = handlingEditor.id
+    req.params.collectionId = 'invalid-id'
     const res = httpMocks.createResponse()
-    const models = Model.build(notFoundError, handlingEditor)
     await require(postInvitationPath)(models)(req, res)
 
     expect(res.statusCode).toBe(404)
@@ -93,7 +91,6 @@ describe('Post handle invitation route handler', () => {
     expect(data.error).toEqual('collection not found')
   })
   it('should return an error when the request user does not have any invitation', async () => {
-    const noInvitationEditor = cloneDeep(handlingEditor)
     const body = {
       type: 'handlingEditor',
       accept: false,
@@ -101,11 +98,9 @@ describe('Post handle invitation route handler', () => {
     const req = httpMocks.createRequest({
       body,
     })
-    delete noInvitationEditor.invitations
-    req.user = noInvitationEditor
+    req.user = author.id
     req.params.collectionId = standardCollection.id
     const res = httpMocks.createResponse()
-    const models = Model.build(standardCollection, noInvitationEditor)
     await require(postInvitationPath)(models)(req, res)
 
     expect(res.statusCode).toBe(400)
@@ -120,10 +115,9 @@ describe('Post handle invitation route handler', () => {
     const req = httpMocks.createRequest({
       body,
     })
-    req.user = handlingEditor
+    req.user = handlingEditor.id
     req.params.collectionId = standardCollection.id
     const res = httpMocks.createResponse()
-    const models = Model.build(standardCollection, handlingEditor)
     await require(postInvitationPath)(models)(req, res)
 
     expect(res.statusCode).toBe(400)
@@ -134,16 +128,15 @@ describe('Post handle invitation route handler', () => {
   })
   it('should return an error when the request collection and the user invitation collection do not match', async () => {
     const body = {
-      type: 'handlingEditor',
+      type: 'reviewer',
       accept: false,
     }
     const req = httpMocks.createRequest({
       body,
     })
-    req.user = handlingEditor
-    req.params.collectionId = '123'
+    req.user = reviewer.id
+    req.params.collectionId = standardCollection.id
     const res = httpMocks.createResponse()
-    const models = Model.build(standardCollection, handlingEditor)
     await require(postInvitationPath)(models)(req, res)
 
     expect(res.statusCode).toBe(400)
diff --git a/packages/component-invite/src/tests/postInvite.test.js b/packages/component-invite/src/tests/postInvite.test.js
index 17e7e9c12d89722970783c9e026f9aac8b1d1593..74aff28e5e9e68ecda6dd0eba2ae819d8d78f7aa 100644
--- a/packages/component-invite/src/tests/postInvite.test.js
+++ b/packages/component-invite/src/tests/postInvite.test.js
@@ -7,6 +7,7 @@ const fixtures = require('./fixtures/fixtures')
 const Chance = require('chance')
 const Model = require('./helpers/Model')
 
+const models = Model.build()
 jest.mock('pubsweet-component-mail-service', () => ({
   setupInviteEmail: jest.fn(),
   setupAssignEmail: jest.fn(),
@@ -31,16 +32,14 @@ notFoundError.status = 404
 
 const { admin, editorInChief, handlingEditor, author } = fixtures.users
 const { standardCollection } = fixtures.collections
-const { heTeam } = fixtures.teams
 const postInvitePath = '../routes/postInvite'
 describe('Post invite route handler', () => {
   it('should return success when the admin invites a global role', async () => {
     const req = httpMocks.createRequest({
       body,
     })
-    req.user = admin
+    req.user = admin.id
     const res = httpMocks.createResponse()
-    const models = Model.build(notFoundError, admin, notFoundError)
     await require(postInvitePath)(models)(req, res)
 
     expect(res.statusCode).toBe(200)
@@ -53,10 +52,9 @@ describe('Post invite route handler', () => {
     const req = httpMocks.createRequest({
       body,
     })
-    req.user = admin
+    req.user = admin.id
     req.params.collectionId = '123'
     const res = httpMocks.createResponse()
-    const models = Model.build(notFoundError, admin)
     await require(postInvitePath)(models)(req, res)
     expect(res.statusCode).toBe(403)
     const data = JSON.parse(res._getData())
@@ -70,9 +68,8 @@ describe('Post invite route handler', () => {
     const req = httpMocks.createRequest({
       body,
     })
-    req.user = admin
+    req.user = admin.id
     const res = httpMocks.createResponse()
-    const models = Model.build(notFoundError, admin)
     await require(postInvitePath)(models)(req, res)
     expect(res.statusCode).toBe(403)
     const data = JSON.parse(res._getData())
@@ -85,9 +82,8 @@ describe('Post invite route handler', () => {
     const req = httpMocks.createRequest({
       body,
     })
-    req.user = admin
+    req.user = admin.id
     const res = httpMocks.createResponse()
-    const models = Model.build(notFoundError, admin)
     await require(postInvitePath)(models)(req, res)
     expect(res.statusCode).toBe(400)
     const data = JSON.parse(res._getData())
@@ -100,10 +96,9 @@ describe('Post invite route handler', () => {
     const req = httpMocks.createRequest({
       body,
     })
-    req.user = editorInChief
+    req.user = editorInChief.id
     req.params.collectionId = '123'
     const res = httpMocks.createResponse()
-    const models = Model.build(notFoundError, editorInChief)
     await require(postInvitePath)(models)(req, res)
     expect(res.statusCode).toBe(403)
     const data = JSON.parse(res._getData())
@@ -115,15 +110,16 @@ describe('Post invite route handler', () => {
     const req = httpMocks.createRequest({
       body,
     })
-    req.user = editorInChief
+    req.user = editorInChief.id
     const res = httpMocks.createResponse()
 
-    const models = Model.build(notFoundError, editorInChief)
     await require(postInvitePath)(models)(req, res)
     expect(res.statusCode).toBe(403)
     const data = JSON.parse(res._getData())
     expect(data.error).toEqual(
-      `${req.user.username} cannot invite a ${body.role} without a collection`,
+      `${editorInChief.username} cannot invite a ${
+        body.role
+      } without a collection`,
     )
   })
   it('should return an error when an handlingEditor invites a reviewer without a collection', async () => {
@@ -132,26 +128,26 @@ describe('Post invite route handler', () => {
     const req = httpMocks.createRequest({
       body,
     })
-    req.user = handlingEditor
+    req.user = handlingEditor.id
     const res = httpMocks.createResponse()
-
-    const models = Model.build(notFoundError, handlingEditor)
     await require(postInvitePath)(models)(req, res)
     expect(res.statusCode).toBe(403)
     const data = JSON.parse(res._getData())
     expect(data.error).toEqual(
-      `${req.user.username} cannot invite a ${body.role} without a collection`,
+      `${handlingEditor.username} cannot invite a ${
+        body.role
+      } without a collection`,
     )
   })
-  it('should return an error when inviting an existing user', async () => {
+  it('should return an error when inviting an existing user with a global role', async () => {
     body.role = globalRoles[random(0, globalRoles.length - 1)]
     body.admin = body.role === 'admin'
+    body.email = author.email
     const req = httpMocks.createRequest({
       body,
     })
-    req.user = admin
+    req.user = admin.id
     const res = httpMocks.createResponse()
-    const models = Model.build(notFoundError, admin, editorInChief)
     await require(postInvitePath)(models)(req, res)
 
     expect(res.statusCode).toBe(400)
@@ -166,10 +162,9 @@ describe('Post invite route handler', () => {
     const req = httpMocks.createRequest({
       body,
     })
-    req.user = editorInChief
-    req.params.collectionId = '123'
+    req.user = editorInChief.id
+    req.params.collectionId = standardCollection.id
     const res = httpMocks.createResponse()
-    const models = Model.build(standardCollection, editorInChief, author)
     await require(postInvitePath)(models)(req, res)
 
     expect(res.statusCode).toBe(200)
@@ -185,11 +180,9 @@ describe('Post invite route handler', () => {
     const req = httpMocks.createRequest({
       body,
     })
-    handlingEditor.teams = [heTeam.id]
-    req.user = handlingEditor
-    req.params.collectionId = '123'
+    req.user = handlingEditor.id
+    req.params.collectionId = standardCollection.id
     const res = httpMocks.createResponse()
-    const models = Model.build(standardCollection, handlingEditor, author)
     await require(postInvitePath)(models)(req, res)
 
     expect(res.statusCode).toBe(200)
@@ -197,4 +190,30 @@ describe('Post invite route handler', () => {
     expect(data.email).toEqual(body.email)
     expect(data.invitations[0].collectionId).toEqual(req.params.collectionId)
   })
+  it('should return an error when inviting his self', async () => {
+    body.role = globalRoles[random(0, globalRoles.length - 1)]
+    body.admin = body.role === 'admin'
+    body.email = admin.email
+    const req = httpMocks.createRequest({
+      body,
+    })
+    req.user = admin.id
+    const res = httpMocks.createResponse()
+    await require(postInvitePath)(models)(req, res)
+
+    expect(res.statusCode).toBe(400)
+    const data = JSON.parse(res._getData())
+    expect(data.error).toEqual('Cannot invite yourself')
+  })
+  it('should return an error when the role is invalid', async () => {
+    body.role = 'someRandomRole'
+    const req = httpMocks.createRequest({
+      body,
+    })
+    req.user = editorInChief.id
+    const res = httpMocks.createResponse()
+    await require(postInvitePath)(models)(req, res)
+    const data = JSON.parse(res._getData())
+    expect(data.error).toEqual(`Role ${body.role} is invalid`)
+  })
 })
diff --git a/yarn.lock b/yarn.lock
index 78fcd80cbea0b5bec10c849f023d4fed20f55693..e24936ff287cce17c86c68a7597c79d9a3443183 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -164,27 +164,6 @@
     redux-form "^7.0.3"
     styled-components "^2.4.0"
 
-"@pubsweet/ui@^0.1.1":
-  version "0.1.3"
-  resolved "https://registry.yarnpkg.com/@pubsweet/ui/-/ui-0.1.3.tgz#3a02eb0d679f36eaf38f87196032b46c371e5477"
-  dependencies:
-    babel-jest "^21.2.0"
-    classnames "^2.2.5"
-    enzyme "^3.2.0"
-    enzyme-adapter-react-15 "^1.0.5"
-    humps "^2.0.1"
-    lodash "^4.17.4"
-    prop-types "^15.5.10"
-    react "^15.6.1"
-    react-dom "^15.6.1"
-    react-feather "^1.0.7"
-    react-redux "^5.0.2"
-    react-router-dom "^4.2.2"
-    react-tag-autocomplete "^5.4.1"
-    recompose "^0.26.0"
-    redux "^3.6.0"
-    redux-form "^7.0.3"
-
 "@pubsweet/ui@^3.0.0", "@pubsweet/ui@^3.1.0":
   version "3.1.0"
   resolved "https://registry.yarnpkg.com/@pubsweet/ui/-/ui-3.1.0.tgz#24c25c29fc36e34b9f654fe4378502232f8204fa"
@@ -8368,7 +8347,7 @@ react-dnd@^2.5.4:
     lodash "^4.2.0"
     prop-types "^15.5.10"
 
-"react-dom@>=15.0.0 <17.0.0", react-dom@^16.2.0:
+"react-dom@>=15.0.0 <17.0.0", react-dom@^16.1.0, react-dom@^16.2.0:
   version "16.2.0"
   resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-16.2.0.tgz#69003178601c0ca19b709b33a83369fe6124c044"
   dependencies:
@@ -8386,7 +8365,7 @@ react-dom@^15.6.1:
     object-assign "^4.1.0"
     prop-types "^15.5.10"
 
-react-feather@^1.0.7, react-feather@^1.0.8:
+react-feather@^1.0.8:
   version "1.0.8"
   resolved "https://registry.yarnpkg.com/react-feather/-/react-feather-1.0.8.tgz#69b13d5c729949f194d33201dee91bab67fa31a2"
 
@@ -8480,7 +8459,7 @@ react-transition-group@^2.0.0, react-transition-group@^2.2.0:
     prop-types "^15.5.8"
     warning "^3.0.0"
 
-"react@>=15.0.0 <17.0.0", react@^16.2.0:
+"react@>=15.0.0 <17.0.0", react@^16.1.0, react@^16.2.0:
   version "16.2.0"
   resolved "https://registry.yarnpkg.com/react/-/react-16.2.0.tgz#a31bd2dab89bff65d42134fa187f24d054c273ba"
   dependencies: