diff --git a/src/ONYXKEYS.js b/src/ONYXKEYS.js
index 0d27ba64dedf..468178fb15ec 100755
--- a/src/ONYXKEYS.js
+++ b/src/ONYXKEYS.js
@@ -239,6 +239,14 @@ export default {
     // Experimental memory only Onyx mode flag
     IS_USING_MEMORY_ONLY_KEYS: 'isUsingMemoryOnlyKeys',
 
+    ONYX_UPDATES: {
+        // The ID of the last Onyx update that was applied to this client
+        LAST_UPDATE_ID: 'onyxUpdatesLastUpdateID',
+
+        // The ID of the previous Onyx update that was applied to this client
+        PREVIOUS_UPDATE_ID: 'onyxUpdatesPreviousUpdateID',
+    },
+
     // Manual request tab selector
     SELECTED_TAB: 'selectedTab',
 
diff --git a/src/libs/Navigation/AppNavigator/AuthScreens.js b/src/libs/Navigation/AppNavigator/AuthScreens.js
index 8fca9aea495c..ddd61da272e6 100644
--- a/src/libs/Navigation/AppNavigator/AuthScreens.js
+++ b/src/libs/Navigation/AppNavigator/AuthScreens.js
@@ -96,6 +96,9 @@ const propTypes = {
     /** Opt-in experimental mode that prevents certain Onyx keys from persisting to disk */
     isUsingMemoryOnlyKeys: PropTypes.bool,
 
+    /** The last Onyx update ID that is stored in Onyx (used for getting incremental updates when reconnecting) */
+    onyxUpdatesLastUpdateID: PropTypes.number,
+
     ...windowDimensionsPropTypes,
 };
 
@@ -105,6 +108,7 @@ const defaultProps = {
         email: null,
     },
     lastOpenedPublicRoomID: null,
+    onyxUpdatesLastUpdateID: 0,
 };
 
 class AuthScreens extends React.Component {
@@ -116,7 +120,7 @@ class AuthScreens extends React.Component {
 
     componentDidMount() {
         NetworkConnection.listenForReconnect();
-        NetworkConnection.onReconnect(() => App.reconnectApp());
+        NetworkConnection.onReconnect(() => App.reconnectApp(this.props.onyxUpdatesLastUpdateID));
         PusherConnectionManager.init();
         Pusher.init({
             appKey: CONFIG.PUSHER.APP_KEY,
@@ -127,14 +131,16 @@ class AuthScreens extends React.Component {
         });
 
         // If we are on this screen then we are "logged in", but the user might not have "just logged in". They could be reopening the app
-        // or returning from background. If so, we'll assume they have some app data already and we can call reconnectApp() instead of openApp().
+        // or returning from background. If so, we'll assume they have some app data already and we can call
+        // reconnectApp(onyxUpdatesLastUpdateID) instead of openApp().
         // Note: If a Guide has enabled the memory only key mode then we do want to run OpenApp as their app will not be rehydrated with
         // the correct state on refresh. They are explicitly opting out of storing data they would need (i.e. reports_) to take advantage of
         // the optimizations performed during ReconnectApp.
-        if (this.props.isUsingMemoryOnlyKeys || SessionUtils.didUserLogInDuringSession()) {
+        const shouldGetAllData = this.props.isUsingMemoryOnlyKeys || SessionUtils.didUserLogInDuringSession();
+        if (shouldGetAllData) {
             App.openApp();
         } else {
-            App.reconnectApp();
+            App.reconnectApp(this.props.onyxUpdatesLastUpdateID);
         }
 
         App.setUpPoliciesAndNavigate(this.props.session, !this.props.isSmallScreenWidth);
@@ -323,5 +329,8 @@ export default compose(
         isUsingMemoryOnlyKeys: {
             key: ONYXKEYS.IS_USING_MEMORY_ONLY_KEYS,
         },
+        onyxUpdatesLastUpdateID: {
+            key: ONYXKEYS.ONYX_UPDATES.LAST_UPDATE_ID,
+        },
     }),
 )(AuthScreens);
diff --git a/src/libs/actions/App.js b/src/libs/actions/App.js
index 925f64bb85e1..ca03380368c2 100644
--- a/src/libs/actions/App.js
+++ b/src/libs/actions/App.js
@@ -122,63 +122,94 @@ AppState.addEventListener('change', (nextAppState) => {
 });
 
 /**
- * Fetches data needed for app initialization
- * @param {boolean} [isReconnecting]
+ * Gets the policy params that are passed to the server in the OpenApp and ReconnectApp API commands. This includes a full list of policy IDs the client knows about as well as when they were last modified.
+ * @returns {Promise}
  */
-function openApp(isReconnecting = false) {
-    isReadyToOpenApp.then(() => {
-        const connectionID = Onyx.connect({
-            key: ONYXKEYS.COLLECTION.POLICY,
-            waitForCollectionCallback: true,
-            callback: (policies) => {
-                // When the app reconnects we do a fast "sync" of the LHN and only return chats that have new messages. We achieve this by sending the most recent reportActionID.
-                // we have locally. And then only update the user about chats with messages that have occurred after that reportActionID.
-                //
-                // - Look through the local report actions and reports to find the most recently modified report action or report.
-                // - We send this to the server so that it can compute which new chats the user needs to see and return only those as an optimization.
-                const params = {policyIDToLastModified: JSON.stringify(getNonOptimisticPolicyIDToLastModifiedMap(policies))};
-                if (isReconnecting) {
-                    Timing.start(CONST.TIMING.CALCULATE_MOST_RECENT_LAST_MODIFIED_ACTION);
-                    params.mostRecentReportActionLastModified = ReportActionsUtils.getMostRecentReportActionLastModified();
-                    Timing.end(CONST.TIMING.CALCULATE_MOST_RECENT_LAST_MODIFIED_ACTION, '', 500);
-                }
-                Onyx.disconnect(connectionID);
-
-                // eslint-disable-next-line rulesdir/no-multiple-api-calls
-                const apiMethod = isReconnecting ? API.write : API.read;
-                apiMethod(isReconnecting ? 'ReconnectApp' : 'OpenApp', params, {
-                    optimisticData: [
-                        {
-                            onyxMethod: Onyx.METHOD.MERGE,
-                            key: ONYXKEYS.IS_LOADING_REPORT_DATA,
-                            value: true,
-                        },
-                    ],
-                    successData: [
-                        {
-                            onyxMethod: Onyx.METHOD.MERGE,
-                            key: ONYXKEYS.IS_LOADING_REPORT_DATA,
-                            value: false,
-                        },
-                    ],
-                    failureData: [
-                        {
-                            onyxMethod: Onyx.METHOD.MERGE,
-                            key: ONYXKEYS.IS_LOADING_REPORT_DATA,
-                            value: false,
-                        },
-                    ],
-                });
-            },
+function getPolicyParamsForOpenOrReconnect() {
+    return new Promise((resolve) => {
+        isReadyToOpenApp.then(() => {
+            const connectionID = Onyx.connect({
+                key: ONYXKEYS.COLLECTION.POLICY,
+                waitForCollectionCallback: true,
+                callback: (policies) => {
+                    Onyx.disconnect(connectionID);
+                    resolve({policyIDToLastModified: JSON.stringify(getNonOptimisticPolicyIDToLastModifiedMap(policies))});
+                },
+            });
         });
     });
 }
 
 /**
- * Refreshes data when the app reconnects
+ * Returns the Onyx data that is used for both the OpenApp and ReconnectApp API commands.
+ * @returns {Object}
+ */
+function getOnyxDataForOpenOrReconnect() {
+    return {
+        optimisticData: [
+            {
+                onyxMethod: Onyx.METHOD.MERGE,
+                key: ONYXKEYS.IS_LOADING_REPORT_DATA,
+                value: true,
+            },
+        ],
+        successData: [
+            {
+                onyxMethod: Onyx.METHOD.MERGE,
+                key: ONYXKEYS.IS_LOADING_REPORT_DATA,
+                value: false,
+            },
+        ],
+        failureData: [
+            {
+                onyxMethod: Onyx.METHOD.MERGE,
+                key: ONYXKEYS.IS_LOADING_REPORT_DATA,
+                value: false,
+            },
+        ],
+    };
+}
+
+/**
+ * Fetches data needed for app initialization
+ */
+function openApp() {
+    getPolicyParamsForOpenOrReconnect().then((policyParams) => {
+        API.read('OpenApp', policyParams, getOnyxDataForOpenOrReconnect());
+    });
+}
+
+/**
+ * Fetches data when the app reconnects to the network
+ * @param {Number} [updateIDFrom] the ID of the Onyx update that we want to start fetching from
+ * @param {Number} [updateIDTo] the ID of the Onyx update that we want to fetch up to
  */
-function reconnectApp() {
-    openApp(true);
+function reconnectApp(updateIDFrom = 0, updateIDTo = 0) {
+    console.debug(`[OnyxUpdates] App reconnecting with updateIDFrom: ${updateIDFrom} and updateIDTo: ${updateIDTo}`);
+    getPolicyParamsForOpenOrReconnect().then((policyParams) => {
+        const params = {...policyParams};
+
+        // When the app reconnects we do a fast "sync" of the LHN and only return chats that have new messages. We achieve this by sending the most recent reportActionID.
+        // we have locally. And then only update the user about chats with messages that have occurred after that reportActionID.
+        //
+        // - Look through the local report actions and reports to find the most recently modified report action or report.
+        // - We send this to the server so that it can compute which new chats the user needs to see and return only those as an optimization.
+        Timing.start(CONST.TIMING.CALCULATE_MOST_RECENT_LAST_MODIFIED_ACTION);
+        params.mostRecentReportActionLastModified = ReportActionsUtils.getMostRecentReportActionLastModified();
+        Timing.end(CONST.TIMING.CALCULATE_MOST_RECENT_LAST_MODIFIED_ACTION, '', 500);
+
+        // Include the update IDs when reconnecting so that the server can send incremental updates if they are available.
+        // Otherwise, a full set of app data will be returned.
+        if (updateIDFrom) {
+            params.updateIDFrom = updateIDFrom;
+        }
+
+        if (updateIDTo) {
+            params.updateIDTo = updateIDTo;
+        }
+
+        API.write('ReconnectApp', params, getOnyxDataForOpenOrReconnect());
+    });
 }
 
 /**
diff --git a/src/libs/actions/User.js b/src/libs/actions/User.js
index f6652bdf8fa2..d07cb23c039c 100644
--- a/src/libs/actions/User.js
+++ b/src/libs/actions/User.js
@@ -552,9 +552,22 @@ function subscribeToUserEvents() {
         if (_.isArray(pushJSON)) {
             updates = pushJSON;
         } else {
-            // const lastUpdateID = pushJSON.lastUpdateID;
-            // const previousUpdateID = pushJSON.previousUpdateID;
             updates = pushJSON.updates;
+
+            // Not always we'll have the lastUpdateID and previousUpdateID properties in the pusher update
+            // until we finish the migration to reliable updates. So let's check it before actually updating
+            // the properties in Onyx
+            if (pushJSON.lastUpdateID && pushJSON.previousUpdateID) {
+                console.debug('[OnyxUpdates] Received lastUpdateID from pusher', pushJSON.lastUpdateID);
+                console.debug('[OnyxUpdates] Received previousUpdateID from pusher', pushJSON.previousUpdateID);
+                // Store these values in Onyx to allow App.reconnectApp() to fetch incremental updates from the server when a previous session is being reconnected to.
+                Onyx.multiSet({
+                    [ONYXKEYS.ONYX_UPDATES.LAST_UPDATE_ID]: Number(pushJSON.lastUpdateID || 0),
+                    [ONYXKEYS.ONYX_UPDATES.PREVIOUS_UPDATE_ID]: Number(pushJSON.previousUpdateID || 0),
+                });
+            } else {
+                console.debug('[OnyxUpdates] No lastUpdateID and previousUpdateID provided');
+            }
         }
         _.each(updates, (multipleEvent) => {
             PusherUtils.triggerMultiEventHandler(multipleEvent.eventType, multipleEvent.data);
