diff --git a/.prettierignore b/.prettierignore
index 50862cc2d..2dbd12114 100644
--- a/.prettierignore
+++ b/.prettierignore
@@ -50,3 +50,6 @@ _locales/**/*.json
 # Symlink into third-party `components`:
 stylesheets/_intlTelInput.scss
 
+# Coverage
+coverage/**
+.nyc_output/**
\ No newline at end of file
diff --git a/libloki/local_loki_server.js b/libloki/local_loki_server.js
new file mode 100644
index 000000000..b7b5c0fc1
--- /dev/null
+++ b/libloki/local_loki_server.js
@@ -0,0 +1,75 @@
+const http = require('http');
+const EventEmitter = require('events');
+
+class LocalLokiServer extends EventEmitter {
+  /**
+   * Creates an instance of LocalLokiServer.
+   * Sends out a `message` event when a new message is received.
+   */
+  constructor() {
+    super();
+    this.server = http.createServer((req, res) => {
+      let body = [];
+
+      // Check endpoints
+      if (req.method === 'POST') {
+        req
+          .on('error', () => {
+            // Internal server error
+            res.statusCode = 500;
+            res.end();
+          })
+          .on('data', chunk => {
+            body.push(chunk);
+          })
+          .on('end', () => {
+            body = Buffer.concat(body).toString();
+
+            // Check endpoints here
+            if (req.url === '/store') {
+              // body is a base64 encoded string
+              this.emit('message', body);
+              res.statusCode = 200;
+              res.end();
+            } else {
+              res.writeHead(404, { 'Content-Type': 'text/plain' });
+              res.end('Invalid endpoint!');
+            }
+          });
+      } else {
+        // Method Not Allowed
+        res.statusCode = 405;
+        res.end();
+      }
+    });
+  }
+
+  async start(port) {
+    // Close the old server
+    await this.close();
+
+    // Start a listening on new server
+    return new Promise((res, rej) => {
+      this.server.listen(port, err => {
+        if (err) {
+          rej(err);
+        } else {
+          res(port);
+        }
+      });
+    });
+  }
+
+  // Async wrapper for http server close
+  close() {
+    if (this.server) {
+      return new Promise(res => {
+        this.server.close(() => res());
+      });
+    }
+
+    return Promise.resolve();
+  }
+}
+
+exports.LocalLokiServer = LocalLokiServer;
diff --git a/libloki/test/node/.eslintrc.js b/libloki/test/node/.eslintrc.js
new file mode 100644
index 000000000..acedc200c
--- /dev/null
+++ b/libloki/test/node/.eslintrc.js
@@ -0,0 +1,31 @@
+// For reference: https://github.com/airbnb/javascript
+
+module.exports = {
+  env: {
+    node: true,
+    mocha: true,
+    browser: true,
+  },
+
+  globals: {
+    check: true,
+    gen: true,
+  },
+
+  parserOptions: {
+    sourceType: 'module',
+  },
+
+  rules: {
+    // We still get the value of this rule, it just allows for dev deps
+    'import/no-extraneous-dependencies': [
+      'error',
+      {
+        devDependencies: true,
+      },
+    ],
+
+    // We want to keep each test structured the same, even if its contents are tiny
+    'arrow-body-style': 'off',
+  },
+};
diff --git a/libloki/test/node/local_loki_server_test.js b/libloki/test/node/local_loki_server_test.js
new file mode 100644
index 000000000..ec75a72f0
--- /dev/null
+++ b/libloki/test/node/local_loki_server_test.js
@@ -0,0 +1,77 @@
+const axios = require('axios');
+const { assert } = require('chai');
+const { LocalLokiServer } = require('../../local_loki_server');
+
+describe('LocalLokiServer', () => {
+  before(async () => {
+    this.server = new LocalLokiServer();
+    await this.server.start(8000);
+  });
+
+  after(() => {
+    this.server.close();
+  });
+
+  it('should return 405 if not a POST request', async () => {
+    try {
+      await axios.get('http://localhost:8000');
+      assert.fail('Got a successful response');
+    } catch (error) {
+      if (error.response) {
+        assert.equal(405, error.response.status);
+        return;
+      }
+      assert.isNotOk(error, 'Another error was receieved');
+    }
+  });
+
+  it('should return 404 if no endpoint provided', async () => {
+    try {
+      await axios.post('http://localhost:8000', { name: 'Test' });
+      assert.fail('Got a successful response');
+    } catch (error) {
+      if (error.response) {
+        assert.equal(404, error.response.status);
+        return;
+      }
+      assert.isNotOk(error, 'Another error was receieved');
+    }
+  });
+
+  it('should return 404 and a string if invalid enpoint is provided', async () => {
+    try {
+      await axios.post('http://localhost:8000/invalid', { name: 'Test' });
+      assert.fail('Got a successful response');
+    } catch (error) {
+      if (error.response) {
+        assert.equal(404, error.response.status);
+        assert.equal('Invalid endpoint!', error.response.data);
+        return;
+      }
+      assert.isNotOk(error, 'Another error was receieved');
+    }
+  });
+
+  describe('/store', async () => {
+    it('should pass the POSTed data to the callback', async () => {
+      const server = new LocalLokiServer();
+      await server.start(8001);
+
+      const promise = new Promise(res => {
+        server.on('message', message => {
+          assert.equal(message, 'This is data');
+          server.close();
+          res();
+        });
+      });
+
+      try {
+        await axios.post('http://localhost:8001/store', 'This is data');
+      } catch (error) {
+        assert.isNotOk(error, 'Error occured');
+      }
+
+      return promise;
+    });
+  });
+});
diff --git a/libtextsecure/http-resources.js b/libtextsecure/http-resources.js
index 071d0ad32..12e736cbd 100644
--- a/libtextsecure/http-resources.js
+++ b/libtextsecure/http-resources.js
@@ -70,25 +70,29 @@
       const newMessages = await filterIncomingMessages(messages);
       newMessages.forEach(async message => {
         const { data } = message;
-        const dataPlaintext = stringToArrayBufferBase64(data);
-        const messageBuf = textsecure.protobuf.WebSocketMessage.decode(
-          dataPlaintext
-        );
-        if (
-          messageBuf.type === textsecure.protobuf.WebSocketMessage.Type.REQUEST
-        ) {
-          handleRequest(
-            new IncomingHttpResponse({
-              verb: messageBuf.request.verb,
-              path: messageBuf.request.path,
-              body: messageBuf.request.body,
-              id: messageBuf.request.id,
-            })
-          );
-        }
+        this.handleMessage(data);
       });
     };
 
