diff --git a/lib/push/send-hooks.react.js b/lib/push/send-hooks.react.js
--- a/lib/push/send-hooks.react.js
+++ b/lib/push/send-hooks.react.js
@@ -1,6 +1,7 @@
 // @flow
 
 import * as React from 'react';
+import uuid from 'uuid';
 
 import {
   preparePushNotifs,
@@ -10,36 +11,111 @@
 import { NeynarClientContext } from '../components/neynar-client-provider.react.js';
 import { usePeerOlmSessionsCreatorContext } from '../components/peer-olm-session-creator-provider.react.js';
 import { thickRawThreadInfosSelector } from '../selectors/thread-selectors.js';
-import type { MessageData } from '../types/message-types.js';
+import { useTunnelbroker } from '../tunnelbroker/tunnelbroker-context.js';
 import type {
-  EncryptedNotifUtilsAPI,
-  SenderDeviceDescriptor,
+  TargetedAPNsNotification,
+  TargetedAndroidNotification,
+  TargetedWebNotification,
+  TargetedWNSNotification,
+  NotificationsCreationData,
 } from '../types/notif-types.js';
+import { deviceToTunnelbrokerMessageTypes } from '../types/tunnelbroker/messages.js';
+import type {
+  TunnelbrokerAPNsNotif,
+  TunnelbrokerFCMNotif,
+  TunnelbrokerWebPushNotif,
+  TunnelbrokerWNSNotif,
+} from '../types/tunnelbroker/notif-types.js';
+import { getConfig } from '../utils/config.js';
+import { getContentSigningKey } from '../utils/crypto-utils.js';
+import { getMessageForException } from '../utils/errors.js';
 import { useSelector } from '../utils/redux-utils.js';
 
-function usePreparePushNotifs(): (
-  encryptedNotifsUtilsAPI: EncryptedNotifUtilsAPI,
-  senderDeviceDescriptor: SenderDeviceDescriptor,
-  messageDatas: $ReadOnlyArray<MessageData>,
+function apnsNotifToTunnelbrokerAPNsNotif(
+  targetedNotification: TargetedAPNsNotification,
+): TunnelbrokerAPNsNotif {
+  const {
+    deliveryID: deviceID,
+    notification: { headers, ...payload },
+  } = targetedNotification;
+
+  const newHeaders = {
+    ...headers,
+    'apns-push-type': 'Alert',
+  };
+
+  return {
+    type: deviceToTunnelbrokerMessageTypes.TUNNELBROKER_APNS_NOTIF,
+    deviceID,
+    headers: JSON.stringify(newHeaders),
+    payload: JSON.stringify(payload),
+    clientMessageID: uuid.v4(),
+  };
+}
+
+function androidNotifToTunnelbrokerFCMNotif(
+  targetedNotification: TargetedAndroidNotification,
+): TunnelbrokerFCMNotif {
+  const {
+    deliveryID: deviceID,
+    notification: { data },
+    priority,
+  } = targetedNotification;
+
+  return {
+    type: deviceToTunnelbrokerMessageTypes.TUNNELBROKER_FCM_NOTIF,
+    deviceID,
+    clientMessageID: uuid.v4(),
+    data: JSON.stringify(data),
+    priority: priority === 'normal' ? 'NORMAL' : 'HIGH',
+  };
+}
+
+function webNotifToTunnelbrokerWebPushNotif(
+  targetedNotification: TargetedWebNotification,
+): TunnelbrokerWebPushNotif {
+  const { deliveryID: deviceID, notification } = targetedNotification;
+  return {
+    type: deviceToTunnelbrokerMessageTypes.TUNNELBROKER_WEB_PUSH_NOTIF,
+    deviceID,
+    clientMessageID: uuid.v4(),
+    payload: JSON.stringify(notification),
+  };
+}
+
+function wnsNotifToTunnelbrokerWNSNofif(
+  targetedNotification: TargetedWNSNotification,
+): TunnelbrokerWNSNotif {
+  const { deliveryID: deviceID, notification } = targetedNotification;
+  return {
+    type: deviceToTunnelbrokerMessageTypes.TUNNELBROKER_WNS_NOTIF,
+    deviceID,
+    clientMessageID: uuid.v4(),
+    payload: JSON.stringify(notification),
+  };
+}
+
+function useSendPushNotifs(): (
+  notifCreationData: NotificationsCreationData,
 ) => Promise<?PerUserTargetedNotifications> {
   const rawMessageInfos = useSelector(state => state.messageStore.messages);
   const thickRawThreadInfos = useSelector(thickRawThreadInfosSelector);
   const auxUserInfos = useSelector(state => state.auxUserStore.auxUserInfos);
   const userInfos = useSelector(state => state.userStore.userInfos);
-
   const { getENSNames } = React.useContext(ENSCacheContext);
   const getFCNames = React.useContext(NeynarClientContext)?.getFCNames;
-
   const { createOlmSessionsWithPeer: olmSessionCreator } =
     usePeerOlmSessionsCreatorContext();
+  const { sendNotif } = useTunnelbroker();
+  const { encryptedNotifUtilsAPI } = getConfig();
 
   return React.useCallback(
-    (
-      encryptedNotifUtilsAPI: EncryptedNotifUtilsAPI,
-      senderDeviceDescriptor: SenderDeviceDescriptor,
-      messageDatas: $ReadOnlyArray<MessageData>,
-    ) => {
-      return preparePushNotifs({
+    async (notifCreationData: NotificationsCreationData) => {
+      const deviceID = await getContentSigningKey();
+      const senderDeviceDescriptor = { senderDeviceID: deviceID };
+      const { messageDatas } = notifCreationData;
+
+      const pushNotifsPreparationInput = {
         encryptedNotifUtilsAPI,
         senderDeviceDescriptor,
         olmSessionCreator,
@@ -50,9 +126,65 @@
         userInfos,
         getENSNames,
         getFCNames,
-      });
+      };
+
+      const preparedPushNotifs = await preparePushNotifs(
+        pushNotifsPreparationInput,
+      );
+
+      if (!preparedPushNotifs) {
+        return;
+      }
+
+      const sendPromises = [];
+      for (const userID in preparedPushNotifs) {
+        for (const notif of preparedPushNotifs[userID]) {
+          if (notif.targetedNotification.notification.encryptionFailed) {
+            continue;
+          }
+
+          let tunnelbrokerNotif;
+          if (notif.platform === 'ios' || notif.platform === 'macos') {
+            tunnelbrokerNotif = apnsNotifToTunnelbrokerAPNsNotif(
+              notif.targetedNotification,
+            );
+          } else if (notif.platform === 'android') {
+            tunnelbrokerNotif = androidNotifToTunnelbrokerFCMNotif(
+              notif.targetedNotification,
+            );
+          } else if (notif.platform === 'web') {
+            tunnelbrokerNotif = webNotifToTunnelbrokerWebPushNotif(
+              notif.targetedNotification,
+            );
+          } else if (notif.platform === 'windows') {
+            tunnelbrokerNotif = wnsNotifToTunnelbrokerWNSNofif(
+              notif.targetedNotification,
+            );
+          } else {
+            continue;
+          }
+
+          sendPromises.push(
+            (async () => {
+              try {
+                await sendNotif(tunnelbrokerNotif);
+              } catch (e) {
+                console.log(
+                  `Failed to send notification to device: ${
+                    tunnelbrokerNotif.deviceID
+                  }. Details: ${getMessageForException(e) ?? ''}`,
+                );
+              }
+            })(),
+          );
+        }
+      }
+
+      await Promise.all(sendPromises);
     },
     [
+      sendNotif,
+      encryptedNotifUtilsAPI,
       olmSessionCreator,
       rawMessageInfos,
       thickRawThreadInfos,
@@ -64,4 +196,4 @@
   );
 }
 
-export { usePreparePushNotifs };
+export { useSendPushNotifs };
diff --git a/lib/types/notif-types.js b/lib/types/notif-types.js
--- a/lib/types/notif-types.js
+++ b/lib/types/notif-types.js
@@ -3,7 +3,7 @@
 import type { EncryptResult } from '@commapp/olm';
 import t, { type TInterface, type TUnion } from 'tcomb';
 
-import type { Platform } from './device-types.js';
+import type { MessageData } from './message-types.js';
 import type { EntityText, ThreadEntity } from '../utils/entity-text.js';
 import { tShape } from '../utils/validation-utils.js';
 
@@ -28,6 +28,10 @@
     prefix: t.maybe(t.String),
   });
 
+export type NotificationsCreationData = {
+  +messageDatas: $ReadOnlyArray<MessageData>,
+};
+
 export type SenderDeviceDescriptor =
   | { +keyserverID: string }
   | { +senderDeviceID: string };
@@ -373,14 +377,14 @@
   +blobHolder?: string,
 };
 
-export type TargetedNotificationWithPlatform = {
-  +platform: Platform,
-  +targetedNotification:
-    | TargetedAPNsNotification
-    | TargetedWNSNotification
-    | TargetedWebNotification
-    | TargetedAndroidNotification,
-};
+export type TargetedNotificationWithPlatform =
+  | {
+      +platform: 'ios' | 'macos',
+      +targetedNotification: TargetedAPNsNotification,
+    }
+  | { +platform: 'android', +targetedNotification: TargetedAndroidNotification }
+  | { +platform: 'web', +targetedNotification: TargetedWebNotification }
+  | { +platform: 'windows', +targetedNotification: TargetedWNSNotification };
 
 export type EncryptedNotifUtilsAPI = {
   +encryptSerializedNotifPayload: (
diff --git a/lib/types/tunnelbroker/messages.js b/lib/types/tunnelbroker/messages.js
--- a/lib/types/tunnelbroker/messages.js
+++ b/lib/types/tunnelbroker/messages.js
@@ -45,6 +45,8 @@
   ANONYMOUS_INITIALIZATION_MESSAGE: 'AnonymousInitializationMessage',
   TUNNELBROKER_APNS_NOTIF: 'APNsNotif',
   TUNNELBROKER_FCM_NOTIF: 'FCMNotif',
+  TUNNELBROKER_WEB_PUSH_NOTIF: 'WebPushNotif',
+  TUNNELBROKER_WNS_NOTIF: 'WNSNotif',
   MESSAGE_TO_DEVICE_REQUEST: 'MessageToDeviceRequest',
   MESSAGE_RECEIVE_CONFIRMATION: 'MessageReceiveConfirmation',
   MESSAGE_TO_TUNNELBROKER_REQUEST: 'MessageToTunnelbrokerRequest',
diff --git a/lib/utils/__mocks__/config.js b/lib/utils/__mocks__/config.js
--- a/lib/utils/__mocks__/config.js
+++ b/lib/utils/__mocks__/config.js
@@ -46,6 +46,12 @@
     searchMessages: jest.fn(),
     fetchMessages: jest.fn(),
   },
+  encryptedNotifUtilsAPI: {
+    encryptSerializedNotifPayload: jest.fn(),
+    uploadLargeNotifPayload: jest.fn(),
+    getEncryptedNotifHash: jest.fn(),
+    getNotifByteSize: jest.fn(),
+  },
 });
 
 const hasConfig = (): boolean => true;
