diff --git a/public/gfx/icon_128x128.png b/public/gfx/icon_128x128.png
new file mode 100644
index 0000000..ec48cc3
Binary files /dev/null and b/public/gfx/icon_128x128.png differ
diff --git a/public/gfx/icon_128x128_error.png b/public/gfx/icon_128x128_error.png
new file mode 100644
index 0000000..672c207
Binary files /dev/null and b/public/gfx/icon_128x128_error.png differ
diff --git a/public/gfx/icon_128x128_info.png b/public/gfx/icon_128x128_info.png
new file mode 100644
index 0000000..e16d45e
Binary files /dev/null and b/public/gfx/icon_128x128_info.png differ
diff --git a/public/gfx/icon_16x16.png b/public/gfx/icon_16x16.png
new file mode 100644
index 0000000..6249c85
Binary files /dev/null and b/public/gfx/icon_16x16.png differ
diff --git a/public/gfx/icon_32x32.png b/public/gfx/icon_32x32.png
new file mode 100644
index 0000000..58b6985
Binary files /dev/null and b/public/gfx/icon_32x32.png differ
diff --git a/public/gfx/icon_64x64.png b/public/gfx/icon_64x64.png
new file mode 100644
index 0000000..259396d
Binary files /dev/null and b/public/gfx/icon_64x64.png differ
diff --git a/public/index.html b/public/index.html
index 4747cb8..a33cdd8 100644
--- a/public/index.html
+++ b/public/index.html
@@ -5,6 +5,11 @@
 	<title>Cryptalk</title>
 
 	<link rel="stylesheet" type="text/css" href="css/default.css">
+
+	<head profile="http://www.w3.org/2005/10/profile">
+	<link rel="icon" 
+	      type="image/png" 
+	      href="gfx/icon_32x32.png">
 </head>
 <body>
 
