diff --git a/packages/component-email/src/routes/emails/helpers.js b/packages/component-email/src/routes/emails/helpers.js
index fe28ae508c0199466df06fa19e94dfa88b111aa7..f0473d53b17cd007085170f8d25542075d8d4e2b 100644
--- a/packages/component-email/src/routes/emails/helpers.js
+++ b/packages/component-email/src/routes/emails/helpers.js
@@ -31,7 +31,7 @@ module.exports = {
     email.content.subject = 'Confirm your email address'
     email.content.ctaLink = services.createUrl(baseUrl, confirmSignUp, {
       userId: user.id,
-      confirmationToken: user.confirmationToken,
+      confirmationToken: user.accessTokens.confirmation,
     })
 
     const { html, text } = email.getBody({
diff --git a/packages/component-email/src/routes/emails/notifications.js b/packages/component-email/src/routes/emails/notifications.js
index 28791d3bae1ac9a59af53b0c7a6620aba7184c0e..0d4f67258a614657a471ecc0a38b4ba72a994407 100644
--- a/packages/component-email/src/routes/emails/notifications.js
+++ b/packages/component-email/src/routes/emails/notifications.js
@@ -9,7 +9,7 @@ const { Email, services } = require('pubsweet-component-helper-service')
 const { sendNewUserEmail, sendSignupEmail } = require('./helpers')
 
 module.exports = {
-  async sendNotifications({ user, baseUrl, role, UserModel }) {
+  async sendNotifications({ user, baseUrl, role }) {
     const email = new Email({
       type: 'user',
       fromEmail: `Hindawi <${staffEmail}>`,
@@ -20,7 +20,7 @@ module.exports = {
       content: {
         ctaLink: services.createUrl(baseUrl, resetPath, {
           email: user.email,
-          token: user.passwordResetToken,
+          token: user.accessTokens.passwordReset,
           firstName: user.firstName,
           lastName: user.lastName,
           affiliation: user.affiliation,
@@ -30,6 +30,7 @@ module.exports = {
         signatureName: 'Hindawi',
         unsubscribeLink: services.createUrl(baseUrl, unsubscribeSlug, {
           id: user.id,
+          token: user.accessTokens.unsubscribe,
         }),
       },
     })
diff --git a/packages/component-email/src/routes/emails/post.js b/packages/component-email/src/routes/emails/post.js
index bb7b30d36cd62189b68cca80c9cf5df08be1cda8..246322fa3a7da378a16d3ade2b684c405b6ec5b8 100644
--- a/packages/component-email/src/routes/emails/post.js
+++ b/packages/component-email/src/routes/emails/post.js
@@ -22,7 +22,7 @@ module.exports = models => async (req, res) => {
     const user = await UserModel.findByEmail(email)
 
     if (type === 'signup') {
-      if (!user.confirmationToken) {
+      if (!user.accessTokens.confirmation) {
         return res
           .status(400)
           .json({ error: 'User does not have a confirmation token.' })
diff --git a/packages/component-fixture-manager/src/fixtures/userData.js b/packages/component-fixture-manager/src/fixtures/userData.js
index d16f25b857f0e7c9cdc2e8346f8da2635f6e3cda..c6291916908889cf22eaf26ed80c52aebab500bb 100644
--- a/packages/component-fixture-manager/src/fixtures/userData.js
+++ b/packages/component-fixture-manager/src/fixtures/userData.js
@@ -20,4 +20,5 @@ module.exports = {
   answerHE: generateUserData(),
   inactiveReviewer: generateUserData(),
   inactiveUser: generateUserData(),
+  editorInChief: generateUserData(),
 }
diff --git a/packages/component-fixture-manager/src/fixtures/users.js b/packages/component-fixture-manager/src/fixtures/users.js
index e118c2bceddd2340a4b63156e6246666cdb8d37d..a315e0e1b720d98a1de3fabcb478d2f428b34ad2 100644
--- a/packages/component-fixture-manager/src/fixtures/users.js
+++ b/packages/component-fixture-manager/src/fixtures/users.js
@@ -1,303 +1,66 @@
-const { heTeamID, revTeamID, authorTeamID } = require('./teamIDs')
-const {
-  handlingEditor,
-  user,
-  admin,
-  author,
-  reviewer,
-  answerReviewer,
-  submittingAuthor,
-  recReviewer,
-  answerHE,
-  inactiveReviewer,
-  inactiveUser,
-} = require('./userData')
 const Chance = require('chance')
+const usersData = require('./userData')
 
 const chance = new Chance()
-const users = {
-  admin: {
-    type: 'user',
-    username: 'admin',
-    email: admin.email,
-    firstName: admin.firstName,
-    lastName: admin.lastName,
-    password: 'test',
-    admin: true,
-    id: admin.id,
-    isActive: true,
-    notifications: {
-      email: {
-        user: true,
-        system: true,
-      },
-    },
-  },
-  editorInChief: {
-    type: 'user',
-    username: chance.word(),
-    email: chance.email(),
-    password: 'password',
-    admin: false,
-    id: chance.guid(),
-    firstName: chance.first(),
-    lastName: chance.last(),
-    affiliation: chance.company(),
-    title: 'Mr',
-    save: jest.fn(() => users.editorInChief),
-    isConfirmed: false,
-    editorInChief: true,
-    isActive: true,
-    notifications: {
-      email: {
-        user: true,
-        system: true,
-      },
-    },
-  },
-  handlingEditor: {
-    type: 'user',
-    username: chance.word(),
-    email: handlingEditor.email,
-    password: 'password',
-    admin: false,
-    id: handlingEditor.id,
-    firstName: handlingEditor.firstName,
-    lastName: handlingEditor.lastName,
-    teams: [heTeamID],
-    save: jest.fn(() => users.handlingEditor),
-    editorInChief: false,
-    handlingEditor: true,
-    title: 'Mr',
-    isActive: true,
-    notifications: {
-      email: {
-        user: true,
-        system: true,
-      },
-    },
-  },
-  answerHE: {
-    type: 'user',
-    username: chance.word(),
-    email: answerHE.email,
-    password: 'password',
-    admin: false,
-    id: answerHE.id,
-    firstName: answerHE.firstName,
-    lastName: answerHE.lastName,
-    teams: [heTeamID],
-    save: jest.fn(() => users.answerHE),
-    editorInChief: false,
-    handlingEditor: true,
-    title: 'Mr',
-    isActive: true,
-    notifications: {
-      email: {
-        user: true,
-        system: true,
-      },
-    },
-  },
-  user: {
+const { heTeamID, revTeamID, authorTeamID } = require('./teamIDs')
+
+const keys = Object.keys(usersData)
+let users = {}
+users = keys.reduce((obj, item) => {
+  const userData = usersData[item]
+  const isHE = item === 'answerHE' || item === 'handlingEditor'
+  let teams = []
+
+  if (isHE) {
+    teams = [heTeamID]
+  }
+  if (item === 'author') {
+    teams = [authorTeamID]
+  }
+  if (
+    ['reviewer', 'answerReviewer', 'recReviewer', 'inactiveReviewer'].includes(
+      item,
+    )
+  ) {
+    teams = [revTeamID]
+  }
+
+  obj[item] = {
+    ...userData,
+    teams,
     type: 'user',
-    username: chance.word(),
-    email: user.email,
+    username: item,
     password: 'password',
-    admin: false,
-    id: user.id,
-    passwordResetToken: chance.hash(),
-    firstName: user.firstName,
-    lastName: user.lastName,
+    handlingEditor: isHE,
+    token: chance.hash(),
+    admin: item === 'admin',
     affiliation: chance.company(),
-    title: 'Mr',
+    isConfirmed: item === 'author',
+    isActive: !['inactiveUser', 'inactiveReviewer'].includes(item),
+    editorInChief: item === 'editorInChief',
+    updateProperties: jest.fn(() => users[item]),
+    passwordResetTimestamp: item === 'author' ? Date.now() : undefined,
     save: jest.fn(function save() {
       return this
     }),
-    isConfirmed: false,
-    updateProperties: jest.fn(() => users.user),
-    teams: [],
-    confirmationToken: chance.hash(),
-    validPassword: jest.fn(function validPassword(password) {
-      return this.password === password
-    }),
-    token: chance.hash(),
-    isActive: true,
     notifications: {
       email: {
         user: true,
         system: true,
       },
     },
-  },
-  author: {
-    type: 'user',
-    username: chance.word(),
-    email: author.email,
-    password: 'password',
-    admin: false,
-    id: author.id,
-    firstName: author.firstName,
-    lastName: author.lastName,
-    affiliation: chance.company(),
-    title: 'Mr',
-    save: jest.fn(() => users.author),
-    isConfirmed: true,
-    passwordResetToken: chance.hash(),
-    passwordResetTimestamp: Date.now(),
-    teams: [authorTeamID],
-    confirmationToken: chance.hash(),
-    isActive: true,
-    notifications: {
-      email: {
-        user: true,
-        system: true,
-      },
+    accessTokens: {
+      confirmation: chance.hash(),
+      unsubscribe: chance.hash(),
+      invitation: item === 'reviewer' ? chance.hash() : undefined,
+      passwordReset: item === 'user' ? chance.hash() : undefined,
     },
-  },
-  reviewer: {
-    type: 'user',
-    username: chance.word(),
-    email: reviewer.email,
-    password: 'password',
-    admin: false,
-    id: reviewer.id,
-    firstName: reviewer.firstName,
-    lastName: reviewer.lastName,
-    affiliation: chance.company(),
-    title: 'Mr',
-    save: jest.fn(() => users.reviewer),
-    isConfirmed: true,
-    teams: [revTeamID],
-    invitationToken: 'inv-token-123',
-    isActive: true,
-    notifications: {
-      email: {
-        user: true,
-        system: true,
-      },
-    },
-  },
-  answerReviewer: {
-    type: 'user',
-    username: chance.word(),
-    email: answerReviewer.email,
-    password: 'password',
-    admin: false,
-    id: answerReviewer.id,
-    firstName: answerReviewer.firstName,
-    lastName: answerReviewer.lastName,
-    affiliation: chance.company(),
-    title: 'Dr',
-    save: jest.fn(() => users.answerReviewer),
-    isConfirmed: true,
-    teams: [revTeamID],
-    invitationToken: 'inv-token-123',
-    isActive: true,
-    notifications: {
-      email: {
-        user: true,
-        system: true,
-      },
-    },
-  },
-  submittingAuthor: {
-    type: 'user',
-    username: 'sauthor',
-    email: submittingAuthor.email,
-    password: 'password',
-    admin: false,
-    id: submittingAuthor.id,
-    passwordResetToken: chance.hash(),
-    firstName: submittingAuthor.firstName,
-    lastName: submittingAuthor.lastName,
-    affiliation: chance.company(),
-    title: 'Mr',
-    save: jest.fn(() => users.submittingAuthor),
-    isConfirmed: false,
-    isActive: true,
-    notifications: {
-      email: {
-        user: true,
-        system: true,
-      },
-    },
-  },
-  recReviewer: {
-    type: 'user',
-    username: chance.word(),
-    email: recReviewer.email,
-    password: 'password',
-    admin: false,
-    id: recReviewer.id,
-    firstName: recReviewer.firstName,
-    lastName: recReviewer.lastName,
-    affiliation: chance.company(),
-    title: 'Mr',
-    save: jest.fn(() => users.recReviewer),
-    isConfirmed: true,
-    teams: [revTeamID],
-    isActive: true,
-    notifications: {
-      email: {
-        user: true,
-        system: true,
-      },
-    },
-  },
-  inactiveReviewer: {
-    type: 'user',
-    username: chance.word(),
-    email: inactiveReviewer.email,
-    password: 'password',
-    admin: false,
-    id: inactiveReviewer.id,
-    firstName: inactiveReviewer.firstName,
-    lastName: inactiveReviewer.lastName,
-    affiliation: chance.company(),
-    title: 'Dr',
-    save: jest.fn(() => users.inactiveReviewer),
-    isConfirmed: true,
-    teams: [revTeamID],
-    isActive: false,
-    notifications: {
-      email: {
-        user: true,
-        system: true,
-      },
-    },
-  },
-  inactiveUser: {
-    type: 'user',
-    username: chance.word(),
-    email: inactiveUser.email,
-    password: 'password',
-    admin: false,
-    id: inactiveUser.id,
-    passwordResetToken: chance.hash(),
-    firstName: inactiveUser.firstName,
-    lastName: inactiveUser.lastName,
-    affiliation: chance.company(),
-    title: 'Mr',
-    save: jest.fn(function save() {
-      return this
-    }),
-    isConfirmed: false,
-    updateProperties: jest.fn(() => users.inactiveUser),
-    teams: [],
-    confirmationToken: chance.hash(),
     validPassword: jest.fn(function validPassword(password) {
       return this.password === password
     }),
-    token: chance.hash(),
-    isActive: false,
-    notifications: {
-      email: {
-        user: true,
-        system: true,
-      },
-    },
-  },
-}
+  }
+
+  return obj
+}, {})
 
 module.exports = users
diff --git a/packages/component-helper-service/src/services/User.js b/packages/component-helper-service/src/services/User.js
index 09c722d31cabe70492fb2c0fcdae381ee4c0c1ef..e37a835dce2cc83b7541bae9f22a808bf86eb908 100644
--- a/packages/component-helper-service/src/services/User.js
+++ b/packages/component-helper-service/src/services/User.js
@@ -1,6 +1,6 @@
 const uuid = require('uuid')
 const { get } = require('lodash')
-const crypto = require('crypto')
+const services = require('./services')
 
 class User {
   constructor({ UserModel = {} }) {
@@ -16,23 +16,26 @@ class User {
       username,
       email,
       password,
-      passwordResetToken: crypto.randomBytes(32).toString('hex'),
-      isConfirmed: false,
       firstName,
       lastName,
       affiliation,
       title,
-      editorInChief: role === 'editorInChief',
+      isActive: true,
+      isConfirmed: false,
       admin: role === 'admin',
+      editorInChief: role === 'editorInChief',
       handlingEditor: role === 'handlingEditor',
-      invitationToken: role === 'reviewer' ? uuid.v4() : '',
-      isActive: true,
       notifications: {
         email: {
           system: true,
           user: true,
         },
       },
+      accessTokens: {
+        invitation: role === 'reviewer' ? services.generateHash() : undefined,
+        unsubscribe: services.generateHash(),
+        passwordReset: services.generateHash(),
+      },
     }
 
     let newUser = new UserModel(userBody)
diff --git a/packages/component-helper-service/src/services/email/Email.js b/packages/component-helper-service/src/services/email/Email.js
index 0271625e2cb99f8db804d43000b6b54a89e78892..0a7c2028bc6dde1ba7259459136a48a541eae620 100644
--- a/packages/component-helper-service/src/services/email/Email.js
+++ b/packages/component-helper-service/src/services/email/Email.js
@@ -85,6 +85,9 @@ class Email {
     logger.info(
       `EMAIL: Sent email from ${from} to ${to} with subject '${subject}'`,
     )
+    logger.debug(
+      `EMAIL: Sent email from ${from} to ${to} with subject '${subject}'`,
+    )
     SendEmail.send(mailData)
   }
 }
diff --git a/packages/component-helper-service/src/services/services.js b/packages/component-helper-service/src/services/services.js
index dab0fae58764405a3b8b4c5add996ee43657fe2b..d9f11a59a70ab9afb4aff0b8d856a6b34ce1a4b0 100644
--- a/packages/component-helper-service/src/services/services.js
+++ b/packages/component-helper-service/src/services/services.js
@@ -13,10 +13,10 @@ const validateEmailAndToken = async ({ email, token, userModel }) => {
   try {
     const user = await userModel.findByEmail(email)
     if (user) {
-      if (token !== user.passwordResetToken) {
+      if (token !== user.accessTokens.passwordReset) {
         logger.error(
           `invite pw reset tokens do not match: REQ ${token} vs. DB ${
-            user.passwordResetToken
+            user.accessTokens.passwordReset
           }`,
         )
         return {
@@ -94,6 +94,14 @@ const getExpectedDate = ({ timestamp = Date.now(), daysExpected = 0 }) => {
 
   return expectedDate
 }
+
+const generateHash = () =>
+  Array.from({ length: 4 }, () =>
+    Math.random()
+      .toString(36)
+      .slice(4),
+  ).join('')
+
 module.exports = {
   checkForUndefinedParams,
   validateEmailAndToken,
@@ -101,4 +109,5 @@ module.exports = {
   getBaseUrl,
   createUrl,
   getExpectedDate,
+  generateHash,
 }
diff --git a/packages/component-invite/src/routes/collectionsInvitations/emails/helpers.js b/packages/component-invite/src/routes/collectionsInvitations/emails/helpers.js
index e2672c268c52646f102c957f2089a053802db8c2..0bdde9b698a634be1a0071414209d53f8b1d65fc 100644
--- a/packages/component-invite/src/routes/collectionsInvitations/emails/helpers.js
+++ b/packages/component-invite/src/routes/collectionsInvitations/emails/helpers.js
@@ -30,6 +30,7 @@ module.exports = {
       unsubscribeSlug,
       {
         id: handlingEditor.id,
+        token: handlingEditor.accessTokens.unsubscribe,
       },
     )
 
@@ -63,13 +64,7 @@ module.exports = {
       email: eic.email,
     }
 
-    email.content.unsubscribeLink = services.createUrl(
-      baseUrl,
-      unsubscribeSlug,
-      {
-        id: eic.id,
-      },
-    )
+    email.content.unsubscribeLink = services.createUrl(baseUrl)
 
     const { html, text } = email.getBody({
       body: getEmailCopy({
diff --git a/packages/component-invite/src/routes/fragmentsInvitations/decline.js b/packages/component-invite/src/routes/fragmentsInvitations/decline.js
index 5ac57713ff800ef188b2a977fc89d4fc1f56d18b..6d661344665ccd70f199eb3b7b024dd46010c033 100644
--- a/packages/component-invite/src/routes/fragmentsInvitations/decline.js
+++ b/packages/component-invite/src/routes/fragmentsInvitations/decline.js
@@ -14,62 +14,73 @@ module.exports = models => async (req, res) => {
     return res.status(400).json({ error: 'Token is required' })
 
   const UserModel = models.User
+
+  const users = await UserModel.all()
+
+  const user = users.find(
+    user => user.accessTokens.invitation === invitationToken,
+  )
+  if (!user) {
+    return res.status(404).json({
+      error: `User not found.`,
+    })
+  }
+
+  let collection = {}
+  let fragment = {}
   try {
-    const user = await UserModel.findOneByField(
-      'invitationToken',
-      invitationToken,
-    )
-    const collection = await models.Collection.find(collectionId)
+    collection = await models.Collection.find(collectionId)
     if (!collection.fragments.includes(fragmentId))
       return res.status(400).json({
         error: `Fragment ${fragmentId} does not match collection ${collectionId}`,
       })
-    const fragment = await models.Fragment.find(fragmentId)
-    fragment.invitations = fragment.invitations || []
-    const invitation = await fragment.invitations.find(
-      invitation => invitation.id === invitationId,
-    )
-
-    const invitationHelper = new Invitation({
-      userId: user.id,
-      role: 'reviewer',
-      invitation,
+    fragment = await models.Fragment.find(fragmentId)
+  } catch (err) {
+    const notFoundError = await services.handleNotFoundError(err, 'Item')
+    return res.status(notFoundError.status).json({
+      error: notFoundError.message,
     })
+  }
 
-    const invitationValidation = invitationHelper.validateInvitation()
-    if (invitationValidation.error)
-      return res.status(invitationValidation.status).json({
-        error: invitationValidation.error,
-      })
-
-    const authsome = authsomeHelper.getAuthsome(models)
-    const canPatch = await authsome.can(req.user, 'PATCH', invitation)
-    if (!canPatch)
-      return res.status(403).json({
-        error: 'Unauthorized.',
-      })
-
-    invitation.respondedOn = Date.now()
-    invitation.hasAnswer = true
-    invitation.isAccepted = false
-    await fragment.save()
+  fragment.invitations = fragment.invitations || []
+  const invitation = await fragment.invitations.find(
+    invitation => invitation.id === invitationId,
+  )
 
-    const baseUrl = services.getBaseUrl(req)
+  const invitationHelper = new Invitation({
+    userId: user.id,
+    role: 'reviewer',
+    invitation,
+  })
 
-    notifications.sendNotifications({
-      baseUrl,
-      fragment,
-      collection,
-      reviewer: user,
-      UserModel: models.User,
-      emailType: 'reviewer-declined',
+  const invitationValidation = invitationHelper.validateInvitation()
+  if (invitationValidation.error)
+    return res.status(invitationValidation.status).json({
+      error: invitationValidation.error,
     })
 
-    return res.status(200).json({})
-  } catch (e) {
-    const notFoundError = await services.handleNotFoundError(e, 'item')
-    return res.status(notFoundError.status).json({
-      error: notFoundError.message,
+  const authsome = authsomeHelper.getAuthsome(models)
+  const canPatch = await authsome.can(req.user, 'PATCH', invitation)
+  if (!canPatch)
+    return res.status(403).json({
+      error: 'Unauthorized.',
     })
-  }
+
+  invitation.respondedOn = Date.now()
+  invitation.hasAnswer = true
+  invitation.isAccepted = false
+  await fragment.save()
+
+  const baseUrl = services.getBaseUrl(req)
+
+  notifications.sendNotifications({
+    baseUrl,
+    fragment,
+    collection,
+    reviewer: user,
+    UserModel: models.User,
+    emailType: 'reviewer-declined',
+  })
+
+  return res.status(200).json({})
 }
diff --git a/packages/component-invite/src/routes/fragmentsInvitations/emails/invitations.js b/packages/component-invite/src/routes/fragmentsInvitations/emails/invitations.js
index fdb2dd642133722baab938d04c2b47e6f48d43a1..aaddd1b1cc5af20e8f0751a34a5da7c0d6ebe422 100644
--- a/packages/component-invite/src/routes/fragmentsInvitations/emails/invitations.js
+++ b/packages/component-invite/src/routes/fragmentsInvitations/emails/invitations.js
@@ -50,7 +50,7 @@ module.exports = {
       agree: false,
       fragmentId: fragment.id,
       collectionId: collection.id,
-      invitationToken: invitedUser.invitationToken,
+      invitationToken: invitedUser.accessTokens.invitation,
     })
 
     let agreeLink = services.createUrl(baseUrl, detailsPath, queryParams)
@@ -58,11 +58,11 @@ module.exports = {
     if (!invitedUser.isConfirmed) {
       queryParams = {
         ...queryParams,
+        agree: true,
+        fragmentId: fragment.id,
         email: invitedUser.email,
-        token: invitedUser.passwordResetToken,
         collectionId: collection.id,
-        fragmentId: fragment.id,
-        agree: true,
+        token: invitedUser.accessTokens.passwordReset,
       }
       agreeLink = services.createUrl(baseUrl, inviteReviewerPath, queryParams)
     }
@@ -88,6 +88,7 @@ module.exports = {
         signatureName: get(collection, 'handlingEditor.name', 'Hindawi'),
         unsubscribeLink: services.createUrl(baseUrl, unsubscribeSlug, {
           id: invitedUser.id,
+          token: invitedUser.accessTokens.unsubscribe,
         }),
       },
     })
diff --git a/packages/component-invite/src/routes/fragmentsInvitations/emails/notifications.js b/packages/component-invite/src/routes/fragmentsInvitations/emails/notifications.js
index c15717402311b42c516697898d97d2767ff44b5a..640974fbcceb3a6b7c53eb905fe7ab20d92b0afe 100644
--- a/packages/component-invite/src/routes/fragmentsInvitations/emails/notifications.js
+++ b/packages/component-invite/src/routes/fragmentsInvitations/emails/notifications.js
@@ -66,12 +66,13 @@ module.exports = {
     }
 
     if (['reviewer-accepted', 'reviewer-declined'].includes(emailType)) {
+      const heUser = await UserModel.find(handlingEditor.id)
       sendHandlingEditorEmail({
         email,
         eicName,
         titleText,
         emailType,
-        handlingEditor,
+        handlingEditor: heUser,
         customId: collection.customId,
         targetUserName: `${reviewer.lastName}`,
       })
@@ -100,6 +101,7 @@ const sendHandlingEditorEmail = ({
       : `${customId}: A reviewer has declined`
   email.content.unsubscribeLink = services.createUrl(baseUrl, unsubscribeSlug, {
     id: handlingEditor.id,
+    token: handlingEditor.accessTokens.unsubscribe,
   })
   email.content.signatureName = eicName
   email.content.signatureJournal = ''
@@ -141,6 +143,7 @@ const sendReviewerEmail = async ({
 
   email.content.unsubscribeLink = services.createUrl(baseUrl, unsubscribeSlug, {
     id: reviewer.id,
+    token: reviewer.accessTokens.unsubscribe,
   })
 
   const { html, text } = email.getBody({
diff --git a/packages/component-invite/src/tests/fragmentsInvitations/decline.test.js b/packages/component-invite/src/tests/fragmentsInvitations/decline.test.js
index 0357e6c500ccc2a865352b93715dd8ea6fed7c83..746129e53cdb35d96903aa48bdb0ed2a165a2af2 100644
--- a/packages/component-invite/src/tests/fragmentsInvitations/decline.test.js
+++ b/packages/component-invite/src/tests/fragmentsInvitations/decline.test.js
@@ -12,7 +12,7 @@ jest.mock('@pubsweet/component-send-email', () => ({
 }))
 
 const reqBody = {
-  invitationToken: 'inv-token-123',
+  invitationToken: fixtures.users.reviewer.accessTokens.invitation,
 }
 const patchPath = '../../routes/fragmentsInvitations/decline'
 describe('Decline fragments invitations route handler', () => {
@@ -74,7 +74,7 @@ describe('Decline fragments invitations route handler', () => {
 
     expect(res.statusCode).toBe(404)
     const data = JSON.parse(res._getData())
-    expect(data.error).toEqual('item not found')
+    expect(data.error).toEqual('Item not found')
   })
   it('should return an error if the fragment does not exists', async () => {
     const { reviewer } = testFixtures.users
@@ -136,7 +136,7 @@ describe('Decline fragments invitations route handler', () => {
     await require(patchPath)(models)(req, res)
     expect(res.statusCode).toBe(404)
     const data = JSON.parse(res._getData())
-    expect(data.error).toEqual('item not found')
+    expect(data.error).toEqual('User not found.')
   })
   it('should return an error when the invitation is already answered', async () => {
     const { reviewer } = testFixtures.users
diff --git a/packages/component-manuscript-manager/src/routes/fragments/notifications/notifications.js b/packages/component-manuscript-manager/src/routes/fragments/notifications/notifications.js
index c7817167d36f538b3fed2c88ddb51eba0ece61c1..bd78d0169c9fadd3bf210d07f64f7f20c8af2b84 100644
--- a/packages/component-manuscript-manager/src/routes/fragments/notifications/notifications.js
+++ b/packages/component-manuscript-manager/src/routes/fragments/notifications/notifications.js
@@ -25,8 +25,9 @@ module.exports = {
     isMajorRecommendation,
   }) {
     const fragmentHelper = new Fragment({ fragment })
+    const handlingEditor = get(collection, 'handlingEditor')
     const parsedFragment = await fragmentHelper.getFragmentData({
-      handlingEditor: collection.handlingEditor,
+      handlingEditor,
     })
     const {
       submittingAuthor,
@@ -53,14 +54,16 @@ module.exports = {
 
     const userHelper = new User({ UserModel })
     const eicName = await userHelper.getEiCName()
+
     if (isNewVersion) {
+      const heUser = await UserModel.find(handlingEditor.id)
       sendHandlingEditorEmail({
         email,
         eicName,
         baseUrl,
         customId,
         title: parsedFragment.title,
-        handlingEditor: get(collection, 'handlingEditor', {}),
+        handlingEditor: heUser,
       })
     }
 
@@ -114,6 +117,7 @@ const sendHandlingEditorEmail = ({
   }
   email.content.unsubscribeLink = services.createUrl(baseUrl, unsubscribeSlug, {
     id: handlingEditor.id,
+    token: handlingEditor.accessTokens.unsubscribe,
   })
   email.content.signatureName = eicName
   email.content.subject = `${customId}: Revision submitted`
@@ -154,6 +158,7 @@ const sendReviewersEmail = async ({
       unsubscribeSlug,
       {
         id: reviewer.id,
+        token: reviewer.accessTokens.unsubscribe,
       },
     )
 
@@ -219,7 +224,8 @@ const sendAuthorsEmail = async ({
       return {
         ...author,
         isConfirmed: user.isConfirmed,
-        passwordResetToken: user.passwordResetToken,
+        unsubscribeToken: user.accessTokens.unsubscribe,
+        passwordResetToken: user.accessTokens.passwordReset,
         ...getEmailCopy({
           emailType: author.isSubmitting
             ? 'submitting-author-manuscript-submitted'
@@ -242,6 +248,7 @@ const sendAuthorsEmail = async ({
       unsubscribeSlug,
       {
         id: author.id,
+        token: author.unsubscribeToken,
       },
     )
 
diff --git a/packages/component-manuscript-manager/src/routes/fragmentsRecommendations/notifications/helpers.js b/packages/component-manuscript-manager/src/routes/fragmentsRecommendations/notifications/helpers.js
index 813b50f59c642813a0abcb651853f5d0c0afd426..ffeb610de4b8dc4921ba7b0a89b4a186c89eddff 100644
--- a/packages/component-manuscript-manager/src/routes/fragmentsRecommendations/notifications/helpers.js
+++ b/packages/component-manuscript-manager/src/routes/fragmentsRecommendations/notifications/helpers.js
@@ -19,6 +19,7 @@ module.exports = {
         unsubscribeSlug,
         {
           id: reviewer.id,
+          token: reviewer.accessTokens.unsubscribe,
         },
       )
       const { html, text } = email.getBody({
@@ -152,6 +153,7 @@ module.exports = {
       unsubscribeSlug,
       {
         id: handlingEditor.id,
+        token: handlingEditor.accessTokens.unsubscribe,
       },
     )
 
@@ -266,6 +268,7 @@ module.exports = {
         unsubscribeSlug,
         {
           id: author.id,
+          token: author.accessTokens.unsubscribe,
         },
       )
       const { html, text } = email.getBody({
@@ -347,6 +350,7 @@ module.exports = {
       unsubscribeSlug,
       {
         id: author.id,
+        token: author.accessTokens.unsubscribe,
       },
     )
     const { html, text } = email.getBody({
diff --git a/packages/component-manuscript-manager/src/routes/fragmentsRecommendations/notifications/notifications.js b/packages/component-manuscript-manager/src/routes/fragmentsRecommendations/notifications/notifications.js
index 9d7420d37a338e0377f8f1af6091e5b25d0436ef..9f6c43f5ea5f30885ad0fa99dc01e372abba8d00 100644
--- a/packages/component-manuscript-manager/src/routes/fragmentsRecommendations/notifications/notifications.js
+++ b/packages/component-manuscript-manager/src/routes/fragmentsRecommendations/notifications/notifications.js
@@ -80,6 +80,8 @@ module.exports = {
       hasPeerReview(collection) &&
       (recommendation !== 'publish' || hasEQA)
     ) {
+      const handlingEditor = get(collection, 'handlingEditor', {})
+      const heUser = await UserModel.find(handlingEditor.id)
       email = helpers.updateEmailContentForHE({
         email,
         baseUrl,
@@ -88,7 +90,7 @@ module.exports = {
         recommendation,
         recommendationType,
         heLastName: collHelper.getHELastName(),
-        handlingEditor: get(collection, 'handlingEditor', {}),
+        handlingEditor: heUser,
       })
       const emailType = helpers.getEmailTypeByRecommendationForHE({
         recommendation,
diff --git a/packages/component-user-manager/src/Users.js b/packages/component-user-manager/src/Users.js
index 9cc9c3f43fdda0bf00af9765aa5d28ca43bcb008..2a61b4cff35bdfa5bfa5cd3e268fc12c8a58535b 100644
--- a/packages/component-user-manager/src/Users.js
+++ b/packages/component-user-manager/src/Users.js
@@ -133,6 +133,7 @@ const Users = app => {
    * @apiParamExample {json} Body
    *    {
    *      "id": "a6184463-b17a-42f8-b02b-ae1d755cdc6b",
+   *      "token": "dssds-132131-42f8-b02b-ae1d755cdc6b"
    *      "subscribe": true,
    *    }
    * @apiSuccessExample {json} Success
diff --git a/packages/component-user-manager/src/routes/fragmentsUsers/emails/notifications.js b/packages/component-user-manager/src/routes/fragmentsUsers/emails/notifications.js
index 71e544c00e30d2d1aa5cd894698484357c98a9d7..26bf63b940fc1a4511342026a5a948873ee0d6e3 100644
--- a/packages/component-user-manager/src/routes/fragmentsUsers/emails/notifications.js
+++ b/packages/component-user-manager/src/routes/fragmentsUsers/emails/notifications.js
@@ -45,6 +45,7 @@ module.exports = {
         ctaLink: services.createUrl(baseUrl, ''),
         unsubscribeLink: services.createUrl(baseUrl, unsubscribeSlug, {
           id: submittingAuthor.id,
+          token: submittingAuthor.accessTokens.unsubscribe,
         }),
       },
     })
@@ -52,11 +53,11 @@ module.exports = {
     if (!submittingAuthor.isConfirmed) {
       email.content.ctaLink = services.createUrl(baseUrl, resetPath, {
         email: submittingAuthor.email,
-        token: submittingAuthor.passwordResetToken,
+        title: submittingAuthor.title,
         firstName: submittingAuthor.firstName,
         lastName: submittingAuthor.lastName,
         affiliation: submittingAuthor.affiliation,
-        title: submittingAuthor.title,
+        token: submittingAuthor.accessTokens.passwordReset,
       })
       email.content.ctaText = 'CONFIRM ACCOUNT'
     }
diff --git a/packages/component-user-manager/src/routes/users/changePassword.js b/packages/component-user-manager/src/routes/users/changePassword.js
index 57c1b2b7b17826155673582772cc2578b5eb441a..30073accccd603baa60cdea466dc3a35ded680f1 100644
--- a/packages/component-user-manager/src/routes/users/changePassword.js
+++ b/packages/component-user-manager/src/routes/users/changePassword.js
@@ -14,6 +14,7 @@ module.exports = models => async (req, res) => {
   let user
   try {
     user = await models.User.find(req.user)
+
     if (!await user.validPassword(password)) {
       return res.status(400).json({ error: 'Wrong username or password.' })
     }
diff --git a/packages/component-user-manager/src/routes/users/confirm.js b/packages/component-user-manager/src/routes/users/confirm.js
index 9db497a2f5f147e1fc324fb862aeb02e9dd6f3cb..aa6875d0ce51d5e067016a8235456770d0d62109 100644
--- a/packages/component-user-manager/src/routes/users/confirm.js
+++ b/packages/component-user-manager/src/routes/users/confirm.js
@@ -21,7 +21,7 @@ module.exports = models => async (req, res) => {
       return res.status(403).json({ error: 'Unauthorized.' })
     }
 
-    if (user.confirmationToken !== confirmationToken) {
+    if (user.accessTokens.confirmation !== confirmationToken) {
       return res.status(400).json({ error: 'Wrong confirmation token.' })
     }
 
@@ -29,7 +29,7 @@ module.exports = models => async (req, res) => {
       return res.status(400).json({ error: 'User is already confirmed.' })
 
     user.isConfirmed = true
-    delete user.confirmationToken
+    delete user.accessTokens.confirmation
     await user.save()
 
     return res.status(200).json({
diff --git a/packages/component-user-manager/src/routes/users/emails/notifications.js b/packages/component-user-manager/src/routes/users/emails/notifications.js
index 344b5c7acd0a51bef1b1cee98e4958df0c458252..461584e1983ee428a48d9336d4c774333a57307e 100644
--- a/packages/component-user-manager/src/routes/users/emails/notifications.js
+++ b/packages/component-user-manager/src/routes/users/emails/notifications.js
@@ -1,14 +1,12 @@
 const config = require('config')
+const { Email, services } = require('pubsweet-component-helper-service')
+
+const { getEmailCopy } = require('./emailCopy')
 
 const forgotPath = config.get('forgot-password.url')
 const { name: journalName, staffEmail } = config.get('journal')
-
 const unsubscribeSlug = config.get('unsubscribe.url')
 
-const { Email, services } = require('pubsweet-component-helper-service')
-
-const { getEmailCopy } = require('./emailCopy')
-
 module.exports = {
   async sendNotifications({ user, baseUrl }) {
     const email = new Email({
@@ -17,7 +15,7 @@ module.exports = {
       content: {
         ctaLink: services.createUrl(baseUrl, forgotPath, {
           email: user.email,
-          token: user.passwordResetToken,
+          token: user.accessTokens.passwordReset,
         }),
         ctaText: 'RESET PASSWORD',
       },
@@ -35,6 +33,7 @@ const sendForgotPasswordEmail = ({ email, baseUrl, user }) => {
   email.content.subject = 'Forgot Password'
   email.content.unsubscribeLink = services.createUrl(baseUrl, unsubscribeSlug, {
     id: user.id,
+    token: user.accessTokens.unsubscribe,
   })
 
   const { html, text } = email.getBody({
diff --git a/packages/component-user-manager/src/routes/users/forgotPassword.js b/packages/component-user-manager/src/routes/users/forgotPassword.js
index 4d0eed77b30d5592f22219c69eb1c06246aeeaa6..55b750e69bfaa886aaae329843c1967e8be6c634 100644
--- a/packages/component-user-manager/src/routes/users/forgotPassword.js
+++ b/packages/component-user-manager/src/routes/users/forgotPassword.js
@@ -32,7 +32,7 @@ module.exports = models => async (req, res) => {
       }
     }
 
-    user.passwordResetToken = generatePasswordHash()
+    user.accessTokens.passwordReset = services.generateHash()
     user.passwordResetTimestamp = Date.now()
     await user.save()
 
@@ -50,10 +50,3 @@ module.exports = models => async (req, res) => {
     message: `A password reset email has been sent to ${email}.`,
   })
 }
-
-const generatePasswordHash = () =>
-  Array.from({ length: 4 }, () =>
-    Math.random()
-      .toString(36)
-      .slice(4),
-  ).join('')
diff --git a/packages/component-user-manager/src/routes/users/post.js b/packages/component-user-manager/src/routes/users/post.js
index 32870956242c4642678383795848b6bb96946d80..8150e2ca77f46131c69325b591e5a58dcd93ad1c 100644
--- a/packages/component-user-manager/src/routes/users/post.js
+++ b/packages/component-user-manager/src/routes/users/post.js
@@ -29,16 +29,19 @@ module.exports = models => async (req, res) => {
       admin: false,
       isActive: true,
       isConfirmed: false,
-      handlingEditor: false,
       editorInChief: false,
+      handlingEditor: false,
       username: req.body.email,
-      confirmationToken: chance.hash(),
       notifications: {
         email: {
           system: true,
           user: true,
         },
       },
+      accessTokens: {
+        confirmation: chance.hash(),
+        unsubscribe: chance.hash(),
+      },
     }
   }
   let user = new models.User(req.body)
diff --git a/packages/component-user-manager/src/routes/users/resetPassword.js b/packages/component-user-manager/src/routes/users/resetPassword.js
index a91886ac3c44cc22b2c5f8d29cb2de3769559dba..b46c9cafdccf18ddab7facd6e629cc4a0f92a065 100644
--- a/packages/component-user-manager/src/routes/users/resetPassword.js
+++ b/packages/component-user-manager/src/routes/users/resetPassword.js
@@ -25,7 +25,7 @@ module.exports = models => async (req, res) => {
 
   req.body.isConfirmed = true
   req.body.isActive = true
-  delete user.passwordResetToken
+  delete user.accessTokens.passwordReset
   delete user.passwordResetTimestamp
   delete req.body.token
   user = await user.updateProperties(req.body)
diff --git a/packages/component-user-manager/src/routes/users/subscribe.js b/packages/component-user-manager/src/routes/users/subscribe.js
index fa90c8481012b0c8e045842764c508d8b0332d48..63a141c9211099ad3692ca09c8b25df472656014 100644
--- a/packages/component-user-manager/src/routes/users/subscribe.js
+++ b/packages/component-user-manager/src/routes/users/subscribe.js
@@ -2,14 +2,17 @@ const { set } = require('lodash')
 const { services } = require('pubsweet-component-helper-service')
 
 module.exports = models => async (req, res) => {
-  const { subscribe, id } = req.body
+  const { subscribe, id, token } = req.body
 
-  if (!services.checkForUndefinedParams(subscribe, id))
+  if (!services.checkForUndefinedParams(subscribe, id, token))
     return res.status(400).json({ error: 'Missing required params.' })
 
   let user
   try {
     user = await models.User.find(id)
+    if (user.accessTokens.unsubscribe !== token) {
+      return res.status(400).json({ error: `Invalid token: ${token}` })
+    }
     set(user, 'notifications.email.user', subscribe)
     user = await user.save()
 
diff --git a/packages/component-user-manager/src/tests/users/confirm.test.js b/packages/component-user-manager/src/tests/users/confirm.test.js
index 0cf81bc161be3dd78733541aded262a6d5d34853..25e773657fb2ccf00ddf7eab2d72b7796e0b2ace 100644
--- a/packages/component-user-manager/src/tests/users/confirm.test.js
+++ b/packages/component-user-manager/src/tests/users/confirm.test.js
@@ -14,7 +14,7 @@ jest.mock('@pubsweet/component-send-email', () => ({
 
 const reqBody = {
   userId: user.id,
-  confirmationToken: user.confirmationToken,
+  confirmationToken: user.accessTokens.confirmation,
 }
 
 const notFoundError = new Error()
@@ -64,7 +64,9 @@ describe('Users confirm route handler', () => {
   })
   it('should return an error when the user is already confirmed', async () => {
     body.userId = author.id
-    body.confirmationToken = author.confirmationToken
+    body.confirmationToken = author.accessTokens.confirmation
+    author.isConfirmed = true
+
     const req = httpMocks.createRequest({ body })
     const res = httpMocks.createResponse()
     await require(forgotPasswordPath)(models)(req, res)
@@ -81,7 +83,7 @@ describe('Users confirm route handler', () => {
   })
   it('should return an error when the user is inactive', async () => {
     body.userId = inactiveUser.id
-    body.confirmationToken = inactiveUser.confirmationToken
+    body.confirmationToken = inactiveUser.accessTokens.confirmation
     const req = httpMocks.createRequest({ body })
     const res = httpMocks.createResponse()
     await require(forgotPasswordPath)(models)(req, res)
diff --git a/packages/component-user-manager/src/tests/users/resetPassword.test.js b/packages/component-user-manager/src/tests/users/resetPassword.test.js
index fbdfe985f0c65f818dda72552f8ea9f9bdd5daff..6b9dfc7e9fcae7d466155e1ad227cb8fce07da07 100644
--- a/packages/component-user-manager/src/tests/users/resetPassword.test.js
+++ b/packages/component-user-manager/src/tests/users/resetPassword.test.js
@@ -21,7 +21,7 @@ const reqBody = {
   title: user.title,
   affiliation: user.affiliation,
   password: 'password',
-  token: user.passwordResetToken,
+  token: user.accessTokens.passwordReset,
   isConfirmed: false,
 }
 
diff --git a/packages/component-user-manager/src/tests/users/subscribe.test.js b/packages/component-user-manager/src/tests/users/subscribe.test.js
new file mode 100644
index 0000000000000000000000000000000000000000..de994a0ab8f92b72c7958f5f13d3e502b6b6a345
--- /dev/null
+++ b/packages/component-user-manager/src/tests/users/subscribe.test.js
@@ -0,0 +1,77 @@
+process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'
+process.env.SUPPRESS_NO_CONFIG_WARNING = true
+
+const httpMocks = require('node-mocks-http')
+const cloneDeep = require('lodash/cloneDeep')
+const Chance = require('chance')
+
+const fixturesService = require('pubsweet-component-fixture-service')
+
+const { Model, fixtures } = fixturesService
+const chance = new Chance()
+
+const { user } = fixtures.users
+jest.mock('@pubsweet/component-send-email', () => ({
+  send: jest.fn(),
+}))
+
+const reqBody = {
+  id: user.id,
+  subscribe: chance.bool(),
+  token: user.accessTokens.unsubscribe,
+}
+
+const notFoundError = new Error()
+notFoundError.name = 'NotFoundError'
+notFoundError.status = 404
+const forgotPasswordPath = '../../routes/users/subscribe'
+
+describe('Users subscribe/unsubscribe route handler', () => {
+  let testFixtures = {}
+  let body = {}
+  let models
+  beforeEach(() => {
+    testFixtures = cloneDeep(fixtures)
+    body = cloneDeep(reqBody)
+    models = Model.build(testFixtures)
+  })
+
+  it('should return an error when some parameters are missing', async () => {
+    delete body.id
+    const req = httpMocks.createRequest({ body })
+
+    const res = httpMocks.createResponse()
+    await require(forgotPasswordPath)(models)(req, res)
+    expect(res.statusCode).toBe(400)
+    const data = JSON.parse(res._getData())
+    expect(data.error).toEqual('Missing required params.')
+  })
+  it('should return an error when the unsubscribe token does not match', async () => {
+    body.token = 'invalid-token'
+
+    const req = httpMocks.createRequest({ body })
+    const res = httpMocks.createResponse()
+    await require(forgotPasswordPath)(models)(req, res)
+    expect(res.statusCode).toBe(400)
+    const data = JSON.parse(res._getData())
+    expect(data.error).toEqual(`Invalid token: ${body.token}`)
+  })
+  it('should return an error when the user does not exist', async () => {
+    body.id = 'invalid-user-id'
+
+    const req = httpMocks.createRequest({ body })
+    const res = httpMocks.createResponse()
+    await require(forgotPasswordPath)(models)(req, res)
+    expect(res.statusCode).toBe(404)
+    const data = JSON.parse(res._getData())
+    expect(data.error).toEqual('User not found')
+  })
+  it('should return success when the body is correct', async () => {
+    const req = httpMocks.createRequest({ body })
+    const res = httpMocks.createResponse()
+    await require(forgotPasswordPath)(models)(req, res)
+    const data = JSON.parse(res._getData())
+
+    expect(data.user.notifications.email.user).toEqual(body.subscribe)
+  })
+})
diff --git a/packages/components-faraday/src/components/Admin/utils.js b/packages/components-faraday/src/components/Admin/utils.js
index bbea0fd9095e78086b3c366b627ad9844e0a21bd..ce4787ba3d305626bfe9ad8ffaea0bfc19204f2c 100644
--- a/packages/components-faraday/src/components/Admin/utils.js
+++ b/packages/components-faraday/src/components/Admin/utils.js
@@ -20,7 +20,6 @@ export const setAdmin = values => {
     ...omit(newValues, ['role']),
     username: newValues.email,
     isConfirmed: false,
-    passwordResetToken: generatePasswordHash(),
     password: 'defaultpass',
     editorInChief: newValues.role === 'editorInChief',
     handlingEditor: newValues.role === 'handlingEditor',
@@ -30,6 +29,10 @@ export const setAdmin = values => {
         user: true,
       },
     },
+    accessTokens: {
+      passwordReset: generatePasswordHash(),
+      unsubscribe: generatePasswordHash(),
+    },
   }
 }
 
@@ -52,6 +55,7 @@ export const parseUpdateUser = values => {
     'isActive',
     'username',
     'country',
+    'accessTokens',
   ]
 
   return pick(parsedValues, valuesToSave)
diff --git a/packages/components-faraday/src/components/UserProfile/EmailNotifications.js b/packages/components-faraday/src/components/UserProfile/EmailNotifications.js
index 0b58340f34d4be6f3c26b31ec6165f21aa8d8a5b..e69d09cb6e39c81824ee6566a4a84ac932294b0d 100644
--- a/packages/components-faraday/src/components/UserProfile/EmailNotifications.js
+++ b/packages/components-faraday/src/components/UserProfile/EmailNotifications.js
@@ -43,12 +43,13 @@ export default compose(
   withFetching,
   withHandlers({
     setSubscription: ({
+      token,
       userId,
       setFetching,
       changeEmailSubscription,
     }) => value => ({ hideModal, setModalError }) => {
       setFetching(true)
-      changeEmailSubscription(userId, value)
+      changeEmailSubscription(userId, value, token)
         .then(() => {
           setFetching(false)
           hideModal()
@@ -60,6 +61,3 @@ export default compose(
     },
   }),
 )(EmailNotifications)
-
-// #region styles
-// #endregion
diff --git a/packages/components-faraday/src/components/UserProfile/Unsubscribe.js b/packages/components-faraday/src/components/UserProfile/Unsubscribe.js
new file mode 100644
index 0000000000000000000000000000000000000000..7057695ee48eebb5c723def37b2d6289d4f145b3
--- /dev/null
+++ b/packages/components-faraday/src/components/UserProfile/Unsubscribe.js
@@ -0,0 +1,54 @@
+import React, { Fragment } from 'react'
+import { get } from 'lodash'
+import { connect } from 'react-redux'
+import { Button, H2 } from '@pubsweet/ui'
+import { withJournal } from 'xpub-journal'
+import { Row } from 'pubsweet-component-faraday-ui'
+import { compose, lifecycle, withState } from 'recompose'
+
+import { parseSearchParams } from '../utils'
+import { changeEmailSubscription } from '../../redux/users'
+
+const Unsubscribe = ({ message, history }) => (
+  <Fragment>
+    <Row mt={3}>
+      <H2>{message}</H2>
+    </Row>
+    <Row mt={3}>
+      <Button onClick={() => history.replace('/')} primary>
+        Go to Dashboard
+      </Button>
+    </Row>
+  </Fragment>
+)
+
+export default compose(
+  connect(null, { changeEmailSubscription }),
+  withState('message', 'setConfirmMessage', 'Loading...'),
+  withJournal,
+  lifecycle({
+    componentDidMount() {
+      const {
+        journal,
+        location,
+        setConfirmMessage,
+        changeEmailSubscription,
+      } = this.props
+      const { id, token } = parseSearchParams(location.search)
+      const confirmMessage = `You have successfully unsubscribed. To re-subscribe go to your profile.`
+      const errorMessage = `Something went wrong. Please try again or contact ${get(
+        journal,
+        'metadata.email',
+        'technology@hindawi.com',
+      )}.`
+
+      changeEmailSubscription(id, false, token)
+        .then(() => {
+          setConfirmMessage(confirmMessage)
+        })
+        .catch(() => {
+          setConfirmMessage(errorMessage)
+        })
+    },
+  }),
+)(Unsubscribe)
diff --git a/packages/components-faraday/src/components/UserProfile/UserProfilePage.js b/packages/components-faraday/src/components/UserProfile/UserProfilePage.js
index fab1c438c0326a3d5fd49122d084172fe199ca39..f77295673fdd027c32abda4c6328a58f8c0c1c9d 100644
--- a/packages/components-faraday/src/components/UserProfile/UserProfilePage.js
+++ b/packages/components-faraday/src/components/UserProfile/UserProfilePage.js
@@ -43,6 +43,7 @@ const UserProfilePage = ({
     <EmailNotifications
       changeEmailSubscription={changeEmailSubscription}
       isSubscribed={get(user, 'notifications.email.user', true)}
+      token={get(user, 'accessTokens.unsubscribe')}
       userId={get(user, 'id')}
     />
     <LinkOrcID
diff --git a/packages/components-faraday/src/components/UserProfile/index.js b/packages/components-faraday/src/components/UserProfile/index.js
index c1e8ffe88850d3ae0c91ea3b866a40bd27d9aaa6..a4f6733b4b9f42c32d44319000a0d9e9c5191bb1 100644
--- a/packages/components-faraday/src/components/UserProfile/index.js
+++ b/packages/components-faraday/src/components/UserProfile/index.js
@@ -1,2 +1,3 @@
 export { default as LinkOrcID } from './LinkOrcID'
 export { default as EmailNotifications } from './EmailNotifications'
+export { default as Unsubscribe } from './Unsubscribe'
diff --git a/packages/components-faraday/src/redux/users.js b/packages/components-faraday/src/redux/users.js
index 460f254083804ddddadef2baf826d461cd88f0d5..c16e8088535f2af5450d21496ee1c8e89ea89863 100644
--- a/packages/components-faraday/src/redux/users.js
+++ b/packages/components-faraday/src/redux/users.js
@@ -22,8 +22,13 @@ export const confirmUser = (userId, confirmationToken) => dispatch =>
     return dispatch(loginSuccess(user))
   })
 
-export const changeEmailSubscription = (id, subscribe = true) => dispatch =>
+export const changeEmailSubscription = (
+  id,
+  subscribe = true,
+  token,
+) => dispatch =>
   update(`/users/subscribe`, {
     id,
+    token,
     subscribe,
   }).then(() => dispatch(actions.getCurrentUser()))
diff --git a/packages/xpub-faraday/config/authsome-helpers.js b/packages/xpub-faraday/config/authsome-helpers.js
index 4d4b8bc3f4ce0500b38a37520a94d30e3043b55c..130516c904c3673eca7b734b7ddff9759e885a56 100644
--- a/packages/xpub-faraday/config/authsome-helpers.js
+++ b/packages/xpub-faraday/config/authsome-helpers.js
@@ -185,12 +185,7 @@ const stripeFragmentByRole = ({
   }
 }
 
-const sensitiveUserProperties = [
-  'passwordResetToken',
-  'passwordHash',
-  'invitationToken',
-  'confirmationToken',
-]
+const sensitiveUserProperties = ['passwordHash', 'accessTokens']
 
 const getUsersList = async ({ UserModel, user }) => {
   const users = await UserModel.all()
diff --git a/packages/xpub-faraday/config/validations.js b/packages/xpub-faraday/config/validations.js
index 8b2dc4aae313169d35555b934fba2ae4fc2d4428..978904daf61d9ad44ee03ad187d33d30dc64ac83 100644
--- a/packages/xpub-faraday/config/validations.js
+++ b/packages/xpub-faraday/config/validations.js
@@ -142,8 +142,6 @@ module.exports = {
     teams: Joi.array(),
     editorInChief: Joi.boolean(),
     handlingEditor: Joi.boolean(),
-    invitationToken: Joi.string().allow(''),
-    confirmationToken: Joi.string().allow(''),
     agreeTC: Joi.boolean(),
     isActive: Joi.boolean(),
     notifications: Joi.object({
@@ -152,6 +150,12 @@ module.exports = {
         user: Joi.boolean().default(true),
       }),
     }),
+    accessTokens: Joi.object({
+      confirmation: Joi.string().allow(''),
+      passwordReset: Joi.string().allow(''),
+      unsubscribe: Joi.string().allow(''),
+      invitation: Joi.string().allow(''),
+    }),
   },
   team: {
     group: Joi.string(),