diff --git a/module/VuFind/src/VuFind/Controller/MyResearchController.php b/module/VuFind/src/VuFind/Controller/MyResearchController.php
index e36aa26ab2bc473f249b8a74c61499095068ea04..65cf6f92243e3f1c6ac16b5ed22e8812f7b94cf0 100644
--- a/module/VuFind/src/VuFind/Controller/MyResearchController.php
+++ b/module/VuFind/src/VuFind/Controller/MyResearchController.php
@@ -1199,7 +1199,9 @@ class MyResearchController extends AbstractBase
             // Do nothing; if we're unable to load information about pickup
             // locations, they are not supported and we should ignore them.
         }
+
         $view->recordList = $recordList;
+        $view->accountStatus = $this->collectRequestAccountStats($recordList);
         return $view;
     }
 
@@ -1264,7 +1266,9 @@ class MyResearchController extends AbstractBase
             // Do nothing; if we're unable to load information about pickup
             // locations, they are not supported and we should ignore them.
         }
+
         $view->recordList = $recordList;
+        $view->accountStatus = $this->collectRequestAccountStats($recordList);
         return $view;
     }
 
@@ -1323,6 +1327,7 @@ class MyResearchController extends AbstractBase
         }
 
         $view->recordList = $recordList;
+        $view->accountStatus = $this->collectRequestAccountStats($recordList);
         return $view;
     }
 
@@ -1379,6 +1384,20 @@ class MyResearchController extends AbstractBase
             $pageEnd = $result['count'];
         }
 
+        // If the results are not paged in the ILS, collect up to date stats for ajax
+        // account notifications:
+        if ((!$pageOptions['ilsPaging'] || !$paginator)
+            && !empty($this->getConfig()->Authentication->enableAjax)
+        ) {
+            $accountStatus = [
+                'ok' => 0,
+                'warn' => 0,
+                'overdue' => 0
+            ];
+        } else {
+            $accountStatus = null;
+        }
+
         $transactions = $hiddenTransactions = [];
         foreach ($result['records'] as $i => $current) {
             // Add renewal details if appropriate:
@@ -1392,6 +1411,20 @@ class MyResearchController extends AbstractBase
                 $renewForm = true;
             }
 
+            if (null !== $accountStatus) {
+                switch ($current['dueStatus'] ?? '') {
+                case 'due':
+                    $accountStatus['warn']++;
+                    break;
+                case 'overdue':
+                    $accountStatus['overdue']++;
+                    break;
+                default:
+                    $accountStatus['ok']++;
+                    break;
+                }
+            }
+
             // Build record driver (only for the current visible page):
             if ($pageOptions['ilsPaging'] || ($i >= $pageStart && $i <= $pageEnd)) {
                 $transactions[] = $this->getDriverForILSRecord($current);
@@ -1409,7 +1442,8 @@ class MyResearchController extends AbstractBase
         return $this->createViewModel(
             compact(
                 'transactions', 'renewForm', 'renewResult', 'paginator', 'ilsPaging',
-                'hiddenTransactions', 'displayItemBarcode', 'sortList', 'params'
+                'hiddenTransactions', 'displayItemBarcode', 'sortList', 'params',
+                'accountStatus'
             )
         );
     }
@@ -1505,6 +1539,7 @@ class MyResearchController extends AbstractBase
         // Get fine details:
         $result = $catalog->getMyFines($patron);
         $fines = [];
+        $totalDue = 0;
         foreach ($result as $row) {
             // Attempt to look up and inject title:
             try {
@@ -1524,10 +1559,20 @@ class MyResearchController extends AbstractBase
             if (!isset($row['title'])) {
                 $row['title'] = null;
             }
+            $totalDue += $row['balance'] ?? 0;
             $fines[] = $row;
         }
 
-        return $this->createViewModel(['fines' => $fines]);
+        // Collect up to date stats for ajax account notifications:
+        if (!empty($this->getConfig()->Authentication->enableAjax)) {
+            $accountStatus = [
+                'total' => $totalDue / 100.00
+            ];
+        } else {
+            $accountStatus = null;
+        }
+
+        return $this->createViewModel(compact('fines', 'accountStatus'));
     }
 
     /**
@@ -2163,4 +2208,34 @@ class MyResearchController extends AbstractBase
             ->get(\VuFind\Config\AccountCapabilities::class);
         return $check->getListTagSetting() === 'enabled';
     }
+
+    /**
+     * Collect up to date status information for ajax account notifications.
+     *
+     * @param array $records Records for holds, ILL requests or storage retrieval
+     * requests
+     *
+     * @return array
+     */
+    protected function collectRequestAccountStats(array $records): ?array
+    {
+        // Collect up to date stats for ajax account notifications:
+        if (empty($this->getConfig()->Authentication->enableAjax)) {
+            return null;
+        }
+        $accountStatus = [
+            'available' => 0,
+            'in_transit' => 0
+        ];
+        foreach ($records as $record) {
+            $request = $record->getExtraDetail('ils_details');
+            if ($request['available'] ?? false) {
+                $accountStatus['available']++;
+            }
+            if ($request['in_transit'] ?? false) {
+                $accountStatus['in_transit']++;
+            }
+        }
+        return $accountStatus;
+    }
 }
