diff --git a/js/background.js b/js/background.js
index 44225cef6..b0e8dffb1 100644
--- a/js/background.js
+++ b/js/background.js
@@ -103,6 +103,7 @@
         messageReceiver.addEventListener('contact', onContactReceived);
         messageReceiver.addEventListener('group', onGroupReceived);
         messageReceiver.addEventListener('sent', onSentMessage);
+        messageReceiver.addEventListener('read', onReadReceipt);
         messageReceiver.addEventListener('error', onError);
 
         window.textsecure.messaging = new textsecure.MessageSender(SERVER_URL, USERNAME, PASSWORD, ATTACHMENT_SERVER_URL);
@@ -228,6 +229,12 @@
         throw e;
     }
 
+    function onReadReceipt(ev) {
+        var timestamp = ev.timestamp.toNumber();
+        var sender    = ev.sender;
+        console.log('read receipt ', sender, timestamp);
+    }
+
     // lazy hack
     window.receipts = new Backbone.Collection();
 
diff --git a/js/libtextsecure.js b/js/libtextsecure.js
index 85c98434d..dbed6013c 100644
--- a/js/libtextsecure.js
+++ b/js/libtextsecure.js
@@ -37034,7 +37034,11 @@ MessageReceiver.prototype.extend({
         }
         if (syncMessage.sent) {
             var sentMessage = syncMessage.sent;
-            console.log('sent message to', sentMessage.destination, sentMessage.timestamp.toNumber(), 'from', envelope.source + '.' + envelope.sourceDevice);
+            console.log('sent message to',
+                    sentMessage.destination,
+                    sentMessage.timestamp.toNumber(),
+                    'from', envelope.source + '.' + envelope.sourceDevice
+            );
             return this.handleSentMessage(
                     sentMessage.destination,
                     sentMessage.timestamp,
@@ -37046,10 +37050,22 @@ MessageReceiver.prototype.extend({
             this.handleGroups(syncMessage.groups);
         } else if (syncMessage.request) {
             console.log('Got SyncMessage Request');
+        } else if (syncMessage.read) {
+            console.log('read messages',
+                    'from', envelope.source + '.' + envelope.sourceDevice);
+            this.handleRead(syncMessage.read);
         } else {
             throw new Error('Got empty SyncMessage');
         }
     },
+    handleRead: function(read) {
+        for (var i = 0; i < read.length; ++i) {
+            var ev = new Event('read');
+            ev.timestamp = read[i].timestamp;
+            ev.sender = read[i].sender;
+            this.dispatchEvent(ev);
+        }
+    },
     handleContacts: function(contacts) {
         console.log('contact sync');
         var eventTarget = this;
@@ -37623,6 +37639,24 @@ MessageSender.prototype = {
             return this.sendIndividualProto(myNumber, contentMessage, Date.now());
         }
     },
+    sendReadReceipts: function(receipts) {
+        var myNumber = textsecure.storage.user.getNumber();
+        var myDevice = textsecure.storage.user.getDeviceId();
+        if (myDevice != 1) {
+            var syncMessage = new textsecure.protobuf.SyncMessage();
+            syncMessage.read = [];
+            for (var i = 0; i < receipts.length; ++i) {
+                var read = new textsecure.protobuf.SyncMessage.Read();
+                read.timestamp = receipts[i].timestamp;
+                read.sender = receipts[i].sender;
+                syncMessage.read.push(read);
+            }
+            var contentMessage = new textsecure.protobuf.Content();
+            contentMessage.syncMessage = syncMessage;
+
+            return this.sendIndividualProto(myNumber, contentMessage, Date.now());
+        }
+    },
 
     sendGroupProto: function(numbers, proto, timestamp) {
         timestamp = timestamp || Date.now();
@@ -37824,6 +37858,7 @@ textsecure.MessageSender = function(url, username, password, attachment_server_u
     this.setGroupAvatar                = sender.setGroupAvatar               .bind(sender);
     this.leaveGroup                    = sender.leaveGroup                   .bind(sender);
     this.sendSyncMessage               = sender.sendSyncMessage              .bind(sender);
+    this.sendReadReceipts              = sender.sendReadReceipts             .bind(sender);
 };
 
 textsecure.MessageSender.prototype = {
diff --git a/libtextsecure/message_receiver.js b/libtextsecure/message_receiver.js
index dd8c8a7a9..ac5283f66 100644
--- a/libtextsecure/message_receiver.js
+++ b/libtextsecure/message_receiver.js
@@ -198,7 +198,11 @@ MessageReceiver.prototype.extend({
         }
         if (syncMessage.sent) {
             var sentMessage = syncMessage.sent;
-            console.log('sent message to', sentMessage.destination, sentMessage.timestamp.toNumber(), 'from', envelope.source + '.' + envelope.sourceDevice);
+            console.log('sent message to',
+                    sentMessage.destination,
+                    sentMessage.timestamp.toNumber(),
+                    'from', envelope.source + '.' + envelope.sourceDevice
+            );
             return this.handleSentMessage(
                     sentMessage.destination,
                     sentMessage.timestamp,
@@ -210,10 +214,22 @@ MessageReceiver.prototype.extend({
             this.handleGroups(syncMessage.groups);
         } else if (syncMessage.request) {
             console.log('Got SyncMessage Request');
+        } else if (syncMessage.read) {
+            console.log('read messages',
+                    'from', envelope.source + '.' + envelope.sourceDevice);
+            this.handleRead(syncMessage.read);
         } else {
             throw new Error('Got empty SyncMessage');
         }
     },
+    handleRead: function(read) {
+        for (var i = 0; i < read.length; ++i) {
+            var ev = new Event('read');
+            ev.timestamp = read[i].timestamp;
+            ev.sender = read[i].sender;
+            this.dispatchEvent(ev);
+        }
+    },
     handleContacts: function(contacts) {
         console.log('contact sync');
         var eventTarget = this;
diff --git a/libtextsecure/sendmessage.js b/libtextsecure/sendmessage.js
index 1b30c0969..7db35edb2 100644
--- a/libtextsecure/sendmessage.js
+++ b/libtextsecure/sendmessage.js
@@ -182,6 +182,24 @@ MessageSender.prototype = {
             return this.sendIndividualProto(myNumber, contentMessage, Date.now());
         }
     },
+    sendReadReceipts: function(receipts) {
+        var myNumber = textsecure.storage.user.getNumber();
+        var myDevice = textsecure.storage.user.getDeviceId();
+        if (myDevice != 1) {
+            var syncMessage = new textsecure.protobuf.SyncMessage();
+            syncMessage.read = [];
+            for (var i = 0; i < receipts.length; ++i) {
+                var read = new textsecure.protobuf.SyncMessage.Read();
+                read.timestamp = receipts[i].timestamp;
+                read.sender = receipts[i].sender;
+                syncMessage.read.push(read);
+            }
+            var contentMessage = new textsecure.protobuf.Content();
+            contentMessage.syncMessage = syncMessage;
+
+            return this.sendIndividualProto(myNumber, contentMessage, Date.now());
+        }
+    },
 
     sendGroupProto: function(numbers, proto, timestamp) {
         timestamp = timestamp || Date.now();
@@ -383,6 +401,7 @@ textsecure.MessageSender = function(url, username, password, attachment_server_u
     this.setGroupAvatar                = sender.setGroupAvatar               .bind(sender);
     this.leaveGroup                    = sender.leaveGroup                   .bind(sender);
     this.sendSyncMessage               = sender.sendSyncMessage              .bind(sender);
+    this.sendReadReceipts              = sender.sendReadReceipts             .bind(sender);
 };
 
 textsecure.MessageSender.prototype = {
diff --git a/protos/IncomingPushMessageSignal.proto b/protos/IncomingPushMessageSignal.proto
index 0c1df06f4..d21b760cf 100644
--- a/protos/IncomingPushMessageSignal.proto
+++ b/protos/IncomingPushMessageSignal.proto
@@ -60,11 +60,16 @@ message SyncMessage {
     }
     optional Type type = 1;
   }
+  message Read {
+    optional string sender    = 1;
+    optional uint64 timestamp = 2;
+  }
 
   optional Sent     sent     = 1;
   optional Contacts contacts = 2;
   optional Groups   groups   = 3;
   optional Request  request  = 4;
+  repeated Read     read     = 5;
 }
 
 message AttachmentPointer {