diff --git a/app/models/Achievement.coffee b/app/models/Achievement.coffee
index 2240345c7..22788e3b2 100644
--- a/app/models/Achievement.coffee
+++ b/app/models/Achievement.coffee
@@ -3,4 +3,8 @@ CocoModel = require './CocoModel'
 module.exports = class Achievement extends CocoModel
   @className: 'Achievement'
   @schema: require 'schemas/models/achievement'
-  urlRoot: '/db/achievement'
\ No newline at end of file
+  urlRoot: '/db/achievement'
+
+  initialize: (id) ->
+    super()
+    @set('_id', id) if id?
\ No newline at end of file
diff --git a/app/models/CocoModel.coffee b/app/models/CocoModel.coffee
index 68a836c3d..19530c174 100644
--- a/app/models/CocoModel.coffee
+++ b/app/models/CocoModel.coffee
@@ -1,6 +1,10 @@
 storage = require 'lib/storage'
 deltasLib = require 'lib/deltas'
 
+class NewAchievementCollection extends Backbone.Collection
+  initialize: (me = require('lib/auth').me) ->
+    @url = "/db/user/#{me.id}/achievements?notified=false"
+
 class CocoModel extends Backbone.Model
   idAttribute: "_id"
   loaded: false
@@ -86,6 +90,7 @@ class CocoModel extends Backbone.Model
       success(@, res) if success
       @markToRevert() if @_revertAttributes
       @clearBackup()
+      CocoModel.pollAchievements()
     options.error = (model, res) =>
       error(@, res) if error
       return unless @notyErrors
@@ -266,4 +271,14 @@ class CocoModel extends Backbone.Model
   getURL: ->
     return if _.isString @url then @url else @url()
 
+  @pollAchievements: ->
+    achievements = new NewAchievementCollection
+    achievements.fetch(
+      success: (collection) ->
+        Backbone.Mediator.publish('achievements:new', collection) unless _.isEmpty(collection.models)
+    )
+
+
+CocoModel.pollAchievements = _.debounce CocoModel.pollAchievements, 500
+
 module.exports = CocoModel
diff --git a/app/schemas/models/achievement.coffee b/app/schemas/models/achievement.coffee
index f8cfa8e2b..38f4a4e50 100644
--- a/app/schemas/models/achievement.coffee
+++ b/app/schemas/models/achievement.coffee
@@ -21,12 +21,12 @@ MongoFindQuerySchema =
   type: 'object'
   patternProperties:
     #'^[-a-zA-Z0-9_]*$':
-    '^[-a-zA-Z0-9]*$':
+    '^[-a-zA-Z0-9\.]*$':
       oneOf: [
         #{ $ref: '#/definitions/' + MongoQueryOperatorSchema.id},
         { type: 'string' }
       ]
-  additionalProperties: true
+  additionalProperties: true # TODO make Treema accept new pattern matched keys
   definitions: {}
 
 MongoFindQuerySchema.definitions[MongoQueryOperatorSchema.id] = MongoQueryOperatorSchema
diff --git a/app/styles/notify.sass b/app/styles/notify.sass
new file mode 100644
index 000000000..ed6c5e6bd
--- /dev/null
+++ b/app/styles/notify.sass
@@ -0,0 +1,19 @@
+.notifyjs-achievement-base
+  opacity: 0.85
+  width: 200px
+  background: #F5F5F5
+  padding: 5px
+  border-radius: 10px
+  text-align: center
+
+  .achievement-title
+    font-weight: bold
+
+  .achievement-image
+    // pass
+
+  .achievement-name
+    // pass
+
+  .achievement-description
+    // pass
\ No newline at end of file
diff --git a/app/templates/achievement_notify.jade b/app/templates/achievement_notify.jade
new file mode 100644
index 000000000..6b19da70c
--- /dev/null
+++ b/app/templates/achievement_notify.jade
@@ -0,0 +1,6 @@
+div
+  div.clearfix
+    div.achievement-title(data-notify-html="title")
+    div.achievement-image(data-notify-html="image")
+    div.achievement-name(data-notify-html="name")
+    div.achievement-description(data-notify-html="description")
\ No newline at end of file
diff --git a/app/templates/base.jade b/app/templates/base.jade
index dc5d79286..01a67687c 100644
--- a/app/templates/base.jade
+++ b/app/templates/base.jade
@@ -12,7 +12,7 @@ body
           
           a.navbar-brand(href='/')
             img(src="/images/pages/base/logo.png", title="CodeCombat - Learn how to code by playing a game", alt="CodeCombat")