diff --git a/themes/bootstrap3/js/account_ajax.js b/themes/bootstrap3/js/account_ajax.js
index f8c9ed85f13a5b891e6ac6db7e21ca6a752fd07f..a3c772819d89c188e4f390d5e98111c65bc39cdb 100644
--- a/themes/bootstrap3/js/account_ajax.js
+++ b/themes/bootstrap3/js/account_ajax.js
@@ -5,6 +5,7 @@ VuFind.register('account', function Account() {
   var MISSING = -2 * Math.PI; // no data available
   var INACTIVE = -3 * Math.PI; // status element missing
   var _statuses = {};
+  var _pendingNotifications = {};
 
   // Account Icons
   var ICON_LEVELS = {
@@ -124,6 +125,18 @@ VuFind.register('account', function Account() {
     }
   };
 
+  var notify = function notify(module, status) {
+    if (Object.prototype.hasOwnProperty.call(_submodules, module) && typeof _submodules[module].updateNeeded !== 'undefined') {
+      if (_submodules[module].updateNeeded(_getStatus(module), status)) {
+        clearCache(module);
+        _load(module);
+      }
+    } else {
+      // We currently support only a single pending notification for each module
+      _pendingNotifications[module] = status;
+    }
+  };
+
   var init = function init() {
     // Update information when certain actions are performed
     $("form[data-clear-account-cache]").submit(function dataClearCacheForm() {
@@ -149,11 +162,17 @@ VuFind.register('account', function Account() {
     } else {
       _statuses[name] = INACTIVE;
     }
+    if (typeof _pendingNotifications[name] !== 'undefined' && _pendingNotifications[name] !== null) {
+      var status = _pendingNotifications[name];
+      _pendingNotifications[name] = null;
+      notify(name, status);
+    }
   };
 
   return {
     init: init,
     clearCache: clearCache,
+    notify: notify,
     // if user is logged out, clear cache instead of register
     register: userIsLoggedIn ? register : clearCache
   };
@@ -171,6 +190,9 @@ $(document).ready(function registerAccountAjax() {
       }
       $element.html('<span class="badge overdue">' + status.display + '</span>');
       return ICON_LEVELS.DANGER;
+    },
+    updateNeeded: function updateNeeded(currentStatus, status) {
+      return status.total !== currentStatus.value;
     }
   });
 
@@ -194,6 +216,9 @@ $(document).ready(function registerAccountAjax() {
       $element.html(html);
       $('[data-toggle="tooltip"]', $element).tooltip();
       return level;
+    },
+    updateNeeded: function updateNeeded(currentStatus, status) {
+      return status.ok !== currentStatus.ok || status.warn !== currentStatus.warn || status.overdue !== currentStatus.overdue;
     }
   });
 
@@ -212,6 +237,9 @@ $(document).ready(function registerAccountAjax() {
       }
       $('[data-toggle="tooltip"]', $element).tooltip();
       return level;
+    },
+    updateNeeded: function updateNeeded(currentStatus, status) {
+      return status.available !== currentStatus.available || status.in_transit !== currentStatus.in_transit;
     }
   });
 
@@ -230,6 +258,9 @@ $(document).ready(function registerAccountAjax() {
       }
       $('[data-toggle="tooltip"]', $element).tooltip();
       return level;
+    },
+    updateNeeded: function updateNeeded(currentStatus, status) {
+      return status.available !== currentStatus.available || status.in_transit !== currentStatus.in_transit;
     }
   });
 
@@ -248,6 +279,9 @@ $(document).ready(function registerAccountAjax() {
       }
       $('[data-toggle="tooltip"]', $element).tooltip();
       return level;
+    },
+    updateNeeded: function updateNeeded(currentStatus, status) {
+      return status.available !== currentStatus.available || status.in_transit !== currentStatus.in_transit;
     }
   });
 
diff --git a/themes/bootstrap3/templates/myresearch/checkedout.phtml b/themes/bootstrap3/templates/myresearch/checkedout.phtml
index 0293e34573a4063c29b274fc09cc96bec9e15759..78ac1ab52d2e16e593303b282c0970702b43b36f 100644
--- a/themes/bootstrap3/templates/myresearch/checkedout.phtml
+++ b/themes/bootstrap3/templates/myresearch/checkedout.phtml
@@ -212,3 +212,5 @@
 <div class="<?=$this->layoutClass('sidebar')?>" id="myresearch-sidebar">
   <?=$this->context($this)->renderInContext("myresearch/menu.phtml", ['active' => 'checkedout'])?>
 </div>