+    this.handleMessage = message => {
+      const dataPlaintext = stringToArrayBufferBase64(message);
+      const messageBuf = textsecure.protobuf.WebSocketMessage.decode(
+        dataPlaintext
+      );
+      if (
+        messageBuf.type === textsecure.protobuf.WebSocketMessage.Type.REQUEST
+      ) {
+        handleRequest(
+          new IncomingHttpResponse({
+            verb: messageBuf.request.verb,
+            path: messageBuf.request.path,
+            body: messageBuf.request.body,
+            id: messageBuf.request.id,
+          })
+        );
+      }
+    };
+
     this.startPolling = async function pollServer(callback) {
       try {
         await server.retrieveMessages(processMessages);
diff --git a/libtextsecure/message_receiver.js b/libtextsecure/message_receiver.js
index c9325707f..d42a04a64 100644
--- a/libtextsecure/message_receiver.js
+++ b/libtextsecure/message_receiver.js
@@ -23,6 +23,7 @@ function MessageReceiver(username, password, signalingKey, options = {}) {
   this.username = username;
   this.password = password;
   this.lokiMessageAPI = window.LokiMessageAPI;
+  this.localServer = new window.LocalLokiServer();
 
   if (!options.serverTrustRoot) {
     throw new Error('Server trust root is required!');
@@ -80,6 +81,11 @@ MessageReceiver.prototype.extend({
         this.onEmpty();
       }
     });
+
+    this.localServer.removeAllListeners();
+    this.localServer.on('message', this.httpPollingResource.handleMessage);
+    this.localServer.start(8000);
+
     // TODO: Rework this socket stuff to work with online messaging
     const useWebSocket = false;
     if (useWebSocket) {
@@ -121,6 +127,11 @@ MessageReceiver.prototype.extend({
       this.wsr.removeEventListener('close', this._onClose);
       this.wsr = null;
     }
+
+    if (this.localServer) {
+      this.localServer.removeAllListeners();
+      this.localServer = null;
+    }
   },
   close() {
     window.log.info('MessageReceiver.close()');
@@ -132,6 +143,10 @@ MessageReceiver.prototype.extend({
       this.wsr.close(3000, 'called close');
     }
 
+    if (this.localServer) {
+      this.localServer.close();
+    }
+
     return this.drain();
   },
   onopen() {
diff --git a/package.json b/package.json
index f4e4e55e5..41fa5f4af 100644
--- a/package.json
+++ b/package.json
@@ -31,8 +31,8 @@
     "test-lib-view": "NODE_ENV=test-lib yarn run start",
     "test-loki-view": "NODE_ENV=test-loki yarn run start",
     "test-electron": "yarn grunt test",
-    "test-node": "mocha --recursive test/app test/modules ts/test",
-    "test-node-coverage": "nyc --reporter=lcov --reporter=text mocha --recursive test/app test/modules ts/test",
+    "test-node": "mocha --recursive test/app test/modules ts/test libloki/test/node",
+    "test-node-coverage": "nyc --reporter=lcov --reporter=text mocha --recursive test/app test/modules ts/test libloki/test/node",
     "eslint": "eslint .",
     "lint": "yarn format --list-different && yarn lint-windows",
     "lint-windows": "yarn eslint && yarn tslint",
@@ -118,6 +118,7 @@
     "@types/sinon": "4.3.1",
     "arraybuffer-loader": "1.0.3",
     "asar": "0.14.0",
+    "axios": "0.18.0",
     "bower": "1.8.2",
     "chai": "4.1.2",
     "electron": "3.0.9",
diff --git a/preload.js b/preload.js
index 11a951517..9defb1067 100644
--- a/preload.js
+++ b/preload.js
@@ -283,6 +283,10 @@ window.LokiMessageAPI = new LokiMessageAPI({
   messageServerPort: config.messageServerPort,
 });
 
+const { LocalLokiServer } = require('./libloki/local_loki_server');
+
+window.LocalLokiServer = LocalLokiServer;
+
 window.mnemonic = require('./libloki/mnemonic');
 const { WorkerInterface } = require('./js/modules/util_worker_interface');
 
diff --git a/yarn.lock b/yarn.lock
index 922dbb1ee..644fa9d38 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -666,6 +666,14 @@ aws4@^1.8.0:
   version "1.8.0"
   resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.8.0.tgz#f0e003d9ca9e7f59c7a508945d7b2ef9a04a542f"
 
+axios@0.18.0:
+  version "0.18.0"
+  resolved "https://registry.yarnpkg.com/axios/-/axios-0.18.0.tgz#32d53e4851efdc0a11993b6cd000789d70c05102"
+  integrity sha1-MtU+SFHv3AoRmTts0AB4nXDAUQI=
+  dependencies:
+    follow-redirects "^1.3.0"
+    is-buffer "^1.1.5"
+
 babel-code-frame@6.26.0, babel-code-frame@^6.22.0, babel-code-frame@^6.26.0:
   version "6.26.0"
   resolved "https://registry.yarnpkg.com/babel-code-frame/-/babel-code-frame-6.26.0.tgz#63fd43f7dc1e3bb7ce35947db8fe369a3f58c74b"
@@ -2028,7 +2036,7 @@ debug@2, debug@2.6.9, debug@^2.1.2, debug@^2.3.3, debug@^2.6.0, debug@^2.6.6:
   dependencies:
     ms "2.0.0"
 
-debug@3.1.0, debug@^3.1.0:
+debug@3.1.0, debug@=3.1.0, debug@^3.1.0:
   version "3.1.0"
   resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261"
   dependencies:
@@ -3225,6 +3233,13 @@ flush-write-stream@^1.0.0:
     inherits "^2.0.1"
     readable-stream "^2.0.4"
 
+follow-redirects@^1.3.0:
+  version "1.6.1"
+  resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.6.1.tgz#514973c44b5757368bad8bddfe52f81f015c94cb"
+  integrity sha512-t2JCjbzxQpWvbhts3l6SH1DKzSrx8a+SsaVf4h6bG4kOXUuPYS/kg2Lr4gQSb7eemaHqJkOThF1BGyjlUkO1GQ==
+  dependencies:
+    debug "=3.1.0"
+
 for-each@^0.3.2:
   version "0.3.2"
   resolved "https://registry.yarnpkg.com/for-each/-/for-each-0.3.2.tgz#2c40450b9348e97f281322593ba96704b9abd4d4"