-        .collapse.navbar-collapse#collapsible-navbar      
+        .collapse.navbar-collapse#collapsible-navbar
           ul.nav.navbar-nav
             li.play
               a.header-font(href='/play', data-i18n="nav.play") Levels
diff --git a/app/views/kinds/RootView.coffee b/app/views/kinds/RootView.coffee
index 3350dc48e..40bc765da 100644
--- a/app/views/kinds/RootView.coffee
+++ b/app/views/kinds/RootView.coffee
@@ -6,6 +6,9 @@ CocoView = require './CocoView'
 {logoutUser, me} = require('lib/auth')
 locale = require 'locale/locale'
 
+AchievementNotify = require '../../templates/achievement_notify'
+Achievement = require '../../models/Achievement'
+
 filterKeyboardEvents = (allowedEvents, func) ->
   return (splat...) ->
     e = splat[0]
@@ -19,6 +22,41 @@ module.exports = class RootView extends CocoView
     'click .toggle-fullscreen': 'toggleFullscreen'
     'click .auth-button': 'onClickAuthbutton'
 
+  subscriptions:
+    'achievements:new': 'handleNewAchievements'
+
+  initialize: ->
+    $ ->
+      $.notify.addStyle 'achievement',
+        html: $(AchievementNotify())
+
+  showNewAchievement: (achievement) ->
+    imageURL = '/file/' + achievement.get('icon')
+    data =
+      title: 'Achievement Unlocked'
+      name: achievement.get('name')
+      image: $("<img src='#{imageURL}' />")
+      description: achievement.get('description')
+
+    options =
+      autoHideDelay: 15000
+      globalPosition: 'bottom right'
+      showDuration: 400
+      style: 'achievement'
+      autoHide: true
+      clickToHide: true
+
+    $.notify( data, options )
+
+  handleNewAchievements: (earnedAchievements) ->
+    # TODO performance?
+    _.each(earnedAchievements.models, (earnedAchievement) =>
+      achievement = new Achievement(earnedAchievement.get('achievement'))
+      achievement.fetch(
+        success: @showNewAchievement
+      )
+    )
+
   logoutAccount: ->
     logoutUser($('#login-email').val())
 
diff --git a/server/achievements/Achievement.coffee b/server/achievements/Achievement.coffee
index e7fa4a214..eff3bc857 100644
--- a/server/achievements/Achievement.coffee
+++ b/server/achievements/Achievement.coffee
@@ -16,7 +16,7 @@ AchievementSchema.methods.objectifyQuery = () ->
   try
     @set('query', JSON.parse(@get('query'))) if typeof @get('query') == "string"
   catch error
-    #log.error "Couldn't convert query string to object because of #{error}"
+    log.error "Couldn't convert query string to object because of #{error}"
     @set('query', {})
 
 AchievementSchema.methods.stringifyQuery = () ->
diff --git a/server/achievements/EarnedAchievement.coffee b/server/achievements/EarnedAchievement.coffee
index 80eee59af..c4f017e38 100644
--- a/server/achievements/EarnedAchievement.coffee
+++ b/server/achievements/EarnedAchievement.coffee
@@ -13,7 +13,7 @@ EarnedAchievementSchema = new mongoose.Schema({
     default: false
 }, {strict:false})
 
