diff --git a/setup.py b/setup.py
index 9357284b..c7f7fab5 100644
--- a/setup.py
+++ b/setup.py
@@ -254,7 +254,9 @@ if __name__ == "__main__":
             install_requires=installRequires,
             extras_require={
                 'qrcode': ['qrcode'],
-                'pyopencl': ['pyopencl']
+                'pyopencl': ['pyopencl'],
+                'notify2': ['pygobject', 'notify2'],
+                'sound:platform_system=="Windows"': ['winsound']
             },
             classifiers=[
                 "License :: OSI Approved :: MIT License"
@@ -278,9 +280,16 @@ if __name__ == "__main__":
                     'popMenuYourIdentities.qrcode = '
                     'pybitmessage.plugins.qrcodeui [qrcode]'
                 ],
-            #    'console_scripts': [
-            #        'pybitmessage = pybitmessage.bitmessagemain:main'
-            #    ]
+                'notification.message': [
+                    'notify2 = pybitmessage.plugins.notification_notify2'
+                    '[notify2]'
+                ],
+                'notification.sound': [
+                    'fallback = pybitmessage.plugins.sound_playfile [sound]'
+                ],
+                # 'console_scripts': [
+                #        'pybitmessage = pybitmessage.bitmessagemain:main'
+                # ]
             },
             scripts=['src/pybitmessage'],
             cmdclass={'install': InstallCmd}
diff --git a/src/bitmessageqt/__init__.py b/src/bitmessageqt/__init__.py
index 014831bf..44dcb8ec 100644
--- a/src/bitmessageqt/__init__.py
+++ b/src/bitmessageqt/__init__.py
@@ -4,8 +4,6 @@ try:
     import gi
     gi.require_version('MessagingMenu', '1.0')
     from gi.repository import MessagingMenu
-    gi.require_version('Notify', '0.7')
-    from gi.repository import Notify
     withMessagingMenu = True
 except (ImportError, ValueError):
     MessagingMenu = None
@@ -62,9 +60,8 @@ import platform
 import textwrap
 import debug
 import random
-import subprocess
 import string
-import datetime
+from datetime import datetime, timedelta
 from helper_sql import *
 import helper_search
 import l10n
@@ -153,7 +150,7 @@ class MyForm(settingsmixin.SMainWindow):
     SOUND_CONNECTION_GREEN = 5
 
     # the last time that a message arrival sound was played
-    lastSoundTime = datetime.datetime.now() - datetime.timedelta(days=1)
+    lastSoundTime = datetime.now() - timedelta(days=1)
 
     # the maximum frequency of message sounds in seconds
     maxSoundFrequencySec = 60
@@ -1368,39 +1365,41 @@ class MyForm(settingsmixin.SMainWindow):
     # returns true if the given sound category is a connection sound
     # rather than a received message sound
     def isConnectionSound(self, category):
-        if (category is self.SOUND_CONNECTED or
-            category is self.SOUND_DISCONNECTED or
-            category is self.SOUND_CONNECTION_GREEN):
-            return True
-        return False
+        return category in (
+            self.SOUND_CONNECTED,
+            self.SOUND_DISCONNECTED,
+            self.SOUND_CONNECTION_GREEN
+        )
 
     # play a sound
     def playSound(self, category, label):
         # filename of the sound to be played
         soundFilename = None
 
-        # whether to play a sound or not
-        play = True
+        def _choose_ext(basename):
+            for ext in ('.wav', '.mp3', '.oga'):
+                if os.path.isfile(basename + ext):
+                    return ext
 
         # if the address had a known label in the address book
-        if label is not None:
+        if label:
             # Does a sound file exist for this particular contact?