diff --git a/lib/utils/config.js b/lib/utils/config.js
--- a/lib/utils/config.js
+++ b/lib/utils/config.js
@@ -8,6 +8,7 @@
 import type { RecoveryActionSource } from '../types/account-types.js';
 import type { OlmAPI } from '../types/crypto-types.js';
 import type { PlatformDetails } from '../types/device-types.js';
+import type { EncryptedNotifUtilsAPI } from '../types/notif-types.js';
 import type { SQLiteAPI } from '../types/sqlite-types.js';
 import type { DispatchActionPromise } from '../utils/redux-promise-utils.js';
 
@@ -29,6 +30,7 @@
   +authoritativeKeyserverID: string,
   +olmAPI: OlmAPI,
   +sqliteAPI: SQLiteAPI,
+  +encryptedNotifUtilsAPI: EncryptedNotifUtilsAPI,
 };
 
 let registeredConfig: ?Config = null;
diff --git a/native/config.js b/native/config.js
--- a/native/config.js
+++ b/native/config.js
@@ -8,6 +8,7 @@
 import { authoritativeKeyserverID } from './authoritative-keyserver.js';
 import { olmAPI } from './crypto/olm-api.js';
 import { sqliteAPI } from './database/sqlite-api.js';
+import encryptedNotifUtilsAPI from './push/encrypted-notif-utils-api.js';
 import { persistConfig, codeVersion } from './redux/persist.js';
 
 registerConfig({
@@ -22,4 +23,5 @@
   authoritativeKeyserverID,
   olmAPI,
   sqliteAPI,
+  encryptedNotifUtilsAPI,
 });
diff --git a/web/app.react.js b/web/app.react.js
--- a/web/app.react.js
+++ b/web/app.react.js
@@ -70,6 +70,7 @@
 import SettingsSwitcher from './navigation-panels/settings-switcher.react.js';
 import Topbar from './navigation-panels/topbar.react.js';
 import useBadgeHandler from './push-notif/badge-handler.react.js';
+import encryptedNotifUtilsAPI from './push-notif/encrypted-notif-utils-api.js';
 import { PushNotificationsHandler } from './push-notif/push-notifs-handler.js';
 import { updateNavInfoActionType } from './redux/action-types.js';
 import DisconnectedBar from './redux/disconnected-bar.js';
@@ -124,6 +125,7 @@
   authoritativeKeyserverID,
   olmAPI,
   sqliteAPI,
+  encryptedNotifUtilsAPI,
 });
 
 const versionBroadcast = new BroadcastChannel('comm_version');