diff --git a/public/js/cryptalk_modules/cryptalk.js b/public/js/cryptalk_modules/cryptalk.js
index 8df7787..775bd7d 100644
--- a/public/js/cryptalk_modules/cryptalk.js
+++ b/public/js/cryptalk_modules/cryptalk.js
@@ -1,7 +1,7 @@
 // Main cryptalk module
 define({
 	compiles: ['$'],
-	requires: ['hosts', 'templates', 'sound', 'fandango']
+	requires: ['hosts', 'templates', 'sound', 'fandango','notifications']
 }, function ($, requires, data) {
 	var socket,
 		key,
@@ -26,10 +26,11 @@ define({
 		},
 
 		// Shortcut
-		hosts = requires.hosts.hosts;
-		fandango = requires.fandango;
-		templates = requires.templates;
-		sound = requires.sound;
+		hosts = requires.hosts.hosts,
+		fandango = requires.fandango,
+		templates = requires.templates,
+		sound = requires.sound,
+		notifications = requires.notifications,
 
 		lockInput = function () {
 			components.input[0].setAttribute('disabled', 'disabled');
@@ -42,8 +43,22 @@ define({
 			components.input.focus();
 		},
 
+		showNotification = function (type, nick, text) {
+			if (!mute) {
+				if ( type == 'message') {
+					notifications.notify(nick.substring(0, 20), text.substring(0, 80),'gfx/icon_128x128.png',true);
+				} else if ( type == 'error' ) {
+					notifications.notify('Cryptalk', text.substring(0, 80),'gfx/icon_128x128_error.png',true);
+				} else {
+					notifications.notify('Cryptalk', text.substring(0, 80),'gfx/icon_128x128_info.png',true);
+				}
+				
+			}
+		},
+
 		// Adds a new message to the DOM
 		post = function (type, text, clearChat, clearBuffer, nick) {
+
 			var tpl = templates.post[type],
 				post,
 				data = fandango.merge({}, settings, {
@@ -60,6 +75,9 @@ define({
 				clearInput();
 			}
 
+			showNotification(type, nick, text)
+
+
 			// Append the post to the chat DOM element
 			components.chat[clearChat ? 'html' : 'append'](post);
 		},
@@ -196,7 +214,7 @@ define({
 							post('error', templates.messages.unable_to_decrypt);
 						} else {
 							post('message', sanitized, false, false, nick);
-							if( !mute ) sound.playTones(sound.messages.message);
+							//if( !mute && !notifications.windowActive()) sound.playTones(sound.messages.message);
 						}
 					})
 
@@ -212,7 +230,7 @@ define({
 								}
 
 								// Play sound
-								if (sound.messages[sanitized] !== undefined && !mute ) sound.playTones(sound.messages[sanitized]);
+								//if (sound.messages[sanitized] !== undefined && !mute && !notifications.windowActive() ) sound.playTones(sound.messages[sanitized]);
 
 							} else {
 								post('error', templates.server.bogus);
@@ -500,13 +518,18 @@ define({
 
 	unlockInput();
 
+	notifications.enableNative();
+
 	// It's possible to provide room and key using the hashtag.
 	// The room and key is then seperated by semicolon (room:key).
 	// If there is no semicolon present, the complete hash will be treated as the room name and the key has to be set manually.
-	if (host && (hash = window.location.hash)) {
-		parts = hash.slice(1).split(':');
+	commands.connect(0, function() {
+		if (host && (hash = window.location.hash)) {
+			parts = hash.slice(1).split(':');
+
+			parts[0] && commands.join(parts[0]);
+			parts[1] && commands.key(parts[1]);
+		}	
+	});
 
-		parts[0] && commands.join(parts[0]);
-		parts[1] && commands.key(parts[1]);
-	}
 });
\ No newline at end of file
diff --git a/public/js/cryptalk_modules/hosts.js b/public/js/cryptalk_modules/hosts.js
index ea61b1a..256731a 100644
--- a/public/js/cryptalk_modules/hosts.js
+++ b/public/js/cryptalk_modules/hosts.js
@@ -12,4 +12,4 @@ define({
 			path: 'http://localhost:8080/js/cryptalk_modules/settings.js'
 		}
 	]
-});
\ No newline at end of file
+});
diff --git a/public/js/cryptalk_modules/notifications.js b/public/js/cryptalk_modules/notifications.js
new file mode 100644
index 0000000..18f85e6
--- /dev/null
+++ b/public/js/cryptalk_modules/notifications.js
@@ -0,0 +1,133 @@
+/*
+Usage
+ 
+Native notifications without fallback:
+	notification.enableNative(); // Once
+	notification.notify("Woop Woop");
+	
+Native notifications with fallback:
+	notification.enableNative(); // Once
+	notification.notify("Woop Woop",true); // True in 2nd parameter enables fallback
+	
+Title blink only:
+	notifications.blinkTitleUntilFocus("Woop Woop",1000);
+	
+*/
+define(function (){
+ 
+	var exports = {},
+ 
+		window_active,
+		blur_delay_timer,
+		native_supported = false,
+ 
+		new_title,
+		original_title,
+		blink_timer,
+		interval,
+
+        now = function () {
+            return performance.now() || Date.now();
+        },
+ 
+		focusCallback = function() {
+			/* Reset everything after regaining focus */
+			resetState();
+		},
+ 
+		resetState = function() {
+			clearTimeout(blur_delay_timer);
+			clearTimeout(blink_timer);
+			if (original_title !== undefined) setTitle(original_title);
+			original_title = undefined;
+			new_title = undefined;
+			window_active = true;
+		},
+ 
+		blurCallback = function() {
+			/* Apply a slight delay to prevent notifications from popping when the notifications
+			   cause the windows to lose focus */
+			clearTimeout(blur_delay_timer);
+			blur_delay_timer = setTimeout(function() { window_active = false; },1000);
+		},
+ 
+		setTitle = function(t) 	{ document.title = t; },
+		getTitle = function() 	{ return document.title; },
+ 
+		doBlink = function() {
+			if(!window_active) {
+				if( getTitle() == original_title )
+					setTitle( new_title );
+				else
+					setTitle( original_title);
+ 
+				blink_timer = setTimeout(doBlink,interval);
+			} else {
+				resetState();
+			}
+		};
+ 
+	exports.enableNative = function() {
+		if( native_supported && Notification.permission !== 'denied' ) {
+			Notification.requestPermission(function (status) {
+				Notification.permission = status;
+			});
+		}
+	};
+
+	exports.windowActive = function() {
+		return window_active;
+	};
+ 
+	exports.blinkTitleUntilFocus = function(t,i) {
+		interval = (i == undefined) ? 1000 : i;
+		if (!window_active) {
+			new_title = t;
+			original_title = getTitle();
+			doBlink();
+		}
+	};
+ 
+	exports.notify = function(title,body,icon,fallback) {
+		// Only notify while in background
+		if( !window_active ) {
+
+			// Set default value for fallback parameter
+			if ( fallback === undefined) fallback = false;
+
+			if ( native_supported && Notification.permission === "granted") {
+
+				// Create notification
+				var n = new Notification(title, {body: body, icon:icon});
+
+				// Handle on show event
+				n.onshow = function () { 
+				  	// Automatically close the notification after 5000ms
+					setTimeout(function(){n.close();},3000);
+				}
+
+			} else if ( fallback ) {
+				exports.blinkTitleUntilFocus("Attention",1000);
+			}
+		}
+	};
+ 
+	native_supported = (window.Notification !== undefined);
+ 
+	// Keep track of document focus/blur
+	if (window.addEventListener){
+		// Normal browsers
+		window.addEventListener("focus", focusCallback, true);
+		window.addEventListener("blur", blurCallback, true);
+	} else {
+		// IE
+		window.observe("focusin", focusCallback);
+		window.observe("focusout", blurCallback);
+	}
+ 
+	// Make sure we are at square one
+	resetState();
+ 
+	return exports;
+ 
+});
\ No newline at end of file
diff --git a/public/js/cryptalk_modules/templates.js b/public/js/cryptalk_modules/templates.js
index 96ee5f9..a134991 100644
--- a/public/js/cryptalk_modules/templates.js
+++ b/public/js/cryptalk_modules/templates.js
@@ -79,8 +79,8 @@ define({
 		leave_from_nowhere: 	'How are you supposed to leave, while being nowhere?',
 
 		// Sounds
-		muted: 					'Notification sounds is now muted.',
-		unmuted: 				'Notifications sounds is now on.',
+		muted: 					'Notifications and sounds are now muted.',
+		unmuted: 				'Notifications and sounds are now on.',
 
 		// Extra variables: 'commandName'
 		unrecognized_command: 	'Unrecognized command: "{commandName}"',
diff --git a/server.js b/server.js
index b0ed501..0f99b9a 100644
--- a/server.js
+++ b/server.js
@@ -1,7 +1,7 @@
 var express = require('express.io'),
-    uuid = require('node-uuid');
+    uuid = require('node-uuid'),
 
-app = express();app.http().io();
+    app = express();app.http().io();
 
 app.use(express.static(__dirname + '/public'));
 
@@ -43,9 +43,38 @@ app.io.route('room', {
 
 app.io.route('message', {
     send: function(req) {
-      if(req.data && req.data.room) req.socket.broadcast.to(req.data.room).emit('message:send', { msg: req.data.msg, nick: req.data.nick} );
-      req.socket.emit('message:send', { msg: req.data.msg, nick: req.data.nick} );
+
+      // Check that the user is in a room
+      if(req.data && req.data.room) {
+
+        // Check that the message size is within bounds
+        var total_msg_size = (req.data.msg) ? req.data.msg.length : 0 + (req.data.nick) ? req.data.nick.length : 0;
+        if( total_msg_size <= 4096) {
+
+          // Check that at least 100ms has passed since last message
+          if( req.socket.last_message === undefined || new Date().getTime() - req.socket.last_message > 100 ) {
+
+            req.socket.broadcast.to(req.data.room).emit('message:send', { msg: req.data.msg, nick: req.data.nick} );
+            req.socket.emit('message:send', { msg: req.data.msg, nick: req.data.nick} );
+
+            req.socket.last_message = new Date().getTime();
+
+          } else {
+
+            // Do not complain if message rate is too fast, that would only generate more traffic
+
+          }
+
+        } else {
+
+          // Message size is out of bounds, complain
+          req.socket.emit('message:server', {msg:'command_failed'} );
+        }
+
+      } 
+
     }
+
 });
 
 app.io.sockets.on('connection', function(socket) {