-            if (os.path.isfile(state.appdata + 'sounds/' + label + '.wav') or
-                os.path.isfile(state.appdata + 'sounds/' + label + '.mp3')):
-                soundFilename = state.appdata + 'sounds/' + label
-
-        # Avoid making sounds more frequently than the threshold.
-        # This suppresses playing sounds repeatedly when there
-        # are many new messages
-        if (soundFilename is None and
-            not self.isConnectionSound(category)):
-            # elapsed time since the last sound was played
-            dt = datetime.datetime.now() - self.lastSoundTime
-            # suppress sounds which are more frequent than the threshold
-            if dt.total_seconds() < self.maxSoundFrequencySec:
-                play = False
+            soundFilename = state.appdata + 'sounds/' + label
+            ext = _choose_ext(soundFilename)
+            if not ext:
+                soundFilename = None
 
         if soundFilename is None:
+            # Avoid making sounds more frequently than the threshold.
+            # This suppresses playing sounds repeatedly when there
+            # are many new messages
+            if not self.isConnectionSound(category):
+                # elapsed time since the last sound was played
+                dt = datetime.now() - self.lastSoundTime
+                # suppress sounds which are more frequent than the threshold
+                if dt.total_seconds() < self.maxSoundFrequencySec:
+                    return
+
             # the sound is for an address which exists in the address book
             if category is self.SOUND_KNOWN:
                 soundFilename = state.appdata + 'sounds/known'
@@ -1415,75 +1414,55 @@ class MyForm(settingsmixin.SMainWindow):
                 soundFilename = state.appdata + 'sounds/disconnected'
             # sound when the connection status becomes green
             elif category is self.SOUND_CONNECTION_GREEN:
-                soundFilename = state.appdata + 'sounds/green'            
+                soundFilename = state.appdata + 'sounds/green'
 
-        if soundFilename is not None and play is True:
-            if not self.isConnectionSound(category):
-                # record the last time that a received message sound was played
-                self.lastSoundTime = datetime.datetime.now()
+        if soundFilename is None:
+            return
 
-            # if not wav then try mp3 format
-            if not os.path.isfile(soundFilename + '.wav'):
-                soundFilename = soundFilename + '.mp3'
-            else:
-                soundFilename = soundFilename + '.wav'
+        if not self.isConnectionSound(category):
+            # record the last time that a received message sound was played
+            self.lastSoundTime = datetime.now()
 
-            if os.path.isfile(soundFilename):
-                if 'linux' in sys.platform:
-                    # Note: QSound was a nice idea but it didn't work
-                    if '.mp3' in soundFilename:
-                        gst_available=False
-                        try:
-                            subprocess.call(["gst123", soundFilename],
-                                            stdin=subprocess.PIPE, 
-                                            stdout=subprocess.PIPE)
-                            gst_available=True
-                        except:
-                            logger.warning("WARNING: gst123 must be installed in order to play mp3 sounds")
-                        if not gst_available:
-                            try:
-                                subprocess.call(["mpg123", soundFilename],
-                                                stdin=subprocess.PIPE, 
-                                                stdout=subprocess.PIPE)
-                                gst_available=True
-                            except:
-                                logger.warning("WARNING: mpg123 must be installed in order to play mp3 sounds")
-                    else:
-                        try:
-                            subprocess.call(["aplay", soundFilename],
-                                            stdin=subprocess.PIPE, 
-                                            stdout=subprocess.PIPE)
-                        except:
-                            logger.warning("WARNING: aplay must be installed in order to play WAV sounds")
-                elif sys.platform[0:3] == 'win':
-                    # use winsound on Windows
-                    import winsound
-                    winsound.PlaySound(soundFilename, winsound.SND_FILENAME)
+        try:  # try already known format
+            soundFilename += ext
+        except (TypeError, NameError):
+            ext = _choose_ext(soundFilename)
+            if not ext:
+                return
+
+            soundFilename += ext
+
+        self._player(soundFilename)
 
     # initialise the message notifier
     def notifierInit(self):
-        if withMessagingMenu:
-            Notify.init('pybitmessage')
-
-    # shows a notification
-    def notifierShow(self, title, subtitle, fromCategory, label):
-        self.playSound(fromCategory, label)
-
-        if withMessagingMenu:
-            n = Notify.Notification.new(
-                title, subtitle, 'notification-message-email')
-            try:
-                n.show()
-            except:
-                # n.show() has been known to throw this exception:
-                # gi._glib.GError: GDBus.Error:org.freedesktop.Notifications.
-                # MaxNotificationsExceeded: Exceeded maximum number of
-                # notifications
-                pass
-            return
-        else:
+        def _simple_notify(
+                title, subtitle, category, label=None, icon=None):
             self.tray.showMessage(title, subtitle, 1, 2000)
 
