diff --git a/README.md b/README.md
index 1928b0c7..6fba50cc 100644
--- a/README.md
+++ b/README.md
@@ -17,9 +17,22 @@ Copy /etc/runbullet/config.example.yaml to /etc/runbullet/config.yaml (system-wi
 
 Edit the file to include:
 
+### For the PushBullet backend
+
 * Your PushBullet access token (create one [here](https://www.pushbullet.com/#settings/account));
 * The name of the (virtual) PushBullet device used to listen for events (create one [here](https://www.pushbullet.com/#devices)).
 
+### For the Apache Kafka backend
+
+* The host and port of the Kafka installation
+* The topic that will be used to deliver and process messages
+
+### For the local socket backend
+
+* The name of the local FIFO that will be used to deliver and process messages
+
+### device_id
+
 Each target device is identified by a unique device_id in the messages sent over your account. The device_id is the hostname by default, unless changed in config.yaml.
 
 Shell interface
@@ -65,10 +78,10 @@ music.mpd:
 pusher --target raspberry --action switch.wemo.on
 ```
 
-* *TODO* `runbullet.plugins.light.hue`: Controls a Philips Hue smart lights system. Requires the package `phue` on the target machine. Example:
+* `runbullet.plugins.light.hue`: Controls a Philips Hue smart lights system. Requires the package `phue` on the target machine. Example:
 
 ```shell
-pusher --target raspberry --action light.hue.set_scene --scene "Sunset" --group "Living Room"
+pusher --target raspberry --action light.hue.scene --name "Sunset" --group "Living Room"
 ```
 
 Writing your plugins
diff --git a/runbullet/backend/__init__.py b/runbullet/backend/__init__.py
index bdb858ab..2ad00cea 100644
--- a/runbullet/backend/__init__.py
+++ b/runbullet/backend/__init__.py
@@ -39,12 +39,16 @@ class Backend(Thread):
             if cls is not object and hasattr(cls, '_init'):
                 cls._init(self, **config)
 
+    def is_local(self):
+        from runbullet.backend.local import LocalBackend
+        return isinstance(self, LocalBackend)
+
     def on_msg(self, msg):
         if 'target' not in msg:
             return  # No target
 
         target = msg.pop('target')
-        if target != runbullet.get_device_id():
+        if target != runbullet.get_device_id() and not self.is_local():
             return  # Not for me
 
         if 'action' not in msg:
diff --git a/runbullet/backend/local/__init__.py b/runbullet/backend/local/__init__.py
new file mode 100644
index 00000000..6d74cf9a
--- /dev/null
+++ b/runbullet/backend/local/__init__.py
@@ -0,0 +1,49 @@
+import logging
+import json
+import os
+import time
+
+from .. import Backend
+
+class LocalBackend(Backend):
+    def _init(self, fifo):
+        self.fifo = fifo
+        try: os.mkfifo(self.fifo)
+        except FileExistsError as e: pass
+        logging.info('Initialized local backend on fifo {}'.format(self.fifo))
+
+    def send_msg(self, msg):
+        if isinstance(msg, dict):
+            msg = json.dumps(msg)
+        if not isinstance(msg, str):
+            msg = json.dumps(msg)
+            raise RuntimeError('Invalid non-JSON message')
+
+        msglen = len(msg)+1  # Include \n
+        msg = bytearray((str(msglen) + '\n' + msg + '\n').encode('utf-8'))
+        with open(self.fifo, 'wb') as f:
+            f.write(msg)
+
+    def run(self):
+        with open(self.fifo, 'rb', 0) as f:
+            while True:
+                try:
+                    msglen = int(f.readline())
+                except ValueError as e:
+                    time.sleep(0.1)
+                    continue
+
+                msg = f.read(msglen-1)
+                if not msg: continue
+
+                try:
+                    msg = json.loads(msg.decode('utf-8'))
+                except Exception as e:
+                    logging.exception(e)
+                    continue
+
+                logging.debug('Received message: {}'.format(msg))
+                self.on_msg(msg)
+
+# vim:sw=4:ts=4:et:
+
diff --git a/runbullet/bin/pusher b/runbullet/bin/pusher
index 91bb64f8..d2d2458f 100755
--- a/runbullet/bin/pusher
+++ b/runbullet/bin/pusher
@@ -10,6 +10,7 @@ import yaml
 from pushbullet import Pushbullet
 from runbullet import parse_config_file
 from runbullet.backend.kafka import KafkaBackend
+from runbullet.backend.local import LocalBackend
 
 
 def print_usage():
@@ -40,10 +41,18 @@ def send_pb_message(pb, device_name, msg):
 def send_kafka_message(backend, msg):
     backend.send_msg(msg)
 
+def send_local_message(backend, msg):
+    backend.send_msg(msg)
+
 def get_backend(config):
     # TODO Refactor this as something better and reuse the same
     # backend classes from the runbullet consumer module
-    if 'backend.pushbullet' in config \
+    if 'backend.local' in config \
+            and 'pusher' in config['backend.local'] \
+            and config['backend.local']['pusher']:
+        c = config['backend.local']
+        return LocalBackend({'fifo': c['fifo']})
+    elif 'backend.pushbullet' in config \
             and 'pusher' in config['backend.pushbullet'] \
             and config['backend.pushbullet']['pusher']:
         API_KEY = config['backend.pushbullet']['token']
@@ -66,8 +75,8 @@ def main():
 
     parser.add_argument('--backend', '-b', dest='backend', required=False,
                         help="Backend to deliver the message " +
-                        "[pushbullet|kafka] (default: whatever specified " +
-                        "in your config with pusher=True)")
+                        "[pushbullet|kafka|local] (default: whatever " +
+                        "specified in your config with pusher=True)")
 
     opts, args = parser.parse_known_args(sys.argv[1:])
     payload = {}
@@ -76,6 +85,11 @@ def main():
         raise RuntimeError('Odd number of key-value options passed: {}'.
                            format(args))
 
+    if opts.target == 'localhost' and 'backend.local' in config:
+        cfg = config['backend.local']
+        cfg['pusher'] = True
+        config = { 'backend.local': cfg }
+
     if opts.backend:
         backend_cfg_name = 'backend.' + opts.backend
         if backend_cfg_name not in config:
@@ -103,6 +117,8 @@ def main():
         send_pb_message(backend, config['backend.pushbullet']['device'], msg)
     elif isinstance(backend, KafkaBackend):
         send_kafka_message(backend, msg)
+    elif isinstance(backend, LocalBackend):
+        send_local_message(backend, msg)
 
 
 if __name__ == '__main__':
diff --git a/runbullet/config.example.yaml b/runbullet/config.example.yaml
index 66349df2..1cd0ad3c 100644
--- a/runbullet/config.example.yaml
+++ b/runbullet/config.example.yaml
@@ -11,6 +11,9 @@ backend.pushbullet:
     token: your_pushbullet_token_here
     device: your_pushbullet_virtual_device_name
 
+backend.local:
+    fifo: /tmp/runbullet.fifo
+
 # device_id: <your_device_id> (default: current hostname)
 # debug: True (default: False)