-# Maybe consider indexing on changed: -1 as well?
 EarnedAchievementSchema.index({user: 1, achievement: 1}, {unique: true, name: 'earned achievement index'})
+EarnedAchievementSchema.index({user: 1, changed: -1}, {name: 'latest '})
 
 module.exports = EarnedAchievement = mongoose.model('EarnedAchievement', EarnedAchievementSchema)
\ No newline at end of file
diff --git a/server/commons/Handler.coffee b/server/commons/Handler.coffee
index 4fa2da2d9..199ae90b8 100644
--- a/server/commons/Handler.coffee
+++ b/server/commons/Handler.coffee
@@ -167,7 +167,7 @@ module.exports = class Handler
       projection = PROJECT
     else if req.query.project
       if @modelClass.className is 'User'
-        projection = PROJECTION
+        projection = PROJECT
         log.warn "Whoa, we haven't yet thought about public properties for User projection yet."
       else
         projection = {}
diff --git a/server/plugins/achievements.coffee b/server/plugins/achievements.coffee
index ef29fcf26..134b76dc2 100644
--- a/server/plugins/achievements.coffee
+++ b/server/plugins/achievements.coffee
@@ -18,8 +18,6 @@ loadAchievements = ->
 
 loadAchievements()
 
-
-# TODO make a difference between '$userID' and '$userObjectID' ?
 module.exports = AchievablePlugin = (schema, options) ->
   checkForAchievement = (doc) ->
     collectionName = doc.constructor.modelName
@@ -43,9 +41,11 @@ module.exports = AchievablePlugin = (schema, options) ->
       for achievement in achievements[category]
         query = achievement.get('query')
         isRepeatable = achievement.get('proportionalTo')?
-        console.log 'isRepeatable: ' + isRepeatable
         alreadyAchieved = if isNew then false else LocalMongo.matchesQuery originalDocObj, query
         newlyAchieved = LocalMongo.matchesQuery(docObj, query)
+        console.log 'isRepeatable: ' + isRepeatable
+        console.log 'alreadyAchieved: ' +  alreadyAchieved
+        console.log 'newlyAchieved: ' + newlyAchieved
 
         userObjectID = doc.get(achievement.get('userField'))
         userID = if _.isObject userObjectID then userObjectID.toHexString() else userObjectID # Standardize! Use strings, not ObjectId's
diff --git a/server/users/user_handler.coffee b/server/users/user_handler.coffee
index 48895ec87..54f87aaf5 100644
--- a/server/users/user_handler.coffee
+++ b/server/users/user_handler.coffee
@@ -21,6 +21,12 @@ candidateProperties = [
   'jobProfile', 'jobProfileApproved', 'jobProfileNotes'
 ]
 
+parseLiteral = (literalString) ->
+  return true if literalString is 'true'
+  return false if literalString is 'false'
+  return number if (number = Number(literalString)) isnt NaN
+  literalString
+
 UserHandler = class UserHandler extends Handler
   modelClass: User
 
@@ -238,11 +244,16 @@ UserHandler = class UserHandler extends Handler
       @sendSuccess(res, documents)
 
   getEarnedAchievements: (req, res, userID) ->
-    query = EarnedAchievement.find(user: userID)
+    queryObject = {$query: {user: userID}, $orderby: {changed: -1}}
+    queryObject.$query[key] = parseLiteral(val) for key, val of req.query
+    query = EarnedAchievement.find(queryObject)
     query.exec (err, documents) =>
       return @sendDatabaseError(res, err) if err?
-      documents = (@formatEntity(req, doc) for doc in documents)
-      @sendSuccess(res, documents)
+      cleandocs = (@formatEntity(req, doc) for doc in documents)
+      for doc in documents  # Maybe move this logic elsewhere
+        doc.set('notified', true)
+        doc.save()
+      @sendSuccess(res, cleandocs)
 
   agreeToEmployerAgreement: (req, res) ->
     userIsAnonymous = req.user?.get('anonymous')