+        self._notifier = _simple_notify
+        self._player = QtGui.QSound.play
+
+        if not get_plugins:
+            return
+
+        for plugin in get_plugins('notification.message'):
+            self._notifier = plugin
+            break
+
+        if not QtGui.QSound.isAvailable():
+            for plugin in get_plugins('notification.sound'):
+                self._player = plugin
+                break
+            else:
+                logger.warning("No sound player plugin found!")
+
+    def notifierShow(
+            self, title, subtitle, category, label=None, icon=None):
+        self.playSound(category, label)
+        self._notifier(
+            unicode(title), unicode(subtitle), category, label, icon)
+
     # tree
     def treeWidgetKeyPressEvent(self, event):
         return self.handleKeyPress(event, self.getCurrentTreeWidget())
@@ -1663,15 +1642,18 @@ class MyForm(settingsmixin.SMainWindow):
 
     def setStatusIcon(self, color):
         # print 'setting status icon color'
+        _notifications_enabled = not BMConfigParser().getboolean(
+            'bitmessagesettings', 'hidetrayconnectionnotifications')
         if color == 'red':
             self.pushButtonStatusIcon.setIcon(
                 QIcon(":/newPrefix/images/redicon.png"))
             shared.statusIconColor = 'red'
             # if the connection is lost then show a notification
-            if self.connected and not BMConfigParser().getboolean('bitmessagesettings', 'hidetrayconnectionnotifications'):
-                self.notifierShow('Bitmessage', unicode(_translate(
-                            "MainWindow", "Connection lost").toUtf8(),'utf-8'),
-                                  self.SOUND_DISCONNECTED, None)
+            if self.connected and _notifications_enabled:
+                self.notifierShow(
+                    'Bitmessage',
+                    _translate("MainWindow", "Connection lost"),
+                    self.SOUND_DISCONNECTED)
             if not BMConfigParser().safeGetBoolean('bitmessagesettings', 'upnp') and \
                 BMConfigParser().get('bitmessagesettings', 'socksproxytype') == "none":
                 self.statusBar().showMessage(_translate(
@@ -1689,10 +1671,11 @@ class MyForm(settingsmixin.SMainWindow):
                 ":/newPrefix/images/yellowicon.png"))
             shared.statusIconColor = 'yellow'
             # if a new connection has been established then show a notification
-            if not self.connected and not BMConfigParser().getboolean('bitmessagesettings', 'hidetrayconnectionnotifications'):
-                self.notifierShow('Bitmessage', unicode(_translate(
-                            "MainWindow", "Connected").toUtf8(),'utf-8'),
-                                  self.SOUND_CONNECTED, None)
+            if not self.connected and _notifications_enabled:
+                self.notifierShow(
+                    'Bitmessage',
+                    _translate("MainWindow", "Connected"),
+                    self.SOUND_CONNECTED)
             self.connected = True
 
             if self.actionStatus is not None:
@@ -1705,10 +1688,11 @@ class MyForm(settingsmixin.SMainWindow):
             self.pushButtonStatusIcon.setIcon(
                 QIcon(":/newPrefix/images/greenicon.png"))
             shared.statusIconColor = 'green'
-            if not self.connected and not BMConfigParser().getboolean('bitmessagesettings', 'hidetrayconnectionnotifications'):
-                self.notifierShow('Bitmessage', unicode(_translate(
-                            "MainWindow", "Connected").toUtf8(),'utf-8'),
-                                  self.SOUND_CONNECTION_GREEN, None)
+            if not self.connected and _notifications_enabled:
+                self.notifierShow(
+                    'Bitmessage',
+                    _translate("MainWindow", "Connected"),
+                    self.SOUND_CONNECTION_GREEN)
             self.connected = True
 
             if self.actionStatus is not None:
