diff --git a/app/assets/javascripts/discourse/models/user.js b/app/assets/javascripts/discourse/models/user.js
index 856e3502e..df389e88a 100644
--- a/app/assets/javascripts/discourse/models/user.js
+++ b/app/assets/javascripts/discourse/models/user.js
@@ -281,17 +281,6 @@ Discourse.User = Discourse.Model.extend({
     return this.get('stats').rejectProperty('isPM');
   }.property('stats.@each.isPM'),
 
-  /**
-  This user's stats, only including PMs.
-
-    @property statsPmsOnly
-    @type {Array}
-  **/
-  statsPmsOnly: function() {
-    if (this.blank('stats')) return [];
-    return this.get('stats').filterProperty('isPM');
-  }.property('stats.@each.isPM'),
-
 
   findDetails: function() {
     var user = this;
diff --git a/app/assets/javascripts/discourse/templates/user/user.js.handlebars b/app/assets/javascripts/discourse/templates/user/user.js.handlebars
index bfe03aa04..6f498db37 100644
--- a/app/assets/javascripts/discourse/templates/user/user.js.handlebars
+++ b/app/assets/javascripts/discourse/templates/user/user.js.handlebars
@@ -29,13 +29,25 @@
         <h3><i class='fa fa-envelope'></i> {{i18n user.private_messages}}</h3>
         <ul class='action-list nav-stacked'>
           <li {{bind-attr class=":noGlyph privateMessagesActive:active"}}>
-            {{#link-to 'userPrivateMessages.index' model}}{{i18n user.messages.all}}<span class='fa fa-chevron-right'></span>{{/link-to}}
+            {{#link-to 'userPrivateMessages.index' model}}
+              {{i18n user.messages.all}}
+              <span class='count'>({{private_messages_stats.all}})</span>
+              <span class='fa fa-chevron-right'></span>
+            {{/link-to}}
           </li>
           <li {{bind-attr class=":noGlyph privateMessagesMineActive:active"}}>
-            {{#link-to 'userPrivateMessages.mine' model}}{{i18n user.messages.mine}}<span class='fa fa-chevron-right'></span>{{/link-to}}
+            {{#link-to 'userPrivateMessages.mine' model}}
+              {{i18n user.messages.mine}}
+              <span class='count'>({{private_messages_stats.mine}})</span>
+              <span class='fa fa-chevron-right'></span>
+            {{/link-to}}
           </li>
           <li {{bind-attr class=":noGlyph privateMessagesUnreadActive:active"}}>
-            {{#link-to 'userPrivateMessages.unread' model}}{{i18n user.messages.unread}}<span class='fa fa-chevron-right'></span>{{/link-to}}
+            {{#link-to 'userPrivateMessages.unread' model}}
+              {{i18n user.messages.unread}}
+              <span class='count'>({{private_messages_stats.unread}})</span>
+              <span class='fa fa-chevron-right'></span>
+            {{/link-to}}
           </li>
         </ul>
       {{/if}}
diff --git a/app/models/user_action.rb b/app/models/user_action.rb
index fc66f1880..9ed54714a 100644
--- a/app/models/user_action.rb
+++ b/app/models/user_action.rb
@@ -73,6 +73,18 @@ SQL
     results
   end
 
+  def self.private_messages_stats(user_id, guardian)
+    return unless guardian.can_see_private_messages?(user_id)
+    # list the stats for: all/mine/unread (topic-based)
+    private_messages = Topic.where("topics.id IN (SELECT topic_id FROM topic_allowed_users WHERE user_id = #{user_id})")
+                            .joins("LEFT OUTER JOIN topic_users AS tu ON (topics.id = tu.topic_id AND tu.user_id = #{user_id})")
+                            .private_messages
+    all = private_messages.count
+    mine = private_messages.where(user_id: user_id).count
+    unread = private_messages.where("tu.last_read_post_number IS NULL OR tu.last_read_post_number < topics.highest_post_number").count
+    { all: all, mine: mine, unread: unread }
+  end
+
   def self.stream_item(action_id, guardian)
     stream(action_id: action_id, guardian: guardian).first
   end
diff --git a/app/serializers/user_serializer.rb b/app/serializers/user_serializer.rb
index a35750c6c..2d0681ed7 100644
--- a/app/serializers/user_serializer.rb
+++ b/app/serializers/user_serializer.rb
@@ -69,7 +69,8 @@ class UserSerializer < BasicUserSerializer
                      :uploaded_avatar_template,
                      :muted_category_ids,
                      :tracked_category_ids,
-                     :watched_category_ids
+                     :watched_category_ids,
+                     :private_messages_stats
 
 
   def auto_track_topics_after_msecs
@@ -131,6 +132,10 @@ class UserSerializer < BasicUserSerializer
     CategoryUser.lookup(object, :watching).pluck(:category_id)
   end
 
+  def private_messages_stats
+    UserAction.private_messages_stats(object.id, scope)
+  end
+
   def bio_cooked
     object.bio_processed
   end
diff --git a/lib/topic_query.rb b/lib/topic_query.rb
index f7b87139f..4c90ba463 100644
--- a/lib/topic_query.rb
+++ b/lib/topic_query.rb
@@ -121,7 +121,7 @@ class TopicQuery
 
   def list_private_messages_unread(user)
     list = private_messages_for(user)
-    list = TopicQuery.unread_filter(list)
+    list = list.where("tu.last_read_post_number IS NULL OR tu.last_read_post_number < topics.highest_post_number")
     TopicList.new(:private_messages, user, list)
   end
 
@@ -165,7 +165,7 @@ class TopicQuery
       options.reverse_merge!(per_page: SiteSetting.topics_per_page)
 
       # Start with a list of all topics
-      result = Topic.where(id: TopicAllowedUser.where(user_id: user.id).pluck(:topic_id))
+      result = Topic.where("topics.id IN (SELECT topic_id FROM topic_allowed_users WHERE user_id = #{user.id.to_i})")
                     .joins("LEFT OUTER JOIN topic_users AS tu ON (topics.id = tu.topic_id AND tu.user_id = #{user.id.to_i})")
                     .order(TopicQuerySQL.order_nocategory_basic_bumped)
                     .private_messages