diff --git a/README.md b/README.md
index dabbd1bdc691cd8d73190e0ee15f9f1959e40afa..537cc2044c468a6c5ca231aeb4e4a45e7f758e5d 100644
--- a/README.md
+++ b/README.md
@@ -86,6 +86,46 @@ const permissions = require('../path/to/myPermissions.js')
 
 Please refer to shield's [documentation](https://github.com/maticzav/graphql-shield#overview) for more details.
 
+### Email middleware
+
+Email notifications get sent out as a side effect of a **_mutation_** in your schema.
+
+Create a file wherever you like:
+
+```js
+const myFunc = async (resolve, parent, args, ctx, info) => {
+  // do some stuff before the resolver
+
+  const result = await resolve(parent, args, ctx, info)
+
+  // do some stuff after the resolver
+
+  // send your email
+
+  return result
+}
+
+module.exports = {
+  // myMutation needs to match a mutation name in your schema
+  myMutation: myFunc,
+}
+```
+
+Now in your config:
+
+```js
+// config/default.js
+const emailService = require('path/to/your/email/file')
+
+{
+  emailMiddleware: {
+    service: emailService
+  }
+}
+```
+
+This middleware uses `graphql-middleware` to provide its functionality, so make sure to read its [documentation](https://github.com/prisma-labs/graphql-middleware) and understand its function signature.
+
 ### Cron support
 
 All you need for cron-based scheduled tasks to run is to provide the path to your cron jobs.
@@ -154,5 +194,4 @@ Useful if you have custom login resolvers.
 
 ### Future features
 
-- Notification middleware
 - Include more pubsweet packages into the bundle
diff --git a/src/graphqlApi.js b/src/graphqlApi.js
index 5635157df7cb696f92ad09c412433a6afae77060..aafe07f4cd33fcd82bdc5a7370759b3d1d56ce68 100644
--- a/src/graphqlApi.js
+++ b/src/graphqlApi.js
@@ -1,13 +1,14 @@
 const { ApolloServer } = require('apollo-server-express')
 const isEmpty = require('lodash/isEmpty')
+const config = require('config')
+
 const logger = require('@pubsweet/logger')
 const errors = require('@pubsweet/errors')
 
-const config = require('config')
-
 const connectors = require('pubsweet-server/src/connectors')
 const loaders = require('pubsweet-server/src/graphql/loaders')
 const helpers = require('pubsweet-server/src/helpers/authorization')
+
 const schema = require('./graphqlSchema')
 
 const hostname = config.has('pubsweet-server.hostname')
diff --git a/src/graphqlSchema.js b/src/graphqlSchema.js
index 896b6815e6d9fa61515f4951c34d385b06b730a7..0643c11b33d62512039d252554dfa294c9309689 100644
--- a/src/graphqlSchema.js
+++ b/src/graphqlSchema.js
@@ -2,12 +2,47 @@ const config = require('config')
 const isEmpty = require('lodash/isEmpty')
 const { applyMiddleware } = require('graphql-middleware')
 const { shield } = require('graphql-shield')
-let schema = require('pubsweet-server/src/graphql/schema')
+
+const logger = require('@pubsweet/logger')
+const schema = require('pubsweet-server/src/graphql/schema')
+
+const emailMiddleware = require('./middleware/email')
+
+const baseMessage = 'Coko server =>'
+
+const logRegistration = name =>
+  logger.info(`${baseMessage} Middleware: Registered ${name} middleware`)
+
+const middleware = []
+console.log('') // eslint-disable-line no-console
+logger.info(`${baseMessage} Registering graphql middleware...`)
+
+/**
+ * Authorization middleware
+ */
 
 const permissions = config.has('permissions') && config.get('permissions')
 
-if (permissions && !isEmpty(permissions)) {
-  schema = applyMiddleware(schema, shield(permissions))
+if (!isEmpty(permissions)) {
+  const authorizationMiddleware = shield(permissions)
+  middleware.push(authorizationMiddleware)
+  logRegistration('authorization')
 }
 
-module.exports = schema
+/**
+ * Email middleware
+ */
+
+const emailConfig =
+  config.has('emailMiddleware') && config.get('emailMiddleware')
+
+if (!isEmpty(emailConfig)) {
+  middleware.push(emailMiddleware)
+  logRegistration('email')
+}
+
+console.log('') // eslint-disable-line no-console
+
+const schemaWithMiddleWare = applyMiddleware(schema, ...middleware)
+
+module.exports = schemaWithMiddleWare
diff --git a/src/middleware/email.js b/src/middleware/email.js
new file mode 100644
index 0000000000000000000000000000000000000000..fb6f07769cd98ea3b2f118954d0ddce43d5139e5
--- /dev/null
+++ b/src/middleware/email.js
@@ -0,0 +1,40 @@
+const config = require('config')
+const { isEmpty } = require('lodash')
+const { middleware } = require('graphql-middleware')
+
+// const logger = require('@pubsweet/logger')
+
+const generator = middleware(schema => {
+  const emailService =
+    config.has('emailMiddleware.service') &&
+    config.get('emailMiddleware.service')
+
+  if (isEmpty(emailService)) {
+    throw new Error('Email middleware: No email service provided!')
+  }
+
+  /**
+   * Email notifications are only applicable on mutations
+   */
+  const typeMap = schema.getTypeMap()
+  const { Mutation } = typeMap
+  const mutationFields = Mutation.getFields()
+
+  const mutationEntries = Object.keys(mutationFields).reduce(
+    (middlewareSchema, field) => {
+      const serviceFieldEntry = emailService[field]
+      if (!serviceFieldEntry) return middlewareSchema
+
+      /* eslint-disable-next-line no-param-reassign */
+      middlewareSchema[field] = serviceFieldEntry
+      return middlewareSchema
+    },
+    {},
+  )
+
+  return {
+    Mutation: mutationEntries,
+  }
+})
+
+module.exports = generator