diff --git a/packages/component-helper-service/src/services/Email.js b/packages/component-helper-service/src/services/Email.js
index 7241792473c594738d65210ccfe98b4e0f08e91f..15d06ecb278aac32ee6928c6eced4c1bf74bc2aa 100644
--- a/packages/component-helper-service/src/services/Email.js
+++ b/packages/component-helper-service/src/services/Email.js
@@ -347,6 +347,30 @@ class Email {
       },
     })
   }
+
+  async setupManuscriptSubmittedEmail() {
+    const {
+      baseUrl,
+      UserModel,
+      collection,
+      parsedFragment: { id, title },
+      authors: { submittingAuthor: { firstName = '', lastName = '' } },
+    } = this
+
+    const userHelper = new User({ UserModel })
+    const eic = await userHelper.getEditorInChief()
+
+    mailService.sendSimpleEmail({
+      toEmail: eic.email,
+      emailType: 'manuscript-submitted',
+      dashboardUrl: baseUrl,
+      meta: {
+        collection,
+        fragment: { id, authorName: `${firstName} ${lastName}`, title },
+        eicName: `${eic.firstName} ${eic.lastName}`,
+      },
+    })
+  }
 }
 
 const getSubject = recommendation =>
diff --git a/packages/component-mail-service/src/Mail.js b/packages/component-mail-service/src/Mail.js
index d40d99d1f2803362450ef95ec51552948ce6e791..39cff7d046e3704ae6abdb3a3cc728672bb1fe38 100644
--- a/packages/component-mail-service/src/Mail.js
+++ b/packages/component-mail-service/src/Mail.js
@@ -7,10 +7,10 @@ const resetPath = config.get('invite-reset-password.url')
 
 module.exports = {
   sendSimpleEmail: async ({
-    toEmail,
-    user,
-    emailType,
-    dashboardUrl,
+    toEmail = '',
+    user = {},
+    emailType = '',
+    dashboardUrl = '',
     meta = {},
   }) => {
     let subject, textBody
@@ -117,6 +117,26 @@ module.exports = {
         textBody = `${replacements.headline}`
         emailTemplate = 'noCTA'
         break
+      case 'manuscript-submitted':
+        subject = `${meta.collection.customId}: Manuscript Submitted`
+        replacements.previewText = 'A new manuscript has been submitted'
+        replacements.headline = `A new manuscript has been submitted.`
+        replacements.paragraph = `You can view the full manuscript titled "${
+          meta.fragment.title
+        }" by ${
+          meta.fragment.authorName
+        } and take further actions by clicking on the following link:`
+        replacements.buttonText = 'MANUSCRIPT DETAILS'
+        replacements.url = helpers.createUrl(
+          dashboardUrl,
+          `/projects/${meta.collection.id}/versions/${
+            meta.fragment.id
+          }/details`,
+        )
+        textBody = `${replacements.headline} ${replacements.paragraph} ${
+          replacements.url
+        } ${replacements.buttonText}`
+        break
       default:
         subject = 'Welcome to Hindawi!'
         break
diff --git a/packages/component-manuscript-manager/src/Fragments.js b/packages/component-manuscript-manager/src/Fragments.js
index 9e3569166d7dd7e7e76f9082412ab3bc8219c16a..01e11a93582bdc5ff37497d2bb7a7c6a980b68c0 100644
--- a/packages/component-manuscript-manager/src/Fragments.js
+++ b/packages/component-manuscript-manager/src/Fragments.js
@@ -9,7 +9,7 @@ const Fragments = app => {
   })
   /**
    * @api {patch} /api/collections/:collectionId/fragments/:fragmentId/submit Submit a revision for a manuscript
-   * @apiGroup FragmentsRecommendations
+   * @apiGroup Fragments
    * @apiParam {collectionId} collectionId Collection id
    * @apiParam {fragmentId} fragmentId Fragment id
    * @apiSuccessExample {json} Success
@@ -28,6 +28,27 @@ const Fragments = app => {
     authBearer,
     require(`${routePath}/patch`)(app.locals.models),
   )
+  /**
+   * @api {post} /api/collections/:collectionId/fragments/:fragmentId/submit Submit a manuscript
+   * @apiGroup Fragments
+   * @apiParam {collectionId} collectionId Collection id
+   * @apiParam {fragmentId} fragmentId Fragment id
+   * @apiSuccessExample {json} Success
+   *   HTTP/1.1 200 OK
+   *   {
+   *
+   *   }
+   * @apiErrorExample {json} Invite user errors
+   *    HTTP/1.1 403 Forbidden
+   *    HTTP/1.1 400 Bad Request
+   *    HTTP/1.1 404 Not Found
+   *    HTTP/1.1 500 Internal Server Error
+   */
+  app.post(
+    `${basePath}`,
+    authBearer,
+    require(`${routePath}/post`)(app.locals.models),
+  )
 }
 
 module.exports = Fragments
diff --git a/packages/component-manuscript-manager/src/routes/fragments/post.js b/packages/component-manuscript-manager/src/routes/fragments/post.js
new file mode 100644
index 0000000000000000000000000000000000000000..d0b5b18f6d4592f681bcd14298c8cedd5c194c0d
--- /dev/null
+++ b/packages/component-manuscript-manager/src/routes/fragments/post.js
@@ -0,0 +1,58 @@
+const {
+  Email,
+  Fragment,
+  services,
+  authsome: authsomeHelper,
+} = require('pubsweet-component-helper-service')
+
+module.exports = models => async (req, res) => {
+  const { collectionId, fragmentId } = req.params
+  let collection, fragment
+
+  try {
+    collection = await models.Collection.find(collectionId)
+    if (!collection.fragments.includes(fragmentId))
+      return res.status(400).json({
+        error: `Collection and fragment do not match.`,
+      })
+    fragment = await models.Fragment.find(fragmentId)
+
+    const authsome = authsomeHelper.getAuthsome(models)
+    const target = {
+      fragment,
+      path: req.route.path,
+    }
+    const canPost = await authsome.can(req.user, 'POST', target)
+    if (!canPost)
+      return res.status(403).json({
+        error: 'Unauthorized.',
+      })
+
+    fragment.submitted = Date.now()
+    fragment = await fragment.save()
+
+    const fragmentHelper = new Fragment({ fragment })
+    const parsedFragment = await fragmentHelper.getFragmentData({
+      handlingEditor: collection.handlingEditor,
+    })
+    const authors = await fragmentHelper.getAuthorData({
+      UserModel: models.User,
+    })
+
+    const email = new Email({
+      authors,
+      collection,
+      parsedFragment,
+      UserModel: models.User,
+      baseUrl: services.getBaseUrl(req),
+    })
+    email.setupManuscriptSubmittedEmail()
+
+    return res.status(200).json(fragment)
+  } catch (e) {
+    const notFoundError = await services.handleNotFoundError(e, 'Item')
+    return res.status(notFoundError.status).json({
+      error: notFoundError.message,
+    })
+  }
+}
diff --git a/packages/component-manuscript-manager/src/tests/fragments/post.test.js b/packages/component-manuscript-manager/src/tests/fragments/post.test.js
new file mode 100644
index 0000000000000000000000000000000000000000..1da5c72f731cdc11512706c2709d014dd2ed1930
--- /dev/null
+++ b/packages/component-manuscript-manager/src/tests/fragments/post.test.js
@@ -0,0 +1,86 @@
+process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'
+process.env.SUPPRESS_NO_CONFIG_WARNING = true
+
+const cloneDeep = require('lodash/cloneDeep')
+const fixturesService = require('pubsweet-component-fixture-service')
+const requests = require('../requests')
+
+const { Model, fixtures } = fixturesService
+jest.mock('pubsweet-component-mail-service', () => ({
+  sendNotificationEmail: jest.fn(),
+}))
+const reqBody = {}
+
+const path = '../routes/fragments/post'
+const route = {
+  path: '/api/collections/:collectionId/fragments/:fragmentId/submit',
+}
+describe('Post fragments route handler', () => {
+  let testFixtures = {}
+  let body = {}
+  let models
+  beforeEach(() => {
+    testFixtures = cloneDeep(fixtures)
+    body = cloneDeep(reqBody)
+    models = Model.build(testFixtures)
+  })
+  it('should return success when the parameters are correct', async () => {
+    const { user } = testFixtures.users
+    const { collection } = testFixtures.collections
+    const { fragment } = testFixtures.fragments
+    const res = await requests.sendRequest({
+      body,
+      userId: user.id,
+      models,
+      route,
+      path,
+      params: {
+        collectionId: collection.id,
+        fragmentId: fragment.id,
+      },
+    })
+
+    expect(res.statusCode).toBe(200)
+  })
+  it('should return an error when the fragmentId does not match the collectionId', async () => {
+    const { user } = testFixtures.users
+    const { collection } = testFixtures.collections
+    const { noParentFragment } = testFixtures.fragments
+
+    const res = await requests.sendRequest({
+      body,
+      userId: user.id,
+      models,
+      route,
+      path,
+      params: {
+        collectionId: collection.id,
+        fragmentId: noParentFragment.id,
+      },
+    })
+
+    expect(res.statusCode).toBe(400)
+    const data = JSON.parse(res._getData())
+    expect(data.error).toEqual('Collection and fragment do not match.')
+  })
+  it('should return an error when the collection does not exist', async () => {
+    const { user } = testFixtures.users
+    const { fragment } = testFixtures.fragments
+
+    const res = await requests.sendRequest({
+      body,
+      userId: user.id,
+      models,
+      route,
+      path,
+      params: {
+        collectionId: 'invalid-id',
+        fragmentId: fragment.id,
+      },
+    })
+
+    expect(res.statusCode).toBe(404)
+    const data = JSON.parse(res._getData())
+    expect(data.error).toEqual('Item not found')
+  })
+})
diff --git a/packages/xpub-faraday/config/authsome-mode.js b/packages/xpub-faraday/config/authsome-mode.js
index 960e0bede30325e9d0dec6bc33e6762880b77e8e..043e5e8c87b8c4c89929611852dab47bbe350e86 100644
--- a/packages/xpub-faraday/config/authsome-mode.js
+++ b/packages/xpub-faraday/config/authsome-mode.js
@@ -192,6 +192,14 @@ async function applyAuthenticatedUserPolicy(user, operation, object, context) {
         roles: ['reviewer', 'handlingEditor'],
       })
     }
+
+    // allow owner to submit a manuscript
+    if (
+      get(object, 'path') ===
+      '/api/collections/:collectionId/fragments/:fragmentId/submit'
+    ) {
+      return helpers.isOwner({ user, object: object.fragment })
+    }
   }
 
   if (operation === 'PATCH') {