@@ -2253,8 +2237,14 @@ class MyForm(settingsmixin.SMainWindow):
         else:
             acct = ret
         self.propagateUnreadCount(acct.address)
-        if BMConfigParser().getboolean('bitmessagesettings', 'showtraynotifications'):
-            self.notifierShow(unicode(_translate("MainWindow",'New Message').toUtf8(),'utf-8'), unicode(_translate("MainWindow",'From ').toUtf8(),'utf-8') + unicode(acct.fromLabel, 'utf-8'), self.SOUND_UNKNOWN, None)
+        if BMConfigParser().getboolean(
+                'bitmessagesettings', 'showtraynotifications'):
+            self.notifierShow(
+                _translate("MainWindow", "New Message"),
+                _translate("MainWindow", "From %1").arg(
+                    unicode(acct.fromLabel, 'utf-8')),
+                self.SOUND_UNKNOWN
+            )
         if self.getCurrentAccount() is not None and ((self.getCurrentFolder(treeWidget) != "inbox" and self.getCurrentFolder(treeWidget) is not None) or self.getCurrentAccount(treeWidget) != acct.address):
             # Ubuntu should notify of new message irespective of whether it's in current message list or not
             self.ubuntuMessagingMenuUpdate(True, None, acct.toLabel)
diff --git a/src/plugins/notification_notify2.py b/src/plugins/notification_notify2.py
new file mode 100644
index 00000000..90f09df3
--- /dev/null
+++ b/src/plugins/notification_notify2.py
@@ -0,0 +1,13 @@
+# -*- coding: utf-8 -*-
+
+import gi
+gi.require_version('Notify', '0.7')
+from gi.repository import Notify
+
+Notify.init('pybitmessage')
+
+
+def connect_plugin(title, subtitle, category, label, icon):
+    if not icon:
+        icon = 'mail-message-new' if category == 2 else 'pybitmessage'
+    Notify.Notification.new(title, subtitle, icon).show()
diff --git a/src/plugins/plugin.py b/src/plugins/plugin.py
index 288be48a..395f0e9d 100644
--- a/src/plugins/plugin.py
+++ b/src/plugins/plugin.py
@@ -3,10 +3,12 @@
 import pkg_resources
 
 
-def get_plugins(group, point, name=None):
+def get_plugins(group, point='', name=None):
     for plugin in pkg_resources.iter_entry_points(group):
-        if plugin.name.startswith(point):
+        if plugin.name == name or plugin.name.startswith(point):
             try:
                 yield plugin.load().connect_plugin
-            except (AttributeError, pkg_resources.DistributionNotFound):
+            except (AttributeError,
+                    pkg_resources.DistributionNotFound,
+                    pkg_resources.UnknownExtra):
                 continue
diff --git a/src/plugins/sound_playfile.py b/src/plugins/sound_playfile.py
new file mode 100644
index 00000000..03a164af
--- /dev/null
+++ b/src/plugins/sound_playfile.py
@@ -0,0 +1,37 @@
+# -*- coding: utf-8 -*-
+
+
+try:
+    import winsound
+
+    def connect_plugin(sound_file):
+        winsound.PlaySound(sound_file, winsound.SND_FILENAME)
+except ImportError:
+    import os
+    import subprocess
+
+    play_cmd = {}
+
+    def connect_plugin(sound_file):
+        global play_cmd
+
+        ext = os.path.splitext(sound_file)[-1]
+        try:
+            subprocess.call([play_cmd[ext], sound_file])
+            return
+        except (KeyError, AttributeError):
+            pass
+
+        programs = ['gst123']
+        if ext == '.wav':
+            programs.append('aplay')
+        elif ext == '.mp3':
+            programs += ['mpg123', 'mpg321', 'mpg321-mpg123']
+        for cmd in programs:
+            try:
+                subprocess.call([cmd, sound_file])
+            except OSError:
+                pass  # log here!
+            else:
+                play_cmd[ext] = cmd
+                break