diff --git a/desktop/flow-typed/npm/electron_v22.0.0.js b/desktop/flow-typed/npm/electron_v22.0.0.js
--- a/desktop/flow-typed/npm/electron_v22.0.0.js
+++ b/desktop/flow-typed/npm/electron_v22.0.0.js
@@ -297,6 +297,75 @@
     ): Menu;
   }
 
+  declare export var pushNotifications: PushNotifications;
+  declare class PushNotifications {
+    registerForAPNSNotifications(): Promise<string>;
+    unregisterForAPNSNotifications(): void;
+
+    on<T: $Keys<PushNotificationsEvents>>(
+      event: T,
+      listener: $ElementType<PushNotificationsEvents, T>,
+    ): void;
+    removeListener<T: $Keys<PushNotificationsEvents>>(
+      event: T,
+      listener: $ElementType<PushNotificationsEvents, T>,
+    ): void;
+  }
+  declare type PushNotificationsEvents = {
+    'received-apns-notification': (
+      event: Event,
+      userInfo: { +[string]: mixed },
+    ) => void,
+  };
+
+  declare export class Notification {
+    constructor(options?: {
+      +title?: string,
+      +subtitle?: string,
+      +body?: string,
+      +silent?: boolean,
+      +hasReply?: boolean,
+      +timeoutType?: 'default' | 'never',
+      +replyPlaceholder?: string,
+      +sound?: string,
+      +urgency?: 'normal' | 'critical' | 'low',
+      +actions?: NotificationAction[],
+      +closeButtonText?: string,
+      +toastXml?: string,
+    }): void;
+    static isSupported(): boolean;
+    show(): void;
+    close(): void;
+    +title: string;
+    +subtitle: string;
+    +body: string;
+    +replyPlaceholder: string;
+    +sound: string;
+    +closeButtonText: string;
+    +silent: boolean;
+    +hasReply: boolean;
+    +urgency: 'normal' | 'critical' | 'low';
+    +timeoutType: 'default' | 'never';
+    +toastXml: string;
+
+    on<T: $Keys<NotificationEvents>>(
+      event: T,
+      listener: $ElementType<NotificationEvents, T>,
+    ): void;
+    removeListener<T: $Keys<NotificationEvents>>(
+      event: T,
+      listener: $ElementType<NotificationEvents, T>,
+    ): void;
+  }
+  declare type NotificationEvents = {
+    show: (event: Event) => void,
+    click: (event: Event) => void,
+    close: (event: Event) => void,
+    reply: (event: Event, reply: string) => void,
+    action: (event: Event, index: number) => void,
+    failed: (event: Event, error: string) => void,
+  };
+
   declare export var shell: Shell;
   declare type Shell = {
     openExternal(
@@ -402,6 +471,8 @@
     ipcMain,
     systemPreferences,
     autoUpdater,
+    pushNotifications,
+    Notification,
   } from 'electron';
 }
 
diff --git a/desktop/src/main.js b/desktop/src/main.js
--- a/desktop/src/main.js
+++ b/desktop/src/main.js
@@ -15,6 +15,10 @@
 
 import { initAutoUpdate } from './auto-update.js';
 import { handleSquirrelEvent } from './handle-squirrel-event.js';
+import {
+  listenForNotifications,
+  registerForNotifications,
+} from './push-notifications.js';
 
 const isDev = process.env.ENV === 'dev';
 const url = isDev ? 'http://localhost:3000/comm/' : 'https://web.comm.app';
@@ -83,7 +87,8 @@
   Menu.setApplicationMenu(menu);
 };
 
-const createMainWindow = () => {
+let mainWindow = null;
+const createMainWindow = (urlPath?: string) => {
   const win = new BrowserWindow({
     show: false,
     width: 1300,
@@ -149,6 +154,7 @@
   autoUpdater.on('update-downloaded', updateDownloaded);
 
   win.on('closed', () => {
+    mainWindow = null;
     ipcMain.removeListener('clear-history', clearHistory);
     ipcMain.removeListener('double-click-top-bar', doubleClickTopBar);
     autoUpdater.removeListener('update-downloaded', updateDownloaded);
@@ -165,7 +171,8 @@
     win.webContents.insertCSS(css);
   })();
 
-  win.loadURL(url);
+  win.loadURL(url + (urlPath ?? ''));
+  mainWindow = win;
 
   return win;
 };
@@ -204,10 +211,10 @@
   return win;
 };
 
-const show = () => {
+const show = (urlPath?: string) => {
   const splash = createSplashWindow();
   const error = createErrorWindow();
-  const main = createMainWindow();
+  const main = createMainWindow(urlPath);
 
   let loadedSuccessfully = true;
   main.webContents.on('did-fail-load', () => {
@@ -235,6 +242,13 @@
       }
 
       main.show();
+
+      if (app.isPackaged) {
+        (async () => {
+          const token = await registerForNotifications();
+          main.webContents.send('on-device-token-registered', token);
+        })();
+      }
     }
   });
 };
@@ -252,6 +266,15 @@
       } catch (error) {
         console.error(error);
       }
+      listenForNotifications(threadID => {
+        if (mainWindow) {
+          mainWindow.webContents.send('on-notification-clicked', {
+            threadID,
+          });
+        } else {
+          show(`chat/thread/${threadID}/`);
+        }
+      });
     }
 
     ipcMain.on('set-badge', (event, value) => {
diff --git a/desktop/src/push-notifications.js b/desktop/src/push-notifications.js
new file mode 100644
--- /dev/null
+++ b/desktop/src/push-notifications.js
@@ -0,0 +1,49 @@
+// @flow
+
+// eslint-disable-next-line import/extensions
+import { pushNotifications, Notification } from 'electron/main';
+
+async function registerForNotifications(): Promise<?string> {
+  if (process.platform !== 'darwin') {
+    return null;
+  }
+
+  try {
+    const token = await pushNotifications.registerForAPNSNotifications();
+    return token;
+  } catch (err) {
+    console.error(err);
+  }
+
+  return null;
+}
+
+function listenForNotifications(handleClick: (threadID: string) => void) {
+  if (process.platform !== 'darwin') {
+    return;
+  }
+  pushNotifications.on('received-apns-notification', (event, userInfo) => {
+    if (
+      typeof userInfo.title !== 'string' ||
+      typeof userInfo.body !== 'string' ||
+      typeof userInfo.threadID !== 'string'
+    ) {
+      console.error(
+        'Notification must contain a string title, body and threadID',
+      );
+      return;
+    }
+    const { title, body, threadID } = userInfo;
+
+    const notif = new Notification({
+      title,
+      body,
+    });
+    notif.on('click', () => {
+      handleClick(threadID);
+    });
+    notif.show();
+  });
+}
+
+export { listenForNotifications, registerForNotifications };