+
+<?=$this->render('myresearch/notify-account-status.phtml', ['method' => 'checkedOut', 'accountStatus' => $this->accountStatus]); ?>
diff --git a/themes/bootstrap3/templates/myresearch/fines.phtml b/themes/bootstrap3/templates/myresearch/fines.phtml
index 08685e2e37f75141bfb9bb20275df876cac38212..df855e2166817aed5703c0759cd0c8d3096367f6 100644
--- a/themes/bootstrap3/templates/myresearch/fines.phtml
+++ b/themes/bootstrap3/templates/myresearch/fines.phtml
@@ -90,3 +90,5 @@
 <div class="<?=$this->layoutClass('sidebar')?>" id="myresearch-sidebar">
   <?=$this->context($this)->renderInContext("myresearch/menu.phtml", ['active' => 'fines'])?>
 </div>
+
+<?=$this->render('myresearch/notify-account-status.phtml', ['method' => 'fines', 'accountStatus' => $this->accountStatus]); ?>
diff --git a/themes/bootstrap3/templates/myresearch/holds.phtml b/themes/bootstrap3/templates/myresearch/holds.phtml
index bee8b3eeebf60d75d06bec53bf7e93f247485468..81d7648935c307f847ff46f6ca85404e891fcec5 100644
--- a/themes/bootstrap3/templates/myresearch/holds.phtml
+++ b/themes/bootstrap3/templates/myresearch/holds.phtml
@@ -187,3 +187,5 @@
 <div class="<?=$this->layoutClass('sidebar')?>" id="myresearch-sidebar">
   <?=$this->context($this)->renderInContext("myresearch/menu.phtml", ['active' => 'holds'])?>
 </div>
+
+<?=$this->render('myresearch/notify-account-status.phtml', ['method' => 'holds', 'accountStatus' => $this->accountStatus]); ?>
diff --git a/themes/bootstrap3/templates/myresearch/illrequests.phtml b/themes/bootstrap3/templates/myresearch/illrequests.phtml
index 24f0906691b6c1c9dcf90f91f90b210859cd7297..1c83d4d83239d1f48d8e635edf9915f6e660eef6 100644
--- a/themes/bootstrap3/templates/myresearch/illrequests.phtml
+++ b/themes/bootstrap3/templates/myresearch/illrequests.phtml
@@ -182,3 +182,5 @@
 <div class="<?=$this->layoutClass('sidebar')?>" id="myresearch-sidebar">
   <?=$this->context($this)->renderInContext("myresearch/menu.phtml", ['active' => 'ILLRequests'])?>
 </div>
+
+<?=$this->render('myresearch/notify-account-status.phtml', ['method' => 'illRequests', 'accountStatus' => $this->accountStatus]); ?>
diff --git a/themes/bootstrap3/templates/myresearch/notify-account-status.phtml b/themes/bootstrap3/templates/myresearch/notify-account-status.phtml
new file mode 100644
index 0000000000000000000000000000000000000000..ff256d6888626371d6abfa1a14759207467b645d
--- /dev/null
+++ b/themes/bootstrap3/templates/myresearch/notify-account-status.phtml
@@ -0,0 +1,5 @@
+<?php
+if (null !== $this->accountStatus) {
+  $notifyScript = 'VuFind.account.notify("' . $this->method . '", ' . json_encode($this->accountStatus) . ');';
+  echo $this->inlineScript(\Laminas\View\Helper\HeadScript::SCRIPT, $notifyScript, 'SET');
+}
diff --git a/themes/bootstrap3/templates/myresearch/storageretrievalrequests.phtml b/themes/bootstrap3/templates/myresearch/storageretrievalrequests.phtml
index a209cd0055756e8c6b24d08d3361611126ca3b46..a138de5ff17ac99fc9265989c6753a640f97f763 100644
--- a/themes/bootstrap3/templates/myresearch/storageretrievalrequests.phtml
+++ b/themes/bootstrap3/templates/myresearch/storageretrievalrequests.phtml
@@ -178,3 +178,5 @@
 <div class="<?=$this->layoutClass('sidebar')?>" id="myresearch-sidebar">
   <?=$this->context($this)->renderInContext("myresearch/menu.phtml", ['active' => 'storageRetrievalRequests'])?>
 </div>
+
+<?=$this->render('myresearch/notify-account-status.phtml', ['method' => 'storageRetrievalRequests', 'accountStatus' => $this->accountStatus]); ?>