From f28accb28f15ff4407aa929ecea0468b8803949d Mon Sep 17 00:00:00 2001
From: shamoon <4887959+shamoon@users.noreply.github.com>
Date: Tue, 4 Mar 2025 08:53:11 -0800
Subject: [PATCH] Very annoying refactor

---
 src-ui/messages.xlf                           | 324 +++++++++---------
 src-ui/src/app/app.component.html             |   2 +-
 src-ui/src/app/app.component.spec.ts          |  59 ++--
 src-ui/src/app/app.component.ts               |  16 +-
 .../admin/config/config.component.spec.ts     |  12 +-
 .../admin/config/config.component.ts          |  19 +-
 .../admin/settings/settings.component.spec.ts |  29 +-
 .../admin/settings/settings.component.ts      |  23 +-
 .../admin/trash/trash.component.spec.ts       |  24 +-
 .../components/admin/trash/trash.component.ts |  20 +-
 .../users-groups.component.spec.ts            |  46 +--
 .../users-groups/users-groups.component.ts    |  38 +-
 .../app-frame/app-frame.component.html        |   2 +-
 .../app-frame/app-frame.component.spec.ts     |  34 +-
 .../app-frame/app-frame.component.ts          |  23 +-
 .../global-search.component.spec.ts           |  22 +-
 .../global-search/global-search.component.ts  |  22 +-
 .../notifications-dropdown.component.html}    |  14 +-
 .../notifications-dropdown.component.scss}    |   2 +-
 .../notifications-dropdown.component.spec.ts} |  51 +--
 .../notifications-dropdown.component.ts       |  47 +++
 .../toasts-dropdown.component.ts              |  42 ---
 .../custom-fields-dropdown.component.spec.ts  |  14 +-
 .../custom-fields-dropdown.component.ts       |  10 +-
 .../user-edit-dialog.component.spec.ts        |  16 +-
 .../user-edit-dialog.component.ts             |  15 +-
 .../email-document-dialog.component.spec.ts   |  14 +-
 .../email-document-dialog.component.ts        |  11 +-
 .../notification-list.component.html          |   3 +
 .../notification-list.component.scss}         |   2 +-
 .../notification-list.component.spec.ts       |  84 +++++
 .../notification-list.component.ts            |  48 +++
 .../notification.component.html}              |  36 +-
 .../notification.component.scss}              |   0
 .../notification.component.spec.ts}           |  34 +-
 .../notification.component.ts}                |  23 +-
 .../profile-edit-dialog.component.spec.ts     |  44 ++-
 .../profile-edit-dialog.component.ts          |  45 ++-
 .../share-links-dialog.component.spec.ts      |  18 +-
 .../share-links-dialog.component.ts           |  18 +-
 .../system-status-dialog.component.spec.ts    |  16 +-
 .../system-status-dialog.component.ts         |   8 +-
 .../common/toasts/toasts.component.html       |   3 -
 .../common/toasts/toasts.component.spec.ts    |  71 ----
 .../common/toasts/toasts.component.ts         |  43 ---
 .../dashboard/dashboard.component.spec.ts     |  14 +-
 .../dashboard/dashboard.component.ts          |  11 +-
 .../document-detail.component.spec.ts         |  53 +--
 .../document-detail.component.ts              |  42 ++-
 .../bulk-editor/bulk-editor.component.spec.ts |  26 +-
 .../bulk-editor/bulk-editor.component.ts      |  12 +-
 .../document-list.component.spec.ts           |  18 +-
 .../document-list/document-list.component.ts  |  10 +-
 .../document-notes.component.spec.ts          |  12 +-
 .../document-notes.component.ts               |   8 +-
 .../file-drop/file-drop.component.spec.ts     |  18 +-
 .../file-drop/file-drop.component.ts          |   6 +-
 .../correspondent-list.component.ts           |   6 +-
 .../custom-fields.component.spec.ts           |  26 +-
 .../custom-fields/custom-fields.component.ts  |  16 +-
 .../document-type-list.component.ts           |   6 +-
 .../manage/mail/mail.component.spec.ts        |  82 ++---
 .../components/manage/mail/mail.component.ts  |  47 +--
 .../management-list.component.spec.ts         |  42 +--
 .../management-list.component.ts              |  24 +-
 .../saved-views/saved-views.component.spec.ts |  22 +-
 .../saved-views/saved-views.component.ts      |  12 +-
 .../storage-path-list.component.ts            |   6 +-
 .../manage/tag-list/tag-list.component.ts     |   6 +-
 .../workflows/workflows.component.spec.ts     |  34 +-
 .../manage/workflows/workflows.component.ts   |  16 +-
 .../src/app/guards/permissions.guard.spec.ts  |  12 +-
 src-ui/src/app/guards/permissions.guard.ts    |   6 +-
 .../app/services/notification.service.spec.ts | 109 ++++++
 .../src/app/services/notification.service.ts  |  87 +++++
 .../src/app/services/settings.service.spec.ts |  12 +-
 src-ui/src/app/services/settings.service.ts   |  16 +-
 src-ui/src/app/services/toast.service.spec.ts | 109 ------
 src-ui/src/app/services/toast.service.ts      |  87 -----
 src-ui/src/styles.scss                        |   2 +-
 src-ui/src/theme.scss                         |   4 +-
 81 files changed, 1315 insertions(+), 1151 deletions(-)
 rename src-ui/src/app/components/app-frame/{toasts-dropdown/toasts-dropdown.component.html => notifications-dropdown/notifications-dropdown.component.html} (61%)
 rename src-ui/src/app/components/app-frame/{toasts-dropdown/toasts-dropdown.component.scss => notifications-dropdown/notifications-dropdown.component.scss} (86%)
 rename src-ui/src/app/components/app-frame/{toasts-dropdown/toasts-dropdown.component.spec.ts => notifications-dropdown/notifications-dropdown.component.spec.ts} (58%)
 create mode 100644 src-ui/src/app/components/app-frame/notifications-dropdown/notifications-dropdown.component.ts
 delete mode 100644 src-ui/src/app/components/app-frame/toasts-dropdown/toasts-dropdown.component.ts
 create mode 100644 src-ui/src/app/components/common/notification-list/notification-list.component.html
 rename src-ui/src/app/components/common/{toasts/toasts.component.scss => notification-list/notification-list.component.scss} (73%)
 create mode 100644 src-ui/src/app/components/common/notification-list/notification-list.component.spec.ts
 create mode 100644 src-ui/src/app/components/common/notification-list/notification-list.component.ts
 rename src-ui/src/app/components/common/{toast/toast.component.html => notification/notification.component.html} (56%)
 rename src-ui/src/app/components/common/{toast/toast.component.scss => notification/notification.component.scss} (100%)
 rename src-ui/src/app/components/common/{toast/toast.component.spec.ts => notification/notification.component.spec.ts} (70%)
 rename src-ui/src/app/components/common/{toast/toast.component.ts => notification/notification.component.ts} (71%)
 delete mode 100644 src-ui/src/app/components/common/toasts/toasts.component.html
 delete mode 100644 src-ui/src/app/components/common/toasts/toasts.component.spec.ts
 delete mode 100644 src-ui/src/app/components/common/toasts/toasts.component.ts
 create mode 100644 src-ui/src/app/services/notification.service.spec.ts
 create mode 100644 src-ui/src/app/services/notification.service.ts
 delete mode 100644 src-ui/src/app/services/toast.service.spec.ts
 delete mode 100644 src-ui/src/app/services/toast.service.ts

diff --git a/src-ui/messages.xlf b/src-ui/messages.xlf
index 58d8d0d4c..804a04774 100644
--- a/src-ui/messages.xlf
+++ b/src-ui/messages.xlf
@@ -612,42 +612,42 @@
         <source>Error retrieving config</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/admin/config/config.component.ts</context>
-          <context context-type="linenumber">103</context>
+          <context context-type="linenumber">104</context>
         </context-group>
       </trans-unit>
       <trans-unit id="1172622527269118932" datatype="html">
         <source>Invalid JSON</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/admin/config/config.component.ts</context>
-          <context context-type="linenumber">129</context>
+          <context context-type="linenumber">132</context>
         </context-group>
       </trans-unit>
       <trans-unit id="5103146006962696736" datatype="html">
         <source>Configuration updated</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/admin/config/config.component.ts</context>
-          <context context-type="linenumber">173</context>
+          <context context-type="linenumber">176</context>
         </context-group>
       </trans-unit>
       <trans-unit id="1664963291286452273" datatype="html">
         <source>An error occurred updating configuration</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/admin/config/config.component.ts</context>
-          <context context-type="linenumber">178</context>
+          <context context-type="linenumber">181</context>
         </context-group>
       </trans-unit>
       <trans-unit id="2653081282186526824" datatype="html">
         <source>File successfully updated</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/admin/config/config.component.ts</context>
-          <context context-type="linenumber">200</context>
+          <context context-type="linenumber">204</context>
         </context-group>
       </trans-unit>
       <trans-unit id="5902783625859504265" datatype="html">
         <source>An error occurred uploading file</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/admin/config/config.component.ts</context>
-          <context context-type="linenumber">205</context>
+          <context context-type="linenumber">210</context>
         </context-group>
       </trans-unit>
       <trans-unit id="4804785061014590286" datatype="html">
@@ -1375,7 +1375,7 @@
           <context context-type="linenumber">340</context>
         </context-group>
         <context-group purpose="location">
-          <context context-type="sourcefile">src/app/components/app-frame/toasts-dropdown/toasts-dropdown.component.html</context>
+          <context context-type="sourcefile">src/app/components/app-frame/notifications-dropdown/notifications-dropdown.component.html</context>
           <context context-type="linenumber">11</context>
         </context-group>
       </trans-unit>
@@ -1500,64 +1500,64 @@
         <source>Use system language</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/admin/settings/settings.component.ts</context>
-          <context context-type="linenumber">76</context>
+          <context context-type="linenumber">79</context>
         </context-group>
       </trans-unit>
       <trans-unit id="7729897675462249787" datatype="html">
         <source>Use date format of display language</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/admin/settings/settings.component.ts</context>
-          <context context-type="linenumber">79</context>
+          <context context-type="linenumber">82</context>
         </context-group>
       </trans-unit>
       <trans-unit id="1235706724900303689" datatype="html">
         <source>Error retrieving users</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/admin/settings/settings.component.ts</context>
-          <context context-type="linenumber">220</context>
+          <context context-type="linenumber">224</context>
         </context-group>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/admin/users-groups/users-groups.component.ts</context>
-          <context context-type="linenumber">59</context>
+          <context context-type="linenumber">60</context>
         </context-group>
       </trans-unit>
       <trans-unit id="3066660568529853846" datatype="html">
         <source>Error retrieving groups</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/admin/settings/settings.component.ts</context>
-          <context context-type="linenumber">239</context>
+          <context context-type="linenumber">246</context>
         </context-group>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/admin/users-groups/users-groups.component.ts</context>
-          <context context-type="linenumber">71</context>
+          <context context-type="linenumber">75</context>
         </context-group>
       </trans-unit>
       <trans-unit id="7217000812750597833" datatype="html">
         <source>Settings were saved successfully.</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/admin/settings/settings.component.ts</context>
-          <context context-type="linenumber">535</context>
+          <context context-type="linenumber">544</context>
         </context-group>
       </trans-unit>
       <trans-unit id="525012668859298131" datatype="html">
         <source>Settings were saved successfully. Reload is required to apply some changes.</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/admin/settings/settings.component.ts</context>
-          <context context-type="linenumber">539</context>
+          <context context-type="linenumber">548</context>
         </context-group>
       </trans-unit>
       <trans-unit id="8491974984518503778" datatype="html">
         <source>Reload now</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/admin/settings/settings.component.ts</context>
-          <context context-type="linenumber">540</context>
+          <context context-type="linenumber">549</context>
         </context-group>
       </trans-unit>
       <trans-unit id="3011185103048412841" datatype="html">
         <source>An error occurred while saving settings.</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/admin/settings/settings.component.ts</context>
-          <context context-type="linenumber">550</context>
+          <context context-type="linenumber">559</context>
         </context-group>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/app-frame/app-frame.component.ts</context>
@@ -2222,23 +2222,23 @@
         </context-group>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/admin/users-groups/users-groups.component.ts</context>
-          <context context-type="linenumber">124</context>
+          <context context-type="linenumber">130</context>
         </context-group>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/admin/users-groups/users-groups.component.ts</context>
-          <context context-type="linenumber">177</context>
+          <context context-type="linenumber">187</context>
         </context-group>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/manage/custom-fields/custom-fields.component.ts</context>
-          <context context-type="linenumber">108</context>
+          <context context-type="linenumber">110</context>
         </context-group>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/manage/mail/mail.component.ts</context>
-          <context context-type="linenumber">195</context>
+          <context context-type="linenumber">200</context>
         </context-group>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/manage/mail/mail.component.ts</context>
-          <context context-type="linenumber">296</context>
+          <context context-type="linenumber">303</context>
         </context-group>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.ts</context>
@@ -2490,66 +2490,66 @@
         <source>Password has been changed, you will be logged out momentarily.</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/admin/users-groups/users-groups.component.ts</context>
-          <context context-type="linenumber">97</context>
+          <context context-type="linenumber">103</context>
         </context-group>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/common/profile-edit-dialog/profile-edit-dialog.component.ts</context>
-          <context context-type="linenumber">198</context>
+          <context context-type="linenumber">200</context>
         </context-group>
       </trans-unit>
       <trans-unit id="2753185112875184719" datatype="html">
         <source>Saved user &quot;<x id="PH" equiv-text="newUser.username"/>&quot;.</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/admin/users-groups/users-groups.component.ts</context>
-          <context context-type="linenumber">104</context>
+          <context context-type="linenumber">110</context>
         </context-group>
       </trans-unit>
       <trans-unit id="3471101514724661554" datatype="html">
         <source>Error saving user.</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/admin/users-groups/users-groups.component.ts</context>
-          <context context-type="linenumber">114</context>
+          <context context-type="linenumber">120</context>
         </context-group>
       </trans-unit>
       <trans-unit id="5565868288871970148" datatype="html">
         <source>Confirm delete user account</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/admin/users-groups/users-groups.component.ts</context>
-          <context context-type="linenumber">122</context>
+          <context context-type="linenumber">128</context>
         </context-group>
       </trans-unit>
       <trans-unit id="8133663925694885325" datatype="html">
         <source>This operation will permanently delete this user account.</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/admin/users-groups/users-groups.component.ts</context>
-          <context context-type="linenumber">123</context>
+          <context context-type="linenumber">129</context>
         </context-group>
       </trans-unit>
       <trans-unit id="1181910457994920507" datatype="html">
         <source>Proceed</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/admin/users-groups/users-groups.component.ts</context>
-          <context context-type="linenumber">126</context>
+          <context context-type="linenumber">132</context>
         </context-group>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/admin/users-groups/users-groups.component.ts</context>
-          <context context-type="linenumber">179</context>
+          <context context-type="linenumber">189</context>
         </context-group>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
-          <context context-type="linenumber">958</context>
+          <context context-type="linenumber">964</context>
         </context-group>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
-          <context context-type="linenumber">1318</context>
+          <context context-type="linenumber">1324</context>
         </context-group>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
-          <context context-type="linenumber">1357</context>
+          <context context-type="linenumber">1363</context>
         </context-group>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
-          <context context-type="linenumber">1398</context>
+          <context context-type="linenumber">1404</context>
         </context-group>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
@@ -2565,15 +2565,15 @@
         </context-group>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/manage/custom-fields/custom-fields.component.ts</context>
-          <context context-type="linenumber">110</context>
+          <context context-type="linenumber">112</context>
         </context-group>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/manage/mail/mail.component.ts</context>
-          <context context-type="linenumber">197</context>
+          <context context-type="linenumber">202</context>
         </context-group>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/manage/mail/mail.component.ts</context>
-          <context context-type="linenumber">298</context>
+          <context context-type="linenumber">305</context>
         </context-group>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.ts</context>
@@ -2588,56 +2588,56 @@
         <source>Deleted user &quot;<x id="PH" equiv-text="user.username"/>&quot;</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/admin/users-groups/users-groups.component.ts</context>
-          <context context-type="linenumber">132</context>
+          <context context-type="linenumber">139</context>
         </context-group>
       </trans-unit>
       <trans-unit id="286457042048584728" datatype="html">
         <source>Error deleting user &quot;<x id="PH" equiv-text="user.username"/>&quot;.</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/admin/users-groups/users-groups.component.ts</context>
-          <context context-type="linenumber">139</context>
+          <context context-type="linenumber">147</context>
         </context-group>
       </trans-unit>
       <trans-unit id="5766640174051730159" datatype="html">
         <source>Saved group &quot;<x id="PH" equiv-text="newGroup.name"/>&quot;.</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/admin/users-groups/users-groups.component.ts</context>
-          <context context-type="linenumber">159</context>
+          <context context-type="linenumber">168</context>
         </context-group>
       </trans-unit>
       <trans-unit id="8382042988405122578" datatype="html">
         <source>Error saving group.</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/admin/users-groups/users-groups.component.ts</context>
-          <context context-type="linenumber">167</context>
+          <context context-type="linenumber">177</context>
         </context-group>
       </trans-unit>
       <trans-unit id="6538873300613683004" datatype="html">
         <source>Confirm delete user group</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/admin/users-groups/users-groups.component.ts</context>
-          <context context-type="linenumber">175</context>
+          <context context-type="linenumber">185</context>
         </context-group>
       </trans-unit>
       <trans-unit id="7710984639498518244" datatype="html">
         <source>This operation will permanently delete this user group.</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/admin/users-groups/users-groups.component.ts</context>
-          <context context-type="linenumber">176</context>
+          <context context-type="linenumber">186</context>
         </context-group>
       </trans-unit>
       <trans-unit id="3756187211130340490" datatype="html">
         <source>Deleted group &quot;<x id="PH" equiv-text="group.name"/>&quot;</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/admin/users-groups/users-groups.component.ts</context>
-          <context context-type="linenumber">185</context>
+          <context context-type="linenumber">196</context>
         </context-group>
       </trans-unit>
       <trans-unit id="1697803415975901060" datatype="html">
         <source>Error deleting group &quot;<x id="PH" equiv-text="group.name"/>&quot;.</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/admin/users-groups/users-groups.component.ts</context>
-          <context context-type="linenumber">192</context>
+          <context context-type="linenumber">204</context>
         </context-group>
       </trans-unit>
       <trans-unit id="7931334600001636863" datatype="html">
@@ -2915,14 +2915,14 @@
         <source>Error updating sidebar views</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/app-frame/app-frame.component.ts</context>
-          <context context-type="linenumber">248</context>
+          <context context-type="linenumber">249</context>
         </context-group>
       </trans-unit>
       <trans-unit id="2526035785704676448" datatype="html">
         <source>An error occurred while saving update checking settings.</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/app-frame/app-frame.component.ts</context>
-          <context context-type="linenumber">269</context>
+          <context context-type="linenumber">272</context>
         </context-group>
       </trans-unit>
       <trans-unit id="4580988005648117665" datatype="html">
@@ -3092,35 +3092,35 @@
         <source>Successfully updated object.</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/app-frame/global-search/global-search.component.ts</context>
-          <context context-type="linenumber">209</context>
+          <context context-type="linenumber">210</context>
         </context-group>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/app-frame/global-search/global-search.component.ts</context>
-          <context context-type="linenumber">247</context>
+          <context context-type="linenumber">253</context>
         </context-group>
       </trans-unit>
       <trans-unit id="1801333259018423190" datatype="html">
         <source>Error occurred saving object.</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/app-frame/global-search/global-search.component.ts</context>
-          <context context-type="linenumber">212</context>
+          <context context-type="linenumber">215</context>
         </context-group>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/app-frame/global-search/global-search.component.ts</context>
-          <context context-type="linenumber">250</context>
+          <context context-type="linenumber">258</context>
         </context-group>
       </trans-unit>
       <trans-unit id="8193912662253833654" datatype="html">
         <source>Clear All</source>
         <context-group purpose="location">
-          <context context-type="sourcefile">src/app/components/app-frame/toasts-dropdown/toasts-dropdown.component.html</context>
+          <context context-type="sourcefile">src/app/components/app-frame/notifications-dropdown/notifications-dropdown.component.html</context>
           <context context-type="linenumber">16</context>
         </context-group>
       </trans-unit>
       <trans-unit id="1656872994210958357" datatype="html">
         <source>No notifications</source>
         <context-group purpose="location">
-          <context context-type="sourcefile">src/app/components/app-frame/toasts-dropdown/toasts-dropdown.component.html</context>
+          <context context-type="sourcefile">src/app/components/app-frame/notifications-dropdown/notifications-dropdown.component.html</context>
           <context context-type="linenumber">20</context>
         </context-group>
       </trans-unit>
@@ -3157,7 +3157,7 @@
         </context-group>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
-          <context context-type="linenumber">911</context>
+          <context context-type="linenumber">914</context>
         </context-group>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
@@ -3318,22 +3318,22 @@
         <source>Saved field &quot;<x id="PH" equiv-text="newField.name"/>&quot;.</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/common/custom-fields-dropdown/custom-fields-dropdown.component.ts</context>
-          <context context-type="linenumber">126</context>
+          <context context-type="linenumber">127</context>
         </context-group>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/manage/custom-fields/custom-fields.component.ts</context>
-          <context context-type="linenumber">89</context>
+          <context context-type="linenumber">90</context>
         </context-group>
       </trans-unit>
       <trans-unit id="1841172489943868696" datatype="html">
         <source>Error saving field.</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/common/custom-fields-dropdown/custom-fields-dropdown.component.ts</context>
-          <context context-type="linenumber">135</context>
+          <context context-type="linenumber">137</context>
         </context-group>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/manage/custom-fields/custom-fields.component.ts</context>
-          <context context-type="linenumber">98</context>
+          <context context-type="linenumber">100</context>
         </context-group>
       </trans-unit>
       <trans-unit id="6048892649018070225" datatype="html">
@@ -3395,7 +3395,7 @@
         </context-group>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
-          <context context-type="linenumber">1375</context>
+          <context context-type="linenumber">1381</context>
         </context-group>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/guards/dirty-saved-view.guard.ts</context>
@@ -4099,6 +4099,10 @@
           <context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
           <context context-type="linenumber">111</context>
         </context-group>
+        <context-group purpose="location">
+          <context context-type="sourcefile">src/app/components/common/notification/notification.component.html</context>
+          <context context-type="linenumber">30</context>
+        </context-group>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/common/system-status-dialog/system-status-dialog.component.html</context>
           <context context-type="linenumber">175</context>
@@ -4111,10 +4115,6 @@
           <context context-type="sourcefile">src/app/components/common/system-status-dialog/system-status-dialog.component.html</context>
           <context context-type="linenumber">243</context>
         </context-group>
-        <context-group purpose="location">
-          <context context-type="sourcefile">src/app/components/common/toast/toast.component.html</context>
-          <context context-type="linenumber">30</context>
-        </context-group>
       </trans-unit>
       <trans-unit id="6886003843406464884" datatype="html">
         <source>Only process attachments</source>
@@ -4523,11 +4523,11 @@
         <source>Totp deactivation failed</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/common/edit-dialog/user-edit-dialog/user-edit-dialog.component.ts</context>
-          <context context-type="linenumber">134</context>
+          <context context-type="linenumber">135</context>
         </context-group>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/common/edit-dialog/user-edit-dialog/user-edit-dialog.component.ts</context>
-          <context context-type="linenumber">139</context>
+          <context context-type="linenumber">142</context>
         </context-group>
       </trans-unit>
       <trans-unit id="8419515490539218007" datatype="html">
@@ -5137,7 +5137,7 @@
         <source>Error emailing document</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/common/email-document-dialog/email-document-dialog.component.ts</context>
-          <context context-type="linenumber">69</context>
+          <context context-type="linenumber">70</context>
         </context-group>
       </trans-unit>
       <trans-unit id="6381578200008167206" datatype="html">
@@ -5452,6 +5452,32 @@
           <context context-type="linenumber">41</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="5611592591303869712" datatype="html">
+        <source>Status</source>
+        <context-group purpose="location">
+          <context context-type="sourcefile">src/app/components/common/notification/notification.component.html</context>
+          <context context-type="linenumber">28</context>
+        </context-group>
+        <context-group purpose="location">
+          <context context-type="sourcefile">src/app/components/common/system-status-dialog/system-status-dialog.component.html</context>
+          <context context-type="linenumber">47</context>
+        </context-group>
+        <context-group purpose="location">
+          <context context-type="sourcefile">src/app/components/manage/mail/mail.component.html</context>
+          <context context-type="linenumber">114</context>
+        </context-group>
+        <context-group purpose="location">
+          <context context-type="sourcefile">src/app/components/manage/workflows/workflows.component.html</context>
+          <context context-type="linenumber">19</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="6732151329960766506" datatype="html">
+        <source>Copy Raw Error</source>
+        <context-group purpose="location">
+          <context context-type="sourcefile">src/app/components/common/notification/notification.component.html</context>
+          <context context-type="linenumber">43</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="2827984212740060090" datatype="html">
         <source>Read more</source>
         <context-group purpose="location">
@@ -5777,71 +5803,71 @@
         <source>Profile updated successfully</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/common/profile-edit-dialog/profile-edit-dialog.component.ts</context>
-          <context context-type="linenumber">195</context>
+          <context context-type="linenumber">196</context>
         </context-group>
       </trans-unit>
       <trans-unit id="3417726855410304962" datatype="html">
         <source>Error saving profile</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/common/profile-edit-dialog/profile-edit-dialog.component.ts</context>
-          <context context-type="linenumber">207</context>
+          <context context-type="linenumber">210</context>
         </context-group>
       </trans-unit>
       <trans-unit id="154249228726292516" datatype="html">
         <source>Error generating auth token</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/common/profile-edit-dialog/profile-edit-dialog.component.ts</context>
-          <context context-type="linenumber">224</context>
+          <context context-type="linenumber">229</context>
         </context-group>
       </trans-unit>
       <trans-unit id="4153637646944982460" datatype="html">
         <source>Error disconnecting social account</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/common/profile-edit-dialog/profile-edit-dialog.component.ts</context>
-          <context context-type="linenumber">249</context>
+          <context context-type="linenumber">254</context>
         </context-group>
       </trans-unit>
       <trans-unit id="5939111172212776886" datatype="html">
         <source>Error fetching TOTP settings</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/common/profile-edit-dialog/profile-edit-dialog.component.ts</context>
-          <context context-type="linenumber">268</context>
+          <context context-type="linenumber">273</context>
         </context-group>
       </trans-unit>
       <trans-unit id="1030314492414713260" datatype="html">
         <source>TOTP activated successfully</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/common/profile-edit-dialog/profile-edit-dialog.component.ts</context>
-          <context context-type="linenumber">289</context>
+          <context context-type="linenumber">295</context>
         </context-group>
       </trans-unit>
       <trans-unit id="3755006064892435830" datatype="html">
         <source>Error activating TOTP</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/common/profile-edit-dialog/profile-edit-dialog.component.ts</context>
-          <context context-type="linenumber">291</context>
+          <context context-type="linenumber">298</context>
         </context-group>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/common/profile-edit-dialog/profile-edit-dialog.component.ts</context>
-          <context context-type="linenumber">297</context>
+          <context context-type="linenumber">305</context>
         </context-group>
       </trans-unit>
       <trans-unit id="5919827473541889422" datatype="html">
         <source>TOTP deactivated successfully</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/common/profile-edit-dialog/profile-edit-dialog.component.ts</context>
-          <context context-type="linenumber">313</context>
+          <context context-type="linenumber">324</context>
         </context-group>
       </trans-unit>
       <trans-unit id="6214722303383624015" datatype="html">
         <source>Error deactivating TOTP</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/common/profile-edit-dialog/profile-edit-dialog.component.ts</context>
-          <context context-type="linenumber">315</context>
+          <context context-type="linenumber">328</context>
         </context-group>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/common/profile-edit-dialog/profile-edit-dialog.component.ts</context>
-          <context context-type="linenumber">320</context>
+          <context context-type="linenumber">335</context>
         </context-group>
       </trans-unit>
       <trans-unit id="6617773613987957957" datatype="html">
@@ -5933,14 +5959,14 @@
         <source>Error deleting link</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/common/share-links-dialog/share-links-dialog.component.ts</context>
-          <context context-type="linenumber">133</context>
+          <context context-type="linenumber">134</context>
         </context-group>
       </trans-unit>
       <trans-unit id="8400747326190565173" datatype="html">
         <source>Error creating link</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/common/share-links-dialog/share-links-dialog.component.ts</context>
-          <context context-type="linenumber">161</context>
+          <context context-type="linenumber">166</context>
         </context-group>
       </trans-unit>
       <trans-unit id="9180110319941008393" datatype="html">
@@ -5999,25 +6025,6 @@
           <context context-type="linenumber">41</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="5611592591303869712" datatype="html">
-        <source>Status</source>
-        <context-group purpose="location">
-          <context context-type="sourcefile">src/app/components/common/system-status-dialog/system-status-dialog.component.html</context>
-          <context context-type="linenumber">47</context>
-        </context-group>
-        <context-group purpose="location">
-          <context context-type="sourcefile">src/app/components/common/toast/toast.component.html</context>
-          <context context-type="linenumber">28</context>
-        </context-group>
-        <context-group purpose="location">
-          <context context-type="sourcefile">src/app/components/manage/mail/mail.component.html</context>
-          <context context-type="linenumber">114</context>
-        </context-group>
-        <context-group purpose="location">
-          <context context-type="sourcefile">src/app/components/manage/workflows/workflows.component.html</context>
-          <context context-type="linenumber">19</context>
-        </context-group>
-      </trans-unit>
       <trans-unit id="2256165083739630668" datatype="html">
         <source>Migration Status</source>
         <context-group purpose="location">
@@ -6131,13 +6138,6 @@
           <context context-type="linenumber">241</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="6732151329960766506" datatype="html">
-        <source>Copy Raw Error</source>
-        <context-group purpose="location">
-          <context context-type="sourcefile">src/app/components/common/toast/toast.component.html</context>
-          <context context-type="linenumber">43</context>
-        </context-group>
-      </trans-unit>
       <trans-unit id="6581372518205328477" datatype="html">
         <source>Hello <x id="PH" equiv-text="this.settingsService.displayName"/>, welcome to <x id="PH_1" equiv-text="environment.appTitle"/></source>
         <context-group purpose="location">
@@ -6163,7 +6163,7 @@
         <source>Error updating dashboard</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/dashboard/dashboard.component.ts</context>
-          <context context-type="linenumber">93</context>
+          <context context-type="linenumber">94</context>
         </context-group>
       </trans-unit>
       <trans-unit id="2946624699882754313" datatype="html">
@@ -6868,21 +6868,21 @@
         <source>Error saving document</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
-          <context context-type="linenumber">880</context>
+          <context context-type="linenumber">881</context>
         </context-group>
       </trans-unit>
       <trans-unit id="8410796510716511826" datatype="html">
         <source>Do you really want to move the document &quot;<x id="PH" equiv-text="this.document.title"/>&quot; to the trash?</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
-          <context context-type="linenumber">912</context>
+          <context context-type="linenumber">915</context>
         </context-group>
       </trans-unit>
       <trans-unit id="282586936710748252" datatype="html">
         <source>Documents can be restored prior to permanent deletion.</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
-          <context context-type="linenumber">913</context>
+          <context context-type="linenumber">916</context>
         </context-group>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
@@ -6893,7 +6893,7 @@
         <source>Move to trash</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
-          <context context-type="linenumber">915</context>
+          <context context-type="linenumber">918</context>
         </context-group>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
@@ -6904,14 +6904,14 @@
         <source>Error deleting document</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
-          <context context-type="linenumber">934</context>
+          <context context-type="linenumber">938</context>
         </context-group>
       </trans-unit>
       <trans-unit id="619486176823357521" datatype="html">
         <source>Reprocess confirm</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
-          <context context-type="linenumber">954</context>
+          <context context-type="linenumber">960</context>
         </context-group>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
@@ -6922,77 +6922,77 @@
         <source>This operation will permanently recreate the archive file for this document.</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
-          <context context-type="linenumber">955</context>
+          <context context-type="linenumber">961</context>
         </context-group>
       </trans-unit>
       <trans-unit id="302054111564709516" datatype="html">
         <source>The archive file will be re-generated with the current settings.</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
-          <context context-type="linenumber">956</context>
+          <context context-type="linenumber">962</context>
         </context-group>
       </trans-unit>
       <trans-unit id="8251197608401006898" datatype="html">
         <source>Reprocess operation for &quot;<x id="PH" equiv-text="this.document.title"/>&quot; will begin in the background. Close and re-open or reload this document after the operation has completed to see new content.</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
-          <context context-type="linenumber">966</context>
+          <context context-type="linenumber">972</context>
         </context-group>
       </trans-unit>
       <trans-unit id="4409560272830824468" datatype="html">
         <source>Error executing operation</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
-          <context context-type="linenumber">977</context>
+          <context context-type="linenumber">983</context>
         </context-group>
       </trans-unit>
       <trans-unit id="6030453331794586802" datatype="html">
         <source>Error downloading document</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
-          <context context-type="linenumber">1024</context>
+          <context context-type="linenumber">1030</context>
         </context-group>
       </trans-unit>
       <trans-unit id="4458954481601077369" datatype="html">
         <source>Page Fit</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
-          <context context-type="linenumber">1103</context>
+          <context context-type="linenumber">1109</context>
         </context-group>
       </trans-unit>
       <trans-unit id="1217563727923422413" datatype="html">
         <source>Split confirm</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
-          <context context-type="linenumber">1316</context>
+          <context context-type="linenumber">1322</context>
         </context-group>
       </trans-unit>
       <trans-unit id="2805304563009985503" datatype="html">
         <source>This operation will split the selected document(s) into new documents.</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
-          <context context-type="linenumber">1317</context>
+          <context context-type="linenumber">1323</context>
         </context-group>
       </trans-unit>
       <trans-unit id="7638681545012641321" datatype="html">
         <source>Split operation for &quot;<x id="PH" equiv-text="this.document.title"/>&quot; will begin in the background.</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
-          <context context-type="linenumber">1333</context>
+          <context context-type="linenumber">1339</context>
         </context-group>
       </trans-unit>
       <trans-unit id="3235014591864339926" datatype="html">
         <source>Error executing split operation</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
-          <context context-type="linenumber">1342</context>
+          <context context-type="linenumber">1348</context>
         </context-group>
       </trans-unit>
       <trans-unit id="6555329262222566158" datatype="html">
         <source>Rotate confirm</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
-          <context context-type="linenumber">1355</context>
+          <context context-type="linenumber">1361</context>
         </context-group>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
@@ -7003,60 +7003,60 @@
         <source>This operation will permanently rotate the original version of the current document.</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
-          <context context-type="linenumber">1356</context>
+          <context context-type="linenumber">1362</context>
         </context-group>
       </trans-unit>
       <trans-unit id="3802852336439815451" datatype="html">
         <source>Rotation of &quot;<x id="PH" equiv-text="this.document.title"/>&quot; will begin in the background. Close and re-open the document after the operation has completed to see the changes.</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
-          <context context-type="linenumber">1372</context>
+          <context context-type="linenumber">1378</context>
         </context-group>
       </trans-unit>
       <trans-unit id="2962674215361798818" datatype="html">
         <source>Error executing rotate operation</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
-          <context context-type="linenumber">1384</context>
+          <context context-type="linenumber">1390</context>
         </context-group>
       </trans-unit>
       <trans-unit id="3539261415918606512" datatype="html">
         <source>Delete pages confirm</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
-          <context context-type="linenumber">1396</context>
+          <context context-type="linenumber">1402</context>
         </context-group>
       </trans-unit>
       <trans-unit id="5854352498125813866" datatype="html">
         <source>This operation will permanently delete the selected pages from the original document.</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
-          <context context-type="linenumber">1397</context>
+          <context context-type="linenumber">1403</context>
         </context-group>
       </trans-unit>
       <trans-unit id="1138505464360427037" datatype="html">
         <source>Delete pages operation for &quot;<x id="PH" equiv-text="this.document.title"/>&quot; will begin in the background. Close and re-open or reload this document after the operation has completed to see the changes.</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
-          <context context-type="linenumber">1412</context>
+          <context context-type="linenumber">1418</context>
         </context-group>
       </trans-unit>
       <trans-unit id="1249139200486584973" datatype="html">
         <source>Error executing delete pages operation</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
-          <context context-type="linenumber">1421</context>
+          <context context-type="linenumber">1427</context>
         </context-group>
       </trans-unit>
       <trans-unit id="6085793215710522488" datatype="html">
         <source>An error occurred loading tiff: <x id="PH" equiv-text="err.toString()"/></source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
-          <context context-type="linenumber">1481</context>
+          <context context-type="linenumber">1487</context>
         </context-group>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
-          <context context-type="linenumber">1485</context>
+          <context context-type="linenumber">1491</context>
         </context-group>
       </trans-unit>
       <trans-unit id="4958946940233632319" datatype="html">
@@ -8270,28 +8270,28 @@
         <source>Confirm delete field</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/manage/custom-fields/custom-fields.component.ts</context>
-          <context context-type="linenumber">106</context>
+          <context context-type="linenumber">108</context>
         </context-group>
       </trans-unit>
       <trans-unit id="2939457975223185057" datatype="html">
         <source>This operation will permanently delete this field.</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/manage/custom-fields/custom-fields.component.ts</context>
-          <context context-type="linenumber">107</context>
+          <context context-type="linenumber">109</context>
         </context-group>
       </trans-unit>
       <trans-unit id="4679555638382452936" datatype="html">
         <source>Deleted field &quot;<x id="PH" equiv-text="field.name"/>&quot;</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/manage/custom-fields/custom-fields.component.ts</context>
-          <context context-type="linenumber">116</context>
+          <context context-type="linenumber">119</context>
         </context-group>
       </trans-unit>
       <trans-unit id="4704551499967874824" datatype="html">
         <source>Error deleting field &quot;<x id="PH" equiv-text="field.name"/>&quot;.</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/manage/custom-fields/custom-fields.component.ts</context>
-          <context context-type="linenumber">125</context>
+          <context context-type="linenumber">129</context>
         </context-group>
       </trans-unit>
       <trans-unit id="8084492669582894778" datatype="html">
@@ -8425,154 +8425,154 @@
         <source>Error retrieving mail rules</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/manage/mail/mail.component.ts</context>
-          <context context-type="linenumber">130</context>
+          <context context-type="linenumber">131</context>
         </context-group>
       </trans-unit>
       <trans-unit id="763945516325093575" datatype="html">
         <source>OAuth2 authentication success</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/manage/mail/mail.component.ts</context>
-          <context context-type="linenumber">138</context>
+          <context context-type="linenumber">142</context>
         </context-group>
       </trans-unit>
       <trans-unit id="9022978370268070156" datatype="html">
         <source>OAuth2 authentication failed, see logs for details</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/manage/mail/mail.component.ts</context>
-          <context context-type="linenumber">149</context>
+          <context context-type="linenumber">154</context>
         </context-group>
       </trans-unit>
       <trans-unit id="6327501535846658797" datatype="html">
         <source>Saved account &quot;<x id="PH" equiv-text="newMailAccount.name"/>&quot;.</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/manage/mail/mail.component.ts</context>
-          <context context-type="linenumber">173</context>
+          <context context-type="linenumber">178</context>
         </context-group>
       </trans-unit>
       <trans-unit id="8067594003836508139" datatype="html">
         <source>Error saving account.</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/manage/mail/mail.component.ts</context>
-          <context context-type="linenumber">185</context>
+          <context context-type="linenumber">190</context>
         </context-group>
       </trans-unit>
       <trans-unit id="5641934153807844674" datatype="html">
         <source>Confirm delete mail account</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/manage/mail/mail.component.ts</context>
-          <context context-type="linenumber">193</context>
+          <context context-type="linenumber">198</context>
         </context-group>
       </trans-unit>
       <trans-unit id="7176985344323395435" datatype="html">
         <source>This operation will permanently delete this mail account.</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/manage/mail/mail.component.ts</context>
-          <context context-type="linenumber">194</context>
+          <context context-type="linenumber">199</context>
         </context-group>
       </trans-unit>
       <trans-unit id="5876433590301754883" datatype="html">
         <source>Deleted mail account &quot;<x id="PH" equiv-text="account.name"/>&quot;</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/manage/mail/mail.component.ts</context>
-          <context context-type="linenumber">204</context>
+          <context context-type="linenumber">209</context>
         </context-group>
       </trans-unit>
       <trans-unit id="5981429299543258715" datatype="html">
         <source>Error deleting mail account &quot;<x id="PH" equiv-text="account.name"/>&quot;.</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/manage/mail/mail.component.ts</context>
-          <context context-type="linenumber">215</context>
+          <context context-type="linenumber">220</context>
         </context-group>
       </trans-unit>
       <trans-unit id="6424800796582120505" datatype="html">
         <source>Processing mail account &quot;<x id="PH" equiv-text="account.name"/>&quot;</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/manage/mail/mail.component.ts</context>
-          <context context-type="linenumber">227</context>
+          <context context-type="linenumber">232</context>
         </context-group>
       </trans-unit>
       <trans-unit id="3138185874003827652" datatype="html">
         <source>Error processing mail account &quot;<x id="PH" equiv-text="account.name"/>&quot;</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/manage/mail/mail.component.ts</context>
-          <context context-type="linenumber">232</context>
+          <context context-type="linenumber">237</context>
         </context-group>
       </trans-unit>
       <trans-unit id="123368655395433699" datatype="html">
         <source>Saved rule &quot;<x id="PH" equiv-text="newMailRule.name"/>&quot;.</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/manage/mail/mail.component.ts</context>
-          <context context-type="linenumber">250</context>
+          <context context-type="linenumber">256</context>
         </context-group>
       </trans-unit>
       <trans-unit id="8951124554918814321" datatype="html">
         <source>Error saving rule.</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/manage/mail/mail.component.ts</context>
-          <context context-type="linenumber">261</context>
+          <context context-type="linenumber">268</context>
         </context-group>
       </trans-unit>
       <trans-unit id="3574401690710711341" datatype="html">
         <source>Rule &quot;<x id="PH" equiv-text="rule.name"/>&quot; enabled.</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/manage/mail/mail.component.ts</context>
-          <context context-type="linenumber">277</context>
+          <context context-type="linenumber">284</context>
         </context-group>
       </trans-unit>
       <trans-unit id="7171685227222299542" datatype="html">
         <source>Rule &quot;<x id="PH" equiv-text="rule.name"/>&quot; disabled.</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/manage/mail/mail.component.ts</context>
-          <context context-type="linenumber">278</context>
+          <context context-type="linenumber">285</context>
         </context-group>
       </trans-unit>
       <trans-unit id="7238791203524413596" datatype="html">
         <source>Error toggling rule &quot;<x id="PH" equiv-text="rule.name"/>&quot;.</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/manage/mail/mail.component.ts</context>
-          <context context-type="linenumber">283</context>
+          <context context-type="linenumber">290</context>
         </context-group>
       </trans-unit>
       <trans-unit id="3896080636020672118" datatype="html">
         <source>Confirm delete mail rule</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/manage/mail/mail.component.ts</context>
-          <context context-type="linenumber">294</context>
+          <context context-type="linenumber">301</context>
         </context-group>
       </trans-unit>
       <trans-unit id="2250372580580310337" datatype="html">
         <source>This operation will permanently delete this mail rule.</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/manage/mail/mail.component.ts</context>
-          <context context-type="linenumber">295</context>
+          <context context-type="linenumber">302</context>
         </context-group>
       </trans-unit>
       <trans-unit id="4357654589451732716" datatype="html">
         <source>Deleted mail rule &quot;<x id="PH" equiv-text="rule.name"/>&quot;</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/manage/mail/mail.component.ts</context>
-          <context context-type="linenumber">305</context>
+          <context context-type="linenumber">312</context>
         </context-group>
       </trans-unit>
       <trans-unit id="1696130068388341598" datatype="html">
         <source>Error deleting mail rule &quot;<x id="PH" equiv-text="rule.name"/>&quot;.</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/manage/mail/mail.component.ts</context>
-          <context context-type="linenumber">316</context>
+          <context context-type="linenumber">323</context>
         </context-group>
       </trans-unit>
       <trans-unit id="3061362835271417984" datatype="html">
         <source>Permissions updated</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/manage/mail/mail.component.ts</context>
-          <context context-type="linenumber">340</context>
+          <context context-type="linenumber">347</context>
         </context-group>
       </trans-unit>
       <trans-unit id="4639647950943944112" datatype="html">
         <source>Error updating permissions</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/manage/mail/mail.component.ts</context>
-          <context context-type="linenumber">345</context>
+          <context context-type="linenumber">352</context>
         </context-group>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.ts</context>
@@ -8737,14 +8737,14 @@
         <source>Objects deleted successfully</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.ts</context>
-          <context context-type="linenumber">352</context>
+          <context context-type="linenumber">353</context>
         </context-group>
       </trans-unit>
       <trans-unit id="8273353839648035634" datatype="html">
         <source>Error deleting objects</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.ts</context>
-          <context context-type="linenumber">358</context>
+          <context context-type="linenumber">360</context>
         </context-group>
       </trans-unit>
       <trans-unit id="1930477323485553035" datatype="html">
@@ -8807,14 +8807,14 @@
         <source>Views saved successfully.</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/manage/saved-views/saved-views.component.ts</context>
-          <context context-type="linenumber">158</context>
+          <context context-type="linenumber">159</context>
         </context-group>
       </trans-unit>
       <trans-unit id="1699877326523238632" datatype="html">
         <source>Error while saving views.</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/manage/saved-views/saved-views.component.ts</context>
-          <context context-type="linenumber">163</context>
+          <context context-type="linenumber">165</context>
         </context-group>
       </trans-unit>
       <trans-unit id="5101757640976222639" datatype="html">
diff --git a/src-ui/src/app/app.component.html b/src-ui/src/app/app.component.html
index 5ffe4aebe..40322c0e3 100644
--- a/src-ui/src/app/app.component.html
+++ b/src-ui/src/app/app.component.html
@@ -1,4 +1,4 @@
-<pngx-toasts></pngx-toasts>
+<pngx-notification-list></pngx-notification-list>
 
 <pngx-file-drop>
   <ng-container content>
diff --git a/src-ui/src/app/app.component.spec.ts b/src-ui/src/app/app.component.spec.ts
index bc59f78dc..b4e46e3ed 100644
--- a/src-ui/src/app/app.component.spec.ts
+++ b/src-ui/src/app/app.component.spec.ts
@@ -14,14 +14,17 @@ import { TourNgBootstrapModule, TourService } from 'ngx-ui-tour-ng-bootstrap'
 import { Subject } from 'rxjs'
 import { routes } from './app-routing.module'
 import { AppComponent } from './app.component'
-import { ToastsComponent } from './components/common/toasts/toasts.component'
+import { NotificationListComponent } from './components/common/notification-list/notification-list.component'
 import { FileDropComponent } from './components/file-drop/file-drop.component'
 import { DirtySavedViewGuard } from './guards/dirty-saved-view.guard'
 import { PermissionsGuard } from './guards/permissions.guard'
 import { HotKeyService } from './services/hot-key.service'
+import {
+  Notification,
+  NotificationService,
+} from './services/notification.service'
 import { PermissionsService } from './services/permissions.service'
 import { SettingsService } from './services/settings.service'
-import { Toast, ToastService } from './services/toast.service'
 import {
   FileStatus,
   WebsocketStatusService,
@@ -33,7 +36,7 @@ describe('AppComponent', () => {
   let tourService: TourService
   let websocketStatusService: WebsocketStatusService
   let permissionsService: PermissionsService
-  let toastService: ToastService
+  let notificationService: NotificationService
   let router: Router
   let settingsService: SettingsService
   let hotKeyService: HotKeyService
@@ -46,7 +49,7 @@ describe('AppComponent', () => {
         NgxFileDropModule,
         NgbModalModule,
         AppComponent,
-        ToastsComponent,
+        NotificationListComponent,
         FileDropComponent,
         NgxBootstrapIconsModule.pick(allIcons),
       ],
@@ -62,7 +65,7 @@ describe('AppComponent', () => {
     websocketStatusService = TestBed.inject(WebsocketStatusService)
     permissionsService = TestBed.inject(PermissionsService)
     settingsService = TestBed.inject(SettingsService)
-    toastService = TestBed.inject(ToastService)
+    notificationService = TestBed.inject(NotificationService)
     router = TestBed.inject(Router)
     hotKeyService = TestBed.inject(HotKeyService)
     fixture = TestBed.createComponent(AppComponent)
@@ -82,12 +85,14 @@ describe('AppComponent', () => {
     expect(document.body.classList).not.toContain('tour-active')
   }))
 
-  it('should display toast on document consumed with link if user has access', () => {
+  it('should display notification on document consumed with link if user has access', () => {
     const navigateSpy = jest.spyOn(router, 'navigate')
     jest.spyOn(permissionsService, 'currentUserCan').mockReturnValue(true)
-    let toast: Toast
-    toastService.getToasts().subscribe((toasts) => (toast = toasts[0]))
-    const toastSpy = jest.spyOn(toastService, 'show')
+    let notification: Notification
+    notificationService
+      .getNotifications()
+      .subscribe((notifications) => (notification = notifications[0]))
+    const notificationSpy = jest.spyOn(notificationService, 'show')
     const fileStatusSubject = new Subject<FileStatus>()
     jest
       .spyOn(websocketStatusService, 'onDocumentConsumptionFinished')
@@ -96,63 +101,65 @@ describe('AppComponent', () => {
     const status = new FileStatus()
     status.documentId = 1
     fileStatusSubject.next(status)
-    expect(toastSpy).toHaveBeenCalled()
-    expect(toast.action).not.toBeUndefined()
-    toast.action()
+    expect(notificationSpy).toHaveBeenCalled()
+    expect(notification.action).not.toBeUndefined()
+    notification.action()
     expect(navigateSpy).toHaveBeenCalledWith(['documents', status.documentId])
   })
 
-  it('should display toast on document consumed without link if user does not have access', () => {
+  it('should display notification on document consumed without link if user does not have access', () => {
     jest.spyOn(permissionsService, 'currentUserCan').mockReturnValue(false)
-    let toast: Toast
-    toastService.getToasts().subscribe((toasts) => (toast = toasts[0]))
-    const toastSpy = jest.spyOn(toastService, 'show')
+    let notification: Notification
+    notificationService
+      .getNotifications()
+      .subscribe((notifications) => (notification = notifications[0]))
+    const notificationSpy = jest.spyOn(notificationService, 'show')
     const fileStatusSubject = new Subject<FileStatus>()
     jest
       .spyOn(websocketStatusService, 'onDocumentConsumptionFinished')
       .mockReturnValue(fileStatusSubject)
     component.ngOnInit()
     fileStatusSubject.next(new FileStatus())
-    expect(toastSpy).toHaveBeenCalled()
-    expect(toast.action).toBeUndefined()
+    expect(notificationSpy).toHaveBeenCalled()
+    expect(notification.action).toBeUndefined()
   })
 
-  it('should display toast on document added', () => {
+  it('should display notification on document added', () => {
     jest.spyOn(permissionsService, 'currentUserCan').mockReturnValue(true)
-    const toastSpy = jest.spyOn(toastService, 'show')
+    const notificationSpy = jest.spyOn(notificationService, 'show')
     const fileStatusSubject = new Subject<FileStatus>()
     jest
       .spyOn(websocketStatusService, 'onDocumentDetected')
       .mockReturnValue(fileStatusSubject)
     component.ngOnInit()
     fileStatusSubject.next(new FileStatus())
-    expect(toastSpy).toHaveBeenCalled()
+    expect(notificationSpy).toHaveBeenCalled()
   })
 
   it('should suppress dashboard notifications if set', () => {
     jest.spyOn(permissionsService, 'currentUserCan').mockReturnValue(true)
     jest.spyOn(settingsService, 'get').mockReturnValue(true)
     jest.spyOn(router, 'url', 'get').mockReturnValue('/dashboard')
-    const toastSpy = jest.spyOn(toastService, 'show')
+    const notificationSpy = jest.spyOn(notificationService, 'show')
     const fileStatusSubject = new Subject<FileStatus>()
     jest
       .spyOn(websocketStatusService, 'onDocumentDetected')
       .mockReturnValue(fileStatusSubject)
     component.ngOnInit()
     fileStatusSubject.next(new FileStatus())
-    expect(toastSpy).not.toHaveBeenCalled()
+    expect(notificationSpy).not.toHaveBeenCalled()
   })
 
-  it('should display toast on document failed', () => {
+  it('should display notification on document failed', () => {
     jest.spyOn(permissionsService, 'currentUserCan').mockReturnValue(true)
-    const toastSpy = jest.spyOn(toastService, 'showError')
+    const notificationSpy = jest.spyOn(notificationService, 'showError')
     const fileStatusSubject = new Subject<FileStatus>()
     jest
       .spyOn(websocketStatusService, 'onDocumentConsumptionFailed')
       .mockReturnValue(fileStatusSubject)
     component.ngOnInit()
     fileStatusSubject.next(new FileStatus())
-    expect(toastSpy).toHaveBeenCalled()
+    expect(notificationSpy).toHaveBeenCalled()
   })
 
   it('should support hotkeys', () => {
diff --git a/src-ui/src/app/app.component.ts b/src-ui/src/app/app.component.ts
index a6c4702b7..51de90414 100644
--- a/src-ui/src/app/app.component.ts
+++ b/src-ui/src/app/app.component.ts
@@ -2,11 +2,12 @@ import { Component, OnDestroy, OnInit, Renderer2 } from '@angular/core'
 import { Router, RouterOutlet } from '@angular/router'
 import { TourNgBootstrapModule, TourService } from 'ngx-ui-tour-ng-bootstrap'
 import { first, Subscription } from 'rxjs'
-import { ToastsComponent } from './components/common/toasts/toasts.component'
+import { NotificationListComponent } from './components/common/notification-list/notification-list.component'
 import { FileDropComponent } from './components/file-drop/file-drop.component'
 import { SETTINGS_KEYS } from './data/ui-settings'
 import { ComponentRouterService } from './services/component-router.service'
 import { HotKeyService } from './services/hot-key.service'
+import { NotificationService } from './services/notification.service'
 import {
   PermissionAction,
   PermissionsService,
@@ -14,7 +15,6 @@ import {
 } from './services/permissions.service'
 import { SettingsService } from './services/settings.service'
 import { TasksService } from './services/tasks.service'
-import { ToastService } from './services/toast.service'
 import { WebsocketStatusService } from './services/websocket-status.service'
 
 @Component({
@@ -23,7 +23,7 @@ import { WebsocketStatusService } from './services/websocket-status.service'
   styleUrls: ['./app.component.scss'],
   imports: [
     FileDropComponent,
-    ToastsComponent,
+    NotificationListComponent,
     TourNgBootstrapModule,
     RouterOutlet,
   ],
@@ -36,7 +36,7 @@ export class AppComponent implements OnInit, OnDestroy {
   constructor(
     private settings: SettingsService,
     private websocketStatusService: WebsocketStatusService,
-    private toastService: ToastService,
+    private notificationService: NotificationService,
     private router: Router,
     private tasksService: TasksService,
     public tourService: TourService,
@@ -91,7 +91,7 @@ export class AppComponent implements OnInit, OnDestroy {
               PermissionType.Document
             )
           ) {
-            this.toastService.show({
+            this.notificationService.show({
               content: $localize`Document ${status.filename} was added to Paperless-ngx.`,
               delay: 10000,
               actionName: $localize`Open document`,
@@ -100,7 +100,7 @@ export class AppComponent implements OnInit, OnDestroy {
               },
             })
           } else {
-            this.toastService.show({
+            this.notificationService.show({
               content: $localize`Document ${status.filename} was added to Paperless-ngx.`,
               delay: 10000,
             })
@@ -115,7 +115,7 @@ export class AppComponent implements OnInit, OnDestroy {
         if (
           this.showNotification(SETTINGS_KEYS.NOTIFICATIONS_CONSUMER_FAILED)
         ) {
-          this.toastService.showError(
+          this.notificationService.showError(
             $localize`Could not add ${status.filename}\: ${status.message}`
           )
         }
@@ -130,7 +130,7 @@ export class AppComponent implements OnInit, OnDestroy {
             SETTINGS_KEYS.NOTIFICATIONS_CONSUMER_NEW_DOCUMENT
           )
         ) {
-          this.toastService.show({
+          this.notificationService.show({
             content: $localize`Document ${status.filename} is being processed by Paperless-ngx.`,
             delay: 5000,
           })
diff --git a/src-ui/src/app/components/admin/config/config.component.spec.ts b/src-ui/src/app/components/admin/config/config.component.spec.ts
index 191532590..818d49111 100644
--- a/src-ui/src/app/components/admin/config/config.component.spec.ts
+++ b/src-ui/src/app/components/admin/config/config.component.spec.ts
@@ -10,8 +10,8 @@ import { NgxBootstrapIconsModule, allIcons } from 'ngx-bootstrap-icons'
 import { of, throwError } from 'rxjs'
 import { OutputTypeConfig } from 'src/app/data/paperless-config'
 import { ConfigService } from 'src/app/services/config.service'
+import { NotificationService } from 'src/app/services/notification.service'
 import { SettingsService } from 'src/app/services/settings.service'
-import { ToastService } from 'src/app/services/toast.service'
 import { FileComponent } from '../../common/input/file/file.component'
 import { NumberComponent } from '../../common/input/number/number.component'
 import { SelectComponent } from '../../common/input/select/select.component'
@@ -24,7 +24,7 @@ describe('ConfigComponent', () => {
   let component: ConfigComponent
   let fixture: ComponentFixture<ConfigComponent>
   let configService: ConfigService
-  let toastService: ToastService
+  let notificationService: NotificationService
   let settingService: SettingsService
 
   beforeEach(async () => {
@@ -51,7 +51,7 @@ describe('ConfigComponent', () => {
     }).compileComponents()
 
     configService = TestBed.inject(ConfigService)
-    toastService = TestBed.inject(ToastService)
+    notificationService = TestBed.inject(NotificationService)
     settingService = TestBed.inject(SettingsService)
     fixture = TestBed.createComponent(ConfigComponent)
     component = fixture.componentInstance
@@ -60,7 +60,7 @@ describe('ConfigComponent', () => {
 
   it('should load config on init, show error if necessary', () => {
     const getSpy = jest.spyOn(configService, 'getConfig')
-    const errorSpy = jest.spyOn(toastService, 'showError')
+    const errorSpy = jest.spyOn(notificationService, 'showError')
     getSpy.mockReturnValueOnce(
       throwError(() => new Error('Error getting config'))
     )
@@ -78,7 +78,7 @@ describe('ConfigComponent', () => {
 
   it('should save config, show error if necessary', () => {
     const saveSpy = jest.spyOn(configService, 'saveConfig')
-    const errorSpy = jest.spyOn(toastService, 'showError')
+    const errorSpy = jest.spyOn(notificationService, 'showError')
     saveSpy.mockReturnValueOnce(
       throwError(() => new Error('Error saving config'))
     )
@@ -112,7 +112,7 @@ describe('ConfigComponent', () => {
 
   it('should upload file, show error if necessary', () => {
     const uploadSpy = jest.spyOn(configService, 'uploadFile')
-    const errorSpy = jest.spyOn(toastService, 'showError')
+    const errorSpy = jest.spyOn(notificationService, 'showError')
     uploadSpy.mockReturnValueOnce(
       throwError(() => new Error('Error uploading file'))
     )
diff --git a/src-ui/src/app/components/admin/config/config.component.ts b/src-ui/src/app/components/admin/config/config.component.ts
index 76f6b8795..f89959abf 100644
--- a/src-ui/src/app/components/admin/config/config.component.ts
+++ b/src-ui/src/app/components/admin/config/config.component.ts
@@ -25,8 +25,8 @@ import {
   PaperlessConfigOptions,
 } from 'src/app/data/paperless-config'
 import { ConfigService } from 'src/app/services/config.service'
+import { NotificationService } from 'src/app/services/notification.service'
 import { SettingsService } from 'src/app/services/settings.service'
-import { ToastService } from 'src/app/services/toast.service'
 import { FileComponent } from '../../common/input/file/file.component'
 import { NumberComponent } from '../../common/input/number/number.component'
 import { SelectComponent } from '../../common/input/select/select.component'
@@ -79,7 +79,7 @@ export class ConfigComponent
 
   constructor(
     private configService: ConfigService,
-    private toastService: ToastService,
+    private notificationService: NotificationService,
     private settingsService: SettingsService
   ) {
     super()
@@ -100,7 +100,10 @@ export class ConfigComponent
         },
         error: (e) => {
           this.loading = false
-          this.toastService.showError($localize`Error retrieving config`, e)
+          this.notificationService.showError(
+            $localize`Error retrieving config`,
+            e
+          )
         },
       })
 
@@ -170,11 +173,11 @@ export class ConfigComponent
           this.initialize(config)
           this.store.next(config)
           this.settingsService.initializeSettings().subscribe()
-          this.toastService.showInfo($localize`Configuration updated`)
+          this.notificationService.showInfo($localize`Configuration updated`)
         },
         error: (e) => {
           this.loading = false
-          this.toastService.showError(
+          this.notificationService.showError(
             $localize`An error occurred updating configuration`,
             e
           )
@@ -197,11 +200,13 @@ export class ConfigComponent
           this.initialize(config)
           this.store.next(config)
           this.settingsService.initializeSettings().subscribe()
-          this.toastService.showInfo($localize`File successfully updated`)
+          this.notificationService.showInfo(
+            $localize`File successfully updated`
+          )
         },
         error: (e) => {
           this.loading = false
-          this.toastService.showError(
+          this.notificationService.showError(
             $localize`An error occurred uploading file`,
             e
           )
diff --git a/src-ui/src/app/components/admin/settings/settings.component.spec.ts b/src-ui/src/app/components/admin/settings/settings.component.spec.ts
index c6eeaf896..28d0c9e1b 100644
--- a/src-ui/src/app/components/admin/settings/settings.component.spec.ts
+++ b/src-ui/src/app/components/admin/settings/settings.component.spec.ts
@@ -29,12 +29,15 @@ import { IfPermissionsDirective } from 'src/app/directives/if-permissions.direct
 import { PermissionsGuard } from 'src/app/guards/permissions.guard'
 import { CustomDatePipe } from 'src/app/pipes/custom-date.pipe'
 import { SafeHtmlPipe } from 'src/app/pipes/safehtml.pipe'
+import {
+  Notification,
+  NotificationService,
+} from 'src/app/services/notification.service'
 import { PermissionsService } from 'src/app/services/permissions.service'
 import { GroupService } from 'src/app/services/rest/group.service'
 import { UserService } from 'src/app/services/rest/user.service'
 import { SettingsService } from 'src/app/services/settings.service'
 import { SystemStatusService } from 'src/app/services/system-status.service'
-import { Toast, ToastService } from 'src/app/services/toast.service'
 import { ConfirmButtonComponent } from '../../common/confirm-button/confirm-button.component'
 import { ConfirmDialogComponent } from '../../common/confirm-dialog/confirm-dialog.component'
 import { CheckComponent } from '../../common/input/check/check.component'
@@ -66,7 +69,7 @@ describe('SettingsComponent', () => {
   let settingsService: SettingsService
   let activatedRoute: ActivatedRoute
   let viewportScroller: ViewportScroller
-  let toastService: ToastService
+  let notificationService: NotificationService
   let userService: UserService
   let permissionsService: PermissionsService
   let groupService: GroupService
@@ -115,7 +118,7 @@ describe('SettingsComponent', () => {
     router = TestBed.inject(Router)
     activatedRoute = TestBed.inject(ActivatedRoute)
     viewportScroller = TestBed.inject(ViewportScroller)
-    toastService = TestBed.inject(ToastService)
+    notificationService = TestBed.inject(NotificationService)
     settingsService = TestBed.inject(SettingsService)
     settingsService.currentUser = users[0]
     userService = TestBed.inject(UserService)
@@ -194,8 +197,8 @@ describe('SettingsComponent', () => {
 
   it('should support save local settings updating appearance settings and calling API, show error', () => {
     completeSetup()
-    const toastErrorSpy = jest.spyOn(toastService, 'showError')
-    const toastSpy = jest.spyOn(toastService, 'show')
+    const notificationErrorSpy = jest.spyOn(notificationService, 'showError')
+    const notificationSpy = jest.spyOn(notificationService, 'show')
     const storeSpy = jest.spyOn(settingsService, 'storeSettings')
     const appearanceSettingsSpy = jest.spyOn(
       settingsService,
@@ -209,7 +212,7 @@ describe('SettingsComponent', () => {
     )
     component.saveSettings()
 
-    expect(toastErrorSpy).toHaveBeenCalled()
+    expect(notificationErrorSpy).toHaveBeenCalled()
     expect(storeSpy).toHaveBeenCalled()
     expect(appearanceSettingsSpy).not.toHaveBeenCalled()
     expect(setSpy).toHaveBeenCalledTimes(29)
@@ -217,14 +220,14 @@ describe('SettingsComponent', () => {
     // succeed
     storeSpy.mockReturnValueOnce(of(true))
     component.saveSettings()
-    expect(toastSpy).toHaveBeenCalled()
+    expect(notificationSpy).toHaveBeenCalled()
     expect(appearanceSettingsSpy).toHaveBeenCalled()
   })
 
   it('should offer reload if settings changes require', () => {
     completeSetup()
-    let toast: Toast
-    toastService.getToasts().subscribe((t) => (toast = t[0]))
+    let toast: Notification
+    notificationService.getNotifications().subscribe((t) => (toast = t[0]))
     component.initialize(true) // reset
     component.store.getValue()['displayLanguage'] = 'en-US'
     component.store.getValue()['updateCheckingEnabled'] = false
@@ -258,7 +261,7 @@ describe('SettingsComponent', () => {
   })
 
   it('should show errors on load if load users failure', () => {
-    const toastErrorSpy = jest.spyOn(toastService, 'showError')
+    const notificationErrorSpy = jest.spyOn(notificationService, 'showError')
     jest
       .spyOn(userService, 'listAll')
       .mockImplementation(() =>
@@ -266,11 +269,11 @@ describe('SettingsComponent', () => {
       )
     completeSetup(userService)
     fixture.detectChanges()
-    expect(toastErrorSpy).toBeCalled()
+    expect(notificationErrorSpy).toBeCalled()
   })
 
   it('should show errors on load if load groups failure', () => {
-    const toastErrorSpy = jest.spyOn(toastService, 'showError')
+    const notificationErrorSpy = jest.spyOn(notificationService, 'showError')
     jest
       .spyOn(groupService, 'listAll')
       .mockImplementation(() =>
@@ -278,7 +281,7 @@ describe('SettingsComponent', () => {
       )
     completeSetup(groupService)
     fixture.detectChanges()
-    expect(toastErrorSpy).toBeCalled()
+    expect(notificationErrorSpy).toBeCalled()
   })
 
   it('should load system status on initialize, show errors if needed', () => {
diff --git a/src-ui/src/app/components/admin/settings/settings.component.ts b/src-ui/src/app/components/admin/settings/settings.component.ts
index 8737be160..14f58bf23 100644
--- a/src-ui/src/app/components/admin/settings/settings.component.ts
+++ b/src-ui/src/app/components/admin/settings/settings.component.ts
@@ -43,6 +43,10 @@ import { User } from 'src/app/data/user'
 import { IfPermissionsDirective } from 'src/app/directives/if-permissions.directive'
 import { CustomDatePipe } from 'src/app/pipes/custom-date.pipe'
 import { DocumentListViewService } from 'src/app/services/document-list-view.service'
+import {
+  Notification,
+  NotificationService,
+} from 'src/app/services/notification.service'
 import {
   PermissionAction,
   PermissionType,
@@ -55,7 +59,6 @@ import {
   SettingsService,
 } from 'src/app/services/settings.service'
 import { SystemStatusService } from 'src/app/services/system-status.service'
-import { Toast, ToastService } from 'src/app/services/toast.service'
 import { CheckComponent } from '../../common/input/check/check.component'
 import { ColorComponent } from '../../common/input/color/color.component'
 import { PermissionsGroupComponent } from '../../common/input/permissions/permissions-group/permissions-group.component'
@@ -181,7 +184,7 @@ export class SettingsComponent
 
   constructor(
     private documentListViewService: DocumentListViewService,
-    private toastService: ToastService,
+    private notificationService: NotificationService,
     private settings: SettingsService,
     @Inject(LOCALE_ID) public currentLocale: string,
     private viewportScroller: ViewportScroller,
@@ -217,7 +220,10 @@ export class SettingsComponent
             this.users = r.results
           },
           error: (e) => {
-            this.toastService.showError($localize`Error retrieving users`, e)
+            this.notificationService.showError(
+              $localize`Error retrieving users`,
+              e
+            )
           },
         })
     }
@@ -236,7 +242,10 @@ export class SettingsComponent
             this.groups = r.results
           },
           error: (e) => {
-            this.toastService.showError($localize`Error retrieving groups`, e)
+            this.notificationService.showError(
+              $localize`Error retrieving groups`,
+              e
+            )
           },
         })
     }
@@ -531,7 +540,7 @@ export class SettingsComponent
           this.store.next(this.settingsForm.value)
           this.settings.updateAppearanceSettings()
           this.settings.initializeDisplayFields()
-          let savedToast: Toast = {
+          let savedToast: Notification = {
             content: $localize`Settings were saved successfully.`,
             delay: 5000,
           }
@@ -543,10 +552,10 @@ export class SettingsComponent
             }
           }
 
-          this.toastService.show(savedToast)
+          this.notificationService.show(savedToast)
         },
         error: (error) => {
-          this.toastService.showError(
+          this.notificationService.showError(
             $localize`An error occurred while saving settings.`,
             error
           )
diff --git a/src-ui/src/app/components/admin/trash/trash.component.spec.ts b/src-ui/src/app/components/admin/trash/trash.component.spec.ts
index aa5a8af0f..fd9d8020c 100644
--- a/src-ui/src/app/components/admin/trash/trash.component.spec.ts
+++ b/src-ui/src/app/components/admin/trash/trash.component.spec.ts
@@ -12,7 +12,7 @@ import {
 import { NgxBootstrapIconsModule, allIcons } from 'ngx-bootstrap-icons'
 import { of, throwError } from 'rxjs'
 import { SafeHtmlPipe } from 'src/app/pipes/safehtml.pipe'
-import { ToastService } from 'src/app/services/toast.service'
+import { NotificationService } from 'src/app/services/notification.service'
 import { TrashService } from 'src/app/services/trash.service'
 import { ConfirmDialogComponent } from '../../common/confirm-dialog/confirm-dialog.component'
 import { PageHeaderComponent } from '../../common/page-header/page-header.component'
@@ -38,7 +38,7 @@ describe('TrashComponent', () => {
   let fixture: ComponentFixture<TrashComponent>
   let trashService: TrashService
   let modalService: NgbModal
-  let toastService: ToastService
+  let notificationService: NotificationService
   let router: Router
 
   beforeEach(async () => {
@@ -60,7 +60,7 @@ describe('TrashComponent', () => {
     fixture = TestBed.createComponent(TrashComponent)
     trashService = TestBed.inject(TrashService)
     modalService = TestBed.inject(NgbModal)
-    toastService = TestBed.inject(ToastService)
+    notificationService = TestBed.inject(NotificationService)
     router = TestBed.inject(Router)
     component = fixture.componentInstance
     fixture.detectChanges()
@@ -88,13 +88,13 @@ describe('TrashComponent', () => {
     modalService.activeInstances.subscribe((instances) => {
       modal = instances[0]
     })
-    const toastErrorSpy = jest.spyOn(toastService, 'showError')
+    const notificationErrorSpy = jest.spyOn(notificationService, 'showError')
 
     // fail first
     trashSpy.mockReturnValue(throwError(() => 'Error'))
     component.delete(documentsInTrash[0])
     modal.componentInstance.confirmClicked.next()
-    expect(toastErrorSpy).toHaveBeenCalled()
+    expect(notificationErrorSpy).toHaveBeenCalled()
 
     trashSpy.mockReturnValue(of('OK'))
     component.delete(documentsInTrash[0])
@@ -109,13 +109,13 @@ describe('TrashComponent', () => {
     modalService.activeInstances.subscribe((instances) => {
       modal = instances[instances.length - 1]
     })
-    const toastErrorSpy = jest.spyOn(toastService, 'showError')
+    const notificationErrorSpy = jest.spyOn(notificationService, 'showError')
 
     // fail first
     trashSpy.mockReturnValue(throwError(() => 'Error'))
     component.emptyTrash()
     modal.componentInstance.confirmClicked.next()
-    expect(toastErrorSpy).toHaveBeenCalled()
+    expect(notificationErrorSpy).toHaveBeenCalled()
 
     trashSpy.mockReturnValue(of('OK'))
     component.emptyTrash()
@@ -131,12 +131,12 @@ describe('TrashComponent', () => {
   it('should support restore document, show error if needed', () => {
     const restoreSpy = jest.spyOn(trashService, 'restoreDocuments')
     const reloadSpy = jest.spyOn(component, 'reload')
-    const toastErrorSpy = jest.spyOn(toastService, 'showError')
+    const notificationErrorSpy = jest.spyOn(notificationService, 'showError')
 
     // fail first
     restoreSpy.mockReturnValue(throwError(() => 'Error'))
     component.restore(documentsInTrash[0])
-    expect(toastErrorSpy).toHaveBeenCalled()
+    expect(notificationErrorSpy).toHaveBeenCalled()
     expect(reloadSpy).not.toHaveBeenCalled()
 
     restoreSpy.mockReturnValue(of('OK'))
@@ -148,12 +148,12 @@ describe('TrashComponent', () => {
   it('should support restore all documents, show error if needed', () => {
     const restoreSpy = jest.spyOn(trashService, 'restoreDocuments')
     const reloadSpy = jest.spyOn(component, 'reload')
-    const toastErrorSpy = jest.spyOn(toastService, 'showError')
+    const notificationErrorSpy = jest.spyOn(notificationService, 'showError')
 
     // fail first
     restoreSpy.mockReturnValue(throwError(() => 'Error'))
     component.restoreAll()
-    expect(toastErrorSpy).toHaveBeenCalled()
+    expect(notificationErrorSpy).toHaveBeenCalled()
     expect(reloadSpy).not.toHaveBeenCalled()
 
     restoreSpy.mockReturnValue(of('OK'))
@@ -167,7 +167,7 @@ describe('TrashComponent', () => {
   it('should offer link to restored document', () => {
     let toasts
     const navigateSpy = jest.spyOn(router, 'navigate')
-    toastService.getToasts().subscribe((allToasts) => {
+    notificationService.getNotifications().subscribe((allToasts) => {
       toasts = [...allToasts]
     })
     jest.spyOn(trashService, 'restoreDocuments').mockReturnValue(of('OK'))
diff --git a/src-ui/src/app/components/admin/trash/trash.component.ts b/src-ui/src/app/components/admin/trash/trash.component.ts
index 1df6ceff4..625fa146f 100644
--- a/src-ui/src/app/components/admin/trash/trash.component.ts
+++ b/src-ui/src/app/components/admin/trash/trash.component.ts
@@ -10,8 +10,8 @@ import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons'
 import { delay, takeUntil, tap } from 'rxjs'
 import { Document } from 'src/app/data/document'
 import { SETTINGS_KEYS } from 'src/app/data/ui-settings'
+import { NotificationService } from 'src/app/services/notification.service'
 import { SettingsService } from 'src/app/services/settings.service'
-import { ToastService } from 'src/app/services/toast.service'
 import { TrashService } from 'src/app/services/trash.service'
 import { ConfirmDialogComponent } from '../../common/confirm-dialog/confirm-dialog.component'
 import { PageHeaderComponent } from '../../common/page-header/page-header.component'
@@ -44,7 +44,7 @@ export class TrashComponent
 
   constructor(
     private trashService: TrashService,
-    private toastService: ToastService,
+    private notificationService: NotificationService,
     private modalService: NgbModal,
     private settingsService: SettingsService,
     private router: Router
@@ -86,14 +86,14 @@ export class TrashComponent
         modal.componentInstance.buttonsEnabled = false
         this.trashService.emptyTrash([document.id]).subscribe({
           next: () => {
-            this.toastService.showInfo(
+            this.notificationService.showInfo(
               $localize`Document "${document.title}" deleted`
             )
             modal.close()
             this.reload()
           },
           error: (err) => {
-            this.toastService.showError(
+            this.notificationService.showError(
               $localize`Error deleting document "${document.title}"`,
               err
             )
@@ -121,13 +121,13 @@ export class TrashComponent
           .emptyTrash(documents ? Array.from(documents) : null)
           .subscribe({
             next: () => {
-              this.toastService.showInfo($localize`Document(s) deleted`)
+              this.notificationService.showInfo($localize`Document(s) deleted`)
               this.allToggled = false
               modal.close()
               this.reload()
             },
             error: (err) => {
-              this.toastService.showError(
+              this.notificationService.showError(
                 $localize`Error deleting document(s)`,
                 err
               )
@@ -140,7 +140,7 @@ export class TrashComponent
   restore(document: Document) {
     this.trashService.restoreDocuments([document.id]).subscribe({
       next: () => {
-        this.toastService.show({
+        this.notificationService.show({
           content: $localize`Document "${document.title}" restored`,
           delay: 5000,
           actionName: $localize`Open document`,
@@ -151,7 +151,7 @@ export class TrashComponent
         this.reload()
       },
       error: (err) => {
-        this.toastService.showError(
+        this.notificationService.showError(
           $localize`Error restoring document "${document.title}"`,
           err
         )
@@ -164,12 +164,12 @@ export class TrashComponent
       .restoreDocuments(documents ? Array.from(documents) : null)
       .subscribe({
         next: () => {
-          this.toastService.showInfo($localize`Document(s) restored`)
+          this.notificationService.showInfo($localize`Document(s) restored`)
           this.allToggled = false
           this.reload()
         },
         error: (err) => {
-          this.toastService.showError(
+          this.notificationService.showError(
             $localize`Error restoring document(s)`,
             err
           )
diff --git a/src-ui/src/app/components/admin/users-groups/users-groups.component.spec.ts b/src-ui/src/app/components/admin/users-groups/users-groups.component.spec.ts
index 559b03f51..5c95aa8a2 100644
--- a/src-ui/src/app/components/admin/users-groups/users-groups.component.spec.ts
+++ b/src-ui/src/app/components/admin/users-groups/users-groups.component.spec.ts
@@ -14,11 +14,11 @@ import { Group } from 'src/app/data/group'
 import { User } from 'src/app/data/user'
 import { PermissionsGuard } from 'src/app/guards/permissions.guard'
 import { CustomDatePipe } from 'src/app/pipes/custom-date.pipe'
+import { NotificationService } from 'src/app/services/notification.service'
 import { PermissionsService } from 'src/app/services/permissions.service'
 import { GroupService } from 'src/app/services/rest/group.service'
 import { UserService } from 'src/app/services/rest/user.service'
 import { SettingsService } from 'src/app/services/settings.service'
-import { ToastService } from 'src/app/services/toast.service'
 import { ConfirmDialogComponent } from '../../common/confirm-dialog/confirm-dialog.component'
 import { GroupEditDialogComponent } from '../../common/edit-dialog/group-edit-dialog/group-edit-dialog.component'
 import { UserEditDialogComponent } from '../../common/edit-dialog/user-edit-dialog/user-edit-dialog.component'
@@ -38,7 +38,7 @@ describe('UsersAndGroupsComponent', () => {
   let fixture: ComponentFixture<UsersAndGroupsComponent>
   let settingsService: SettingsService
   let modalService: NgbModal
-  let toastService: ToastService
+  let notificationService: NotificationService
   let userService: UserService
   let permissionsService: PermissionsService
   let groupService: GroupService
@@ -59,7 +59,7 @@ describe('UsersAndGroupsComponent', () => {
     settingsService.currentUser = users[0]
     userService = TestBed.inject(UserService)
     modalService = TestBed.inject(NgbModal)
-    toastService = TestBed.inject(ToastService)
+    notificationService = TestBed.inject(NotificationService)
     permissionsService = TestBed.inject(PermissionsService)
     jest.spyOn(permissionsService, 'currentUserCan').mockReturnValue(true)
     jest
@@ -104,13 +104,13 @@ describe('UsersAndGroupsComponent', () => {
     modalService.activeInstances.subscribe((refs) => (modal = refs[0]))
     component.editUser(users[0])
     const editDialog = modal.componentInstance as UserEditDialogComponent
-    const toastErrorSpy = jest.spyOn(toastService, 'showError')
-    const toastInfoSpy = jest.spyOn(toastService, 'showInfo')
+    const notificationErrorSpy = jest.spyOn(notificationService, 'showError')
+    const notificationInfoSpy = jest.spyOn(notificationService, 'showInfo')
     editDialog.failed.emit()
-    expect(toastErrorSpy).toBeCalled()
+    expect(notificationErrorSpy).toBeCalled()
     settingsService.currentUser = users[1] // simulate logged in as different user
     editDialog.succeeded.emit(users[0])
-    expect(toastInfoSpy).toHaveBeenCalledWith(
+    expect(notificationInfoSpy).toHaveBeenCalledWith(
       `Saved user "${users[0].username}".`
     )
     component.editUser()
@@ -123,18 +123,18 @@ describe('UsersAndGroupsComponent', () => {
     component.deleteUser(users[0])
     const deleteDialog = modal.componentInstance as ConfirmDialogComponent
     const deleteSpy = jest.spyOn(userService, 'delete')
-    const toastErrorSpy = jest.spyOn(toastService, 'showError')
-    const toastInfoSpy = jest.spyOn(toastService, 'showInfo')
+    const notificationErrorSpy = jest.spyOn(notificationService, 'showError')
+    const notificationInfoSpy = jest.spyOn(notificationService, 'showInfo')
     const listAllSpy = jest.spyOn(userService, 'listAll')
     deleteSpy.mockReturnValueOnce(
       throwError(() => new Error('error deleting user'))
     )
     deleteDialog.confirm()
-    expect(toastErrorSpy).toBeCalled()
+    expect(notificationErrorSpy).toBeCalled()
     deleteSpy.mockReturnValueOnce(of(true))
     deleteDialog.confirm()
     expect(listAllSpy).toHaveBeenCalled()
-    expect(toastInfoSpy).toHaveBeenCalledWith('Deleted user "user1"')
+    expect(notificationInfoSpy).toHaveBeenCalledWith('Deleted user "user1"')
   })
 
   it('should logout current user if password changed, after delay', fakeAsync(() => {
@@ -163,12 +163,12 @@ describe('UsersAndGroupsComponent', () => {
     modalService.activeInstances.subscribe((refs) => (modal = refs[0]))
     component.editGroup(groups[0])
     const editDialog = modal.componentInstance as GroupEditDialogComponent
-    const toastErrorSpy = jest.spyOn(toastService, 'showError')
-    const toastInfoSpy = jest.spyOn(toastService, 'showInfo')
+    const notificationErrorSpy = jest.spyOn(notificationService, 'showError')
+    const notificationInfoSpy = jest.spyOn(notificationService, 'showInfo')
     editDialog.failed.emit()
-    expect(toastErrorSpy).toBeCalled()
+    expect(notificationErrorSpy).toBeCalled()
     editDialog.succeeded.emit(groups[0])
-    expect(toastInfoSpy).toHaveBeenCalledWith(
+    expect(notificationInfoSpy).toHaveBeenCalledWith(
       `Saved group "${groups[0].name}".`
     )
     component.editGroup()
@@ -181,18 +181,18 @@ describe('UsersAndGroupsComponent', () => {
     component.deleteGroup(groups[0])
     const deleteDialog = modal.componentInstance as ConfirmDialogComponent
     const deleteSpy = jest.spyOn(groupService, 'delete')
-    const toastErrorSpy = jest.spyOn(toastService, 'showError')
-    const toastInfoSpy = jest.spyOn(toastService, 'showInfo')
+    const notificationErrorSpy = jest.spyOn(notificationService, 'showError')
+    const notificationInfoSpy = jest.spyOn(notificationService, 'showInfo')
     const listAllSpy = jest.spyOn(groupService, 'listAll')
     deleteSpy.mockReturnValueOnce(
       throwError(() => new Error('error deleting group'))
     )
     deleteDialog.confirm()
-    expect(toastErrorSpy).toBeCalled()
+    expect(notificationErrorSpy).toBeCalled()
     deleteSpy.mockReturnValueOnce(of(true))
     deleteDialog.confirm()
     expect(listAllSpy).toHaveBeenCalled()
-    expect(toastInfoSpy).toHaveBeenCalledWith('Deleted group "group1"')
+    expect(notificationInfoSpy).toHaveBeenCalledWith('Deleted group "group1"')
   })
 
   it('should get group name', () => {
@@ -202,7 +202,7 @@ describe('UsersAndGroupsComponent', () => {
   })
 
   it('should show errors on load if load users failure', () => {
-    const toastErrorSpy = jest.spyOn(toastService, 'showError')
+    const notificationErrorSpy = jest.spyOn(notificationService, 'showError')
     jest
       .spyOn(userService, 'listAll')
       .mockImplementation(() =>
@@ -210,11 +210,11 @@ describe('UsersAndGroupsComponent', () => {
       )
     completeSetup(userService)
     fixture.detectChanges()
-    expect(toastErrorSpy).toBeCalled()
+    expect(notificationErrorSpy).toBeCalled()
   })
 
   it('should show errors on load if load groups failure', () => {
-    const toastErrorSpy = jest.spyOn(toastService, 'showError')
+    const notificationErrorSpy = jest.spyOn(notificationService, 'showError')
     jest
       .spyOn(groupService, 'listAll')
       .mockImplementation(() =>
@@ -222,6 +222,6 @@ describe('UsersAndGroupsComponent', () => {
       )
     completeSetup(groupService)
     fixture.detectChanges()
-    expect(toastErrorSpy).toBeCalled()
+    expect(notificationErrorSpy).toBeCalled()
   })
 })
diff --git a/src-ui/src/app/components/admin/users-groups/users-groups.component.ts b/src-ui/src/app/components/admin/users-groups/users-groups.component.ts
index 9ed73cde4..3d3f27fa9 100644
--- a/src-ui/src/app/components/admin/users-groups/users-groups.component.ts
+++ b/src-ui/src/app/components/admin/users-groups/users-groups.component.ts
@@ -5,11 +5,11 @@ import { Subject, first, takeUntil } from 'rxjs'
 import { Group } from 'src/app/data/group'
 import { User } from 'src/app/data/user'
 import { IfPermissionsDirective } from 'src/app/directives/if-permissions.directive'
+import { NotificationService } from 'src/app/services/notification.service'
 import { PermissionsService } from 'src/app/services/permissions.service'
 import { GroupService } from 'src/app/services/rest/group.service'
 import { UserService } from 'src/app/services/rest/user.service'
 import { SettingsService } from 'src/app/services/settings.service'
-import { ToastService } from 'src/app/services/toast.service'
 import { ConfirmDialogComponent } from '../../common/confirm-dialog/confirm-dialog.component'
 import { EditDialogMode } from '../../common/edit-dialog/edit-dialog.component'
 import { GroupEditDialogComponent } from '../../common/edit-dialog/group-edit-dialog/group-edit-dialog.component'
@@ -39,7 +39,7 @@ export class UsersAndGroupsComponent
   constructor(
     private usersService: UserService,
     private groupsService: GroupService,
-    private toastService: ToastService,
+    private notificationService: NotificationService,
     private modalService: NgbModal,
     public permissionsService: PermissionsService,
     private settings: SettingsService
@@ -56,7 +56,10 @@ export class UsersAndGroupsComponent
           this.users = r.results
         },
         error: (e) => {
-          this.toastService.showError($localize`Error retrieving users`, e)
+          this.notificationService.showError(
+            $localize`Error retrieving users`,
+            e
+          )
         },
       })
 
@@ -68,7 +71,10 @@ export class UsersAndGroupsComponent
           this.groups = r.results
         },
         error: (e) => {
-          this.toastService.showError($localize`Error retrieving groups`, e)
+          this.notificationService.showError(
+            $localize`Error retrieving groups`,
+            e
+          )
         },
       })
   }
@@ -93,14 +99,14 @@ export class UsersAndGroupsComponent
           newUser.id === this.settings.currentUser.id &&
           (modal.componentInstance as UserEditDialogComponent).passwordIsSet
         ) {
-          this.toastService.showInfo(
+          this.notificationService.showInfo(
             $localize`Password has been changed, you will be logged out momentarily.`
           )
           setTimeout(() => {
             window.location.href = `${window.location.origin}/accounts/logout/?next=/accounts/login/?next=/`
           }, 2500)
         } else {
-          this.toastService.showInfo(
+          this.notificationService.showInfo(
             $localize`Saved user "${newUser.username}".`
           )
           this.usersService.listAll().subscribe((r) => {
@@ -111,7 +117,7 @@ export class UsersAndGroupsComponent
     modal.componentInstance.failed
       .pipe(takeUntil(this.unsubscribeNotifier))
       .subscribe((e) => {
-        this.toastService.showError($localize`Error saving user.`, e)
+        this.notificationService.showError($localize`Error saving user.`, e)
       })
   }
 
@@ -129,13 +135,15 @@ export class UsersAndGroupsComponent
       this.usersService.delete(user).subscribe({
         next: () => {
           modal.close()
-          this.toastService.showInfo($localize`Deleted user "${user.username}"`)
+          this.notificationService.showInfo(
+            $localize`Deleted user "${user.username}"`
+          )
           this.usersService.listAll().subscribe((r) => {
             this.users = r.results
           })
         },
         error: (e) => {
-          this.toastService.showError(
+          this.notificationService.showError(
             $localize`Error deleting user "${user.username}".`,
             e
           )
@@ -156,7 +164,9 @@ export class UsersAndGroupsComponent
     modal.componentInstance.succeeded
       .pipe(takeUntil(this.unsubscribeNotifier))
       .subscribe((newGroup) => {
-        this.toastService.showInfo($localize`Saved group "${newGroup.name}".`)
+        this.notificationService.showInfo(
+          $localize`Saved group "${newGroup.name}".`
+        )
         this.groupsService.listAll().subscribe((r) => {
           this.groups = r.results
         })
@@ -164,7 +174,7 @@ export class UsersAndGroupsComponent
     modal.componentInstance.failed
       .pipe(takeUntil(this.unsubscribeNotifier))
       .subscribe((e) => {
-        this.toastService.showError($localize`Error saving group.`, e)
+        this.notificationService.showError($localize`Error saving group.`, e)
       })
   }
 
@@ -182,13 +192,15 @@ export class UsersAndGroupsComponent
       this.groupsService.delete(group).subscribe({
         next: () => {
           modal.close()
-          this.toastService.showInfo($localize`Deleted group "${group.name}"`)
+          this.notificationService.showInfo(
+            $localize`Deleted group "${group.name}"`
+          )
           this.groupsService.listAll().subscribe((r) => {
             this.groups = r.results
           })
         },
         error: (e) => {
-          this.toastService.showError(
+          this.notificationService.showError(
             $localize`Error deleting group "${group.name}".`,
             e
           )
diff --git a/src-ui/src/app/components/app-frame/app-frame.component.html b/src-ui/src/app/components/app-frame/app-frame.component.html
index b3d515274..d2fd2c34e 100644
--- a/src-ui/src/app/components/app-frame/app-frame.component.html
+++ b/src-ui/src/app/components/app-frame/app-frame.component.html
@@ -30,7 +30,7 @@
     </div>
   </div>
   <ul ngbNav class="order-sm-3">
-    <pngx-toasts-dropdown></pngx-toasts-dropdown>
+    <pngx-notifications-dropdown></pngx-notifications-dropdown>
     <li ngbDropdown class="nav-item dropdown">
       <button class="btn ps-1 border-0" id="userDropdown" ngbDropdownToggle>
         <i-bs width="1.3em" height="1.3em" name="person-circle"></i-bs>
diff --git a/src-ui/src/app/components/app-frame/app-frame.component.spec.ts b/src-ui/src/app/components/app-frame/app-frame.component.spec.ts
index f1d54ba70..bb57b94ff 100644
--- a/src-ui/src/app/components/app-frame/app-frame.component.spec.ts
+++ b/src-ui/src/app/components/app-frame/app-frame.component.spec.ts
@@ -26,13 +26,13 @@ import {
   DjangoMessageLevel,
   DjangoMessagesService,
 } from 'src/app/services/django-messages.service'
+import { NotificationService } from 'src/app/services/notification.service'
 import { OpenDocumentsService } from 'src/app/services/open-documents.service'
 import { PermissionsService } from 'src/app/services/permissions.service'
 import { RemoteVersionService } from 'src/app/services/rest/remote-version.service'
 import { SavedViewService } from 'src/app/services/rest/saved-view.service'
 import { SearchService } from 'src/app/services/rest/search.service'
 import { SettingsService } from 'src/app/services/settings.service'
-import { ToastService } from 'src/app/services/toast.service'
 import { environment } from 'src/environments/environment'
 import { ProfileEditDialogComponent } from '../common/profile-edit-dialog/profile-edit-dialog.component'
 import { DocumentDetailComponent } from '../document-detail/document-detail.component'
@@ -86,7 +86,7 @@ describe('AppFrameComponent', () => {
   let settingsService: SettingsService
   let permissionsService: PermissionsService
   let remoteVersionService: RemoteVersionService
-  let toastService: ToastService
+  let notificationService: NotificationService
   let messagesService: DjangoMessagesService
   let openDocumentsService: OpenDocumentsService
   let router: Router
@@ -126,7 +126,7 @@ describe('AppFrameComponent', () => {
         PermissionsService,
         RemoteVersionService,
         IfPermissionsDirective,
-        ToastService,
+        NotificationService,
         DjangoMessagesService,
         OpenDocumentsService,
         SearchService,
@@ -157,7 +157,7 @@ describe('AppFrameComponent', () => {
     const savedViewService = TestBed.inject(SavedViewService)
     permissionsService = TestBed.inject(PermissionsService)
     remoteVersionService = TestBed.inject(RemoteVersionService)
-    toastService = TestBed.inject(ToastService)
+    notificationService = TestBed.inject(NotificationService)
     messagesService = TestBed.inject(DjangoMessagesService)
     openDocumentsService = TestBed.inject(OpenDocumentsService)
     modalService = TestBed.inject(NgbModal)
@@ -216,7 +216,7 @@ describe('AppFrameComponent', () => {
 
   it('should show error on toggle update checking if store settings fails', () => {
     jest.spyOn(console, 'warn').mockImplementation(() => {})
-    const toastSpy = jest.spyOn(toastService, 'showError')
+    const notificationSpy = jest.spyOn(notificationService, 'showError')
     settingsService.set(SETTINGS_KEYS.UPDATE_CHECKING_ENABLED, false)
     component.setUpdateChecking(true)
     httpTestingController
@@ -225,7 +225,7 @@ describe('AppFrameComponent', () => {
         status: 500,
         statusText: 'error',
       })
-    expect(toastSpy).toHaveBeenCalled()
+    expect(notificationSpy).toHaveBeenCalled()
   })
 
   it('should support toggling slim sidebar and saving', fakeAsync(() => {
@@ -245,7 +245,7 @@ describe('AppFrameComponent', () => {
 
   it('should show error on toggle slim sidebar if store settings fails', () => {
     jest.spyOn(console, 'warn').mockImplementation(() => {})
-    const toastSpy = jest.spyOn(toastService, 'showError')
+    const notificationSpy = jest.spyOn(notificationService, 'showError')
     component.toggleSlimSidebar()
     httpTestingController
       .expectOne(`${environment.apiBaseUrl}ui_settings/`)
@@ -253,7 +253,7 @@ describe('AppFrameComponent', () => {
         status: 500,
         statusText: 'error',
       })
-    expect(toastSpy).toHaveBeenCalled()
+    expect(notificationSpy).toHaveBeenCalled()
   })
 
   it('should support collapsible menu', () => {
@@ -305,7 +305,7 @@ describe('AppFrameComponent', () => {
 
   it('should update saved view sorting on drag + drop, show info', () => {
     const settingsSpy = jest.spyOn(settingsService, 'updateSidebarViewsSort')
-    const toastSpy = jest.spyOn(toastService, 'showInfo')
+    const notificationSpy = jest.spyOn(notificationService, 'showInfo')
     jest.spyOn(settingsService, 'storeSettings').mockReturnValue(of(true))
     component.onDrop({ previousIndex: 0, currentIndex: 1 } as CdkDragDrop<
       SavedView[]
@@ -315,7 +315,7 @@ describe('AppFrameComponent', () => {
       saved_views[0],
       saved_views[3],
     ])
-    expect(toastSpy).toHaveBeenCalled()
+    expect(notificationSpy).toHaveBeenCalled()
   })
 
   it('should update saved view sorting on drag + drop, show error', () => {
@@ -326,14 +326,14 @@ describe('AppFrameComponent', () => {
     fixture = TestBed.createComponent(AppFrameComponent)
     component = fixture.componentInstance
     fixture.detectChanges()
-    const toastSpy = jest.spyOn(toastService, 'showError')
+    const notificationSpy = jest.spyOn(notificationService, 'showError')
     jest
       .spyOn(settingsService, 'storeSettings')
       .mockReturnValue(throwError(() => new Error('unable to save')))
     component.onDrop({ previousIndex: 0, currentIndex: 2 } as CdkDragDrop<
       SavedView[]
     >)
-    expect(toastSpy).toHaveBeenCalled()
+    expect(notificationSpy).toHaveBeenCalled()
   })
 
   it('should support edit profile', () => {
@@ -345,9 +345,9 @@ describe('AppFrameComponent', () => {
     })
   })
 
-  it('should show toasts for django messages', () => {
-    const toastErrorSpy = jest.spyOn(toastService, 'showError')
-    const toastInfoSpy = jest.spyOn(toastService, 'showInfo')
+  it('should show notifications for django messages', () => {
+    const notificationErrorSpy = jest.spyOn(notificationService, 'showError')
+    const notificationInfoSpy = jest.spyOn(notificationService, 'showInfo')
     jest.spyOn(messagesService, 'get').mockReturnValue([
       { level: DjangoMessageLevel.WARNING, message: 'Test warning' },
       { level: DjangoMessageLevel.ERROR, message: 'Test error' },
@@ -356,7 +356,7 @@ describe('AppFrameComponent', () => {
       { level: DjangoMessageLevel.DEBUG, message: 'Test debug' },
     ])
     component.ngOnInit()
-    expect(toastErrorSpy).toHaveBeenCalledTimes(2)
-    expect(toastInfoSpy).toHaveBeenCalledTimes(3)
+    expect(notificationErrorSpy).toHaveBeenCalledTimes(2)
+    expect(notificationInfoSpy).toHaveBeenCalledTimes(3)
   })
 })
diff --git a/src-ui/src/app/components/app-frame/app-frame.component.ts b/src-ui/src/app/components/app-frame/app-frame.component.ts
index fabcbf7d1..9ea0a6aea 100644
--- a/src-ui/src/app/components/app-frame/app-frame.component.ts
+++ b/src-ui/src/app/components/app-frame/app-frame.component.ts
@@ -29,6 +29,7 @@ import {
   DjangoMessageLevel,
   DjangoMessagesService,
 } from 'src/app/services/django-messages.service'
+import { NotificationService } from 'src/app/services/notification.service'
 import { OpenDocumentsService } from 'src/app/services/open-documents.service'
 import {
   PermissionAction,
@@ -42,13 +43,12 @@ import {
 import { SavedViewService } from 'src/app/services/rest/saved-view.service'
 import { SettingsService } from 'src/app/services/settings.service'
 import { TasksService } from 'src/app/services/tasks.service'
-import { ToastService } from 'src/app/services/toast.service'
 import { environment } from 'src/environments/environment'
 import { ProfileEditDialogComponent } from '../common/profile-edit-dialog/profile-edit-dialog.component'
 import { DocumentDetailComponent } from '../document-detail/document-detail.component'
 import { ComponentWithPermissions } from '../with-permissions/with-permissions.component'
 import { GlobalSearchComponent } from './global-search/global-search.component'
-import { ToastsDropdownComponent } from './toasts-dropdown/toasts-dropdown.component'
+import { NotificationsDropdownComponent } from './notifications-dropdown/notifications-dropdown.component'
 
 @Component({
   selector: 'pngx-app-frame',
@@ -58,7 +58,7 @@ import { ToastsDropdownComponent } from './toasts-dropdown/toasts-dropdown.compo
     GlobalSearchComponent,
     DocumentTitlePipe,
     IfPermissionsDirective,
-    ToastsDropdownComponent,
+    NotificationsDropdownComponent,
     RouterModule,
     NgClass,
     NgbDropdownModule,
@@ -89,7 +89,7 @@ export class AppFrameComponent
     private remoteVersionService: RemoteVersionService,
     public settingsService: SettingsService,
     public tasksService: TasksService,
-    private readonly toastService: ToastService,
+    private readonly notificationService: NotificationService,
     private modalService: NgbModal,
     public permissionsService: PermissionsService,
     private djangoMessagesService: DjangoMessagesService
@@ -123,12 +123,12 @@ export class AppFrameComponent
       switch (message.level) {
         case DjangoMessageLevel.ERROR:
         case DjangoMessageLevel.WARNING:
-          this.toastService.showError(message.message)
+          this.notificationService.showError(message.message)
           break
         case DjangoMessageLevel.SUCCESS:
         case DjangoMessageLevel.INFO:
         case DjangoMessageLevel.DEBUG:
-          this.toastService.showInfo(message.message)
+          this.notificationService.showInfo(message.message)
           break
       }
     })
@@ -157,7 +157,7 @@ export class AppFrameComponent
       .pipe(first())
       .subscribe({
         error: (error) => {
-          this.toastService.showError(
+          this.notificationService.showError(
             $localize`An error occurred while saving settings.`
           )
           console.warn(error)
@@ -242,10 +242,13 @@ export class AppFrameComponent
 
     this.settingsService.updateSidebarViewsSort(sidebarViews).subscribe({
       next: () => {
-        this.toastService.showInfo($localize`Sidebar views updated`)
+        this.notificationService.showInfo($localize`Sidebar views updated`)
       },
       error: (e) => {
-        this.toastService.showError($localize`Error updating sidebar views`, e)
+        this.notificationService.showError(
+          $localize`Error updating sidebar views`,
+          e
+        )
       },
     })
   }
@@ -265,7 +268,7 @@ export class AppFrameComponent
       .pipe(first())
       .subscribe({
         error: (error) => {
-          this.toastService.showError(
+          this.notificationService.showError(
             $localize`An error occurred while saving update checking settings.`
           )
           console.warn(error)
diff --git a/src-ui/src/app/components/app-frame/global-search/global-search.component.spec.ts b/src-ui/src/app/components/app-frame/global-search/global-search.component.spec.ts
index db407c228..e09d2e667 100644
--- a/src-ui/src/app/components/app-frame/global-search/global-search.component.spec.ts
+++ b/src-ui/src/app/components/app-frame/global-search/global-search.component.spec.ts
@@ -28,10 +28,10 @@ import {
 } from 'src/app/data/filter-rule-type'
 import { GlobalSearchType, SETTINGS_KEYS } from 'src/app/data/ui-settings'
 import { DocumentListViewService } from 'src/app/services/document-list-view.service'
+import { NotificationService } from 'src/app/services/notification.service'
 import { DocumentService } from 'src/app/services/rest/document.service'
 import { SearchService } from 'src/app/services/rest/search.service'
 import { SettingsService } from 'src/app/services/settings.service'
-import { ToastService } from 'src/app/services/toast.service'
 import { queryParamsFromFilterRules } from 'src/app/utils/query-params'
 import { CorrespondentEditDialogComponent } from '../../common/edit-dialog/correspondent-edit-dialog/correspondent-edit-dialog.component'
 import { CustomFieldEditDialogComponent } from '../../common/edit-dialog/custom-field-edit-dialog/custom-field-edit-dialog.component'
@@ -133,7 +133,7 @@ describe('GlobalSearchComponent', () => {
   let modalService: NgbModal
   let documentService: DocumentService
   let documentListViewService: DocumentListViewService
-  let toastService: ToastService
+  let notificationService: NotificationService
   let settingsService: SettingsService
 
   beforeEach(async () => {
@@ -157,7 +157,7 @@ describe('GlobalSearchComponent', () => {
     modalService = TestBed.inject(NgbModal)
     documentService = TestBed.inject(DocumentService)
     documentListViewService = TestBed.inject(DocumentListViewService)
-    toastService = TestBed.inject(ToastService)
+    notificationService = TestBed.inject(NotificationService)
     settingsService = TestBed.inject(SettingsService)
 
     fixture = TestBed.createComponent(GlobalSearchComponent)
@@ -397,16 +397,16 @@ describe('GlobalSearchComponent', () => {
     })
 
     const editDialog = modal.componentInstance as CustomFieldEditDialogComponent
-    const toastErrorSpy = jest.spyOn(toastService, 'showError')
-    const toastInfoSpy = jest.spyOn(toastService, 'showInfo')
+    const notificationErrorSpy = jest.spyOn(notificationService, 'showError')
+    const notificationInfoSpy = jest.spyOn(notificationService, 'showInfo')
 
     // fail first
     editDialog.failed.emit({ error: 'error creating item' })
-    expect(toastErrorSpy).toHaveBeenCalled()
+    expect(notificationErrorSpy).toHaveBeenCalled()
 
     // succeed
     editDialog.succeeded.emit(true)
-    expect(toastInfoSpy).toHaveBeenCalled()
+    expect(notificationInfoSpy).toHaveBeenCalled()
   })
 
   it('should support secondary action', () => {
@@ -448,16 +448,16 @@ describe('GlobalSearchComponent', () => {
     })
 
     const editDialog = modal.componentInstance as CustomFieldEditDialogComponent
-    const toastErrorSpy = jest.spyOn(toastService, 'showError')
-    const toastInfoSpy = jest.spyOn(toastService, 'showInfo')
+    const notificationErrorSpy = jest.spyOn(notificationService, 'showError')
+    const notificationInfoSpy = jest.spyOn(notificationService, 'showInfo')
 
     // fail first
     editDialog.failed.emit({ error: 'error creating item' })
-    expect(toastErrorSpy).toHaveBeenCalled()
+    expect(notificationErrorSpy).toHaveBeenCalled()
 
     // succeed
     editDialog.succeeded.emit(true)
-    expect(toastInfoSpy).toHaveBeenCalled()
+    expect(notificationInfoSpy).toHaveBeenCalled()
   })
 
   it('should support reset', () => {
diff --git a/src-ui/src/app/components/app-frame/global-search/global-search.component.ts b/src-ui/src/app/components/app-frame/global-search/global-search.component.ts
index 8ef466d5b..dcf2c8cfc 100644
--- a/src-ui/src/app/components/app-frame/global-search/global-search.component.ts
+++ b/src-ui/src/app/components/app-frame/global-search/global-search.component.ts
@@ -31,6 +31,7 @@ import { GlobalSearchType, SETTINGS_KEYS } from 'src/app/data/ui-settings'
 import { CustomDatePipe } from 'src/app/pipes/custom-date.pipe'
 import { DocumentListViewService } from 'src/app/services/document-list-view.service'
 import { HotKeyService } from 'src/app/services/hot-key.service'
+import { NotificationService } from 'src/app/services/notification.service'
 import {
   PermissionAction,
   PermissionsService,
@@ -41,7 +42,6 @@ import {
   SearchService,
 } from 'src/app/services/rest/search.service'
 import { SettingsService } from 'src/app/services/settings.service'
-import { ToastService } from 'src/app/services/toast.service'
 import { paramsFromViewState } from 'src/app/utils/query-params'
 import { CorrespondentEditDialogComponent } from '../../common/edit-dialog/correspondent-edit-dialog/correspondent-edit-dialog.component'
 import { CustomFieldEditDialogComponent } from '../../common/edit-dialog/custom-field-edit-dialog/custom-field-edit-dialog.component'
@@ -97,7 +97,7 @@ export class GlobalSearchComponent implements OnInit {
     private documentService: DocumentService,
     private documentListViewService: DocumentListViewService,
     private permissionsService: PermissionsService,
-    private toastService: ToastService,
+    private notificationService: NotificationService,
     private hotkeyService: HotKeyService,
     private settingsService: SettingsService
   ) {
@@ -206,10 +206,15 @@ export class GlobalSearchComponent implements OnInit {
       modalRef.componentInstance.dialogMode = EditDialogMode.EDIT
       modalRef.componentInstance.object = object
       modalRef.componentInstance.succeeded.subscribe(() => {
-        this.toastService.showInfo($localize`Successfully updated object.`)
+        this.notificationService.showInfo(
+          $localize`Successfully updated object.`
+        )
       })
       modalRef.componentInstance.failed.subscribe((e) => {
-        this.toastService.showError($localize`Error occurred saving object.`, e)
+        this.notificationService.showError(
+          $localize`Error occurred saving object.`,
+          e
+        )
       })
     }
   }
@@ -244,10 +249,15 @@ export class GlobalSearchComponent implements OnInit {
       modalRef.componentInstance.dialogMode = EditDialogMode.EDIT
       modalRef.componentInstance.object = object
       modalRef.componentInstance.succeeded.subscribe(() => {
-        this.toastService.showInfo($localize`Successfully updated object.`)
+        this.notificationService.showInfo(
+          $localize`Successfully updated object.`
+        )
       })
       modalRef.componentInstance.failed.subscribe((e) => {
-        this.toastService.showError($localize`Error occurred saving object.`, e)
+        this.notificationService.showError(
+          $localize`Error occurred saving object.`,
+          e
+        )
       })
     }
   }
diff --git a/src-ui/src/app/components/app-frame/toasts-dropdown/toasts-dropdown.component.html b/src-ui/src/app/components/app-frame/notifications-dropdown/notifications-dropdown.component.html
similarity index 61%
rename from src-ui/src/app/components/app-frame/toasts-dropdown/toasts-dropdown.component.html
rename to src-ui/src/app/components/app-frame/notifications-dropdown/notifications-dropdown.component.html
index 6e49c1763..5710e67ef 100644
--- a/src-ui/src/app/components/app-frame/toasts-dropdown/toasts-dropdown.component.html
+++ b/src-ui/src/app/components/app-frame/notifications-dropdown/notifications-dropdown.component.html
@@ -1,7 +1,7 @@
 
 <li ngbDropdown class="nav-item" (openChange)="onOpenChange($event)">
-  @if (toasts.length) {
-    <span class="badge rounded-pill z-3 pe-none bg-secondary me-2 position-absolute top-0 left-0">{{ toasts.length }}</span>
+  @if (notifications.length) {
+    <span class="badge rounded-pill z-3 pe-none bg-secondary me-2 position-absolute top-0 left-0">{{ notifications.length }}</span>
   }
   <button class="btn border-0" id="notificationsDropdown" ngbDropdownToggle>
     <i-bs width="1.3em" height="1.3em" name="bell"></i-bs>
@@ -11,17 +11,17 @@
       <h6 i18n>Notifications</h6>
       <div class="btn-group ms-auto">
         <button class="btn btn-sm btn-outline-secondary mb-2 ms-auto"
-          (click)="toastService.clearToasts()"
-          [disabled]="toasts.length === 0"
+          (click)="notificationService.clearNotifications()"
+          [disabled]="notifications.length === 0"
           i18n>Clear All</button>
       </div>
     </div>
-    @if (toasts.length === 0) {
+    @if (notifications.length === 0) {
       <p class="text-center mb-0 small text-muted"><em i18n>No notifications</em></p>
     }
     <div class="scroll-list">
-      @for (toast of toasts; track toast.id) {
-        <pngx-toast [autohide]="false" [toast]="toast" (hidden)="onHidden(toast)" (close)="toastService.closeToast(toast)"></pngx-toast>
+      @for (notification of notifications; track notification.id) {
+        <pngx-notification [autohide]="false" [notification]="notification" (hidden)="onHidden(notification)" (close)="notificationService.closeNotification(notification)"></pngx-notification>
       }
       </div>
   </div>
diff --git a/src-ui/src/app/components/app-frame/toasts-dropdown/toasts-dropdown.component.scss b/src-ui/src/app/components/app-frame/notifications-dropdown/notifications-dropdown.component.scss
similarity index 86%
rename from src-ui/src/app/components/app-frame/toasts-dropdown/toasts-dropdown.component.scss
rename to src-ui/src/app/components/app-frame/notifications-dropdown/notifications-dropdown.component.scss
index 2332e710d..87bddd134 100644
--- a/src-ui/src/app/components/app-frame/toasts-dropdown/toasts-dropdown.component.scss
+++ b/src-ui/src/app/components/app-frame/notifications-dropdown/notifications-dropdown.component.scss
@@ -1,5 +1,5 @@
 .dropdown-menu {
-  width: var(--pngx-toast-max-width);
+  width: var(--pngx-notification-max-width);
 }
 
 .dropdown-menu .scroll-list {
diff --git a/src-ui/src/app/components/app-frame/toasts-dropdown/toasts-dropdown.component.spec.ts b/src-ui/src/app/components/app-frame/notifications-dropdown/notifications-dropdown.component.spec.ts
similarity index 58%
rename from src-ui/src/app/components/app-frame/toasts-dropdown/toasts-dropdown.component.spec.ts
rename to src-ui/src/app/components/app-frame/notifications-dropdown/notifications-dropdown.component.spec.ts
index 33b948f30..981c6ae8e 100644
--- a/src-ui/src/app/components/app-frame/toasts-dropdown/toasts-dropdown.component.spec.ts
+++ b/src-ui/src/app/components/app-frame/notifications-dropdown/notifications-dropdown.component.spec.ts
@@ -9,10 +9,13 @@ import {
 } from '@angular/core/testing'
 import { NgxBootstrapIconsModule, allIcons } from 'ngx-bootstrap-icons'
 import { Subject } from 'rxjs'
-import { Toast, ToastService } from 'src/app/services/toast.service'
-import { ToastsDropdownComponent } from './toasts-dropdown.component'
+import {
+  Notification,
+  NotificationService,
+} from 'src/app/services/notification.service'
+import { NotificationsDropdownComponent } from './notifications-dropdown.component'
 
-const toasts = [
+const notifications = [
   {
     id: 'abc-123',
     content: 'foo bar',
@@ -38,16 +41,16 @@ const toasts = [
   },
 ]
 
-describe('ToastsDropdownComponent', () => {
-  let component: ToastsDropdownComponent
-  let fixture: ComponentFixture<ToastsDropdownComponent>
-  let toastService: ToastService
-  let toastsSubject: Subject<Toast[]> = new Subject()
+describe('NotificationsDropdownComponent', () => {
+  let component: NotificationsDropdownComponent
+  let fixture: ComponentFixture<NotificationsDropdownComponent>
+  let notificationService: NotificationService
+  let notificationsSubject: Subject<Notification[]> = new Subject()
 
   beforeEach(async () => {
     TestBed.configureTestingModule({
       imports: [
-        ToastsDropdownComponent,
+        NotificationsDropdownComponent,
         NgxBootstrapIconsModule.pick(allIcons),
       ],
       providers: [
@@ -56,24 +59,26 @@ describe('ToastsDropdownComponent', () => {
       ],
     }).compileComponents()
 
-    fixture = TestBed.createComponent(ToastsDropdownComponent)
-    toastService = TestBed.inject(ToastService)
-    jest.spyOn(toastService, 'getToasts').mockReturnValue(toastsSubject)
+    fixture = TestBed.createComponent(NotificationsDropdownComponent)
+    notificationService = TestBed.inject(NotificationService)
+    jest
+      .spyOn(notificationService, 'getNotifications')
+      .mockReturnValue(notificationsSubject)
 
     component = fixture.componentInstance
 
     fixture.detectChanges()
   })
 
-  it('should call getToasts and return toasts', fakeAsync(() => {
-    const spy = jest.spyOn(toastService, 'getToasts')
+  it('should call getNotifications and return notifications', fakeAsync(() => {
+    const spy = jest.spyOn(notificationService, 'getNotifications')
 
     component.ngOnInit()
-    toastsSubject.next(toasts)
+    notificationsSubject.next(notifications)
     fixture.detectChanges()
 
     expect(spy).toHaveBeenCalled()
-    expect(component.toasts).toContainEqual({
+    expect(component.notifications).toContainEqual({
       id: 'abc-123',
       content: 'foo bar',
       delay: 5000,
@@ -84,9 +89,9 @@ describe('ToastsDropdownComponent', () => {
     discardPeriodicTasks()
   }))
 
-  it('should show a toast', fakeAsync(() => {
+  it('should show a notification', fakeAsync(() => {
     component.ngOnInit()
-    toastsSubject.next(toasts)
+    notificationsSubject.next(notifications)
     fixture.detectChanges()
 
     expect(fixture.nativeElement.textContent).toContain('foo bar')
@@ -96,12 +101,16 @@ describe('ToastsDropdownComponent', () => {
     discardPeriodicTasks()
   }))
 
-  it('should toggle suppressPopupToasts', fakeAsync((finish) => {
+  it('should toggle suppressPopupNotifications', fakeAsync((finish) => {
     component.ngOnInit()
     fixture.detectChanges()
-    toastsSubject.next(toasts)
+    notificationsSubject.next(notifications)
 
-    const spy = jest.spyOn(toastService, 'suppressPopupToasts', 'set')
+    const spy = jest.spyOn(
+      notificationService,
+      'suppressPopupNotifications',
+      'set'
+    )
     component.onOpenChange(true)
     expect(spy).toHaveBeenCalledWith(true)
 
diff --git a/src-ui/src/app/components/app-frame/notifications-dropdown/notifications-dropdown.component.ts b/src-ui/src/app/components/app-frame/notifications-dropdown/notifications-dropdown.component.ts
new file mode 100644
index 000000000..a610dd7d0
--- /dev/null
+++ b/src-ui/src/app/components/app-frame/notifications-dropdown/notifications-dropdown.component.ts
@@ -0,0 +1,47 @@
+import { Component, OnDestroy, OnInit } from '@angular/core'
+import {
+  NgbDropdownModule,
+  NgbProgressbarModule,
+} from '@ng-bootstrap/ng-bootstrap'
+import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons'
+import { Subscription } from 'rxjs'
+import {
+  Notification,
+  NotificationService,
+} from 'src/app/services/notification.service'
+import { NotificationComponent } from '../../common/notification/notification.component'
+
+@Component({
+  selector: 'pngx-notifications-dropdown',
+  templateUrl: './notifications-dropdown.component.html',
+  styleUrls: ['./notifications-dropdown.component.scss'],
+  imports: [
+    NotificationComponent,
+    NgbDropdownModule,
+    NgbProgressbarModule,
+    NgxBootstrapIconsModule,
+  ],
+})
+export class NotificationsDropdownComponent implements OnInit, OnDestroy {
+  constructor(public notificationService: NotificationService) {}
+
+  private subscription: Subscription
+
+  public notifications: Notification[] = []
+
+  ngOnDestroy(): void {
+    this.subscription?.unsubscribe()
+  }
+
+  ngOnInit(): void {
+    this.subscription = this.notificationService
+      .getNotifications()
+      .subscribe((notifications) => {
+        this.notifications = [...notifications]
+      })
+  }
+
+  onOpenChange(open: boolean): void {
+    this.notificationService.suppressPopupNotifications = open
+  }
+}
diff --git a/src-ui/src/app/components/app-frame/toasts-dropdown/toasts-dropdown.component.ts b/src-ui/src/app/components/app-frame/toasts-dropdown/toasts-dropdown.component.ts
deleted file mode 100644
index c04d758af..000000000
--- a/src-ui/src/app/components/app-frame/toasts-dropdown/toasts-dropdown.component.ts
+++ /dev/null
@@ -1,42 +0,0 @@
-import { Component, OnDestroy, OnInit } from '@angular/core'
-import {
-  NgbDropdownModule,
-  NgbProgressbarModule,
-} from '@ng-bootstrap/ng-bootstrap'
-import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons'
-import { Subscription } from 'rxjs'
-import { Toast, ToastService } from 'src/app/services/toast.service'
-import { ToastComponent } from '../../common/toast/toast.component'
-
-@Component({
-  selector: 'pngx-toasts-dropdown',
-  templateUrl: './toasts-dropdown.component.html',
-  styleUrls: ['./toasts-dropdown.component.scss'],
-  imports: [
-    ToastComponent,
-    NgbDropdownModule,
-    NgbProgressbarModule,
-    NgxBootstrapIconsModule,
-  ],
-})
-export class ToastsDropdownComponent implements OnInit, OnDestroy {
-  constructor(public toastService: ToastService) {}
-
-  private subscription: Subscription
-
-  public toasts: Toast[] = []
-
-  ngOnDestroy(): void {
-    this.subscription?.unsubscribe()
-  }
-
-  ngOnInit(): void {
-    this.subscription = this.toastService.getToasts().subscribe((toasts) => {
-      this.toasts = [...toasts]
-    })
-  }
-
-  onOpenChange(open: boolean): void {
-    this.toastService.suppressPopupToasts = open
-  }
-}
diff --git a/src-ui/src/app/components/common/custom-fields-dropdown/custom-fields-dropdown.component.spec.ts b/src-ui/src/app/components/common/custom-fields-dropdown/custom-fields-dropdown.component.spec.ts
index 78df9c74f..93f9e3571 100644
--- a/src-ui/src/app/components/common/custom-fields-dropdown/custom-fields-dropdown.component.spec.ts
+++ b/src-ui/src/app/components/common/custom-fields-dropdown/custom-fields-dropdown.component.spec.ts
@@ -18,9 +18,9 @@ import { NgSelectModule } from '@ng-select/ng-select'
 import { NgxBootstrapIconsModule, allIcons } from 'ngx-bootstrap-icons'
 import { of } from 'rxjs'
 import { CustomField, CustomFieldDataType } from 'src/app/data/custom-field'
+import { NotificationService } from 'src/app/services/notification.service'
 import { CustomFieldsService } from 'src/app/services/rest/custom-fields.service'
 import { SettingsService } from 'src/app/services/settings.service'
-import { ToastService } from 'src/app/services/toast.service'
 import { CustomFieldEditDialogComponent } from '../edit-dialog/custom-field-edit-dialog/custom-field-edit-dialog.component'
 import { SelectComponent } from '../input/select/select.component'
 import { CustomFieldsDropdownComponent } from './custom-fields-dropdown.component'
@@ -42,7 +42,7 @@ describe('CustomFieldsDropdownComponent', () => {
   let component: CustomFieldsDropdownComponent
   let fixture: ComponentFixture<CustomFieldsDropdownComponent>
   let customFieldService: CustomFieldsService
-  let toastService: ToastService
+  let notificationService: NotificationService
   let modalService: NgbModal
   let settingsService: SettingsService
 
@@ -64,7 +64,7 @@ describe('CustomFieldsDropdownComponent', () => {
       ],
     })
     customFieldService = TestBed.inject(CustomFieldsService)
-    toastService = TestBed.inject(ToastService)
+    notificationService = TestBed.inject(NotificationService)
     modalService = TestBed.inject(NgbModal)
     jest.spyOn(customFieldService, 'listAll').mockReturnValue(
       of({
@@ -113,8 +113,8 @@ describe('CustomFieldsDropdownComponent', () => {
   it('should support creating field, show error if necessary, then add', fakeAsync(() => {
     let modal: NgbModalRef
     modalService.activeInstances.subscribe((m) => (modal = m[m.length - 1]))
-    const toastErrorSpy = jest.spyOn(toastService, 'showError')
-    const toastInfoSpy = jest.spyOn(toastService, 'showInfo')
+    const notificationErrorSpy = jest.spyOn(notificationService, 'showError')
+    const notificationInfoSpy = jest.spyOn(notificationService, 'showInfo')
     const getFieldsSpy = jest.spyOn(
       CustomFieldsDropdownComponent.prototype as any,
       'getFields'
@@ -129,13 +129,13 @@ describe('CustomFieldsDropdownComponent', () => {
 
     // fail first
     editDialog.failed.emit({ error: 'error creating field' })
-    expect(toastErrorSpy).toHaveBeenCalled()
+    expect(notificationErrorSpy).toHaveBeenCalled()
     expect(getFieldsSpy).not.toHaveBeenCalled()
 
     // succeed
     editDialog.succeeded.emit(fields[0])
     tick(100)
-    expect(toastInfoSpy).toHaveBeenCalled()
+    expect(notificationInfoSpy).toHaveBeenCalled()
     expect(getFieldsSpy).toHaveBeenCalled()
     expect(addFieldSpy).toHaveBeenCalled()
   }))
diff --git a/src-ui/src/app/components/common/custom-fields-dropdown/custom-fields-dropdown.component.ts b/src-ui/src/app/components/common/custom-fields-dropdown/custom-fields-dropdown.component.ts
index 9e211edd0..ba23d5d05 100644
--- a/src-ui/src/app/components/common/custom-fields-dropdown/custom-fields-dropdown.component.ts
+++ b/src-ui/src/app/components/common/custom-fields-dropdown/custom-fields-dropdown.component.ts
@@ -14,13 +14,13 @@ import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons'
 import { first, takeUntil } from 'rxjs'
 import { CustomField, DATA_TYPE_LABELS } from 'src/app/data/custom-field'
 import { CustomFieldInstance } from 'src/app/data/custom-field-instance'
+import { NotificationService } from 'src/app/services/notification.service'
 import {
   PermissionAction,
   PermissionType,
   PermissionsService,
 } from 'src/app/services/permissions.service'
 import { CustomFieldsService } from 'src/app/services/rest/custom-fields.service'
-import { ToastService } from 'src/app/services/toast.service'
 import { LoadingComponentWithPermissions } from '../../loading-component/loading.component'
 import { CustomFieldEditDialogComponent } from '../edit-dialog/custom-field-edit-dialog/custom-field-edit-dialog.component'
 
@@ -78,7 +78,7 @@ export class CustomFieldsDropdownComponent extends LoadingComponentWithPermissio
   constructor(
     private customFieldsService: CustomFieldsService,
     private modalService: NgbModal,
-    private toastService: ToastService,
+    private notificationService: NotificationService,
     private permissionsService: PermissionsService
   ) {
     super()
@@ -123,7 +123,9 @@ export class CustomFieldsDropdownComponent extends LoadingComponentWithPermissio
     modal.componentInstance.succeeded
       .pipe(takeUntil(this.unsubscribeNotifier))
       .subscribe((newField) => {
-        this.toastService.showInfo($localize`Saved field "${newField.name}".`)
+        this.notificationService.showInfo(
+          $localize`Saved field "${newField.name}".`
+        )
         this.customFieldsService.clearCache()
         this.getFields()
         this.created.emit(newField)
@@ -132,7 +134,7 @@ export class CustomFieldsDropdownComponent extends LoadingComponentWithPermissio
     modal.componentInstance.failed
       .pipe(takeUntil(this.unsubscribeNotifier))
       .subscribe((e) => {
-        this.toastService.showError($localize`Error saving field.`, e)
+        this.notificationService.showError($localize`Error saving field.`, e)
       })
   }
 
diff --git a/src-ui/src/app/components/common/edit-dialog/user-edit-dialog/user-edit-dialog.component.spec.ts b/src-ui/src/app/components/common/edit-dialog/user-edit-dialog/user-edit-dialog.component.spec.ts
index 9ffa1ea95..b090a28af 100644
--- a/src-ui/src/app/components/common/edit-dialog/user-edit-dialog/user-edit-dialog.component.spec.ts
+++ b/src-ui/src/app/components/common/edit-dialog/user-edit-dialog/user-edit-dialog.component.spec.ts
@@ -12,11 +12,11 @@ import { NgxBootstrapIconsModule, allIcons } from 'ngx-bootstrap-icons'
 import { of, throwError } from 'rxjs'
 import { IfOwnerDirective } from 'src/app/directives/if-owner.directive'
 import { IfPermissionsDirective } from 'src/app/directives/if-permissions.directive'
+import { NotificationService } from 'src/app/services/notification.service'
 import { PermissionsService } from 'src/app/services/permissions.service'
 import { GroupService } from 'src/app/services/rest/group.service'
 import { UserService } from 'src/app/services/rest/user.service'
 import { SettingsService } from 'src/app/services/settings.service'
-import { ToastService } from 'src/app/services/toast.service'
 import { PasswordComponent } from '../../input/password/password.component'
 import { PermissionsFormComponent } from '../../input/permissions/permissions-form/permissions-form.component'
 import { SelectComponent } from '../../input/select/select.component'
@@ -29,7 +29,7 @@ describe('UserEditDialogComponent', () => {
   let component: UserEditDialogComponent
   let settingsService: SettingsService
   let permissionsService: PermissionsService
-  let toastService: ToastService
+  let notificationService: NotificationService
   let fixture: ComponentFixture<UserEditDialogComponent>
 
   beforeEach(async () => {
@@ -75,7 +75,7 @@ describe('UserEditDialogComponent', () => {
     settingsService = TestBed.inject(SettingsService)
     settingsService.currentUser = { id: 99, username: 'user99' }
     permissionsService = TestBed.inject(PermissionsService)
-    toastService = TestBed.inject(ToastService)
+    notificationService = TestBed.inject(NotificationService)
     component = fixture.componentInstance
 
     fixture.detectChanges()
@@ -133,22 +133,22 @@ describe('UserEditDialogComponent', () => {
       component['service'] as UserService,
       'deactivateTotp'
     )
-    const toastErrorSpy = jest.spyOn(toastService, 'showError')
-    const toastInfoSpy = jest.spyOn(toastService, 'showInfo')
+    const notificationErrorSpy = jest.spyOn(notificationService, 'showError')
+    const notificationInfoSpy = jest.spyOn(notificationService, 'showInfo')
     deactivateSpy.mockReturnValueOnce(throwError(() => new Error('error')))
     component.deactivateTotp()
     expect(deactivateSpy).toHaveBeenCalled()
-    expect(toastErrorSpy).toHaveBeenCalled()
+    expect(notificationErrorSpy).toHaveBeenCalled()
 
     deactivateSpy.mockReturnValueOnce(of(false))
     component.deactivateTotp()
     expect(deactivateSpy).toHaveBeenCalled()
-    expect(toastErrorSpy).toHaveBeenCalled()
+    expect(notificationErrorSpy).toHaveBeenCalled()
 
     deactivateSpy.mockReturnValueOnce(of(true))
     component.deactivateTotp()
     expect(deactivateSpy).toHaveBeenCalled()
-    expect(toastInfoSpy).toHaveBeenCalled()
+    expect(notificationInfoSpy).toHaveBeenCalled()
   })
 
   it('should check superuser status of current user', () => {
diff --git a/src-ui/src/app/components/common/edit-dialog/user-edit-dialog/user-edit-dialog.component.ts b/src-ui/src/app/components/common/edit-dialog/user-edit-dialog/user-edit-dialog.component.ts
index 7ba0f5ceb..298651c6a 100644
--- a/src-ui/src/app/components/common/edit-dialog/user-edit-dialog/user-edit-dialog.component.ts
+++ b/src-ui/src/app/components/common/edit-dialog/user-edit-dialog/user-edit-dialog.component.ts
@@ -10,11 +10,11 @@ import { first } from 'rxjs'
 import { EditDialogComponent } from 'src/app/components/common/edit-dialog/edit-dialog.component'
 import { Group } from 'src/app/data/group'
 import { User } from 'src/app/data/user'
+import { NotificationService } from 'src/app/services/notification.service'
 import { PermissionsService } from 'src/app/services/permissions.service'
 import { GroupService } from 'src/app/services/rest/group.service'
 import { UserService } from 'src/app/services/rest/user.service'
 import { SettingsService } from 'src/app/services/settings.service'
-import { ToastService } from 'src/app/services/toast.service'
 import { PasswordComponent } from '../../input/password/password.component'
 import { SelectComponent } from '../../input/select/select.component'
 import { TextComponent } from '../../input/text/text.component'
@@ -46,7 +46,7 @@ export class UserEditDialogComponent
     activeModal: NgbActiveModal,
     groupsService: GroupService,
     settingsService: SettingsService,
-    private toastService: ToastService,
+    private notificationService: NotificationService,
     private permissionsService: PermissionsService
   ) {
     super(service, activeModal, service, settingsService)
@@ -128,15 +128,20 @@ export class UserEditDialogComponent
         next: (result) => {
           this.totpLoading = false
           if (result) {
-            this.toastService.showInfo($localize`Totp deactivated`)
+            this.notificationService.showInfo($localize`Totp deactivated`)
             this.object.is_mfa_enabled = false
           } else {
-            this.toastService.showError($localize`Totp deactivation failed`)
+            this.notificationService.showError(
+              $localize`Totp deactivation failed`
+            )
           }
         },
         error: (e) => {
           this.totpLoading = false
-          this.toastService.showError($localize`Totp deactivation failed`, e)
+          this.notificationService.showError(
+            $localize`Totp deactivation failed`,
+            e
+          )
         },
       })
   }
diff --git a/src-ui/src/app/components/common/email-document-dialog/email-document-dialog.component.spec.ts b/src-ui/src/app/components/common/email-document-dialog/email-document-dialog.component.spec.ts
index 7a3659205..15495b369 100644
--- a/src-ui/src/app/components/common/email-document-dialog/email-document-dialog.component.spec.ts
+++ b/src-ui/src/app/components/common/email-document-dialog/email-document-dialog.component.spec.ts
@@ -6,9 +6,9 @@ import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'
 import { NgxBootstrapIconsModule, allIcons } from 'ngx-bootstrap-icons'
 import { of, throwError } from 'rxjs'
 import { IfPermissionsDirective } from 'src/app/directives/if-permissions.directive'
+import { NotificationService } from 'src/app/services/notification.service'
 import { PermissionsService } from 'src/app/services/permissions.service'
 import { DocumentService } from 'src/app/services/rest/document.service'
-import { ToastService } from 'src/app/services/toast.service'
 import { EmailDocumentDialogComponent } from './email-document-dialog.component'
 
 describe('EmailDocumentDialogComponent', () => {
@@ -16,7 +16,7 @@ describe('EmailDocumentDialogComponent', () => {
   let fixture: ComponentFixture<EmailDocumentDialogComponent>
   let documentService: DocumentService
   let permissionsService: PermissionsService
-  let toastService: ToastService
+  let notificationService: NotificationService
 
   beforeEach(async () => {
     await TestBed.configureTestingModule({
@@ -34,7 +34,7 @@ describe('EmailDocumentDialogComponent', () => {
 
     fixture = TestBed.createComponent(EmailDocumentDialogComponent)
     documentService = TestBed.inject(DocumentService)
-    toastService = TestBed.inject(ToastService)
+    notificationService = TestBed.inject(NotificationService)
     component = fixture.componentInstance
     fixture.detectChanges()
   })
@@ -47,8 +47,8 @@ describe('EmailDocumentDialogComponent', () => {
   })
 
   it('should support sending document via email, showing error if needed', () => {
-    const toastErrorSpy = jest.spyOn(toastService, 'showError')
-    const toastSuccessSpy = jest.spyOn(toastService, 'showInfo')
+    const notificationErrorSpy = jest.spyOn(notificationService, 'showError')
+    const notificationSuccessSpy = jest.spyOn(notificationService, 'showInfo')
     component.emailAddress = 'hello@paperless-ngx.com'
     component.emailSubject = 'Hello'
     component.emailMessage = 'World'
@@ -56,11 +56,11 @@ describe('EmailDocumentDialogComponent', () => {
       .spyOn(documentService, 'emailDocument')
       .mockReturnValue(throwError(() => new Error('Unable to email document')))
     component.emailDocument()
-    expect(toastErrorSpy).toHaveBeenCalled()
+    expect(notificationErrorSpy).toHaveBeenCalled()
 
     jest.spyOn(documentService, 'emailDocument').mockReturnValue(of(true))
     component.emailDocument()
-    expect(toastSuccessSpy).toHaveBeenCalled()
+    expect(notificationSuccessSpy).toHaveBeenCalled()
   })
 
   it('should close the dialog', () => {
diff --git a/src-ui/src/app/components/common/email-document-dialog/email-document-dialog.component.ts b/src-ui/src/app/components/common/email-document-dialog/email-document-dialog.component.ts
index ab8b9768b..a028ba702 100644
--- a/src-ui/src/app/components/common/email-document-dialog/email-document-dialog.component.ts
+++ b/src-ui/src/app/components/common/email-document-dialog/email-document-dialog.component.ts
@@ -2,8 +2,8 @@ import { Component, Input } from '@angular/core'
 import { FormsModule } from '@angular/forms'
 import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'
 import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons'
+import { NotificationService } from 'src/app/services/notification.service'
 import { DocumentService } from 'src/app/services/rest/document.service'
-import { ToastService } from 'src/app/services/toast.service'
 import { LoadingComponentWithPermissions } from '../../loading-component/loading.component'
 
 @Component({
@@ -40,7 +40,7 @@ export class EmailDocumentDialogComponent extends LoadingComponentWithPermission
   constructor(
     private activeModal: NgbActiveModal,
     private documentService: DocumentService,
-    private toastService: ToastService
+    private notificationService: NotificationService
   ) {
     super()
     this.loading = false
@@ -62,11 +62,14 @@ export class EmailDocumentDialogComponent extends LoadingComponentWithPermission
           this.emailAddress = ''
           this.emailSubject = ''
           this.emailMessage = ''
-          this.toastService.showInfo($localize`Email sent`)
+          this.notificationService.showInfo($localize`Email sent`)
         },
         error: (e) => {
           this.loading = false
-          this.toastService.showError($localize`Error emailing document`, e)
+          this.notificationService.showError(
+            $localize`Error emailing document`,
+            e
+          )
         },
       })
   }
diff --git a/src-ui/src/app/components/common/notification-list/notification-list.component.html b/src-ui/src/app/components/common/notification-list/notification-list.component.html
new file mode 100644
index 000000000..b383a142b
--- /dev/null
+++ b/src-ui/src/app/components/common/notification-list/notification-list.component.html
@@ -0,0 +1,3 @@
+@for (notification of notifications; track notification.id) {
+  <pngx-notification [notification]="notification" [autohide]="true" (close)="closeNotification()"></pngx-notification>
+}
diff --git a/src-ui/src/app/components/common/toasts/toasts.component.scss b/src-ui/src/app/components/common/notification-list/notification-list.component.scss
similarity index 73%
rename from src-ui/src/app/components/common/toasts/toasts.component.scss
rename to src-ui/src/app/components/common/notification-list/notification-list.component.scss
index e0a069dda..14729fb15 100644
--- a/src-ui/src/app/components/common/toasts/toasts.component.scss
+++ b/src-ui/src/app/components/common/notification-list/notification-list.component.scss
@@ -1,7 +1,7 @@
 :host {
   position: fixed;
   top: 0;
-  right: calc(50% - (var(--pngx-toast-max-width) / 2));
+  right: calc(50% - (var(--pngx-notification-max-width) / 2));
   margin: 0.3em;
   z-index: 1200;
 }
diff --git a/src-ui/src/app/components/common/notification-list/notification-list.component.spec.ts b/src-ui/src/app/components/common/notification-list/notification-list.component.spec.ts
new file mode 100644
index 000000000..69614aad2
--- /dev/null
+++ b/src-ui/src/app/components/common/notification-list/notification-list.component.spec.ts
@@ -0,0 +1,84 @@
+import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http'
+import { provideHttpClientTesting } from '@angular/common/http/testing'
+import { ComponentFixture, TestBed } from '@angular/core/testing'
+import { NgxBootstrapIconsModule, allIcons } from 'ngx-bootstrap-icons'
+import { Subject } from 'rxjs'
+import {
+  Notification,
+  NotificationService,
+} from 'src/app/services/notification.service'
+import { NotificationListComponent } from './notification-list.component'
+
+const notification = {
+  content: 'Error 2 content',
+  delay: 5000,
+  error: {
+    url: 'https://example.com',
+    status: 500,
+    statusText: 'Internal Server Error',
+    message: 'Internal server error 500 message',
+    error: { detail: 'Error 2 message details' },
+  },
+}
+
+describe('NotificationListComponent', () => {
+  let component: NotificationListComponent
+  let fixture: ComponentFixture<NotificationListComponent>
+  let notificationService: NotificationService
+  let notificationSubject: Subject<Notification> = new Subject()
+
+  beforeEach(async () => {
+    TestBed.configureTestingModule({
+      imports: [
+        NotificationListComponent,
+        NgxBootstrapIconsModule.pick(allIcons),
+      ],
+      providers: [
+        provideHttpClient(withInterceptorsFromDi()),
+        provideHttpClientTesting(),
+      ],
+    }).compileComponents()
+
+    fixture = TestBed.createComponent(NotificationListComponent)
+    notificationService = TestBed.inject(NotificationService)
+    jest.replaceProperty(
+      notificationService,
+      'showNotification',
+      notificationSubject
+    )
+
+    component = fixture.componentInstance
+
+    fixture.detectChanges()
+  })
+
+  it('should create', () => {
+    expect(component).toBeTruthy()
+  })
+
+  it('should close notification', () => {
+    component.notifications = [notification]
+    const closenotificationSpy = jest.spyOn(
+      notificationService,
+      'closeNotification'
+    )
+    component.closeNotification()
+    expect(component.notifications).toEqual([])
+    expect(closenotificationSpy).toHaveBeenCalledWith(notification)
+  })
+
+  it('should unsubscribe', () => {
+    const unsubscribeSpy = jest.spyOn(
+      (component as any).subscription,
+      'unsubscribe'
+    )
+    component.ngOnDestroy()
+    expect(unsubscribeSpy).toHaveBeenCalled()
+  })
+
+  it('should subscribe to notificationService', () => {
+    component.ngOnInit()
+    notificationSubject.next(notification)
+    expect(component.notifications).toEqual([notification])
+  })
+})
diff --git a/src-ui/src/app/components/common/notification-list/notification-list.component.ts b/src-ui/src/app/components/common/notification-list/notification-list.component.ts
new file mode 100644
index 000000000..8028f7801
--- /dev/null
+++ b/src-ui/src/app/components/common/notification-list/notification-list.component.ts
@@ -0,0 +1,48 @@
+import { Component, OnDestroy, OnInit } from '@angular/core'
+import {
+  NgbAccordionModule,
+  NgbProgressbarModule,
+} from '@ng-bootstrap/ng-bootstrap'
+import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons'
+import { Subscription } from 'rxjs'
+import {
+  Notification,
+  NotificationService,
+} from 'src/app/services/notification.service'
+import { NotificationComponent } from '../notification/notification.component'
+
+@Component({
+  selector: 'pngx-notification-list',
+  templateUrl: './notification-list.component.html',
+  styleUrls: ['./notification-list.component.scss'],
+  imports: [
+    NotificationComponent,
+    NgbAccordionModule,
+    NgbProgressbarModule,
+    NgxBootstrapIconsModule,
+  ],
+})
+export class NotificationListComponent implements OnInit, OnDestroy {
+  constructor(public notificationService: NotificationService) {}
+
+  private subscription: Subscription
+
+  public notifications: Notification[] = [] // array to force change detection
+
+  ngOnDestroy(): void {
+    this.subscription?.unsubscribe()
+  }
+
+  ngOnInit(): void {
+    this.subscription = this.notificationService.showNotification.subscribe(
+      (notification) => {
+        this.notifications = notification ? [notification] : []
+      }
+    )
+  }
+
+  closeNotification() {
+    this.notificationService.closeNotification(this.notifications[0])
+    this.notifications = []
+  }
+}
diff --git a/src-ui/src/app/components/common/toast/toast.component.html b/src-ui/src/app/components/common/notification/notification.component.html
similarity index 56%
rename from src-ui/src/app/components/common/toast/toast.component.html
rename to src-ui/src/app/components/common/notification/notification.component.html
index fc8e85e9c..4c46a1fa3 100644
--- a/src-ui/src/app/components/common/toast/toast.component.html
+++ b/src-ui/src/app/components/common/notification/notification.component.html
@@ -1,39 +1,39 @@
 <ngb-toast
     [autohide]="autohide"
-    [delay]="toast.delay"
-    [class]="toast.classname"
+    [delay]="notification.delay"
+    [class]="notification.classname"
     [class.mb-2]="true"
-    (shown)="onShown(toast)"
-    (hidden)="hidden.emit(toast)">
+    (shown)="onShown(notification)"
+    (hidden)="hidden.emit(notification)">
         @if (autohide) {
-            <ngb-progressbar class="position-absolute h-100 w-100 top-90 start-0 bottom-0 end-0 pe-none" type="dark" [max]="toast.delay" [value]="toast.delayRemaining"></ngb-progressbar>
-            <span class="visually-hidden">{{ toast.delayRemaining / 1000 | number: '1.0-0' }} seconds</span>
+            <ngb-progressbar class="position-absolute h-100 w-100 top-90 start-0 bottom-0 end-0 pe-none" type="dark" [max]="notification.delay" [value]="notification.delayRemaining"></ngb-progressbar>
+            <span class="visually-hidden">{{ notification.delayRemaining / 1000 | number: '1.0-0' }} seconds</span>
         }
         <div class="d-flex align-items-top">
-        @if (!toast.error) {
+        @if (!notification.error) {
             <i-bs width="0.9em" height="0.9em" name="info-circle"></i-bs>
         }
-        @if (toast.error) {
+        @if (notification.error) {
             <i-bs width="0.9em" height="0.9em" name="exclamation-triangle"></i-bs>
         }
         <div>
-            <p class="ms-2 mb-0">{{toast.content}}</p>
-            @if (toast.error) {
+            <p class="ms-2 mb-0">{{notification.content}}</p>
+            @if (notification.error) {
             <details class="ms-2">
                 <div class="mt-2 ms-n4 me-n2 small">
-                @if (isDetailedError(toast.error)) {
+                @if (isDetailedError(notification.error)) {
                     <dl class="row mb-0">
                     <dt class="col-sm-3 fw-normal text-end">URL</dt>
-                    <dd class="col-sm-9">{{ toast.error.url }}</dd>
+                    <dd class="col-sm-9">{{ notification.error.url }}</dd>
                     <dt class="col-sm-3 fw-normal text-end" i18n>Status</dt>
-                    <dd class="col-sm-9">{{ toast.error.status }} <em>{{ toast.error.statusText }}</em></dd>
+                    <dd class="col-sm-9">{{ notification.error.status }} <em>{{ notification.error.statusText }}</em></dd>
                     <dt class="col-sm-3 fw-normal text-end" i18n>Error</dt>
-                    <dd class="col-sm-9">{{ getErrorText(toast.error) }}</dd>
+                    <dd class="col-sm-9">{{ getErrorText(notification.error) }}</dd>
                     </dl>
                 }
                 <div class="row">
                     <div class="col offset-sm-3">
-                    <button class="btn btn-sm btn-outline-secondary" (click)="copyError(toast.error)">
+                    <button class="btn btn-sm btn-outline-secondary" (click)="copyError(notification.error)">
                         @if (!copied) {
                         <i-bs name="clipboard"></i-bs>&nbsp;
                         }
@@ -47,10 +47,10 @@
                 </div>
             </details>
             }
-            @if (toast.action) {
-            <p class="mb-0 mt-2"><button class="btn btn-sm btn-outline-secondary" (click)="close.emit(toast); toast.action()">{{toast.actionName}}</button></p>
+            @if (notification.action) {
+            <p class="mb-0 mt-2"><button class="btn btn-sm btn-outline-secondary" (click)="close.emit(notification); notification.action()">{{notification.actionName}}</button></p>
             }
         </div>
-        <button type="button" class="btn-close ms-auto flex-shrink-0" data-bs-dismiss="toast" aria-label="Close" (click)="close.emit(toast);"></button>
+        <button type="button" class="btn-close ms-auto flex-shrink-0" data-bs-dismiss="notification" aria-label="Close" (click)="close.emit(notification);"></button>
     </div>
 </ngb-toast>
diff --git a/src-ui/src/app/components/common/toast/toast.component.scss b/src-ui/src/app/components/common/notification/notification.component.scss
similarity index 100%
rename from src-ui/src/app/components/common/toast/toast.component.scss
rename to src-ui/src/app/components/common/notification/notification.component.scss
diff --git a/src-ui/src/app/components/common/toast/toast.component.spec.ts b/src-ui/src/app/components/common/notification/notification.component.spec.ts
similarity index 70%
rename from src-ui/src/app/components/common/toast/toast.component.spec.ts
rename to src-ui/src/app/components/common/notification/notification.component.spec.ts
index c5d52a28f..05e4c7a37 100644
--- a/src-ui/src/app/components/common/toast/toast.component.spec.ts
+++ b/src-ui/src/app/components/common/notification/notification.component.spec.ts
@@ -9,15 +9,15 @@ import {
 
 import { Clipboard } from '@angular/cdk/clipboard'
 import { allIcons, NgxBootstrapIconsModule } from 'ngx-bootstrap-icons'
-import { ToastComponent } from './toast.component'
+import { NotificationComponent } from './notification.component'
 
-const toast1 = {
+const notification1 = {
   content: 'Error 1 content',
   delay: 5000,
   error: 'Error 1 string',
 }
 
-const toast2 = {
+const notification2 = {
   content: 'Error 2 content',
   delay: 5000,
   error: {
@@ -29,17 +29,17 @@ const toast2 = {
   },
 }
 
-describe('ToastComponent', () => {
-  let component: ToastComponent
-  let fixture: ComponentFixture<ToastComponent>
+describe('NotificationComponent', () => {
+  let component: NotificationComponent
+  let fixture: ComponentFixture<NotificationComponent>
   let clipboard: Clipboard
 
   beforeEach(async () => {
     await TestBed.configureTestingModule({
-      imports: [ToastComponent, NgxBootstrapIconsModule.pick(allIcons)],
+      imports: [NotificationComponent, NgxBootstrapIconsModule.pick(allIcons)],
     }).compileComponents()
 
-    fixture = TestBed.createComponent(ToastComponent)
+    fixture = TestBed.createComponent(NotificationComponent)
     clipboard = TestBed.inject(Clipboard)
     component = fixture.componentInstance
   })
@@ -48,18 +48,18 @@ describe('ToastComponent', () => {
     expect(component).toBeTruthy()
   })
 
-  it('should countdown toast', fakeAsync(() => {
-    component.toast = toast2
+  it('should countdown notification', fakeAsync(() => {
+    component.notification = notification2
     fixture.detectChanges()
-    component.onShown(toast2)
+    component.onShown(notification2)
     tick(5000)
-    expect(component.toast.delayRemaining).toEqual(0)
+    expect(component.notification.delayRemaining).toEqual(0)
     flush()
     discardPeriodicTasks()
   }))
 
-  it('should show an error if given with toast', fakeAsync(() => {
-    component.toast = toast1
+  it('should show an error if given with notification', fakeAsync(() => {
+    component.notification = notification1
     fixture.detectChanges()
 
     expect(fixture.nativeElement.querySelector('details')).not.toBeNull()
@@ -70,7 +70,7 @@ describe('ToastComponent', () => {
   }))
 
   it('should show error details, support copy', fakeAsync(() => {
-    component.toast = toast2
+    component.notification = notification2
     fixture.detectChanges()
 
     expect(fixture.nativeElement.querySelector('details')).not.toBeNull()
@@ -79,7 +79,7 @@ describe('ToastComponent', () => {
     )
 
     const copySpy = jest.spyOn(clipboard, 'copy')
-    component.copyError(toast2.error)
+    component.copyError(notification2.error)
     expect(copySpy).toHaveBeenCalled()
 
     flush()
@@ -87,7 +87,7 @@ describe('ToastComponent', () => {
   }))
 
   it('should parse error text, add ellipsis', () => {
-    expect(component.getErrorText(toast2.error)).toEqual(
+    expect(component.getErrorText(notification2.error)).toEqual(
       'Error 2 message details'
     )
     expect(component.getErrorText({ error: 'Error string no detail' })).toEqual(
diff --git a/src-ui/src/app/components/common/toast/toast.component.ts b/src-ui/src/app/components/common/notification/notification.component.ts
similarity index 71%
rename from src-ui/src/app/components/common/toast/toast.component.ts
rename to src-ui/src/app/components/common/notification/notification.component.ts
index 5ebfdbe82..1c7bed37e 100644
--- a/src-ui/src/app/components/common/toast/toast.component.ts
+++ b/src-ui/src/app/components/common/notification/notification.component.ts
@@ -7,42 +7,43 @@ import {
 } from '@ng-bootstrap/ng-bootstrap'
 import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons'
 import { interval, take } from 'rxjs'
-import { Toast } from 'src/app/services/toast.service'
+import { Notification } from 'src/app/services/notification.service'
 
 @Component({
-  selector: 'pngx-toast',
+  selector: 'pngx-notification',
   imports: [
     DecimalPipe,
     NgbToastModule,
     NgbProgressbarModule,
     NgxBootstrapIconsModule,
   ],
-  templateUrl: './toast.component.html',
-  styleUrl: './toast.component.scss',
+  templateUrl: './notification.component.html',
+  styleUrl: './notification.component.scss',
 })
-export class ToastComponent {
-  @Input() toast: Toast
+export class NotificationComponent {
+  @Input() notification: Notification
 
   @Input() autohide: boolean = true
 
-  @Output() hidden: EventEmitter<Toast> = new EventEmitter<Toast>()
+  @Output() hidden: EventEmitter<Notification> =
+    new EventEmitter<Notification>()
 
-  @Output() close: EventEmitter<Toast> = new EventEmitter<Toast>()
+  @Output() close: EventEmitter<Notification> = new EventEmitter<Notification>()
 
   public copied: boolean = false
 
   constructor(private clipboard: Clipboard) {}
 
-  onShown(toast: Toast) {
+  onShown(notification: Notification) {
     if (!this.autohide) return
 
     const refreshInterval = 150
-    const delay = toast.delay - 500 // for fade animation
+    const delay = notification.delay - 500 // for fade animation
 
     interval(refreshInterval)
       .pipe(take(Math.round(delay / refreshInterval)))
       .subscribe((count) => {
-        toast.delayRemaining = Math.max(
+        notification.delayRemaining = Math.max(
           0,
           delay - refreshInterval * (count + 1)
         )
diff --git a/src-ui/src/app/components/common/profile-edit-dialog/profile-edit-dialog.component.spec.ts b/src-ui/src/app/components/common/profile-edit-dialog/profile-edit-dialog.component.spec.ts
index 64e122612..1edeedf68 100644
--- a/src-ui/src/app/components/common/profile-edit-dialog/profile-edit-dialog.component.spec.ts
+++ b/src-ui/src/app/components/common/profile-edit-dialog/profile-edit-dialog.component.spec.ts
@@ -16,8 +16,8 @@ import {
 } from '@ng-bootstrap/ng-bootstrap'
 import { NgxBootstrapIconsModule, allIcons } from 'ngx-bootstrap-icons'
 import { of, throwError } from 'rxjs'
+import { NotificationService } from 'src/app/services/notification.service'
 import { ProfileService } from 'src/app/services/profile.service'
-import { ToastService } from 'src/app/services/toast.service'
 import { ConfirmButtonComponent } from '../confirm-button/confirm-button.component'
 import { PasswordComponent } from '../input/password/password.component'
 import { TextComponent } from '../input/text/text.component'
@@ -44,7 +44,7 @@ describe('ProfileEditDialogComponent', () => {
   let component: ProfileEditDialogComponent
   let fixture: ComponentFixture<ProfileEditDialogComponent>
   let profileService: ProfileService
-  let toastService: ToastService
+  let notificationService: NotificationService
   let clipboard: Clipboard
 
   beforeEach(() => {
@@ -64,7 +64,7 @@ describe('ProfileEditDialogComponent', () => {
       providers: [NgbActiveModal, provideHttpClient(withInterceptorsFromDi())],
     })
     profileService = TestBed.inject(ProfileService)
-    toastService = TestBed.inject(ToastService)
+    notificationService = TestBed.inject(NotificationService)
     clipboard = TestBed.inject(Clipboard)
     fixture = TestBed.createComponent(ProfileEditDialogComponent)
     component = fixture.componentInstance
@@ -94,13 +94,13 @@ describe('ProfileEditDialogComponent', () => {
       auth_token: profile.auth_token,
     }
     const updateSpy = jest.spyOn(profileService, 'update')
-    const errorSpy = jest.spyOn(toastService, 'showError')
+    const errorSpy = jest.spyOn(notificationService, 'showError')
     updateSpy.mockReturnValueOnce(throwError(() => new Error('failed to save')))
     component.save()
     expect(errorSpy).toHaveBeenCalled()
 
     updateSpy.mockClear()
-    const infoSpy = jest.spyOn(toastService, 'showInfo')
+    const infoSpy = jest.spyOn(notificationService, 'showInfo')
     component.form.patchValue(newProfile)
     updateSpy.mockReturnValueOnce(of(newProfile))
     component.save()
@@ -239,7 +239,7 @@ describe('ProfileEditDialogComponent', () => {
     getSpy.mockReturnValue(of(profile))
 
     const generateSpy = jest.spyOn(profileService, 'generateAuthToken')
-    const errorSpy = jest.spyOn(toastService, 'showError')
+    const errorSpy = jest.spyOn(notificationService, 'showError')
     generateSpy.mockReturnValueOnce(
       throwError(() => new Error('failed to generate'))
     )
@@ -275,7 +275,7 @@ describe('ProfileEditDialogComponent', () => {
     getSpy.mockImplementation(() => of(profile))
     component.ngOnInit()
 
-    const errorSpy = jest.spyOn(toastService, 'showError')
+    const errorSpy = jest.spyOn(notificationService, 'showError')
 
     expect(component.socialAccounts).toContainEqual(socialAccount)
 
@@ -300,13 +300,13 @@ describe('ProfileEditDialogComponent', () => {
       secret: 'secret',
     }
     const getSpy = jest.spyOn(profileService, 'getTotpSettings')
-    const toastSpy = jest.spyOn(toastService, 'showError')
+    const notificationSpy = jest.spyOn(notificationService, 'showError')
     getSpy.mockReturnValueOnce(
       throwError(() => new Error('failed to get settings'))
     )
     component.gettotpSettings()
     expect(getSpy).toHaveBeenCalled()
-    expect(toastSpy).toHaveBeenCalled()
+    expect(notificationSpy).toHaveBeenCalled()
 
     getSpy.mockReturnValue(of(settings))
     component.gettotpSettings()
@@ -316,8 +316,8 @@ describe('ProfileEditDialogComponent', () => {
 
   it('should activate totp', () => {
     const activateSpy = jest.spyOn(profileService, 'activateTotp')
-    const toastErrorSpy = jest.spyOn(toastService, 'showError')
-    const toastInfoSpy = jest.spyOn(toastService, 'showInfo')
+    const notificationErrorSpy = jest.spyOn(notificationService, 'showError')
+    const notificationInfoSpy = jest.spyOn(notificationService, 'showInfo')
     const error = new Error('failed to activate totp')
     activateSpy.mockReturnValueOnce(throwError(() => error))
     component.totpSettings = {
@@ -331,38 +331,44 @@ describe('ProfileEditDialogComponent', () => {
       component.totpSettings.secret,
       component.form.get('totp_code').value
     )
-    expect(toastErrorSpy).toHaveBeenCalled()
+    expect(notificationErrorSpy).toHaveBeenCalled()
 
     activateSpy.mockReturnValueOnce(of({ success: false, recovery_codes: [] }))
     component.activateTotp()
-    expect(toastErrorSpy).toHaveBeenCalledWith('Error activating TOTP', error)
+    expect(notificationErrorSpy).toHaveBeenCalledWith(
+      'Error activating TOTP',
+      error
+    )
 
     activateSpy.mockReturnValueOnce(
       of({ success: true, recovery_codes: ['1', '2', '3'] })
     )
     component.activateTotp()
-    expect(toastInfoSpy).toHaveBeenCalled()
+    expect(notificationInfoSpy).toHaveBeenCalled()
     expect(component.isTotpEnabled).toBeTruthy()
     expect(component.recoveryCodes).toEqual(['1', '2', '3'])
   })
 
   it('should deactivate totp', () => {
     const deactivateSpy = jest.spyOn(profileService, 'deactivateTotp')
-    const toastErrorSpy = jest.spyOn(toastService, 'showError')
-    const toastInfoSpy = jest.spyOn(toastService, 'showInfo')
+    const notificationErrorSpy = jest.spyOn(notificationService, 'showError')
+    const notificationInfoSpy = jest.spyOn(notificationService, 'showInfo')
     const error = new Error('failed to deactivate totp')
     deactivateSpy.mockReturnValueOnce(throwError(() => error))
     component.deactivateTotp()
     expect(deactivateSpy).toHaveBeenCalled()
-    expect(toastErrorSpy).toHaveBeenCalled()
+    expect(notificationErrorSpy).toHaveBeenCalled()
 
     deactivateSpy.mockReturnValueOnce(of(false))
     component.deactivateTotp()
-    expect(toastErrorSpy).toHaveBeenCalledWith('Error deactivating TOTP', error)
+    expect(notificationErrorSpy).toHaveBeenCalledWith(
+      'Error deactivating TOTP',
+      error
+    )
 
     deactivateSpy.mockReturnValueOnce(of(true))
     component.deactivateTotp()
-    expect(toastInfoSpy).toHaveBeenCalled()
+    expect(notificationInfoSpy).toHaveBeenCalled()
     expect(component.isTotpEnabled).toBeFalsy()
   })
 
diff --git a/src-ui/src/app/components/common/profile-edit-dialog/profile-edit-dialog.component.ts b/src-ui/src/app/components/common/profile-edit-dialog/profile-edit-dialog.component.ts
index afffa7693..ea681a145 100644
--- a/src-ui/src/app/components/common/profile-edit-dialog/profile-edit-dialog.component.ts
+++ b/src-ui/src/app/components/common/profile-edit-dialog/profile-edit-dialog.component.ts
@@ -19,8 +19,8 @@ import {
   TotpSettings,
 } from 'src/app/data/user-profile'
 import { SafeHtmlPipe } from 'src/app/pipes/safehtml.pipe'
+import { NotificationService } from 'src/app/services/notification.service'
 import { ProfileService } from 'src/app/services/profile.service'
-import { ToastService } from 'src/app/services/toast.service'
 import { LoadingComponentWithPermissions } from '../../loading-component/loading.component'
 import { ConfirmButtonComponent } from '../confirm-button/confirm-button.component'
 import { PasswordComponent } from '../input/password/password.component'
@@ -86,7 +86,7 @@ export class ProfileEditDialogComponent
   constructor(
     private profileService: ProfileService,
     public activeModal: NgbActiveModal,
-    private toastService: ToastService,
+    private notificationService: NotificationService,
     private clipboard: Clipboard
   ) {
     super()
@@ -192,9 +192,11 @@ export class ProfileEditDialogComponent
       .pipe(takeUntil(this.unsubscribeNotifier))
       .subscribe({
         next: () => {
-          this.toastService.showInfo($localize`Profile updated successfully`)
+          this.notificationService.showInfo(
+            $localize`Profile updated successfully`
+          )
           if (passwordChanged) {
-            this.toastService.showInfo(
+            this.notificationService.showInfo(
               $localize`Password has been changed, you will be logged out momentarily.`
             )
             setTimeout(() => {
@@ -204,7 +206,10 @@ export class ProfileEditDialogComponent
           this.activeModal.close()
         },
         error: (error) => {
-          this.toastService.showError($localize`Error saving profile`, error)
+          this.notificationService.showError(
+            $localize`Error saving profile`,
+            error
+          )
           this.networkActive = false
         },
       })
@@ -220,7 +225,7 @@ export class ProfileEditDialogComponent
         this.form.patchValue({ auth_token: token })
       },
       error: (error) => {
-        this.toastService.showError(
+        this.notificationService.showError(
           $localize`Error generating auth token`,
           error
         )
@@ -245,7 +250,7 @@ export class ProfileEditDialogComponent
           this.socialAccounts = this.socialAccounts.filter((a) => a.id != id)
         },
         error: (error) => {
-          this.toastService.showError(
+          this.notificationService.showError(
             $localize`Error disconnecting social account`,
             error
           )
@@ -264,7 +269,7 @@ export class ProfileEditDialogComponent
           this.totpSettings = totpSettings
         },
         error: (error) => {
-          this.toastService.showError(
+          this.notificationService.showError(
             $localize`Error fetching TOTP settings`,
             error
           )
@@ -286,15 +291,20 @@ export class ProfileEditDialogComponent
           this.recoveryCodes = activationResponse.recovery_codes
           this.form.get('totp_code').enable()
           if (activationResponse.success) {
-            this.toastService.showInfo($localize`TOTP activated successfully`)
+            this.notificationService.showInfo(
+              $localize`TOTP activated successfully`
+            )
           } else {
-            this.toastService.showError($localize`Error activating TOTP`)
+            this.notificationService.showError($localize`Error activating TOTP`)
           }
         },
         error: (error) => {
           this.totpLoading = false
           this.form.get('totp_code').enable()
-          this.toastService.showError($localize`Error activating TOTP`, error)
+          this.notificationService.showError(
+            $localize`Error activating TOTP`,
+            error
+          )
         },
       })
   }
@@ -310,14 +320,21 @@ export class ProfileEditDialogComponent
           this.isTotpEnabled = !success
           this.recoveryCodes = null
           if (success) {
-            this.toastService.showInfo($localize`TOTP deactivated successfully`)
+            this.notificationService.showInfo(
+              $localize`TOTP deactivated successfully`
+            )
           } else {
-            this.toastService.showError($localize`Error deactivating TOTP`)
+            this.notificationService.showError(
+              $localize`Error deactivating TOTP`
+            )
           }
         },
         error: (error) => {
           this.totpLoading = false
-          this.toastService.showError($localize`Error deactivating TOTP`, error)
+          this.notificationService.showError(
+            $localize`Error deactivating TOTP`,
+            error
+          )
         },
       })
   }
diff --git a/src-ui/src/app/components/common/share-links-dialog/share-links-dialog.component.spec.ts b/src-ui/src/app/components/common/share-links-dialog/share-links-dialog.component.spec.ts
index 3f60b6733..b4c02289e 100644
--- a/src-ui/src/app/components/common/share-links-dialog/share-links-dialog.component.spec.ts
+++ b/src-ui/src/app/components/common/share-links-dialog/share-links-dialog.component.spec.ts
@@ -15,8 +15,8 @@ import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'
 import { NgxBootstrapIconsModule, allIcons } from 'ngx-bootstrap-icons'
 import { of, throwError } from 'rxjs'
 import { FileVersion, ShareLink } from 'src/app/data/share-link'
+import { NotificationService } from 'src/app/services/notification.service'
 import { ShareLinkService } from 'src/app/services/rest/share-link.service'
-import { ToastService } from 'src/app/services/toast.service'
 import { environment } from 'src/environments/environment'
 import { ShareLinksDialogComponent } from './share-links-dialog.component'
 
@@ -24,7 +24,7 @@ describe('ShareLinksDialogComponent', () => {
   let component: ShareLinksDialogComponent
   let fixture: ComponentFixture<ShareLinksDialogComponent>
   let shareLinkService: ShareLinkService
-  let toastService: ToastService
+  let notificationService: NotificationService
   let httpController: HttpTestingController
   let clipboard: Clipboard
 
@@ -43,7 +43,7 @@ describe('ShareLinksDialogComponent', () => {
 
     fixture = TestBed.createComponent(ShareLinksDialogComponent)
     shareLinkService = TestBed.inject(ShareLinkService)
-    toastService = TestBed.inject(ToastService)
+    notificationService = TestBed.inject(NotificationService)
     httpController = TestBed.inject(HttpTestingController)
     clipboard = TestBed.inject(Clipboard)
 
@@ -89,7 +89,7 @@ describe('ShareLinksDialogComponent', () => {
   })
 
   it('should show error on refresh if needed', () => {
-    const toastSpy = jest.spyOn(toastService, 'showError')
+    const notificationSpy = jest.spyOn(notificationService, 'showError')
     jest
       .spyOn(shareLinkService, 'getLinksForDocument')
       .mockReturnValueOnce(throwError(() => new Error('Unable to get links')))
@@ -97,7 +97,7 @@ describe('ShareLinksDialogComponent', () => {
 
     component.ngOnInit()
     fixture.detectChanges()
-    expect(toastSpy).toHaveBeenCalled()
+    expect(notificationSpy).toHaveBeenCalled()
   })
 
   it('should support link creation then refresh & copy url', fakeAsync(() => {
@@ -138,7 +138,7 @@ describe('ShareLinksDialogComponent', () => {
     const expiration = new Date()
     expiration.setDate(expiration.getDate() + 7)
 
-    const toastSpy = jest.spyOn(toastService, 'showError')
+    const notificationSpy = jest.spyOn(notificationService, 'showError')
 
     component.createLink()
 
@@ -150,7 +150,7 @@ describe('ShareLinksDialogComponent', () => {
       )
     fixture.detectChanges()
 
-    expect(toastSpy).toHaveBeenCalled()
+    expect(notificationSpy).toHaveBeenCalled()
   })
 
   it('should support delete links & refresh', () => {
@@ -165,13 +165,13 @@ describe('ShareLinksDialogComponent', () => {
   })
 
   it('should show error on delete if needed', () => {
-    const toastSpy = jest.spyOn(toastService, 'showError')
+    const notificationSpy = jest.spyOn(notificationService, 'showError')
     jest
       .spyOn(shareLinkService, 'delete')
       .mockReturnValueOnce(throwError(() => new Error('Unable to delete link')))
     component.delete(null)
     fixture.detectChanges()
-    expect(toastSpy).toHaveBeenCalled()
+    expect(notificationSpy).toHaveBeenCalled()
   })
 
   it('should format days remaining', () => {
diff --git a/src-ui/src/app/components/common/share-links-dialog/share-links-dialog.component.ts b/src-ui/src/app/components/common/share-links-dialog/share-links-dialog.component.ts
index 19123f73e..9d1350dcf 100644
--- a/src-ui/src/app/components/common/share-links-dialog/share-links-dialog.component.ts
+++ b/src-ui/src/app/components/common/share-links-dialog/share-links-dialog.component.ts
@@ -5,8 +5,8 @@ import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'
 import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons'
 import { first } from 'rxjs'
 import { FileVersion, ShareLink } from 'src/app/data/share-link'
+import { NotificationService } from 'src/app/services/notification.service'
 import { ShareLinkService } from 'src/app/services/rest/share-link.service'
-import { ToastService } from 'src/app/services/toast.service'
 import { environment } from 'src/environments/environment'
 
 @Component({
@@ -61,7 +61,7 @@ export class ShareLinksDialogComponent implements OnInit {
   constructor(
     private activeModal: NgbActiveModal,
     private shareLinkService: ShareLinkService,
-    private toastService: ToastService,
+    private notificationService: NotificationService,
     private clipboard: Clipboard
   ) {}
 
@@ -81,7 +81,7 @@ export class ShareLinksDialogComponent implements OnInit {
           this.shareLinks = results
         },
         error: (e) => {
-          this.toastService.showError(
+          this.notificationService.showError(
             $localize`Error retrieving links`,
             10000,
             e
@@ -130,7 +130,11 @@ export class ShareLinksDialogComponent implements OnInit {
         this.refresh()
       },
       error: (e) => {
-        this.toastService.showError($localize`Error deleting link`, 10000, e)
+        this.notificationService.showError(
+          $localize`Error deleting link`,
+          10000,
+          e
+        )
       },
     })
   }
@@ -158,7 +162,11 @@ export class ShareLinksDialogComponent implements OnInit {
         },
         error: (e) => {
           this.loading = false
-          this.toastService.showError($localize`Error creating link`, 10000, e)
+          this.notificationService.showError(
+            $localize`Error creating link`,
+            10000,
+            e
+          )
         },
       })
   }
diff --git a/src-ui/src/app/components/common/system-status-dialog/system-status-dialog.component.spec.ts b/src-ui/src/app/components/common/system-status-dialog/system-status-dialog.component.spec.ts
index 28a0889ab..45786900b 100644
--- a/src-ui/src/app/components/common/system-status-dialog/system-status-dialog.component.spec.ts
+++ b/src-ui/src/app/components/common/system-status-dialog/system-status-dialog.component.spec.ts
@@ -16,9 +16,9 @@ import {
   SystemStatus,
   SystemStatusItemStatus,
 } from 'src/app/data/system-status'
+import { NotificationService } from 'src/app/services/notification.service'
 import { SystemStatusService } from 'src/app/services/system-status.service'
 import { TasksService } from 'src/app/services/tasks.service'
-import { ToastService } from 'src/app/services/toast.service'
 import { SystemStatusDialogComponent } from './system-status-dialog.component'
 
 const status: SystemStatus = {
@@ -61,7 +61,7 @@ describe('SystemStatusDialogComponent', () => {
   let clipboard: Clipboard
   let tasksService: TasksService
   let systemStatusService: SystemStatusService
-  let toastService: ToastService
+  let notificationService: NotificationService
 
   beforeEach(async () => {
     await TestBed.configureTestingModule({
@@ -82,7 +82,7 @@ describe('SystemStatusDialogComponent', () => {
     clipboard = TestBed.inject(Clipboard)
     tasksService = TestBed.inject(TasksService)
     systemStatusService = TestBed.inject(SystemStatusService)
-    toastService = TestBed.inject(ToastService)
+    notificationService = TestBed.inject(NotificationService)
     fixture.detectChanges()
   })
 
@@ -116,9 +116,9 @@ describe('SystemStatusDialogComponent', () => {
     expect(component.isRunning(PaperlessTaskName.SanityCheck)).toBeFalsy()
   })
 
-  it('should support running tasks, refresh status and show toasts', () => {
-    const toastSpy = jest.spyOn(toastService, 'showInfo')
-    const toastErrorSpy = jest.spyOn(toastService, 'showError')
+  it('should support running tasks, refresh status and show notifications', () => {
+    const notificationSpy = jest.spyOn(notificationService, 'showInfo')
+    const notificationErrorSpy = jest.spyOn(notificationService, 'showError')
     const getStatusSpy = jest.spyOn(systemStatusService, 'get')
     const runSpy = jest.spyOn(tasksService, 'run')
 
@@ -126,7 +126,7 @@ describe('SystemStatusDialogComponent', () => {
     runSpy.mockReturnValue(throwError(() => new Error('error')))
     component.runTask(PaperlessTaskName.IndexOptimize)
     expect(runSpy).toHaveBeenCalledWith(PaperlessTaskName.IndexOptimize)
-    expect(toastErrorSpy).toHaveBeenCalledWith(
+    expect(notificationErrorSpy).toHaveBeenCalledWith(
       `Failed to start task ${PaperlessTaskName.IndexOptimize}, see the logs for more details`,
       expect.any(Error)
     )
@@ -138,7 +138,7 @@ describe('SystemStatusDialogComponent', () => {
     expect(runSpy).toHaveBeenCalledWith(PaperlessTaskName.IndexOptimize)
 
     expect(getStatusSpy).toHaveBeenCalled()
-    expect(toastSpy).toHaveBeenCalledWith(
+    expect(notificationSpy).toHaveBeenCalledWith(
       `Task ${PaperlessTaskName.IndexOptimize} started`
     )
   })
diff --git a/src-ui/src/app/components/common/system-status-dialog/system-status-dialog.component.ts b/src-ui/src/app/components/common/system-status-dialog/system-status-dialog.component.ts
index c7ba3c57a..b039b6244 100644
--- a/src-ui/src/app/components/common/system-status-dialog/system-status-dialog.component.ts
+++ b/src-ui/src/app/components/common/system-status-dialog/system-status-dialog.component.ts
@@ -14,10 +14,10 @@ import {
 } from 'src/app/data/system-status'
 import { CustomDatePipe } from 'src/app/pipes/custom-date.pipe'
 import { FileSizePipe } from 'src/app/pipes/file-size.pipe'
+import { NotificationService } from 'src/app/services/notification.service'
 import { PermissionsService } from 'src/app/services/permissions.service'
 import { SystemStatusService } from 'src/app/services/system-status.service'
 import { TasksService } from 'src/app/services/tasks.service'
-import { ToastService } from 'src/app/services/toast.service'
 
 @Component({
   selector: 'pngx-system-status-dialog',
@@ -51,7 +51,7 @@ export class SystemStatusDialogComponent {
     private clipboard: Clipboard,
     private systemStatusService: SystemStatusService,
     private tasksService: TasksService,
-    private toastService: ToastService,
+    private notificationService: NotificationService,
     private permissionsService: PermissionsService
   ) {}
 
@@ -79,7 +79,7 @@ export class SystemStatusDialogComponent {
 
   public runTask(taskName: PaperlessTaskName) {
     this.runningTasks.add(taskName)
-    this.toastService.showInfo(`Task ${taskName} started`)
+    this.notificationService.showInfo(`Task ${taskName} started`)
     this.tasksService.run(taskName).subscribe({
       next: () => {
         this.runningTasks.delete(taskName)
@@ -91,7 +91,7 @@ export class SystemStatusDialogComponent {
       },
       error: (err) => {
         this.runningTasks.delete(taskName)
-        this.toastService.showError(
+        this.notificationService.showError(
           `Failed to start task ${taskName}, see the logs for more details`,
           err
         )
diff --git a/src-ui/src/app/components/common/toasts/toasts.component.html b/src-ui/src/app/components/common/toasts/toasts.component.html
deleted file mode 100644
index 2178a2023..000000000
--- a/src-ui/src/app/components/common/toasts/toasts.component.html
+++ /dev/null
@@ -1,3 +0,0 @@
-@for (toast of toasts; track toast.id) {
-  <pngx-toast [toast]="toast" [autohide]="true" (close)="closeToast()"></pngx-toast>
-}
diff --git a/src-ui/src/app/components/common/toasts/toasts.component.spec.ts b/src-ui/src/app/components/common/toasts/toasts.component.spec.ts
deleted file mode 100644
index bbea04c9c..000000000
--- a/src-ui/src/app/components/common/toasts/toasts.component.spec.ts
+++ /dev/null
@@ -1,71 +0,0 @@
-import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http'
-import { provideHttpClientTesting } from '@angular/common/http/testing'
-import { ComponentFixture, TestBed } from '@angular/core/testing'
-import { NgxBootstrapIconsModule, allIcons } from 'ngx-bootstrap-icons'
-import { Subject } from 'rxjs'
-import { Toast, ToastService } from 'src/app/services/toast.service'
-import { ToastsComponent } from './toasts.component'
-
-const toast = {
-  content: 'Error 2 content',
-  delay: 5000,
-  error: {
-    url: 'https://example.com',
-    status: 500,
-    statusText: 'Internal Server Error',
-    message: 'Internal server error 500 message',
-    error: { detail: 'Error 2 message details' },
-  },
-}
-
-describe('ToastsComponent', () => {
-  let component: ToastsComponent
-  let fixture: ComponentFixture<ToastsComponent>
-  let toastService: ToastService
-  let toastSubject: Subject<Toast> = new Subject()
-
-  beforeEach(async () => {
-    TestBed.configureTestingModule({
-      imports: [ToastsComponent, NgxBootstrapIconsModule.pick(allIcons)],
-      providers: [
-        provideHttpClient(withInterceptorsFromDi()),
-        provideHttpClientTesting(),
-      ],
-    }).compileComponents()
-
-    fixture = TestBed.createComponent(ToastsComponent)
-    toastService = TestBed.inject(ToastService)
-    jest.replaceProperty(toastService, 'showToast', toastSubject)
-
-    component = fixture.componentInstance
-
-    fixture.detectChanges()
-  })
-
-  it('should create', () => {
-    expect(component).toBeTruthy()
-  })
-
-  it('should close toast', () => {
-    component.toasts = [toast]
-    const closeToastSpy = jest.spyOn(toastService, 'closeToast')
-    component.closeToast()
-    expect(component.toasts).toEqual([])
-    expect(closeToastSpy).toHaveBeenCalledWith(toast)
-  })
-
-  it('should unsubscribe', () => {
-    const unsubscribeSpy = jest.spyOn(
-      (component as any).subscription,
-      'unsubscribe'
-    )
-    component.ngOnDestroy()
-    expect(unsubscribeSpy).toHaveBeenCalled()
-  })
-
-  it('should subscribe to toastService', () => {
-    component.ngOnInit()
-    toastSubject.next(toast)
-    expect(component.toasts).toEqual([toast])
-  })
-})
diff --git a/src-ui/src/app/components/common/toasts/toasts.component.ts b/src-ui/src/app/components/common/toasts/toasts.component.ts
deleted file mode 100644
index 53b6e1895..000000000
--- a/src-ui/src/app/components/common/toasts/toasts.component.ts
+++ /dev/null
@@ -1,43 +0,0 @@
-import { Component, OnDestroy, OnInit } from '@angular/core'
-import {
-  NgbAccordionModule,
-  NgbProgressbarModule,
-} from '@ng-bootstrap/ng-bootstrap'
-import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons'
-import { Subscription } from 'rxjs'
-import { Toast, ToastService } from 'src/app/services/toast.service'
-import { ToastComponent } from '../toast/toast.component'
-
-@Component({
-  selector: 'pngx-toasts',
-  templateUrl: './toasts.component.html',
-  styleUrls: ['./toasts.component.scss'],
-  imports: [
-    ToastComponent,
-    NgbAccordionModule,
-    NgbProgressbarModule,
-    NgxBootstrapIconsModule,
-  ],
-})
-export class ToastsComponent implements OnInit, OnDestroy {
-  constructor(public toastService: ToastService) {}
-
-  private subscription: Subscription
-
-  public toasts: Toast[] = [] // array to force change detection
-
-  ngOnDestroy(): void {
-    this.subscription?.unsubscribe()
-  }
-
-  ngOnInit(): void {
-    this.subscription = this.toastService.showToast.subscribe((toast) => {
-      this.toasts = toast ? [toast] : []
-    })
-  }
-
-  closeToast() {
-    this.toastService.closeToast(this.toasts[0])
-    this.toasts = []
-  }
-}
diff --git a/src-ui/src/app/components/dashboard/dashboard.component.spec.ts b/src-ui/src/app/components/dashboard/dashboard.component.spec.ts
index 2ff5eab78..2459233dd 100644
--- a/src-ui/src/app/components/dashboard/dashboard.component.spec.ts
+++ b/src-ui/src/app/components/dashboard/dashboard.component.spec.ts
@@ -12,10 +12,10 @@ import { SavedView } from 'src/app/data/saved-view'
 import { SETTINGS_KEYS } from 'src/app/data/ui-settings'
 import { IfPermissionsDirective } from 'src/app/directives/if-permissions.directive'
 import { PermissionsGuard } from 'src/app/guards/permissions.guard'
+import { NotificationService } from 'src/app/services/notification.service'
 import { PermissionsService } from 'src/app/services/permissions.service'
 import { SavedViewService } from 'src/app/services/rest/saved-view.service'
 import { SettingsService } from 'src/app/services/settings.service'
-import { ToastService } from 'src/app/services/toast.service'
 import { LogoComponent } from '../common/logo/logo.component'
 import { PageHeaderComponent } from '../common/page-header/page-header.component'
 import { DashboardComponent } from './dashboard.component'
@@ -68,7 +68,7 @@ describe('DashboardComponent', () => {
   let fixture: ComponentFixture<DashboardComponent>
   let settingsService: SettingsService
   let tourService: TourService
-  let toastService: ToastService
+  let notificationService: NotificationService
 
   beforeEach(async () => {
     TestBed.configureTestingModule({
@@ -121,7 +121,7 @@ describe('DashboardComponent', () => {
       if (key === SETTINGS_KEYS.DASHBOARD_VIEWS_SORT_ORDER) return [0, 2, 3]
     })
     tourService = TestBed.inject(TourService)
-    toastService = TestBed.inject(ToastService)
+    notificationService = TestBed.inject(NotificationService)
     fixture = TestBed.createComponent(DashboardComponent)
     component = fixture.componentInstance
 
@@ -166,7 +166,7 @@ describe('DashboardComponent', () => {
 
   it('should update saved view sorting on drag + drop, show info', () => {
     const settingsSpy = jest.spyOn(settingsService, 'updateDashboardViewsSort')
-    const toastSpy = jest.spyOn(toastService, 'showInfo')
+    const notificationSpy = jest.spyOn(notificationService, 'showInfo')
     jest.spyOn(settingsService, 'storeSettings').mockReturnValue(of(true))
     component.onDrop({ previousIndex: 0, currentIndex: 1 } as CdkDragDrop<
       SavedView[]
@@ -176,7 +176,7 @@ describe('DashboardComponent', () => {
       saved_views[0],
       saved_views[3],
     ])
-    expect(toastSpy).toHaveBeenCalled()
+    expect(notificationSpy).toHaveBeenCalled()
   })
 
   it('should update saved view sorting on drag + drop, show error', () => {
@@ -187,13 +187,13 @@ describe('DashboardComponent', () => {
     fixture = TestBed.createComponent(DashboardComponent)
     component = fixture.componentInstance
     fixture.detectChanges()
-    const toastSpy = jest.spyOn(toastService, 'showError')
+    const notificationSpy = jest.spyOn(notificationService, 'showError')
     jest
       .spyOn(settingsService, 'storeSettings')
       .mockReturnValue(throwError(() => new Error('unable to save')))
     component.onDrop({ previousIndex: 0, currentIndex: 2 } as CdkDragDrop<
       SavedView[]
     >)
-    expect(toastSpy).toHaveBeenCalled()
+    expect(notificationSpy).toHaveBeenCalled()
   })
 })
diff --git a/src-ui/src/app/components/dashboard/dashboard.component.ts b/src-ui/src/app/components/dashboard/dashboard.component.ts
index 94637ba0f..5ebd39a61 100644
--- a/src-ui/src/app/components/dashboard/dashboard.component.ts
+++ b/src-ui/src/app/components/dashboard/dashboard.component.ts
@@ -9,9 +9,9 @@ import { Component } from '@angular/core'
 import { TourNgBootstrapModule, TourService } from 'ngx-ui-tour-ng-bootstrap'
 import { SavedView } from 'src/app/data/saved-view'
 import { IfPermissionsDirective } from 'src/app/directives/if-permissions.directive'
+import { NotificationService } from 'src/app/services/notification.service'
 import { SavedViewService } from 'src/app/services/rest/saved-view.service'
 import { SettingsService } from 'src/app/services/settings.service'
-import { ToastService } from 'src/app/services/toast.service'
 import { environment } from 'src/environments/environment'
 import { LogoComponent } from '../common/logo/logo.component'
 import { PageHeaderComponent } from '../common/page-header/page-header.component'
@@ -43,7 +43,7 @@ export class DashboardComponent extends ComponentWithPermissions {
     public settingsService: SettingsService,
     public savedViewService: SavedViewService,
     private tourService: TourService,
-    private toastService: ToastService
+    private notificationService: NotificationService
   ) {
     super()
 
@@ -87,10 +87,13 @@ export class DashboardComponent extends ComponentWithPermissions {
       .updateDashboardViewsSort(this.dashboardViews)
       .subscribe({
         next: () => {
-          this.toastService.showInfo($localize`Dashboard updated`)
+          this.notificationService.showInfo($localize`Dashboard updated`)
         },
         error: (e) => {
-          this.toastService.showError($localize`Error updating dashboard`, e)
+          this.notificationService.showError(
+            $localize`Error updating dashboard`,
+            e
+          )
         },
       })
   }
diff --git a/src-ui/src/app/components/document-detail/document-detail.component.spec.ts b/src-ui/src/app/components/document-detail/document-detail.component.spec.ts
index b85a7eaf4..28b2d85b5 100644
--- a/src-ui/src/app/components/document-detail/document-detail.component.spec.ts
+++ b/src-ui/src/app/components/document-detail/document-detail.component.spec.ts
@@ -48,6 +48,7 @@ import { CustomDatePipe } from 'src/app/pipes/custom-date.pipe'
 import { DocumentTitlePipe } from 'src/app/pipes/document-title.pipe'
 import { ComponentRouterService } from 'src/app/services/component-router.service'
 import { DocumentListViewService } from 'src/app/services/document-list-view.service'
+import { NotificationService } from 'src/app/services/notification.service'
 import { OpenDocumentsService } from 'src/app/services/open-documents.service'
 import { PermissionsService } from 'src/app/services/permissions.service'
 import { CorrespondentService } from 'src/app/services/rest/correspondent.service'
@@ -58,7 +59,6 @@ import { StoragePathService } from 'src/app/services/rest/storage-path.service'
 import { TagService } from 'src/app/services/rest/tag.service'
 import { UserService } from 'src/app/services/rest/user.service'
 import { SettingsService } from 'src/app/services/settings.service'
-import { ToastService } from 'src/app/services/toast.service'
 import { environment } from 'src/environments/environment'
 import { ConfirmDialogComponent } from '../common/confirm-dialog/confirm-dialog.component'
 import { CustomFieldsDropdownComponent } from '../common/custom-fields-dropdown/custom-fields-dropdown.component'
@@ -127,7 +127,7 @@ describe('DocumentDetailComponent', () => {
   let documentService: DocumentService
   let openDocumentsService: OpenDocumentsService
   let modalService: NgbModal
-  let toastService: ToastService
+  let notificationService: NotificationService
   let documentListViewService: DocumentListViewService
   let settingsService: SettingsService
   let customFieldsService: CustomFieldsService
@@ -264,7 +264,7 @@ describe('DocumentDetailComponent', () => {
     openDocumentsService = TestBed.inject(OpenDocumentsService)
     documentService = TestBed.inject(DocumentService)
     modalService = TestBed.inject(NgbModal)
-    toastService = TestBed.inject(ToastService)
+    notificationService = TestBed.inject(NotificationService)
     documentListViewService = TestBed.inject(DocumentListViewService)
     settingsService = TestBed.inject(SettingsService)
     settingsService.currentUser = { id: 1 }
@@ -447,68 +447,68 @@ describe('DocumentDetailComponent', () => {
     expect(navigateSpy).toHaveBeenCalledWith(['404'], { replaceUrl: true })
   })
 
-  it('should support save, close and show success toast', () => {
+  it('should support save, close and show success notification', () => {
     initNormally()
     component.title = 'Foo Bar'
     const closeSpy = jest.spyOn(component, 'close')
     const updateSpy = jest.spyOn(documentService, 'update')
-    const toastSpy = jest.spyOn(toastService, 'showInfo')
+    const notificationSpy = jest.spyOn(notificationService, 'showInfo')
     updateSpy.mockImplementation((o) => of(doc))
     component.save(true)
     expect(updateSpy).toHaveBeenCalled()
     expect(closeSpy).toHaveBeenCalled()
-    expect(toastSpy).toHaveBeenCalledWith(
+    expect(notificationSpy).toHaveBeenCalledWith(
       'Document "Doc 3" saved successfully.'
     )
   })
 
-  it('should support save without close and show success toast', () => {
+  it('should support save without close and show success notification', () => {
     initNormally()
     component.title = 'Foo Bar'
     const closeSpy = jest.spyOn(component, 'close')
     const updateSpy = jest.spyOn(documentService, 'update')
-    const toastSpy = jest.spyOn(toastService, 'showInfo')
+    const notificationSpy = jest.spyOn(notificationService, 'showInfo')
     updateSpy.mockImplementation((o) => of(doc))
     component.save()
     expect(updateSpy).toHaveBeenCalled()
     expect(closeSpy).not.toHaveBeenCalled()
-    expect(toastSpy).toHaveBeenCalledWith(
+    expect(notificationSpy).toHaveBeenCalledWith(
       'Document "Doc 3" saved successfully.'
     )
   })
 
-  it('should show toast error on save if error occurs', () => {
+  it('should show notification error on save if error occurs', () => {
     currentUserHasObjectPermissions = true
     initNormally()
     component.title = 'Foo Bar'
     const closeSpy = jest.spyOn(component, 'close')
     const updateSpy = jest.spyOn(documentService, 'update')
-    const toastSpy = jest.spyOn(toastService, 'showError')
+    const notificationSpy = jest.spyOn(notificationService, 'showError')
     const error = new Error('failed to save')
     updateSpy.mockImplementation(() => throwError(() => error))
     component.save()
     expect(updateSpy).toHaveBeenCalled()
     expect(closeSpy).not.toHaveBeenCalled()
-    expect(toastSpy).toHaveBeenCalledWith(
+    expect(notificationSpy).toHaveBeenCalledWith(
       'Error saving document "Doc 3"',
       error
     )
   })
 
-  it('should show error toast on save but close if user can no longer edit', () => {
+  it('should show error notification on save but close if user can no longer edit', () => {
     currentUserHasObjectPermissions = false
     initNormally()
     component.title = 'Foo Bar'
     const closeSpy = jest.spyOn(component, 'close')
     const updateSpy = jest.spyOn(documentService, 'update')
-    const toastSpy = jest.spyOn(toastService, 'showInfo')
+    const notificationSpy = jest.spyOn(notificationService, 'showInfo')
     updateSpy.mockImplementation(() =>
       throwError(() => new Error('failed to save'))
     )
     component.save(true)
     expect(updateSpy).toHaveBeenCalled()
     expect(closeSpy).toHaveBeenCalled()
-    expect(toastSpy).toHaveBeenCalledWith(
+    expect(notificationSpy).toHaveBeenCalledWith(
       'Document "Doc 3" saved successfully.'
     )
   })
@@ -531,19 +531,19 @@ describe('DocumentDetailComponent', () => {
     expect
   })
 
-  it('should show toast error on save & next if error occurs', () => {
+  it('should show notification error on save & next if error occurs', () => {
     currentUserHasObjectPermissions = true
     initNormally()
     component.title = 'Foo Bar'
     const closeSpy = jest.spyOn(component, 'close')
     const updateSpy = jest.spyOn(documentService, 'update')
-    const toastSpy = jest.spyOn(toastService, 'showError')
+    const notificationSpy = jest.spyOn(notificationService, 'showError')
     const error = new Error('failed to save')
     updateSpy.mockImplementation(() => throwError(() => error))
     component.saveEditNext()
     expect(updateSpy).toHaveBeenCalled()
     expect(closeSpy).not.toHaveBeenCalled()
-    expect(toastSpy).toHaveBeenCalledWith('Error saving document', error)
+    expect(notificationSpy).toHaveBeenCalledWith('Error saving document', error)
   })
 
   it('should show save button and save & close or save & next', () => {
@@ -668,13 +668,13 @@ describe('DocumentDetailComponent', () => {
     let openModal: NgbModalRef
     modalService.activeInstances.subscribe((modal) => (openModal = modal[0]))
     const modalSpy = jest.spyOn(modalService, 'open')
-    const toastSpy = jest.spyOn(toastService, 'showInfo')
+    const notificationSpy = jest.spyOn(notificationService, 'showInfo')
     component.reprocess()
     const modalCloseSpy = jest.spyOn(openModal, 'close')
     openModal.componentInstance.confirmClicked.next()
     expect(bulkEditSpy).toHaveBeenCalledWith([doc.id], 'reprocess', {})
     expect(modalSpy).toHaveBeenCalled()
-    expect(toastSpy).toHaveBeenCalled()
+    expect(notificationSpy).toHaveBeenCalled()
     expect(modalCloseSpy).toHaveBeenCalled()
   })
 
@@ -683,12 +683,12 @@ describe('DocumentDetailComponent', () => {
     const bulkEditSpy = jest.spyOn(documentService, 'bulkEdit')
     let openModal: NgbModalRef
     modalService.activeInstances.subscribe((modal) => (openModal = modal[0]))
-    const toastSpy = jest.spyOn(toastService, 'showError')
+    const notificationSpy = jest.spyOn(notificationService, 'showError')
     component.reprocess()
     const modalCloseSpy = jest.spyOn(openModal, 'close')
     bulkEditSpy.mockReturnValue(throwError(() => new Error('error occurred')))
     openModal.componentInstance.confirmClicked.next()
-    expect(toastSpy).toHaveBeenCalled()
+    expect(notificationSpy).toHaveBeenCalled()
     expect(modalCloseSpy).not.toHaveBeenCalled()
   })
 
@@ -942,9 +942,12 @@ describe('DocumentDetailComponent', () => {
     jest
       .spyOn(documentService, 'getMetadata')
       .mockReturnValue(throwError(() => error))
-    const toastSpy = jest.spyOn(toastService, 'showError')
+    const notificationSpy = jest.spyOn(notificationService, 'showError')
     initNormally()
-    expect(toastSpy).toHaveBeenCalledWith('Error retrieving metadata', error)
+    expect(notificationSpy).toHaveBeenCalledWith(
+      'Error retrieving metadata',
+      error
+    )
   })
 
   it('should display custom fields', () => {
@@ -1028,7 +1031,7 @@ describe('DocumentDetailComponent', () => {
 
   it('should show error if needed for get suggestions', () => {
     const suggestionsSpy = jest.spyOn(documentService, 'getSuggestions')
-    const errorSpy = jest.spyOn(toastService, 'showError')
+    const errorSpy = jest.spyOn(notificationService, 'showError')
     suggestionsSpy.mockImplementationOnce(() =>
       throwError(() => new Error('failed to get suggestions'))
     )
diff --git a/src-ui/src/app/components/document-detail/document-detail.component.ts b/src-ui/src/app/components/document-detail/document-detail.component.ts
index 27a74cfcd..ab8a8943e 100644
--- a/src-ui/src/app/components/document-detail/document-detail.component.ts
+++ b/src-ui/src/app/components/document-detail/document-detail.component.ts
@@ -63,6 +63,7 @@ import { SafeUrlPipe } from 'src/app/pipes/safeurl.pipe'
 import { ComponentRouterService } from 'src/app/services/component-router.service'
 import { DocumentListViewService } from 'src/app/services/document-list-view.service'
 import { HotKeyService } from 'src/app/services/hot-key.service'
+import { NotificationService } from 'src/app/services/notification.service'
 import { OpenDocumentsService } from 'src/app/services/open-documents.service'
 import {
   PermissionAction,
@@ -76,7 +77,6 @@ import { DocumentService } from 'src/app/services/rest/document.service'
 import { StoragePathService } from 'src/app/services/rest/storage-path.service'
 import { UserService } from 'src/app/services/rest/user.service'
 import { SettingsService } from 'src/app/services/settings.service'
-import { ToastService } from 'src/app/services/toast.service'
 import { ISODateAdapter } from 'src/app/utils/ngb-iso-date-adapter'
 import * as UTIF from 'utif'
 import { ConfirmDialogComponent } from '../common/confirm-dialog/confirm-dialog.component'
@@ -268,7 +268,7 @@ export class DocumentDetailComponent
     private openDocumentService: OpenDocumentsService,
     private documentListViewService: DocumentListViewService,
     private documentTitlePipe: DocumentTitlePipe,
-    private toastService: ToastService,
+    private notificationService: NotificationService,
     private settings: SettingsService,
     private storagePathService: StoragePathService,
     private permissionsService: PermissionsService,
@@ -628,7 +628,7 @@ export class DocumentDetailComponent
         },
         error: (error) => {
           this.metadata = {} // allow display to fallback to <object> tag
-          this.toastService.showError(
+          this.notificationService.showError(
             $localize`Error retrieving metadata`,
             error
           )
@@ -657,7 +657,7 @@ export class DocumentDetailComponent
           },
           error: (error) => {
             this.suggestions = null
-            this.toastService.showError(
+            this.notificationService.showError(
               $localize`Error retrieving suggestions.`,
               error
             )
@@ -809,7 +809,7 @@ export class DocumentDetailComponent
           this.store.next(newValues)
           this.openDocumentService.setDirty(this.document, false)
           this.openDocumentService.save()
-          this.toastService.showInfo(
+          this.notificationService.showInfo(
             $localize`Document "${newValues.title}" saved successfully.`
           )
           this.networkActive = false
@@ -825,13 +825,13 @@ export class DocumentDetailComponent
         error: (error) => {
           this.networkActive = false
           if (!this.userCanEdit) {
-            this.toastService.showInfo(
+            this.notificationService.showInfo(
               $localize`Document "${this.document.title}" saved successfully.`
             )
             close && this.close()
           } else {
             this.error = error.error
-            this.toastService.showError(
+            this.notificationService.showError(
               $localize`Error saving document "${this.document.title}"`,
               error
             )
@@ -877,7 +877,10 @@ export class DocumentDetailComponent
         error: (error) => {
           this.networkActive = false
           this.error = error.error
-          this.toastService.showError($localize`Error saving document`, error)
+          this.notificationService.showError(
+            $localize`Error saving document`,
+            error
+          )
         },
       })
   }
@@ -931,7 +934,10 @@ export class DocumentDetailComponent
           this.close()
         },
         error: (error) => {
-          this.toastService.showError($localize`Error deleting document`, error)
+          this.notificationService.showError(
+            $localize`Error deleting document`,
+            error
+          )
           modal.componentInstance.buttonsEnabled = true
           this.subscribeModalDelete(modal)
         },
@@ -962,7 +968,7 @@ export class DocumentDetailComponent
         .bulkEdit([this.document.id], 'reprocess', {})
         .subscribe({
           next: () => {
-            this.toastService.showInfo(
+            this.notificationService.showInfo(
               $localize`Reprocess operation for "${this.document.title}" will begin in the background. Close and re-open or reload this document after the operation has completed to see new content.`
             )
             if (modal) {
@@ -973,7 +979,7 @@ export class DocumentDetailComponent
             if (modal) {
               modal.componentInstance.buttonsEnabled = true
             }
-            this.toastService.showError(
+            this.notificationService.showError(
               $localize`Error executing operation`,
               error
             )
@@ -1020,7 +1026,7 @@ export class DocumentDetailComponent
       },
       error: (error) => {
         this.downloading = false
-        this.toastService.showError(
+        this.notificationService.showError(
           $localize`Error downloading document`,
           error
         )
@@ -1329,7 +1335,7 @@ export class DocumentDetailComponent
           .pipe(first(), takeUntil(this.unsubscribeNotifier))
           .subscribe({
             next: () => {
-              this.toastService.showInfo(
+              this.notificationService.showInfo(
                 $localize`Split operation for "${this.document.title}" will begin in the background.`
               )
               modal.close()
@@ -1338,7 +1344,7 @@ export class DocumentDetailComponent
               if (modal) {
                 modal.componentInstance.buttonsEnabled = true
               }
-              this.toastService.showError(
+              this.notificationService.showError(
                 $localize`Error executing split operation`,
                 error
               )
@@ -1368,7 +1374,7 @@ export class DocumentDetailComponent
           .pipe(first(), takeUntil(this.unsubscribeNotifier))
           .subscribe({
             next: () => {
-              this.toastService.show({
+              this.notificationService.show({
                 content: $localize`Rotation of "${this.document.title}" will begin in the background. Close and re-open the document after the operation has completed to see the changes.`,
                 delay: 8000,
                 action: this.close.bind(this),
@@ -1380,7 +1386,7 @@ export class DocumentDetailComponent
               if (modal) {
                 modal.componentInstance.buttonsEnabled = true
               }
-              this.toastService.showError(
+              this.notificationService.showError(
                 $localize`Error executing rotate operation`,
                 error
               )
@@ -1408,7 +1414,7 @@ export class DocumentDetailComponent
           .pipe(first(), takeUntil(this.unsubscribeNotifier))
           .subscribe({
             next: () => {
-              this.toastService.showInfo(
+              this.notificationService.showInfo(
                 $localize`Delete pages operation for "${this.document.title}" will begin in the background. Close and re-open or reload this document after the operation has completed to see the changes.`
               )
               modal.close()
@@ -1417,7 +1423,7 @@ export class DocumentDetailComponent
               if (modal) {
                 modal.componentInstance.buttonsEnabled = true
               }
-              this.toastService.showError(
+              this.notificationService.showError(
                 $localize`Error executing delete pages operation`,
                 error
               )
diff --git a/src-ui/src/app/components/document-list/bulk-editor/bulk-editor.component.spec.ts b/src-ui/src/app/components/document-list/bulk-editor/bulk-editor.component.spec.ts
index aa4a07d12..58e8d9beb 100644
--- a/src-ui/src/app/components/document-list/bulk-editor/bulk-editor.component.spec.ts
+++ b/src-ui/src/app/components/document-list/bulk-editor/bulk-editor.component.spec.ts
@@ -16,6 +16,7 @@ import { StoragePath } from 'src/app/data/storage-path'
 import { Tag } from 'src/app/data/tag'
 import { FilterPipe } from 'src/app/pipes/filter.pipe'
 import { DocumentListViewService } from 'src/app/services/document-list-view.service'
+import { NotificationService } from 'src/app/services/notification.service'
 import { PermissionsService } from 'src/app/services/permissions.service'
 import { CorrespondentService } from 'src/app/services/rest/correspondent.service'
 import { CustomFieldsService } from 'src/app/services/rest/custom-fields.service'
@@ -29,7 +30,6 @@ import { StoragePathService } from 'src/app/services/rest/storage-path.service'
 import { TagService } from 'src/app/services/rest/tag.service'
 import { UserService } from 'src/app/services/rest/user.service'
 import { SettingsService } from 'src/app/services/settings.service'
-import { ToastService } from 'src/app/services/toast.service'
 import { environment } from 'src/environments/environment'
 import { CorrespondentEditDialogComponent } from '../../common/edit-dialog/correspondent-edit-dialog/correspondent-edit-dialog.component'
 import { CustomFieldEditDialogComponent } from '../../common/edit-dialog/custom-field-edit-dialog/custom-field-edit-dialog.component'
@@ -64,7 +64,7 @@ describe('BulkEditorComponent', () => {
   let permissionsService: PermissionsService
   let documentListViewService: DocumentListViewService
   let documentService: DocumentService
-  let toastService: ToastService
+  let notificationService: NotificationService
   let modalService: NgbModal
   let tagService: TagService
   let correspondentsService: CorrespondentService
@@ -160,7 +160,7 @@ describe('BulkEditorComponent', () => {
     permissionsService = TestBed.inject(PermissionsService)
     documentListViewService = TestBed.inject(DocumentListViewService)
     documentService = TestBed.inject(DocumentService)
-    toastService = TestBed.inject(ToastService)
+    notificationService = TestBed.inject(NotificationService)
     modalService = TestBed.inject(NgbModal)
     tagService = TestBed.inject(TagService)
     correspondentsService = TestBed.inject(CorrespondentService)
@@ -884,7 +884,7 @@ describe('BulkEditorComponent', () => {
     expect(button.nativeElement.disabled).toBeTruthy()
   })
 
-  it('should show a warning toast on bulk edit error', () => {
+  it('should show a warning notification on bulk edit error', () => {
     jest
       .spyOn(documentService, 'bulkEdit')
       .mockReturnValue(
@@ -902,12 +902,12 @@ describe('BulkEditorComponent', () => {
       .mockReturnValue(true)
     component.showConfirmationDialogs = false
     fixture.detectChanges()
-    const toastSpy = jest.spyOn(toastService, 'showError')
+    const notificationSpy = jest.spyOn(notificationService, 'showError')
     component.setTags({
       itemsToAdd: [{ id: 0 }],
       itemsToRemove: [],
     })
-    expect(toastSpy).toHaveBeenCalled()
+    expect(notificationSpy).toHaveBeenCalled()
   })
 
   it('should support redo ocr', () => {
@@ -1391,8 +1391,14 @@ describe('BulkEditorComponent', () => {
       .spyOn(documentListViewService, 'selected', 'get')
       .mockReturnValue(new Set([3, 4]))
     fixture.detectChanges()
-    const toastServiceShowInfoSpy = jest.spyOn(toastService, 'showInfo')
-    const toastServiceShowErrorSpy = jest.spyOn(toastService, 'showError')
+    const notificationServiceShowInfoSpy = jest.spyOn(
+      notificationService,
+      'showInfo'
+    )
+    const notificationServiceShowErrorSpy = jest.spyOn(
+      notificationService,
+      'showError'
+    )
     const listReloadSpy = jest.spyOn(documentListViewService, 'reload')
 
     component.customFields = [
@@ -1410,11 +1416,11 @@ describe('BulkEditorComponent', () => {
     expect(modal.componentInstance.documents).toEqual([3, 4])
 
     modal.componentInstance.failed.emit()
-    expect(toastServiceShowErrorSpy).toHaveBeenCalled()
+    expect(notificationServiceShowErrorSpy).toHaveBeenCalled()
     expect(listReloadSpy).not.toHaveBeenCalled()
 
     modal.componentInstance.succeeded.emit()
-    expect(toastServiceShowInfoSpy).toHaveBeenCalled()
+    expect(notificationServiceShowInfoSpy).toHaveBeenCalled()
     expect(listReloadSpy).toHaveBeenCalled()
     httpTestingController.match(
       `${environment.apiBaseUrl}documents/?page=1&page_size=50&ordering=-created&truncate_content=true`
diff --git a/src-ui/src/app/components/document-list/bulk-editor/bulk-editor.component.ts b/src-ui/src/app/components/document-list/bulk-editor/bulk-editor.component.ts
index 9864761fa..72fccac0d 100644
--- a/src-ui/src/app/components/document-list/bulk-editor/bulk-editor.component.ts
+++ b/src-ui/src/app/components/document-list/bulk-editor/bulk-editor.component.ts
@@ -23,6 +23,7 @@ import { Tag } from 'src/app/data/tag'
 import { SETTINGS_KEYS } from 'src/app/data/ui-settings'
 import { IfPermissionsDirective } from 'src/app/directives/if-permissions.directive'
 import { DocumentListViewService } from 'src/app/services/document-list-view.service'
+import { NotificationService } from 'src/app/services/notification.service'
 import { OpenDocumentsService } from 'src/app/services/open-documents.service'
 import {
   PermissionAction,
@@ -39,7 +40,6 @@ import {
 import { StoragePathService } from 'src/app/services/rest/storage-path.service'
 import { TagService } from 'src/app/services/rest/tag.service'
 import { SettingsService } from 'src/app/services/settings.service'
-import { ToastService } from 'src/app/services/toast.service'
 import { MergeConfirmDialogComponent } from '../../common/confirm-dialog/merge-confirm-dialog/merge-confirm-dialog.component'
 import { RotateConfirmDialogComponent } from '../../common/confirm-dialog/rotate-confirm-dialog/rotate-confirm-dialog.component'
 import { CorrespondentEditDialogComponent } from '../../common/edit-dialog/correspondent-edit-dialog/correspondent-edit-dialog.component'
@@ -113,7 +113,7 @@ export class BulkEditorComponent
     private modalService: NgbModal,
     private openDocumentService: OpenDocumentsService,
     private settings: SettingsService,
-    private toastService: ToastService,
+    private notificationService: NotificationService,
     private storagePathService: StoragePathService,
     private customFieldService: CustomFieldsService,
     private permissionService: PermissionsService
@@ -284,7 +284,7 @@ export class BulkEditorComponent
           if (modal) {
             modal.componentInstance.buttonsEnabled = true
           }
-          this.toastService.showError(
+          this.notificationService.showError(
             $localize`Error executing bulk operation`,
             error
           )
@@ -859,7 +859,7 @@ export class BulkEditorComponent
         }
         mergeDialog.buttonsEnabled = false
         this.executeBulkOperation(modal, 'merge', args, mergeDialog.documentIDs)
-        this.toastService.showInfo(
+        this.notificationService.showInfo(
           $localize`Merged document will be queued for consumption.`
         )
       })
@@ -882,7 +882,7 @@ export class BulkEditorComponent
 
     dialog.documents = Array.from(this.list.selected)
     dialog.succeeded.subscribe((result) => {
-      this.toastService.showInfo($localize`Custom fields updated.`)
+      this.notificationService.showInfo($localize`Custom fields updated.`)
       this.list.reload()
       this.list.reduceSelectionToFilter()
       this.list.selected.forEach((id) => {
@@ -890,7 +890,7 @@ export class BulkEditorComponent
       })
     })
     dialog.failed.subscribe((error) => {
-      this.toastService.showError(
+      this.notificationService.showError(
         $localize`Error updating custom fields.`,
         error
       )
diff --git a/src-ui/src/app/components/document-list/document-list.component.spec.ts b/src-ui/src/app/components/document-list/document-list.component.spec.ts
index aae043fdb..290892411 100644
--- a/src-ui/src/app/components/document-list/document-list.component.spec.ts
+++ b/src-ui/src/app/components/document-list/document-list.component.spec.ts
@@ -39,11 +39,11 @@ import { FilterPipe } from 'src/app/pipes/filter.pipe'
 import { SafeHtmlPipe } from 'src/app/pipes/safehtml.pipe'
 import { UsernamePipe } from 'src/app/pipes/username.pipe'
 import { DocumentListViewService } from 'src/app/services/document-list-view.service'
+import { NotificationService } from 'src/app/services/notification.service'
 import { PermissionsService } from 'src/app/services/permissions.service'
 import { DocumentService } from 'src/app/services/rest/document.service'
 import { SavedViewService } from 'src/app/services/rest/saved-view.service'
 import { SettingsService } from 'src/app/services/settings.service'
-import { ToastService } from 'src/app/services/toast.service'
 import {
   FileStatus,
   WebsocketStatusService,
@@ -85,7 +85,7 @@ describe('DocumentListComponent', () => {
   let savedViewService: SavedViewService
   let router: Router
   let activatedRoute: ActivatedRoute
-  let toastService: ToastService
+  let notificationService: NotificationService
   let modalService: NgbModal
   let settingsService: SettingsService
   let permissionService: PermissionsService
@@ -116,7 +116,7 @@ describe('DocumentListComponent', () => {
     savedViewService = TestBed.inject(SavedViewService)
     router = TestBed.inject(Router)
     activatedRoute = TestBed.inject(ActivatedRoute)
-    toastService = TestBed.inject(ToastService)
+    notificationService = TestBed.inject(NotificationService)
     modalService = TestBed.inject(NgbModal)
     settingsService = TestBed.inject(SettingsService)
     permissionService = TestBed.inject(PermissionsService)
@@ -405,11 +405,11 @@ describe('DocumentListComponent', () => {
     delete modifiedView.name
     const savedViewServicePatch = jest.spyOn(savedViewService, 'patch')
     savedViewServicePatch.mockReturnValue(of(modifiedView))
-    const toastSpy = jest.spyOn(toastService, 'showInfo')
+    const notificationSpy = jest.spyOn(notificationService, 'showInfo')
 
     component.saveViewConfig()
     expect(savedViewServicePatch).toHaveBeenCalledWith(modifiedView)
-    expect(toastSpy).toHaveBeenCalledWith(
+    expect(notificationSpy).toHaveBeenCalledWith(
       `View "${view.name}" saved successfully.`
     )
   })
@@ -427,12 +427,12 @@ describe('DocumentListComponent', () => {
         },
       ],
     })
-    const toastErrorSpy = jest.spyOn(toastService, 'showError')
+    const notificationErrorSpy = jest.spyOn(notificationService, 'showError')
     jest
       .spyOn(savedViewService, 'patch')
       .mockReturnValueOnce(throwError(() => new Error('Error saving view')))
     component.saveViewConfig()
-    expect(toastErrorSpy).toHaveBeenCalledWith(
+    expect(notificationErrorSpy).toHaveBeenCalledWith(
       'Failed to save view "Saved View 10".',
       expect.any(Error)
     )
@@ -467,7 +467,7 @@ describe('DocumentListComponent', () => {
     let openModal: NgbModalRef
     modalService.activeInstances.subscribe((modal) => (openModal = modal[0]))
     const modalSpy = jest.spyOn(modalService, 'open')
-    const toastSpy = jest.spyOn(toastService, 'showInfo')
+    const notificationSpy = jest.spyOn(notificationService, 'showInfo')
     const savedViewServiceCreate = jest.spyOn(savedViewService, 'create')
     savedViewServiceCreate.mockReturnValueOnce(of(modifiedView))
     component.saveViewConfigAs()
@@ -480,7 +480,7 @@ describe('DocumentListComponent', () => {
     })
     expect(savedViewServiceCreate).toHaveBeenCalled()
     expect(modalSpy).toHaveBeenCalled()
-    expect(toastSpy).toHaveBeenCalled()
+    expect(notificationSpy).toHaveBeenCalled()
     expect(modalCloseSpy).toHaveBeenCalled()
   })
 
diff --git a/src-ui/src/app/components/document-list/document-list.component.ts b/src-ui/src/app/components/document-list/document-list.component.ts
index f6b7c181b..397cd0e53 100644
--- a/src-ui/src/app/components/document-list/document-list.component.ts
+++ b/src-ui/src/app/components/document-list/document-list.component.ts
@@ -45,11 +45,11 @@ import { StoragePathNamePipe } from 'src/app/pipes/storage-path-name.pipe'
 import { UsernamePipe } from 'src/app/pipes/username.pipe'
 import { DocumentListViewService } from 'src/app/services/document-list-view.service'
 import { HotKeyService } from 'src/app/services/hot-key.service'
+import { NotificationService } from 'src/app/services/notification.service'
 import { OpenDocumentsService } from 'src/app/services/open-documents.service'
 import { PermissionsService } from 'src/app/services/permissions.service'
 import { SavedViewService } from 'src/app/services/rest/saved-view.service'
 import { SettingsService } from 'src/app/services/settings.service'
-import { ToastService } from 'src/app/services/toast.service'
 import { WebsocketStatusService } from 'src/app/services/websocket-status.service'
 import {
   filterRulesDiffer,
@@ -111,7 +111,7 @@ export class DocumentListComponent
     public savedViewService: SavedViewService,
     public route: ActivatedRoute,
     private router: Router,
-    private toastService: ToastService,
+    private notificationService: NotificationService,
     private modalService: NgbModal,
     private websocketStatusService: WebsocketStatusService,
     public openDocumentsService: OpenDocumentsService,
@@ -380,13 +380,13 @@ export class DocumentListComponent
         .subscribe({
           next: (view) => {
             this.unmodifiedSavedView = view
-            this.toastService.showInfo(
+            this.notificationService.showInfo(
               $localize`View "${this.list.activeSavedViewTitle}" saved successfully.`
             )
             this.unmodifiedFilterRules = this.list.filterRules
           },
           error: (err) => {
-            this.toastService.showError(
+            this.notificationService.showError(
               $localize`Failed to save view "${this.list.activeSavedViewTitle}".`,
               err
             )
@@ -430,7 +430,7 @@ export class DocumentListComponent
         .subscribe({
           next: () => {
             modal.close()
-            this.toastService.showInfo(
+            this.notificationService.showInfo(
               $localize`View "${savedView.name}" created successfully.`
             )
           },
diff --git a/src-ui/src/app/components/document-notes/document-notes.component.spec.ts b/src-ui/src/app/components/document-notes/document-notes.component.spec.ts
index 1c86c03a5..ff5714538 100644
--- a/src-ui/src/app/components/document-notes/document-notes.component.spec.ts
+++ b/src-ui/src/app/components/document-notes/document-notes.component.spec.ts
@@ -9,10 +9,10 @@ import { of, throwError } from 'rxjs'
 import { DocumentNote } from 'src/app/data/document-note'
 import { IfPermissionsDirective } from 'src/app/directives/if-permissions.directive'
 import { CustomDatePipe } from 'src/app/pipes/custom-date.pipe'
+import { NotificationService } from 'src/app/services/notification.service'
 import { PermissionsService } from 'src/app/services/permissions.service'
 import { DocumentNotesService } from 'src/app/services/rest/document-notes.service'
 import { UserService } from 'src/app/services/rest/user.service'
-import { ToastService } from 'src/app/services/toast.service'
 import { DocumentNotesComponent } from './document-notes.component'
 
 const notes: DocumentNote[] = [
@@ -52,7 +52,7 @@ describe('DocumentNotesComponent', () => {
   let component: DocumentNotesComponent
   let fixture: ComponentFixture<DocumentNotesComponent>
   let notesService: DocumentNotesService
-  let toastService: ToastService
+  let notificationService: NotificationService
 
   beforeEach(async () => {
     TestBed.configureTestingModule({
@@ -103,7 +103,7 @@ describe('DocumentNotesComponent', () => {
     }).compileComponents()
 
     notesService = TestBed.inject(DocumentNotesService)
-    toastService = TestBed.inject(ToastService)
+    notificationService = TestBed.inject(NotificationService)
     fixture = TestBed.createComponent(DocumentNotesComponent)
     component = fixture.componentInstance
     fixture.detectChanges()
@@ -162,11 +162,11 @@ describe('DocumentNotesComponent', () => {
     fixture.detectChanges()
     const addSpy = jest.spyOn(notesService, 'addNote')
     addSpy.mockReturnValueOnce(throwError(() => new Error('error saving note')))
-    const toastsSpy = jest.spyOn(toastService, 'showError')
+    const notificationsSpy = jest.spyOn(notificationService, 'showError')
     const addButton = fixture.debugElement.query(By.css('button'))
     addButton.triggerEventHandler('click')
     expect(addSpy).toHaveBeenCalledWith(12, note)
-    expect(toastsSpy).toHaveBeenCalled()
+    expect(notificationsSpy).toHaveBeenCalled()
 
     addSpy.mockReturnValueOnce(
       of([...notes, { id: 31, note, user: { id: 1 } }])
@@ -194,7 +194,7 @@ describe('DocumentNotesComponent', () => {
     fixture.detectChanges()
     const deleteButton = fixture.debugElement.queryAll(By.css('button'))[1] // 0 is add button
     const deleteSpy = jest.spyOn(notesService, 'deleteNote')
-    const toastsSpy = jest.spyOn(toastService, 'showError')
+    const toastsSpy = jest.spyOn(notificationService, 'showError')
     deleteSpy.mockReturnValueOnce(
       throwError(() => new Error('error deleting note'))
     )
diff --git a/src-ui/src/app/components/document-notes/document-notes.component.ts b/src-ui/src/app/components/document-notes/document-notes.component.ts
index 685d2d93e..32877826f 100644
--- a/src-ui/src/app/components/document-notes/document-notes.component.ts
+++ b/src-ui/src/app/components/document-notes/document-notes.component.ts
@@ -10,9 +10,9 @@ import { DocumentNote } from 'src/app/data/document-note'
 import { User } from 'src/app/data/user'
 import { IfPermissionsDirective } from 'src/app/directives/if-permissions.directive'
 import { CustomDatePipe } from 'src/app/pipes/custom-date.pipe'
+import { NotificationService } from 'src/app/services/notification.service'
 import { DocumentNotesService } from 'src/app/services/rest/document-notes.service'
 import { UserService } from 'src/app/services/rest/user.service'
-import { ToastService } from 'src/app/services/toast.service'
 import { ComponentWithPermissions } from '../with-permissions/with-permissions.component'
 
 @Component({
@@ -50,7 +50,7 @@ export class DocumentNotesComponent extends ComponentWithPermissions {
 
   constructor(
     private notesService: DocumentNotesService,
-    private toastService: ToastService,
+    private notificationService: NotificationService,
     private usersService: UserService
   ) {
     super()
@@ -78,7 +78,7 @@ export class DocumentNotesComponent extends ComponentWithPermissions {
       },
       error: (e) => {
         this.networkActive = false
-        this.toastService.showError($localize`Error saving note`, e)
+        this.notificationService.showError($localize`Error saving note`, e)
       },
     })
   }
@@ -92,7 +92,7 @@ export class DocumentNotesComponent extends ComponentWithPermissions {
       },
       error: (e) => {
         this.networkActive = false
-        this.toastService.showError($localize`Error deleting note`, e)
+        this.notificationService.showError($localize`Error deleting note`, e)
       },
     })
   }
diff --git a/src-ui/src/app/components/file-drop/file-drop.component.spec.ts b/src-ui/src/app/components/file-drop/file-drop.component.spec.ts
index bd3a56a3f..fa5ebc807 100644
--- a/src-ui/src/app/components/file-drop/file-drop.component.spec.ts
+++ b/src-ui/src/app/components/file-drop/file-drop.component.spec.ts
@@ -10,24 +10,28 @@ import {
 } from '@angular/core/testing'
 import { By } from '@angular/platform-browser'
 import { NgxFileDropEntry, NgxFileDropModule } from 'ngx-file-drop'
+import { NotificationService } from 'src/app/services/notification.service'
 import { PermissionsService } from 'src/app/services/permissions.service'
 import { SettingsService } from 'src/app/services/settings.service'
-import { ToastService } from 'src/app/services/toast.service'
 import { UploadDocumentsService } from 'src/app/services/upload-documents.service'
-import { ToastsComponent } from '../common/toasts/toasts.component'
+import { NotificationListComponent } from '../common/notification-list/notification-list.component'
 import { FileDropComponent } from './file-drop.component'
 
 describe('FileDropComponent', () => {
   let component: FileDropComponent
   let fixture: ComponentFixture<FileDropComponent>
   let permissionsService: PermissionsService
-  let toastService: ToastService
+  let notificationService: NotificationService
   let settingsService: SettingsService
   let uploadDocumentsService: UploadDocumentsService
 
   beforeEach(() => {
     TestBed.configureTestingModule({
-      imports: [NgxFileDropModule, FileDropComponent, ToastsComponent],
+      imports: [
+        NgxFileDropModule,
+        FileDropComponent,
+        NotificationListComponent,
+      ],
       providers: [
         provideHttpClient(withInterceptorsFromDi()),
         provideHttpClientTesting(),
@@ -36,7 +40,7 @@ describe('FileDropComponent', () => {
 
     permissionsService = TestBed.inject(PermissionsService)
     settingsService = TestBed.inject(SettingsService)
-    toastService = TestBed.inject(ToastService)
+    notificationService = TestBed.inject(NotificationService)
     uploadDocumentsService = TestBed.inject(UploadDocumentsService)
 
     fixture = TestBed.createComponent(FileDropComponent)
@@ -101,7 +105,7 @@ describe('FileDropComponent', () => {
     fixture.detectChanges()
     expect(dropzone.classes['hide']).toBeTruthy()
     // drop
-    const toastSpy = jest.spyOn(toastService, 'show')
+    const notificationSpy = jest.spyOn(notificationService, 'show')
     const uploadSpy = jest.spyOn(
       UploadDocumentsService.prototype as any,
       'uploadFile'
@@ -135,7 +139,7 @@ describe('FileDropComponent', () => {
       } as unknown as NgxFileDropEntry,
     ])
     tick(3000)
-    expect(toastSpy).toHaveBeenCalled()
+    expect(notificationSpy).toHaveBeenCalled()
     expect(uploadSpy).toHaveBeenCalled()
     discardPeriodicTasks()
   }))
diff --git a/src-ui/src/app/components/file-drop/file-drop.component.ts b/src-ui/src/app/components/file-drop/file-drop.component.ts
index 49eb423b2..65ebe6523 100644
--- a/src-ui/src/app/components/file-drop/file-drop.component.ts
+++ b/src-ui/src/app/components/file-drop/file-drop.component.ts
@@ -4,13 +4,13 @@ import {
   NgxFileDropEntry,
   NgxFileDropModule,
 } from 'ngx-file-drop'
+import { NotificationService } from 'src/app/services/notification.service'
 import {
   PermissionAction,
   PermissionsService,
   PermissionType,
 } from 'src/app/services/permissions.service'
 import { SettingsService } from 'src/app/services/settings.service'
-import { ToastService } from 'src/app/services/toast.service'
 import { UploadDocumentsService } from 'src/app/services/upload-documents.service'
 
 @Component({
@@ -26,7 +26,7 @@ export class FileDropComponent {
 
   constructor(
     private settings: SettingsService,
-    private toastService: ToastService,
+    private notificationService: NotificationService,
     private uploadDocumentsService: UploadDocumentsService,
     private permissionsService: PermissionsService
   ) {}
@@ -90,7 +90,7 @@ export class FileDropComponent {
   public dropped(files: NgxFileDropEntry[]) {
     this.uploadDocumentsService.onNgxFileDrop(files)
     if (files.length > 0)
-      this.toastService.showInfo($localize`Initiating upload...`, 3000)
+      this.notificationService.showInfo($localize`Initiating upload...`, 3000)
   }
 
   @HostListener('window:blur', ['$event']) public onWindowBlur() {
diff --git a/src-ui/src/app/components/manage/correspondent-list/correspondent-list.component.ts b/src-ui/src/app/components/manage/correspondent-list/correspondent-list.component.ts
index 62427f5b4..e47422a58 100644
--- a/src-ui/src/app/components/manage/correspondent-list/correspondent-list.component.ts
+++ b/src-ui/src/app/components/manage/correspondent-list/correspondent-list.component.ts
@@ -13,12 +13,12 @@ import { IfPermissionsDirective } from 'src/app/directives/if-permissions.direct
 import { SortableDirective } from 'src/app/directives/sortable.directive'
 import { CustomDatePipe } from 'src/app/pipes/custom-date.pipe'
 import { DocumentListViewService } from 'src/app/services/document-list-view.service'
+import { NotificationService } from 'src/app/services/notification.service'
 import {
   PermissionsService,
   PermissionType,
 } from 'src/app/services/permissions.service'
 import { CorrespondentService } from 'src/app/services/rest/correspondent.service'
-import { ToastService } from 'src/app/services/toast.service'
 import { CorrespondentEditDialogComponent } from '../../common/edit-dialog/correspondent-edit-dialog/correspondent-edit-dialog.component'
 import { PageHeaderComponent } from '../../common/page-header/page-header.component'
 import { ManagementListComponent } from '../management-list/management-list.component'
@@ -45,7 +45,7 @@ export class CorrespondentListComponent extends ManagementListComponent<Correspo
   constructor(
     correspondentsService: CorrespondentService,
     modalService: NgbModal,
-    toastService: ToastService,
+    notificationService: NotificationService,
     documentListViewService: DocumentListViewService,
     permissionsService: PermissionsService,
     private datePipe: CustomDatePipe
@@ -54,7 +54,7 @@ export class CorrespondentListComponent extends ManagementListComponent<Correspo
       correspondentsService,
       modalService,
       CorrespondentEditDialogComponent,
-      toastService,
+      notificationService,
       documentListViewService,
       permissionsService,
       FILTER_HAS_CORRESPONDENT_ANY,
diff --git a/src-ui/src/app/components/manage/custom-fields/custom-fields.component.spec.ts b/src-ui/src/app/components/manage/custom-fields/custom-fields.component.spec.ts
index e94470d64..a27702ad2 100644
--- a/src-ui/src/app/components/manage/custom-fields/custom-fields.component.spec.ts
+++ b/src-ui/src/app/components/manage/custom-fields/custom-fields.component.spec.ts
@@ -21,10 +21,10 @@ import {
 import { FILTER_CUSTOM_FIELDS_QUERY } from 'src/app/data/filter-rule-type'
 import { IfPermissionsDirective } from 'src/app/directives/if-permissions.directive'
 import { DocumentListViewService } from 'src/app/services/document-list-view.service'
+import { NotificationService } from 'src/app/services/notification.service'
 import { PermissionsService } from 'src/app/services/permissions.service'
 import { CustomFieldsService } from 'src/app/services/rest/custom-fields.service'
 import { SettingsService } from 'src/app/services/settings.service'
-import { ToastService } from 'src/app/services/toast.service'
 import { ConfirmDialogComponent } from '../../common/confirm-dialog/confirm-dialog.component'
 import { CustomFieldEditDialogComponent } from '../../common/edit-dialog/custom-field-edit-dialog/custom-field-edit-dialog.component'
 import { PageHeaderComponent } from '../../common/page-header/page-header.component'
@@ -48,7 +48,7 @@ describe('CustomFieldsComponent', () => {
   let fixture: ComponentFixture<CustomFieldsComponent>
   let customFieldsService: CustomFieldsService
   let modalService: NgbModal
-  let toastService: ToastService
+  let notificationService: NotificationService
   let listViewService: DocumentListViewService
   let settingsService: SettingsService
 
@@ -89,7 +89,7 @@ describe('CustomFieldsComponent', () => {
       })
     )
     modalService = TestBed.inject(NgbModal)
-    toastService = TestBed.inject(ToastService)
+    notificationService = TestBed.inject(NotificationService)
     listViewService = TestBed.inject(DocumentListViewService)
     settingsService = TestBed.inject(SettingsService)
     settingsService.currentUser = { id: 0, username: 'test' }
@@ -104,8 +104,8 @@ describe('CustomFieldsComponent', () => {
   it('should support create, show notification on error / success', () => {
     let modal: NgbModalRef
     modalService.activeInstances.subscribe((m) => (modal = m[m.length - 1]))
-    const toastErrorSpy = jest.spyOn(toastService, 'showError')
-    const toastInfoSpy = jest.spyOn(toastService, 'showInfo')
+    const notificationErrorSpy = jest.spyOn(notificationService, 'showError')
+    const notificationInfoSpy = jest.spyOn(notificationService, 'showInfo')
     const reloadSpy = jest.spyOn(component, 'reload')
 
     const createButton = fixture.debugElement.queryAll(By.css('button'))[1]
@@ -116,12 +116,12 @@ describe('CustomFieldsComponent', () => {
 
     // fail first
     editDialog.failed.emit({ error: 'error creating item' })
-    expect(toastErrorSpy).toHaveBeenCalled()
+    expect(notificationErrorSpy).toHaveBeenCalled()
     expect(reloadSpy).not.toHaveBeenCalled()
 
     // succeed
     editDialog.succeeded.emit(fields[0])
-    expect(toastInfoSpy).toHaveBeenCalled()
+    expect(notificationInfoSpy).toHaveBeenCalled()
     expect(reloadSpy).toHaveBeenCalled()
     jest.advanceTimersByTime(100)
   })
@@ -129,8 +129,8 @@ describe('CustomFieldsComponent', () => {
   it('should support edit, show notification on error / success', () => {
     let modal: NgbModalRef
     modalService.activeInstances.subscribe((m) => (modal = m[m.length - 1]))
-    const toastErrorSpy = jest.spyOn(toastService, 'showError')
-    const toastInfoSpy = jest.spyOn(toastService, 'showInfo')
+    const notificationErrorSpy = jest.spyOn(notificationService, 'showError')
+    const notificationInfoSpy = jest.spyOn(notificationService, 'showInfo')
     const reloadSpy = jest.spyOn(component, 'reload')
 
     const editButton = fixture.debugElement.queryAll(By.css('button'))[2]
@@ -142,19 +142,19 @@ describe('CustomFieldsComponent', () => {
 
     // fail first
     editDialog.failed.emit({ error: 'error editing item' })
-    expect(toastErrorSpy).toHaveBeenCalled()
+    expect(notificationErrorSpy).toHaveBeenCalled()
     expect(reloadSpy).not.toHaveBeenCalled()
 
     // succeed
     editDialog.succeeded.emit(fields[0])
-    expect(toastInfoSpy).toHaveBeenCalled()
+    expect(notificationInfoSpy).toHaveBeenCalled()
     expect(reloadSpy).toHaveBeenCalled()
   })
 
   it('should support delete, show notification on error / success', () => {
     let modal: NgbModalRef
     modalService.activeInstances.subscribe((m) => (modal = m[m.length - 1]))
-    const toastErrorSpy = jest.spyOn(toastService, 'showError')
+    const notificationErrorSpy = jest.spyOn(notificationService, 'showError')
     const deleteSpy = jest.spyOn(customFieldsService, 'delete')
     const reloadSpy = jest.spyOn(component, 'reload')
 
@@ -167,7 +167,7 @@ describe('CustomFieldsComponent', () => {
     // fail first
     deleteSpy.mockReturnValueOnce(throwError(() => new Error('error deleting')))
     editDialog.confirmClicked.emit()
-    expect(toastErrorSpy).toHaveBeenCalled()
+    expect(notificationErrorSpy).toHaveBeenCalled()
     expect(reloadSpy).not.toHaveBeenCalled()
 
     // succeed
diff --git a/src-ui/src/app/components/manage/custom-fields/custom-fields.component.ts b/src-ui/src/app/components/manage/custom-fields/custom-fields.component.ts
index b4fd9738d..caf20bbb1 100644
--- a/src-ui/src/app/components/manage/custom-fields/custom-fields.component.ts
+++ b/src-ui/src/app/components/manage/custom-fields/custom-fields.component.ts
@@ -14,12 +14,12 @@ import {
 import { FILTER_CUSTOM_FIELDS_QUERY } from 'src/app/data/filter-rule-type'
 import { IfPermissionsDirective } from 'src/app/directives/if-permissions.directive'
 import { DocumentListViewService } from 'src/app/services/document-list-view.service'
+import { NotificationService } from 'src/app/services/notification.service'
 import { PermissionsService } from 'src/app/services/permissions.service'
 import { CustomFieldsService } from 'src/app/services/rest/custom-fields.service'
 import { DocumentService } from 'src/app/services/rest/document.service'
 import { SavedViewService } from 'src/app/services/rest/saved-view.service'
 import { SettingsService } from 'src/app/services/settings.service'
-import { ToastService } from 'src/app/services/toast.service'
 import { ConfirmDialogComponent } from '../../common/confirm-dialog/confirm-dialog.component'
 import { CustomFieldEditDialogComponent } from '../../common/edit-dialog/custom-field-edit-dialog/custom-field-edit-dialog.component'
 import { EditDialogMode } from '../../common/edit-dialog/edit-dialog.component'
@@ -48,7 +48,7 @@ export class CustomFieldsComponent
     private customFieldsService: CustomFieldsService,
     public permissionsService: PermissionsService,
     private modalService: NgbModal,
-    private toastService: ToastService,
+    private notificationService: NotificationService,
     private documentListViewService: DocumentListViewService,
     private settingsService: SettingsService,
     private documentService: DocumentService,
@@ -86,7 +86,9 @@ export class CustomFieldsComponent
     modal.componentInstance.succeeded
       .pipe(takeUntil(this.unsubscribeNotifier))
       .subscribe((newField) => {
-        this.toastService.showInfo($localize`Saved field "${newField.name}".`)
+        this.notificationService.showInfo(
+          $localize`Saved field "${newField.name}".`
+        )
         this.customFieldsService.clearCache()
         this.settingsService.initializeDisplayFields()
         this.documentService.reload()
@@ -95,7 +97,7 @@ export class CustomFieldsComponent
     modal.componentInstance.failed
       .pipe(takeUntil(this.unsubscribeNotifier))
       .subscribe((e) => {
-        this.toastService.showError($localize`Error saving field.`, e)
+        this.notificationService.showError($localize`Error saving field.`, e)
       })
   }
 
@@ -113,7 +115,9 @@ export class CustomFieldsComponent
       this.customFieldsService.delete(field).subscribe({
         next: () => {
           modal.close()
-          this.toastService.showInfo($localize`Deleted field "${field.name}"`)
+          this.notificationService.showInfo(
+            $localize`Deleted field "${field.name}"`
+          )
           this.customFieldsService.clearCache()
           this.settingsService.initializeDisplayFields()
           this.documentService.reload()
@@ -121,7 +125,7 @@ export class CustomFieldsComponent
           this.reload()
         },
         error: (e) => {
-          this.toastService.showError(
+          this.notificationService.showError(
             $localize`Error deleting field "${field.name}".`,
             e
           )
diff --git a/src-ui/src/app/components/manage/document-type-list/document-type-list.component.ts b/src-ui/src/app/components/manage/document-type-list/document-type-list.component.ts
index 0bca3df1b..6c353f91d 100644
--- a/src-ui/src/app/components/manage/document-type-list/document-type-list.component.ts
+++ b/src-ui/src/app/components/manage/document-type-list/document-type-list.component.ts
@@ -12,12 +12,12 @@ import { FILTER_HAS_DOCUMENT_TYPE_ANY } from 'src/app/data/filter-rule-type'
 import { IfPermissionsDirective } from 'src/app/directives/if-permissions.directive'
 import { SortableDirective } from 'src/app/directives/sortable.directive'
 import { DocumentListViewService } from 'src/app/services/document-list-view.service'
+import { NotificationService } from 'src/app/services/notification.service'
 import {
   PermissionsService,
   PermissionType,
 } from 'src/app/services/permissions.service'
 import { DocumentTypeService } from 'src/app/services/rest/document-type.service'
-import { ToastService } from 'src/app/services/toast.service'
 import { DocumentTypeEditDialogComponent } from '../../common/edit-dialog/document-type-edit-dialog/document-type-edit-dialog.component'
 import { PageHeaderComponent } from '../../common/page-header/page-header.component'
 import { ManagementListComponent } from '../management-list/management-list.component'
@@ -43,7 +43,7 @@ export class DocumentTypeListComponent extends ManagementListComponent<DocumentT
   constructor(
     documentTypeService: DocumentTypeService,
     modalService: NgbModal,
-    toastService: ToastService,
+    notificationService: NotificationService,
     documentListViewService: DocumentListViewService,
     permissionsService: PermissionsService
   ) {
@@ -51,7 +51,7 @@ export class DocumentTypeListComponent extends ManagementListComponent<DocumentT
       documentTypeService,
       modalService,
       DocumentTypeEditDialogComponent,
-      toastService,
+      notificationService,
       documentListViewService,
       permissionsService,
       FILTER_HAS_DOCUMENT_TYPE_ANY,
diff --git a/src-ui/src/app/components/manage/mail/mail.component.spec.ts b/src-ui/src/app/components/manage/mail/mail.component.spec.ts
index b9f02343d..5e554e512 100644
--- a/src-ui/src/app/components/manage/mail/mail.component.spec.ts
+++ b/src-ui/src/app/components/manage/mail/mail.component.spec.ts
@@ -24,11 +24,11 @@ import { IfPermissionsDirective } from 'src/app/directives/if-permissions.direct
 import { PermissionsGuard } from 'src/app/guards/permissions.guard'
 import { CustomDatePipe } from 'src/app/pipes/custom-date.pipe'
 import { SafeHtmlPipe } from 'src/app/pipes/safehtml.pipe'
+import { NotificationService } from 'src/app/services/notification.service'
 import { PermissionsService } from 'src/app/services/permissions.service'
 import { MailAccountService } from 'src/app/services/rest/mail-account.service'
 import { MailRuleService } from 'src/app/services/rest/mail-rule.service'
 import { SettingsService } from 'src/app/services/settings.service'
-import { ToastService } from 'src/app/services/toast.service'
 import { ConfirmDialogComponent } from '../../common/confirm-dialog/confirm-dialog.component'
 import { EditDialogMode } from '../../common/edit-dialog/edit-dialog.component'
 import { MailAccountEditDialogComponent } from '../../common/edit-dialog/mail-account-edit-dialog/mail-account-edit-dialog.component'
@@ -63,7 +63,7 @@ describe('MailComponent', () => {
   let mailAccountService: MailAccountService
   let mailRuleService: MailRuleService
   let modalService: NgbModal
-  let toastService: ToastService
+  let notificationService: NotificationService
   let permissionsService: PermissionsService
   let activatedRoute: ActivatedRoute
   let settingsService: SettingsService
@@ -111,7 +111,7 @@ describe('MailComponent', () => {
     mailAccountService = TestBed.inject(MailAccountService)
     mailRuleService = TestBed.inject(MailRuleService)
     modalService = TestBed.inject(NgbModal)
-    toastService = TestBed.inject(ToastService)
+    notificationService = TestBed.inject(NotificationService)
     permissionsService = TestBed.inject(PermissionsService)
     activatedRoute = TestBed.inject(ActivatedRoute)
     settingsService = TestBed.inject(SettingsService)
@@ -157,25 +157,25 @@ describe('MailComponent', () => {
   }
 
   it('should show errors on load if load mailAccounts failure', () => {
-    const toastErrorSpy = jest.spyOn(toastService, 'showError')
+    const notificationErrorSpy = jest.spyOn(notificationService, 'showError')
     jest
       .spyOn(mailAccountService, 'listAll')
       .mockImplementation(() =>
         throwError(() => new Error('failed to load mail accounts'))
       )
     completeSetup(mailAccountService)
-    expect(toastErrorSpy).toBeCalled()
+    expect(notificationErrorSpy).toBeCalled()
   })
 
   it('should show errors on load if load mailRules failure', () => {
-    const toastErrorSpy = jest.spyOn(toastService, 'showError')
+    const notificationErrorSpy = jest.spyOn(notificationService, 'showError')
     jest
       .spyOn(mailRuleService, 'listAll')
       .mockImplementation(() =>
         throwError(() => new Error('failed to load mail rules'))
       )
     completeSetup(mailRuleService)
-    expect(toastErrorSpy).toBeCalled()
+    expect(notificationErrorSpy).toBeCalled()
   })
 
   it('should support edit / create mail account, show error if needed', () => {
@@ -184,12 +184,12 @@ describe('MailComponent', () => {
     modalService.activeInstances.subscribe((refs) => (modal = refs[0]))
     component.editMailAccount(mailAccounts[0] as MailAccount)
     let editDialog = modal.componentInstance as MailAccountEditDialogComponent
-    const toastErrorSpy = jest.spyOn(toastService, 'showError')
-    const toastInfoSpy = jest.spyOn(toastService, 'showInfo')
+    const notificationErrorSpy = jest.spyOn(notificationService, 'showError')
+    const notificationInfoSpy = jest.spyOn(notificationService, 'showInfo')
     editDialog.failed.emit()
-    expect(toastErrorSpy).toBeCalled()
+    expect(notificationErrorSpy).toBeCalled()
     editDialog.succeeded.emit(mailAccounts[0])
-    expect(toastInfoSpy).toHaveBeenCalledWith(
+    expect(notificationInfoSpy).toHaveBeenCalledWith(
       `Saved account "${mailAccounts[0].name}".`
     )
     editDialog.cancel()
@@ -203,35 +203,37 @@ describe('MailComponent', () => {
     component.deleteMailAccount(mailAccounts[0] as MailAccount)
     const deleteDialog = modal.componentInstance as ConfirmDialogComponent
     const deleteSpy = jest.spyOn(mailAccountService, 'delete')
-    const toastErrorSpy = jest.spyOn(toastService, 'showError')
-    const toastInfoSpy = jest.spyOn(toastService, 'showInfo')
+    const notificationErrorSpy = jest.spyOn(notificationService, 'showError')
+    const notificationInfoSpy = jest.spyOn(notificationService, 'showInfo')
     const listAllSpy = jest.spyOn(mailAccountService, 'listAll')
     deleteSpy.mockReturnValueOnce(
       throwError(() => new Error('error deleting mail account'))
     )
     deleteDialog.confirm()
-    expect(toastErrorSpy).toBeCalled()
+    expect(notificationErrorSpy).toBeCalled()
     deleteSpy.mockReturnValueOnce(of(true))
     deleteDialog.confirm()
     expect(listAllSpy).toHaveBeenCalled()
-    expect(toastInfoSpy).toHaveBeenCalledWith('Deleted mail account "account1"')
+    expect(notificationInfoSpy).toHaveBeenCalledWith(
+      'Deleted mail account "account1"'
+    )
   })
 
   it('should support process mail account, show error if needed', () => {
     completeSetup()
     const processSpy = jest.spyOn(mailAccountService, 'processAccount')
-    const toastErrorSpy = jest.spyOn(toastService, 'showError')
-    const toastInfoSpy = jest.spyOn(toastService, 'showInfo')
+    const notificationErrorSpy = jest.spyOn(notificationService, 'showError')
+    const notificationInfoSpy = jest.spyOn(notificationService, 'showInfo')
     component.processAccount(mailAccounts[0] as MailAccount)
     expect(processSpy).toHaveBeenCalled()
     processSpy.mockReturnValueOnce(
       throwError(() => new Error('error processing mail account'))
     )
     component.processAccount(mailAccounts[0] as MailAccount)
-    expect(toastErrorSpy).toHaveBeenCalled()
+    expect(notificationErrorSpy).toHaveBeenCalled()
     processSpy.mockReturnValueOnce(of(true))
     component.processAccount(mailAccounts[0] as MailAccount)
-    expect(toastInfoSpy).toHaveBeenCalledWith(
+    expect(notificationInfoSpy).toHaveBeenCalledWith(
       'Processing mail account "account1"'
     )
   })
@@ -242,12 +244,12 @@ describe('MailComponent', () => {
     modalService.activeInstances.subscribe((refs) => (modal = refs[0]))
     component.editMailRule(mailRules[0] as MailRule)
     const editDialog = modal.componentInstance as MailRuleEditDialogComponent
-    const toastErrorSpy = jest.spyOn(toastService, 'showError')
-    const toastInfoSpy = jest.spyOn(toastService, 'showInfo')
+    const notificationErrorSpy = jest.spyOn(notificationService, 'showError')
+    const notificationInfoSpy = jest.spyOn(notificationService, 'showInfo')
     editDialog.failed.emit()
-    expect(toastErrorSpy).toBeCalled()
+    expect(notificationErrorSpy).toBeCalled()
     editDialog.succeeded.emit(mailRules[0])
-    expect(toastInfoSpy).toHaveBeenCalledWith(
+    expect(notificationInfoSpy).toHaveBeenCalledWith(
       `Saved rule "${mailRules[0].name}".`
     )
     editDialog.cancel()
@@ -272,18 +274,20 @@ describe('MailComponent', () => {
     component.deleteMailRule(mailRules[0] as MailRule)
     const deleteDialog = modal.componentInstance as ConfirmDialogComponent
     const deleteSpy = jest.spyOn(mailRuleService, 'delete')
-    const toastErrorSpy = jest.spyOn(toastService, 'showError')
-    const toastInfoSpy = jest.spyOn(toastService, 'showInfo')
+    const notificationErrorSpy = jest.spyOn(notificationService, 'showError')
+    const notificationInfoSpy = jest.spyOn(notificationService, 'showInfo')
     const listAllSpy = jest.spyOn(mailRuleService, 'listAll')
     deleteSpy.mockReturnValueOnce(
       throwError(() => new Error('error deleting mail rule "rule1"'))
     )
     deleteDialog.confirm()
-    expect(toastErrorSpy).toBeCalled()
+    expect(notificationErrorSpy).toBeCalled()
     deleteSpy.mockReturnValueOnce(of(true))
     deleteDialog.confirm()
     expect(listAllSpy).toHaveBeenCalled()
-    expect(toastInfoSpy).toHaveBeenCalledWith('Deleted mail rule "rule1"')
+    expect(notificationInfoSpy).toHaveBeenCalledWith(
+      'Deleted mail rule "rule1"'
+    )
   })
 
   it('should support edit permissions on mail rule objects', () => {
@@ -303,8 +307,8 @@ describe('MailComponent', () => {
     }
     let modal: NgbModalRef
     modalService.activeInstances.subscribe((refs) => (modal = refs[0]))
-    const toastErrorSpy = jest.spyOn(toastService, 'showError')
-    const toastInfoSpy = jest.spyOn(toastService, 'showInfo')
+    const notificationErrorSpy = jest.spyOn(notificationService, 'showError')
+    const notificationInfoSpy = jest.spyOn(notificationService, 'showInfo')
     const rulePatchSpy = jest.spyOn(mailRuleService, 'patch')
     component.editPermissions(mailRules[0] as MailRule)
     expect(modal).not.toBeUndefined()
@@ -316,10 +320,10 @@ describe('MailComponent', () => {
     )
     dialog.confirmClicked.emit({ permissions: perms, merge: true })
     expect(rulePatchSpy).toHaveBeenCalled()
-    expect(toastErrorSpy).toHaveBeenCalled()
+    expect(notificationErrorSpy).toHaveBeenCalled()
     rulePatchSpy.mockReturnValueOnce(of(mailRules[0] as MailRule))
     dialog.confirmClicked.emit({ permissions: perms, merge: true })
-    expect(toastInfoSpy).toHaveBeenCalledWith('Permissions updated')
+    expect(notificationInfoSpy).toHaveBeenCalledWith('Permissions updated')
 
     modalService.dismissAll()
   })
@@ -356,15 +360,15 @@ describe('MailComponent', () => {
     const toggleInput = fixture.debugElement.query(
       By.css('input[type="checkbox"]')
     )
-    const toastErrorSpy = jest.spyOn(toastService, 'showError')
-    const toastInfoSpy = jest.spyOn(toastService, 'showInfo')
+    const notificationErrorSpy = jest.spyOn(notificationService, 'showError')
+    const notificationInfoSpy = jest.spyOn(notificationService, 'showInfo')
     // fail first
     patchSpy.mockReturnValueOnce(
       throwError(() => new Error('Error getting config'))
     )
     toggleInput.nativeElement.click()
     expect(patchSpy).toHaveBeenCalled()
-    expect(toastErrorSpy).toHaveBeenCalled()
+    expect(notificationErrorSpy).toHaveBeenCalled()
     // succeed second
     patchSpy.mockReturnValueOnce(of(mailRules[0] as MailRule))
     toggleInput.nativeElement.click()
@@ -373,7 +377,7 @@ describe('MailComponent', () => {
     )
     toggleInput.nativeElement.click()
     expect(patchSpy).toHaveBeenCalled()
-    expect(toastInfoSpy).toHaveBeenCalled()
+    expect(notificationInfoSpy).toHaveBeenCalled()
   })
 
   it('should show success message when oauth account is connected', () => {
@@ -381,9 +385,9 @@ describe('MailComponent', () => {
     jest
       .spyOn(activatedRoute, 'queryParamMap', 'get')
       .mockReturnValue(of(convertToParamMap(queryParams)))
-    const toastInfoSpy = jest.spyOn(toastService, 'showInfo')
+    const notificationInfoSpy = jest.spyOn(notificationService, 'showInfo')
     completeSetup()
-    expect(toastInfoSpy).toHaveBeenCalled()
+    expect(notificationInfoSpy).toHaveBeenCalled()
   })
 
   it('should show error message when oauth account connect fails', () => {
@@ -391,9 +395,9 @@ describe('MailComponent', () => {
     jest
       .spyOn(activatedRoute, 'queryParamMap', 'get')
       .mockReturnValue(of(convertToParamMap(queryParams)))
-    const toastErrorSpy = jest.spyOn(toastService, 'showError')
+    const notificationErrorSpy = jest.spyOn(notificationService, 'showError')
     completeSetup()
-    expect(toastErrorSpy).toHaveBeenCalled()
+    expect(notificationErrorSpy).toHaveBeenCalled()
   })
 
   it('should open account edit dialog if oauth account is connected', () => {
diff --git a/src-ui/src/app/components/manage/mail/mail.component.ts b/src-ui/src/app/components/manage/mail/mail.component.ts
index 8d4222516..ebe5231e5 100644
--- a/src-ui/src/app/components/manage/mail/mail.component.ts
+++ b/src-ui/src/app/components/manage/mail/mail.component.ts
@@ -11,6 +11,7 @@ import { ObjectWithPermissions } from 'src/app/data/object-with-permissions'
 import { SETTINGS_KEYS } from 'src/app/data/ui-settings'
 import { IfOwnerDirective } from 'src/app/directives/if-owner.directive'
 import { IfPermissionsDirective } from 'src/app/directives/if-permissions.directive'
+import { NotificationService } from 'src/app/services/notification.service'
 import {
   PermissionAction,
   PermissionsService,
@@ -19,7 +20,6 @@ import { AbstractPaperlessService } from 'src/app/services/rest/abstract-paperle
 import { MailAccountService } from 'src/app/services/rest/mail-account.service'
 import { MailRuleService } from 'src/app/services/rest/mail-rule.service'
 import { SettingsService } from 'src/app/services/settings.service'
-import { ToastService } from 'src/app/services/toast.service'
 import { ConfirmDialogComponent } from '../../common/confirm-dialog/confirm-dialog.component'
 import { EditDialogMode } from '../../common/edit-dialog/edit-dialog.component'
 import { MailAccountEditDialogComponent } from '../../common/edit-dialog/mail-account-edit-dialog/mail-account-edit-dialog.component'
@@ -71,7 +71,7 @@ export class MailComponent
   constructor(
     public mailAccountService: MailAccountService,
     public mailRuleService: MailRuleService,
-    private toastService: ToastService,
+    private notificationService: NotificationService,
     private modalService: NgbModal,
     public permissionsService: PermissionsService,
     private settingsService: SettingsService,
@@ -104,7 +104,7 @@ export class MailComponent
           this.showAccounts = true
         },
         error: (e) => {
-          this.toastService.showError(
+          this.notificationService.showError(
             $localize`Error retrieving mail accounts`,
             e
           )
@@ -127,7 +127,10 @@ export class MailComponent
           this.showRules = true
         },
         error: (e) => {
-          this.toastService.showError($localize`Error retrieving mail rules`, e)
+          this.notificationService.showError(
+            $localize`Error retrieving mail rules`,
+            e
+          )
         },
       })
 
@@ -135,7 +138,9 @@ export class MailComponent
       if (params.get('oauth_success')) {
         const success = params.get('oauth_success') === '1'
         if (success) {
-          this.toastService.showInfo($localize`OAuth2 authentication success`)
+          this.notificationService.showInfo(
+            $localize`OAuth2 authentication success`
+          )
           this.oAuthAccountId = parseInt(params.get('account_id'))
           if (this.mailAccounts.length > 0) {
             this.editMailAccount(
@@ -145,7 +150,7 @@ export class MailComponent
             )
           }
         } else {
-          this.toastService.showError(
+          this.notificationService.showError(
             $localize`OAuth2 authentication failed, see logs for details`
           )
         }
@@ -169,7 +174,7 @@ export class MailComponent
     modal.componentInstance.succeeded
       .pipe(takeUntil(this.unsubscribeNotifier))
       .subscribe((newMailAccount) => {
-        this.toastService.showInfo(
+        this.notificationService.showInfo(
           $localize`Saved account "${newMailAccount.name}".`
         )
         this.mailAccountService.clearCache()
@@ -182,7 +187,7 @@ export class MailComponent
     modal.componentInstance.failed
       .pipe(takeUntil(this.unsubscribeNotifier))
       .subscribe((e) => {
-        this.toastService.showError($localize`Error saving account.`, e)
+        this.notificationService.showError($localize`Error saving account.`, e)
       })
   }
 
@@ -200,7 +205,7 @@ export class MailComponent
       this.mailAccountService.delete(account).subscribe({
         next: () => {
           modal.close()
-          this.toastService.showInfo(
+          this.notificationService.showInfo(
             $localize`Deleted mail account "${account.name}"`
           )
           this.mailAccountService.clearCache()
@@ -211,7 +216,7 @@ export class MailComponent
             })
         },
         error: (e) => {
-          this.toastService.showError(
+          this.notificationService.showError(
             $localize`Error deleting mail account "${account.name}".`,
             e
           )
@@ -223,12 +228,12 @@ export class MailComponent
   processAccount(account: MailAccount) {
     this.mailAccountService.processAccount(account).subscribe({
       next: () => {
-        this.toastService.showInfo(
+        this.notificationService.showInfo(
           $localize`Processing mail account "${account.name}"`
         )
       },
       error: (e) => {
-        this.toastService.showError(
+        this.notificationService.showError(
           $localize`Error processing mail account "${account.name}"`,
           e
         )
@@ -247,7 +252,9 @@ export class MailComponent
     modal.componentInstance.succeeded
       .pipe(takeUntil(this.unsubscribeNotifier))
       .subscribe((newMailRule) => {
-        this.toastService.showInfo($localize`Saved rule "${newMailRule.name}".`)
+        this.notificationService.showInfo(
+          $localize`Saved rule "${newMailRule.name}".`
+        )
         this.mailRuleService.clearCache()
         this.mailRuleService
           .listAll(null, null, { full_perms: true })
@@ -258,7 +265,7 @@ export class MailComponent
     modal.componentInstance.failed
       .pipe(takeUntil(this.unsubscribeNotifier))
       .subscribe((e) => {
-        this.toastService.showError($localize`Error saving rule.`, e)
+        this.notificationService.showError($localize`Error saving rule.`, e)
       })
   }
 
@@ -272,14 +279,14 @@ export class MailComponent
   onMailRuleEnableToggled(rule: MailRule) {
     this.mailRuleService.patch(rule).subscribe({
       next: () => {
-        this.toastService.showInfo(
+        this.notificationService.showInfo(
           rule.enabled
             ? $localize`Rule "${rule.name}" enabled.`
             : $localize`Rule "${rule.name}" disabled.`
         )
       },
       error: (e) => {
-        this.toastService.showError(
+        this.notificationService.showError(
           $localize`Error toggling rule "${rule.name}".`,
           e
         )
@@ -301,7 +308,7 @@ export class MailComponent
       this.mailRuleService.delete(rule).subscribe({
         next: () => {
           modal.close()
-          this.toastService.showInfo(
+          this.notificationService.showInfo(
             $localize`Deleted mail rule "${rule.name}"`
           )
           this.mailRuleService.clearCache()
@@ -312,7 +319,7 @@ export class MailComponent
             })
         },
         error: (e) => {
-          this.toastService.showError(
+          this.notificationService.showError(
             $localize`Error deleting mail rule "${rule.name}".`,
             e
           )
@@ -337,11 +344,11 @@ export class MailComponent
         object['set_permissions'] = permissions['set_permissions']
         service.patch(object).subscribe({
           next: () => {
-            this.toastService.showInfo($localize`Permissions updated`)
+            this.notificationService.showInfo($localize`Permissions updated`)
             modal.close()
           },
           error: (e) => {
-            this.toastService.showError(
+            this.notificationService.showError(
               $localize`Error updating permissions`,
               e
             )
diff --git a/src-ui/src/app/components/manage/management-list/management-list.component.spec.ts b/src-ui/src/app/components/manage/management-list/management-list.component.spec.ts
index 291a71fa3..a167edecc 100644
--- a/src-ui/src/app/components/manage/management-list/management-list.component.spec.ts
+++ b/src-ui/src/app/components/manage/management-list/management-list.component.spec.ts
@@ -35,13 +35,13 @@ import { SortableDirective } from 'src/app/directives/sortable.directive'
 import { PermissionsGuard } from 'src/app/guards/permissions.guard'
 import { SafeHtmlPipe } from 'src/app/pipes/safehtml.pipe'
 import { DocumentListViewService } from 'src/app/services/document-list-view.service'
+import { NotificationService } from 'src/app/services/notification.service'
 import {
   PermissionAction,
   PermissionsService,
 } from 'src/app/services/permissions.service'
 import { BulkEditObjectOperation } from 'src/app/services/rest/abstract-name-filter-service'
 import { TagService } from 'src/app/services/rest/tag.service'
-import { ToastService } from 'src/app/services/toast.service'
 import { ConfirmDialogComponent } from '../../common/confirm-dialog/confirm-dialog.component'
 import { EditDialogComponent } from '../../common/edit-dialog/edit-dialog.component'
 import { PageHeaderComponent } from '../../common/page-header/page-header.component'
@@ -76,7 +76,7 @@ describe('ManagementListComponent', () => {
   let fixture: ComponentFixture<ManagementListComponent<Tag>>
   let tagService: TagService
   let modalService: NgbModal
-  let toastService: ToastService
+  let notificationService: NotificationService
   let documentListViewService: DocumentListViewService
   let permissionsService: PermissionsService
 
@@ -129,7 +129,7 @@ describe('ManagementListComponent', () => {
       .spyOn(permissionsService, 'currentUserOwnsObject')
       .mockReturnValue(true)
     modalService = TestBed.inject(NgbModal)
-    toastService = TestBed.inject(ToastService)
+    notificationService = TestBed.inject(NotificationService)
     documentListViewService = TestBed.inject(DocumentListViewService)
     fixture = TestBed.createComponent(TagListComponent)
     component = fixture.componentInstance
@@ -160,8 +160,8 @@ describe('ManagementListComponent', () => {
   it('should support create, show notification on error / success', () => {
     let modal: NgbModalRef
     modalService.activeInstances.subscribe((m) => (modal = m[m.length - 1]))
-    const toastErrorSpy = jest.spyOn(toastService, 'showError')
-    const toastInfoSpy = jest.spyOn(toastService, 'showInfo')
+    const notificationErrorSpy = jest.spyOn(notificationService, 'showError')
+    const notificationInfoSpy = jest.spyOn(notificationService, 'showInfo')
     const reloadSpy = jest.spyOn(component, 'reloadData')
 
     const createButton = fixture.debugElement.queryAll(By.css('button'))[3]
@@ -172,20 +172,20 @@ describe('ManagementListComponent', () => {
 
     // fail first
     editDialog.failed.emit({ error: 'error creating item' })
-    expect(toastErrorSpy).toHaveBeenCalled()
+    expect(notificationErrorSpy).toHaveBeenCalled()
     expect(reloadSpy).not.toHaveBeenCalled()
 
     // succeed
     editDialog.succeeded.emit()
-    expect(toastInfoSpy).toHaveBeenCalled()
+    expect(notificationInfoSpy).toHaveBeenCalled()
     expect(reloadSpy).toHaveBeenCalled()
   })
 
   it('should support edit, show notification on error / success', () => {
     let modal: NgbModalRef
     modalService.activeInstances.subscribe((m) => (modal = m[m.length - 1]))
-    const toastErrorSpy = jest.spyOn(toastService, 'showError')
-    const toastInfoSpy = jest.spyOn(toastService, 'showInfo')
+    const notificationErrorSpy = jest.spyOn(notificationService, 'showError')
+    const notificationInfoSpy = jest.spyOn(notificationService, 'showInfo')
     const reloadSpy = jest.spyOn(component, 'reloadData')
 
     const editButton = fixture.debugElement.queryAll(By.css('button'))[6]
@@ -197,19 +197,19 @@ describe('ManagementListComponent', () => {
 
     // fail first
     editDialog.failed.emit({ error: 'error editing item' })
-    expect(toastErrorSpy).toHaveBeenCalled()
+    expect(notificationErrorSpy).toHaveBeenCalled()
     expect(reloadSpy).not.toHaveBeenCalled()
 
     // succeed
     editDialog.succeeded.emit()
-    expect(toastInfoSpy).toHaveBeenCalled()
+    expect(notificationInfoSpy).toHaveBeenCalled()
     expect(reloadSpy).toHaveBeenCalled()
   })
 
   it('should support delete, show notification on error / success', () => {
     let modal: NgbModalRef
     modalService.activeInstances.subscribe((m) => (modal = m[m.length - 1]))
-    const toastErrorSpy = jest.spyOn(toastService, 'showError')
+    const notificationErrorSpy = jest.spyOn(notificationService, 'showError')
     const deleteSpy = jest.spyOn(tagService, 'delete')
     const reloadSpy = jest.spyOn(component, 'reloadData')
 
@@ -222,7 +222,7 @@ describe('ManagementListComponent', () => {
     // fail first
     deleteSpy.mockReturnValueOnce(throwError(() => new Error('error deleting')))
     editDialog.confirmClicked.emit()
-    expect(toastErrorSpy).toHaveBeenCalled()
+    expect(notificationErrorSpy).toHaveBeenCalled()
     expect(reloadSpy).not.toHaveBeenCalled()
 
     // succeed
@@ -293,22 +293,22 @@ describe('ManagementListComponent', () => {
     bulkEditPermsSpy.mockReturnValueOnce(
       throwError(() => new Error('error setting permissions'))
     )
-    const errorToastSpy = jest.spyOn(toastService, 'showError')
+    const errornotificationSpy = jest.spyOn(notificationService, 'showError')
     modal.componentInstance.confirmClicked.emit({
       permissions: {},
       merge: true,
     })
     expect(bulkEditPermsSpy).toHaveBeenCalled()
-    expect(errorToastSpy).toHaveBeenCalled()
+    expect(errornotificationSpy).toHaveBeenCalled()
 
-    const successToastSpy = jest.spyOn(toastService, 'showInfo')
+    const successnotificationSpy = jest.spyOn(notificationService, 'showInfo')
     bulkEditPermsSpy.mockReturnValueOnce(of('OK'))
     modal.componentInstance.confirmClicked.emit({
       permissions: {},
       merge: true,
     })
     expect(bulkEditPermsSpy).toHaveBeenCalled()
-    expect(successToastSpy).toHaveBeenCalled()
+    expect(successnotificationSpy).toHaveBeenCalled()
   })
 
   it('should support bulk delete objects', () => {
@@ -327,19 +327,19 @@ describe('ManagementListComponent', () => {
     bulkEditSpy.mockReturnValueOnce(
       throwError(() => new Error('error setting permissions'))
     )
-    const errorToastSpy = jest.spyOn(toastService, 'showError')
+    const errornotificationSpy = jest.spyOn(notificationService, 'showError')
     modal.componentInstance.confirmClicked.emit(null)
     expect(bulkEditSpy).toHaveBeenCalledWith(
       Array.from(selected),
       BulkEditObjectOperation.Delete
     )
-    expect(errorToastSpy).toHaveBeenCalled()
+    expect(errornotificationSpy).toHaveBeenCalled()
 
-    const successToastSpy = jest.spyOn(toastService, 'showInfo')
+    const successnotificationSpy = jest.spyOn(notificationService, 'showInfo')
     bulkEditSpy.mockReturnValueOnce(of('OK'))
     modal.componentInstance.confirmClicked.emit(null)
     expect(bulkEditSpy).toHaveBeenCalled()
-    expect(successToastSpy).toHaveBeenCalled()
+    expect(successnotificationSpy).toHaveBeenCalled()
   })
 
   it('should disallow bulk permissions or delete objects if no global perms', () => {
diff --git a/src-ui/src/app/components/manage/management-list/management-list.component.ts b/src-ui/src/app/components/manage/management-list/management-list.component.ts
index 7f7721485..6d43741f6 100644
--- a/src-ui/src/app/components/manage/management-list/management-list.component.ts
+++ b/src-ui/src/app/components/manage/management-list/management-list.component.ts
@@ -27,6 +27,7 @@ import {
   SortEvent,
 } from 'src/app/directives/sortable.directive'
 import { DocumentListViewService } from 'src/app/services/document-list-view.service'
+import { NotificationService } from 'src/app/services/notification.service'
 import {
   PermissionAction,
   PermissionsService,
@@ -36,7 +37,6 @@ import {
   AbstractNameFilterService,
   BulkEditObjectOperation,
 } from 'src/app/services/rest/abstract-name-filter-service'
-import { ToastService } from 'src/app/services/toast.service'
 import { ConfirmDialogComponent } from '../../common/confirm-dialog/confirm-dialog.component'
 import { EditDialogMode } from '../../common/edit-dialog/edit-dialog.component'
 import { PermissionsDialogComponent } from '../../common/permissions-dialog/permissions-dialog.component'
@@ -63,7 +63,7 @@ export abstract class ManagementListComponent<T extends MatchingModel>
     protected service: AbstractNameFilterService<T>,
     private modalService: NgbModal,
     private editDialogComponent: any,
-    private toastService: ToastService,
+    private notificationService: NotificationService,
     private documentListViewService: DocumentListViewService,
     private permissionsService: PermissionsService,
     protected filterRuleType: number,
@@ -173,12 +173,12 @@ export abstract class ManagementListComponent<T extends MatchingModel>
     activeModal.componentInstance.dialogMode = EditDialogMode.CREATE
     activeModal.componentInstance.succeeded.subscribe(() => {
       this.reloadData()
-      this.toastService.showInfo(
+      this.notificationService.showInfo(
         $localize`Successfully created ${this.typeName}.`
       )
     })
     activeModal.componentInstance.failed.subscribe((e) => {
-      this.toastService.showError(
+      this.notificationService.showError(
         $localize`Error occurred while creating ${this.typeName}.`,
         e
       )
@@ -193,12 +193,12 @@ export abstract class ManagementListComponent<T extends MatchingModel>
     activeModal.componentInstance.dialogMode = EditDialogMode.EDIT
     activeModal.componentInstance.succeeded.subscribe(() => {
       this.reloadData()
-      this.toastService.showInfo(
+      this.notificationService.showInfo(
         $localize`Successfully updated ${this.typeName} "${object.name}".`
       )
     })
     activeModal.componentInstance.failed.subscribe((e) => {
-      this.toastService.showError(
+      this.notificationService.showError(
         $localize`Error occurred while saving ${this.typeName}.`,
         e
       )
@@ -234,7 +234,7 @@ export abstract class ManagementListComponent<T extends MatchingModel>
           },
           error: (error) => {
             activeModal.componentInstance.buttonsEnabled = true
-            this.toastService.showError(
+            this.notificationService.showError(
               $localize`Error while deleting element`,
               error
             )
@@ -313,14 +313,14 @@ export abstract class ManagementListComponent<T extends MatchingModel>
           .subscribe({
             next: () => {
               modal.close()
-              this.toastService.showInfo(
+              this.notificationService.showInfo(
                 $localize`Permissions updated successfully`
               )
               this.reloadData()
             },
             error: (error) => {
               modal.componentInstance.buttonsEnabled = true
-              this.toastService.showError(
+              this.notificationService.showError(
                 $localize`Error updating permissions`,
                 error
               )
@@ -349,12 +349,14 @@ export abstract class ManagementListComponent<T extends MatchingModel>
         .subscribe({
           next: () => {
             modal.close()
-            this.toastService.showInfo($localize`Objects deleted successfully`)
+            this.notificationService.showInfo(
+              $localize`Objects deleted successfully`
+            )
             this.reloadData()
           },
           error: (error) => {
             modal.componentInstance.buttonsEnabled = true
-            this.toastService.showError(
+            this.notificationService.showError(
               $localize`Error deleting objects`,
               error
             )
diff --git a/src-ui/src/app/components/manage/saved-views/saved-views.component.spec.ts b/src-ui/src/app/components/manage/saved-views/saved-views.component.spec.ts
index 10bc5db8e..18cc3e4b7 100644
--- a/src-ui/src/app/components/manage/saved-views/saved-views.component.spec.ts
+++ b/src-ui/src/app/components/manage/saved-views/saved-views.component.spec.ts
@@ -10,10 +10,10 @@ import { of, throwError } from 'rxjs'
 import { SavedView } from 'src/app/data/saved-view'
 import { IfPermissionsDirective } from 'src/app/directives/if-permissions.directive'
 import { PermissionsGuard } from 'src/app/guards/permissions.guard'
+import { NotificationService } from 'src/app/services/notification.service'
 import { PermissionsService } from 'src/app/services/permissions.service'
 import { CustomFieldsService } from 'src/app/services/rest/custom-fields.service'
 import { SavedViewService } from 'src/app/services/rest/saved-view.service'
-import { ToastService } from 'src/app/services/toast.service'
 import { ConfirmButtonComponent } from '../../common/confirm-button/confirm-button.component'
 import { CheckComponent } from '../../common/input/check/check.component'
 import { DragDropSelectComponent } from '../../common/input/drag-drop-select/drag-drop-select.component'
@@ -32,7 +32,7 @@ describe('SavedViewsComponent', () => {
   let component: SavedViewsComponent
   let fixture: ComponentFixture<SavedViewsComponent>
   let savedViewService: SavedViewService
-  let toastService: ToastService
+  let notificationService: NotificationService
 
   beforeEach(async () => {
     TestBed.configureTestingModule({
@@ -77,7 +77,7 @@ describe('SavedViewsComponent', () => {
     }).compileComponents()
 
     savedViewService = TestBed.inject(SavedViewService)
-    toastService = TestBed.inject(ToastService)
+    notificationService = TestBed.inject(NotificationService)
     fixture = TestBed.createComponent(SavedViewsComponent)
     component = fixture.componentInstance
 
@@ -93,8 +93,8 @@ describe('SavedViewsComponent', () => {
   })
 
   it('should support save saved views, show error', () => {
-    const toastErrorSpy = jest.spyOn(toastService, 'showError')
-    const toastSpy = jest.spyOn(toastService, 'show')
+    const notificationErrorSpy = jest.spyOn(notificationService, 'showError')
+    const notificationSpy = jest.spyOn(notificationService, 'show')
     const savedViewPatchSpy = jest.spyOn(savedViewService, 'patchMany')
 
     const toggle = fixture.debugElement.query(
@@ -108,16 +108,16 @@ describe('SavedViewsComponent', () => {
       throwError(() => new Error('unable to save saved views'))
     )
     component.save()
-    expect(toastErrorSpy).toHaveBeenCalled()
+    expect(notificationErrorSpy).toHaveBeenCalled()
     expect(savedViewPatchSpy).toHaveBeenCalled()
-    toastSpy.mockClear()
-    toastErrorSpy.mockClear()
+    notificationSpy.mockClear()
+    notificationErrorSpy.mockClear()
     savedViewPatchSpy.mockClear()
 
     // succeed saved views
     savedViewPatchSpy.mockReturnValueOnce(of(savedViews as SavedView[]))
     component.save()
-    expect(toastErrorSpy).not.toHaveBeenCalled()
+    expect(notificationErrorSpy).not.toHaveBeenCalled()
     expect(savedViewPatchSpy).toHaveBeenCalled()
   })
 
@@ -150,12 +150,12 @@ describe('SavedViewsComponent', () => {
   })
 
   it('should support delete saved view', () => {
-    const toastSpy = jest.spyOn(toastService, 'showInfo')
+    const notificationSpy = jest.spyOn(notificationService, 'showInfo')
     const deleteSpy = jest.spyOn(savedViewService, 'delete')
     deleteSpy.mockReturnValue(of(true))
     component.deleteSavedView(savedViews[0] as SavedView)
     expect(deleteSpy).toHaveBeenCalled()
-    expect(toastSpy).toHaveBeenCalledWith(
+    expect(notificationSpy).toHaveBeenCalledWith(
       `Saved view "${savedViews[0].name}" deleted.`
     )
   })
diff --git a/src-ui/src/app/components/manage/saved-views/saved-views.component.ts b/src-ui/src/app/components/manage/saved-views/saved-views.component.ts
index ee2b00c2b..1803e2b1c 100644
--- a/src-ui/src/app/components/manage/saved-views/saved-views.component.ts
+++ b/src-ui/src/app/components/manage/saved-views/saved-views.component.ts
@@ -11,9 +11,9 @@ import { BehaviorSubject, Observable, takeUntil } from 'rxjs'
 import { DisplayMode } from 'src/app/data/document'
 import { SavedView } from 'src/app/data/saved-view'
 import { IfPermissionsDirective } from 'src/app/directives/if-permissions.directive'
+import { NotificationService } from 'src/app/services/notification.service'
 import { SavedViewService } from 'src/app/services/rest/saved-view.service'
 import { SettingsService } from 'src/app/services/settings.service'
-import { ToastService } from 'src/app/services/toast.service'
 import { ConfirmButtonComponent } from '../../common/confirm-button/confirm-button.component'
 import { DragDropSelectComponent } from '../../common/input/drag-drop-select/drag-drop-select.component'
 import { NumberComponent } from '../../common/input/number/number.component'
@@ -58,7 +58,7 @@ export class SavedViewsComponent
   constructor(
     private savedViewService: SavedViewService,
     private settings: SettingsService,
-    private toastService: ToastService
+    private notificationService: NotificationService
   ) {
     super()
     this.settings.organizingSidebarSavedViews = true
@@ -129,7 +129,7 @@ export class SavedViewsComponent
     this.savedViewService.delete(savedView).subscribe(() => {
       this.savedViewsGroup.removeControl(savedView.id.toString())
       this.savedViews.splice(this.savedViews.indexOf(savedView), 1)
-      this.toastService.showInfo(
+      this.notificationService.showInfo(
         $localize`Saved view "${savedView.name}" deleted.`
       )
       this.savedViewService.clearCache()
@@ -155,11 +155,13 @@ export class SavedViewsComponent
     if (changed.length) {
       this.savedViewService.patchMany(changed).subscribe({
         next: () => {
-          this.toastService.showInfo($localize`Views saved successfully.`)
+          this.notificationService.showInfo(
+            $localize`Views saved successfully.`
+          )
           this.store.next(this.savedViewsForm.value)
         },
         error: (error) => {
-          this.toastService.showError(
+          this.notificationService.showError(
             $localize`Error while saving views.`,
             error
           )
diff --git a/src-ui/src/app/components/manage/storage-path-list/storage-path-list.component.ts b/src-ui/src/app/components/manage/storage-path-list/storage-path-list.component.ts
index 89a243324..d49bf4255 100644
--- a/src-ui/src/app/components/manage/storage-path-list/storage-path-list.component.ts
+++ b/src-ui/src/app/components/manage/storage-path-list/storage-path-list.component.ts
@@ -13,12 +13,12 @@ import { IfPermissionsDirective } from 'src/app/directives/if-permissions.direct
 import { SortableDirective } from 'src/app/directives/sortable.directive'
 import { SafeHtmlPipe } from 'src/app/pipes/safehtml.pipe'
 import { DocumentListViewService } from 'src/app/services/document-list-view.service'
+import { NotificationService } from 'src/app/services/notification.service'
 import {
   PermissionsService,
   PermissionType,
 } from 'src/app/services/permissions.service'
 import { StoragePathService } from 'src/app/services/rest/storage-path.service'
-import { ToastService } from 'src/app/services/toast.service'
 import { StoragePathEditDialogComponent } from '../../common/edit-dialog/storage-path-edit-dialog/storage-path-edit-dialog.component'
 import { PageHeaderComponent } from '../../common/page-header/page-header.component'
 import { ManagementListComponent } from '../management-list/management-list.component'
@@ -45,7 +45,7 @@ export class StoragePathListComponent extends ManagementListComponent<StoragePat
   constructor(
     directoryService: StoragePathService,
     modalService: NgbModal,
-    toastService: ToastService,
+    notificationService: NotificationService,
     documentListViewService: DocumentListViewService,
     permissionsService: PermissionsService
   ) {
@@ -53,7 +53,7 @@ export class StoragePathListComponent extends ManagementListComponent<StoragePat
       directoryService,
       modalService,
       StoragePathEditDialogComponent,
-      toastService,
+      notificationService,
       documentListViewService,
       permissionsService,
       FILTER_HAS_STORAGE_PATH_ANY,
diff --git a/src-ui/src/app/components/manage/tag-list/tag-list.component.ts b/src-ui/src/app/components/manage/tag-list/tag-list.component.ts
index f0d7e7959..47190e2ca 100644
--- a/src-ui/src/app/components/manage/tag-list/tag-list.component.ts
+++ b/src-ui/src/app/components/manage/tag-list/tag-list.component.ts
@@ -13,12 +13,12 @@ import { IfPermissionsDirective } from 'src/app/directives/if-permissions.direct
 import { SortableDirective } from 'src/app/directives/sortable.directive'
 import { SafeHtmlPipe } from 'src/app/pipes/safehtml.pipe'
 import { DocumentListViewService } from 'src/app/services/document-list-view.service'
+import { NotificationService } from 'src/app/services/notification.service'
 import {
   PermissionsService,
   PermissionType,
 } from 'src/app/services/permissions.service'
 import { TagService } from 'src/app/services/rest/tag.service'
-import { ToastService } from 'src/app/services/toast.service'
 import { TagEditDialogComponent } from '../../common/edit-dialog/tag-edit-dialog/tag-edit-dialog.component'
 import { PageHeaderComponent } from '../../common/page-header/page-header.component'
 import { ManagementListComponent } from '../management-list/management-list.component'
@@ -45,7 +45,7 @@ export class TagListComponent extends ManagementListComponent<Tag> {
   constructor(
     tagService: TagService,
     modalService: NgbModal,
-    toastService: ToastService,
+    notificationService: NotificationService,
     documentListViewService: DocumentListViewService,
     permissionsService: PermissionsService
   ) {
@@ -53,7 +53,7 @@ export class TagListComponent extends ManagementListComponent<Tag> {
       tagService,
       modalService,
       TagEditDialogComponent,
-      toastService,
+      notificationService,
       documentListViewService,
       permissionsService,
       FILTER_HAS_TAGS_ALL,
diff --git a/src-ui/src/app/components/manage/workflows/workflows.component.spec.ts b/src-ui/src/app/components/manage/workflows/workflows.component.spec.ts
index 636e03d54..58f8fbe9a 100644
--- a/src-ui/src/app/components/manage/workflows/workflows.component.spec.ts
+++ b/src-ui/src/app/components/manage/workflows/workflows.component.spec.ts
@@ -19,9 +19,9 @@ import {
   WorkflowTriggerType,
 } from 'src/app/data/workflow-trigger'
 import { IfPermissionsDirective } from 'src/app/directives/if-permissions.directive'
+import { NotificationService } from 'src/app/services/notification.service'
 import { PermissionsService } from 'src/app/services/permissions.service'
 import { WorkflowService } from 'src/app/services/rest/workflow.service'
-import { ToastService } from 'src/app/services/toast.service'
 import { ConfirmDialogComponent } from '../../common/confirm-dialog/confirm-dialog.component'
 import { EditDialogMode } from '../../common/edit-dialog/edit-dialog.component'
 import { WorkflowEditDialogComponent } from '../../common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component'
@@ -77,7 +77,7 @@ describe('WorkflowsComponent', () => {
   let fixture: ComponentFixture<WorkflowsComponent>
   let workflowService: WorkflowService
   let modalService: NgbModal
-  let toastService: ToastService
+  let notificationService: NotificationService
 
   beforeEach(() => {
     TestBed.configureTestingModule({
@@ -116,7 +116,7 @@ describe('WorkflowsComponent', () => {
       })
     )
     modalService = TestBed.inject(NgbModal)
-    toastService = TestBed.inject(ToastService)
+    notificationService = TestBed.inject(NotificationService)
     jest.useFakeTimers()
     fixture = TestBed.createComponent(WorkflowsComponent)
     component = fixture.componentInstance
@@ -127,8 +127,8 @@ describe('WorkflowsComponent', () => {
   it('should support create, show notification on error / success', () => {
     let modal: NgbModalRef
     modalService.activeInstances.subscribe((m) => (modal = m[m.length - 1]))
-    const toastErrorSpy = jest.spyOn(toastService, 'showError')
-    const toastInfoSpy = jest.spyOn(toastService, 'showInfo')
+    const notificationErrorSpy = jest.spyOn(notificationService, 'showError')
+    const notificationInfoSpy = jest.spyOn(notificationService, 'showInfo')
     const reloadSpy = jest.spyOn(component, 'reload')
 
     const createButton = fixture.debugElement.queryAll(By.css('button'))[1]
@@ -139,20 +139,20 @@ describe('WorkflowsComponent', () => {
 
     // fail first
     editDialog.failed.emit({ error: 'error creating item' })
-    expect(toastErrorSpy).toHaveBeenCalled()
+    expect(notificationErrorSpy).toHaveBeenCalled()
     expect(reloadSpy).not.toHaveBeenCalled()
 
     // succeed
     editDialog.succeeded.emit(workflows[0])
-    expect(toastInfoSpy).toHaveBeenCalled()
+    expect(notificationInfoSpy).toHaveBeenCalled()
     expect(reloadSpy).toHaveBeenCalled()
   })
 
   it('should support edit, show notification on error / success', () => {
     let modal: NgbModalRef
     modalService.activeInstances.subscribe((m) => (modal = m[m.length - 1]))
-    const toastErrorSpy = jest.spyOn(toastService, 'showError')
-    const toastInfoSpy = jest.spyOn(toastService, 'showInfo')
+    const notificationErrorSpy = jest.spyOn(notificationService, 'showError')
+    const notificationInfoSpy = jest.spyOn(notificationService, 'showInfo')
     const reloadSpy = jest.spyOn(component, 'reload')
 
     const editButton = fixture.debugElement.queryAll(By.css('button'))[2]
@@ -164,12 +164,12 @@ describe('WorkflowsComponent', () => {
 
     // fail first
     editDialog.failed.emit({ error: 'error editing item' })
-    expect(toastErrorSpy).toHaveBeenCalled()
+    expect(notificationErrorSpy).toHaveBeenCalled()
     expect(reloadSpy).not.toHaveBeenCalled()
 
     // succeed
     editDialog.succeeded.emit(workflows[0])
-    expect(toastInfoSpy).toHaveBeenCalled()
+    expect(notificationInfoSpy).toHaveBeenCalled()
     expect(reloadSpy).toHaveBeenCalled()
   })
 
@@ -240,7 +240,7 @@ describe('WorkflowsComponent', () => {
   it('should support delete, show notification on error / success', () => {
     let modal: NgbModalRef
     modalService.activeInstances.subscribe((m) => (modal = m[m.length - 1]))
-    const toastErrorSpy = jest.spyOn(toastService, 'showError')
+    const notificationErrorSpy = jest.spyOn(notificationService, 'showError')
     const deleteSpy = jest.spyOn(workflowService, 'delete')
     const reloadSpy = jest.spyOn(component, 'reload')
 
@@ -253,7 +253,7 @@ describe('WorkflowsComponent', () => {
     // fail first
     deleteSpy.mockReturnValueOnce(throwError(() => new Error('error deleting')))
     editDialog.confirmClicked.emit()
-    expect(toastErrorSpy).toHaveBeenCalled()
+    expect(notificationErrorSpy).toHaveBeenCalled()
     expect(reloadSpy).not.toHaveBeenCalled()
 
     // succeed
@@ -267,21 +267,21 @@ describe('WorkflowsComponent', () => {
     const toggleInput = fixture.debugElement.query(
       By.css('input[type="checkbox"]')
     )
-    const toastErrorSpy = jest.spyOn(toastService, 'showError')
-    const toastInfoSpy = jest.spyOn(toastService, 'showInfo')
+    const notificationErrorSpy = jest.spyOn(notificationService, 'showError')
+    const notificationInfoSpy = jest.spyOn(notificationService, 'showInfo')
     // fail first
     patchSpy.mockReturnValueOnce(
       throwError(() => new Error('Error getting config'))
     )
     toggleInput.nativeElement.click()
     expect(patchSpy).toHaveBeenCalled()
-    expect(toastErrorSpy).toHaveBeenCalled()
+    expect(notificationErrorSpy).toHaveBeenCalled()
     // succeed second
     patchSpy.mockReturnValueOnce(of(workflows[0]))
     toggleInput.nativeElement.click()
     patchSpy.mockReturnValueOnce(of({ ...workflows[0], enabled: false }))
     toggleInput.nativeElement.click()
     expect(patchSpy).toHaveBeenCalled()
-    expect(toastInfoSpy).toHaveBeenCalled()
+    expect(notificationInfoSpy).toHaveBeenCalled()
   })
 })
diff --git a/src-ui/src/app/components/manage/workflows/workflows.component.ts b/src-ui/src/app/components/manage/workflows/workflows.component.ts
index edbca44c8..056e48111 100644
--- a/src-ui/src/app/components/manage/workflows/workflows.component.ts
+++ b/src-ui/src/app/components/manage/workflows/workflows.component.ts
@@ -5,9 +5,9 @@ import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons'
 import { delay, takeUntil, tap } from 'rxjs'
 import { Workflow } from 'src/app/data/workflow'
 import { IfPermissionsDirective } from 'src/app/directives/if-permissions.directive'
+import { NotificationService } from 'src/app/services/notification.service'
 import { PermissionsService } from 'src/app/services/permissions.service'
 import { WorkflowService } from 'src/app/services/rest/workflow.service'
-import { ToastService } from 'src/app/services/toast.service'
 import { ConfirmDialogComponent } from '../../common/confirm-dialog/confirm-dialog.component'
 import { EditDialogMode } from '../../common/edit-dialog/edit-dialog.component'
 import {
@@ -40,7 +40,7 @@ export class WorkflowsComponent
     private workflowService: WorkflowService,
     public permissionsService: PermissionsService,
     private modalService: NgbModal,
-    private toastService: ToastService
+    private notificationService: NotificationService
   ) {
     super()
   }
@@ -90,7 +90,7 @@ export class WorkflowsComponent
     modal.componentInstance.succeeded
       .pipe(takeUntil(this.unsubscribeNotifier))
       .subscribe((newWorkflow) => {
-        this.toastService.showInfo(
+        this.notificationService.showInfo(
           $localize`Saved workflow "${newWorkflow.name}".`
         )
         this.workflowService.clearCache()
@@ -99,7 +99,7 @@ export class WorkflowsComponent
     modal.componentInstance.failed
       .pipe(takeUntil(this.unsubscribeNotifier))
       .subscribe((e) => {
-        this.toastService.showError($localize`Error saving workflow.`, e)
+        this.notificationService.showError($localize`Error saving workflow.`, e)
       })
   }
 
@@ -142,14 +142,14 @@ export class WorkflowsComponent
       this.workflowService.delete(workflow).subscribe({
         next: () => {
           modal.close()
-          this.toastService.showInfo(
+          this.notificationService.showInfo(
             $localize`Deleted workflow "${workflow.name}".`
           )
           this.workflowService.clearCache()
           this.reload()
         },
         error: (e) => {
-          this.toastService.showError(
+          this.notificationService.showError(
             $localize`Error deleting workflow "${workflow.name}".`,
             e
           )
@@ -161,7 +161,7 @@ export class WorkflowsComponent
   toggleWorkflowEnabled(workflow: Workflow) {
     this.workflowService.patch(workflow).subscribe({
       next: () => {
-        this.toastService.showInfo(
+        this.notificationService.showInfo(
           workflow.enabled
             ? $localize`Enabled workflow "${workflow.name}"`
             : $localize`Disabled workflow "${workflow.name}"`
@@ -170,7 +170,7 @@ export class WorkflowsComponent
         this.reload()
       },
       error: (e) => {
-        this.toastService.showError(
+        this.notificationService.showError(
           $localize`Error toggling workflow "${workflow.name}".`,
           e
         )
diff --git a/src-ui/src/app/guards/permissions.guard.spec.ts b/src-ui/src/app/guards/permissions.guard.spec.ts
index 77fe615e1..76ce781a3 100644
--- a/src-ui/src/app/guards/permissions.guard.spec.ts
+++ b/src-ui/src/app/guards/permissions.guard.spec.ts
@@ -1,12 +1,12 @@
 import { TestBed } from '@angular/core/testing'
 import { ActivatedRoute, RouterState } from '@angular/router'
 import { TourService } from 'ngx-ui-tour-ng-bootstrap'
+import { NotificationService } from '../services/notification.service'
 import {
   PermissionAction,
   PermissionType,
   PermissionsService,
 } from '../services/permissions.service'
-import { ToastService } from '../services/toast.service'
 import { PermissionsGuard } from './permissions.guard'
 
 describe('PermissionsGuard', () => {
@@ -15,7 +15,7 @@ describe('PermissionsGuard', () => {
   let route: ActivatedRoute
   let routerState: RouterState
   let tourService: TourService
-  let toastService: ToastService
+  let notificationService: NotificationService
 
   beforeEach(() => {
     TestBed.configureTestingModule({
@@ -44,13 +44,13 @@ describe('PermissionsGuard', () => {
           },
         },
         TourService,
-        ToastService,
+        NotificationService,
       ],
     })
 
     permissionsService = TestBed.inject(PermissionsService)
     tourService = TestBed.inject(TourService)
-    toastService = TestBed.inject(ToastService)
+    notificationService = TestBed.inject(NotificationService)
     guard = TestBed.inject(PermissionsGuard)
     route = TestBed.inject(ActivatedRoute)
     routerState = TestBed.inject(RouterState)
@@ -88,11 +88,11 @@ describe('PermissionsGuard', () => {
       })
     jest.spyOn(tourService, 'getStatus').mockImplementation(() => 2)
 
-    const toastSpy = jest.spyOn(toastService, 'showError')
+    const notificationSpy = jest.spyOn(notificationService, 'showError')
 
     const canActivate = guard.canActivate(route.snapshot, routerState.snapshot)
 
     expect(canActivate).toHaveProperty('root') // returns UrlTree
-    expect(toastSpy).toHaveBeenCalled()
+    expect(notificationSpy).toHaveBeenCalled()
   })
 })
diff --git a/src-ui/src/app/guards/permissions.guard.ts b/src-ui/src/app/guards/permissions.guard.ts
index c820edea2..2b3486887 100644
--- a/src-ui/src/app/guards/permissions.guard.ts
+++ b/src-ui/src/app/guards/permissions.guard.ts
@@ -6,15 +6,15 @@ import {
   UrlTree,
 } from '@angular/router'
 import { TourService } from 'ngx-ui-tour-ng-bootstrap'
+import { NotificationService } from '../services/notification.service'
 import { PermissionsService } from '../services/permissions.service'
-import { ToastService } from '../services/toast.service'
 
 @Injectable()
 export class PermissionsGuard {
   constructor(
     private permissionsService: PermissionsService,
     private router: Router,
-    private toastService: ToastService,
+    private notificationService: NotificationService,
     private tourService: TourService
   ) {}
 
@@ -32,7 +32,7 @@ export class PermissionsGuard {
     ) {
       // Check if tour is running 1 = TourState.ON
       if (this.tourService.getStatus() !== 1) {
-        this.toastService.showError(
+        this.notificationService.showError(
           $localize`You don't have permissions to do that`
         )
       }
diff --git a/src-ui/src/app/services/notification.service.spec.ts b/src-ui/src/app/services/notification.service.spec.ts
new file mode 100644
index 000000000..2729dd89e
--- /dev/null
+++ b/src-ui/src/app/services/notification.service.spec.ts
@@ -0,0 +1,109 @@
+import { TestBed } from '@angular/core/testing'
+import { NotificationService } from './notification.service'
+
+describe('NotificationService', () => {
+  let notificationService: NotificationService
+
+  beforeEach(() => {
+    TestBed.configureTestingModule({
+      providers: [NotificationService],
+    })
+
+    notificationService = TestBed.inject(NotificationService)
+  })
+
+  it('adds notification on show', () => {
+    const notification = {
+      title: 'Title',
+      content: 'content',
+      delay: 5000,
+    }
+    notificationService.show(notification)
+
+    notificationService.getNotifications().subscribe((notifications) => {
+      expect(notifications).toContainEqual(notification)
+    })
+  })
+
+  it('adds a unique id to notification on show', () => {
+    const notification = {
+      title: 'Title',
+      content: 'content',
+      delay: 5000,
+    }
+    notificationService.show(notification)
+
+    notificationService.getNotifications().subscribe((notifications) => {
+      expect(notifications[0].id).toBeDefined()
+    })
+  })
+
+  it('parses error string to object on show', () => {
+    const notification = {
+      title: 'Title',
+      content: 'content',
+      delay: 5000,
+      error: 'Error string',
+    }
+    notificationService.show(notification)
+
+    notificationService.getNotifications().subscribe((notifications) => {
+      expect(notifications[0].error).toEqual('Error string')
+    })
+  })
+
+  it('creates notifications with defaults on showInfo and showError', () => {
+    notificationService.showInfo('Info notification')
+    notificationService.showError('Error notification')
+
+    notificationService.getNotifications().subscribe((notifications) => {
+      expect(notifications).toContainEqual({
+        content: 'Info notification',
+        delay: 5000,
+      })
+      expect(notifications).toContainEqual({
+        content: 'Error notification',
+        delay: 10000,
+      })
+    })
+  })
+
+  it('removes notification on close', () => {
+    const notification = {
+      title: 'Title',
+      content: 'content',
+      delay: 5000,
+    }
+    notificationService.show(notification)
+    notificationService.closeNotification(notification)
+
+    notificationService.getNotifications().subscribe((notifications) => {
+      expect(notifications).toHaveLength(0)
+    })
+  })
+
+  it('clears all notifications on clear', () => {
+    notificationService.showInfo('Info notification')
+    notificationService.showError('Error notification')
+    notificationService.clearNotifications()
+
+    notificationService.getNotifications().subscribe((notifications) => {
+      expect(notifications).toHaveLength(0)
+    })
+  })
+
+  it('suppresses popup notifications if suppressPopupNotifications is true', (finish) => {
+    notificationService.showNotification.subscribe((notification) => {
+      expect(notification).not.toBeNull()
+    })
+    notificationService.showInfo('Info notification')
+
+    notificationService.showNotification.subscribe((notification) => {
+      expect(notification).toBeNull()
+      finish()
+    })
+
+    notificationService.suppressPopupNotifications = true
+    notificationService.showInfo('Info notification')
+  })
+})
diff --git a/src-ui/src/app/services/notification.service.ts b/src-ui/src/app/services/notification.service.ts
new file mode 100644
index 000000000..63ed69fe2
--- /dev/null
+++ b/src-ui/src/app/services/notification.service.ts
@@ -0,0 +1,87 @@
+import { Injectable } from '@angular/core'
+import { Subject } from 'rxjs'
+import { v4 as uuidv4 } from 'uuid'
+
+export interface Notification {
+  id?: string
+
+  content: string
+
+  delay: number
+
+  delayRemaining?: number
+
+  action?: any
+
+  actionName?: string
+
+  classname?: string
+
+  error?: any
+}
+
+@Injectable({
+  providedIn: 'root',
+})
+export class NotificationService {
+  constructor() {}
+  _suppressPopupNotifications: boolean
+
+  set suppressPopupNotifications(value: boolean) {
+    this._suppressPopupNotifications = value
+    this.showNotification.next(null)
+  }
+
+  private notifications: Notification[] = []
+
+  private notificationsSubject: Subject<Notification[]> = new Subject()
+
+  public showNotification: Subject<Notification> = new Subject()
+
+  show(notification: Notification) {
+    if (!notification.id) {
+      notification.id = uuidv4()
+    }
+    if (typeof notification.error === 'string') {
+      try {
+        notification.error = JSON.parse(notification.error)
+      } catch (e) {}
+    }
+    this.notifications.unshift(notification)
+    if (!this._suppressPopupNotifications) {
+      this.showNotification.next(notification)
+    }
+    this.notificationsSubject.next(this.notifications)
+  }
+
+  showError(content: string, error: any = null, delay: number = 10000) {
+    this.show({
+      content: content,
+      delay: delay,
+      classname: 'error',
+      error,
+    })
+  }
+
+  showInfo(content: string, delay: number = 5000) {
+    this.show({ content: content, delay: delay })
+  }
+
+  closeNotification(notification: Notification) {
+    let index = this.notifications.findIndex((t) => t.id == notification.id)
+    if (index > -1) {
+      this.notifications.splice(index, 1)
+      this.notificationsSubject.next(this.notifications)
+    }
+  }
+
+  getNotifications() {
+    return this.notificationsSubject
+  }
+
+  clearNotifications() {
+    this.notifications = []
+    this.notificationsSubject.next(this.notifications)
+    this.showNotification.next(null)
+  }
+}
diff --git a/src-ui/src/app/services/settings.service.spec.ts b/src-ui/src/app/services/settings.service.spec.ts
index df44013f4..5353a6f1b 100644
--- a/src-ui/src/app/services/settings.service.spec.ts
+++ b/src-ui/src/app/services/settings.service.spec.ts
@@ -14,10 +14,10 @@ import { CustomFieldDataType } from '../data/custom-field'
 import { DEFAULT_DISPLAY_FIELDS, DisplayField } from '../data/document'
 import { SavedView } from '../data/saved-view'
 import { SETTINGS_KEYS, UiSettings } from '../data/ui-settings'
+import { NotificationService } from './notification.service'
 import { PermissionsService } from './permissions.service'
 import { CustomFieldsService } from './rest/custom-fields.service'
 import { SettingsService } from './settings.service'
-import { ToastService } from './toast.service'
 
 const customFields = [
   {
@@ -41,7 +41,7 @@ describe('SettingsService', () => {
   let customFieldsService: CustomFieldsService
   let permissionService: PermissionsService
   let subscription: Subscription
-  let toastService: ToastService
+  let notificationService: NotificationService
 
   const ui_settings: UiSettings = {
     user: {
@@ -105,7 +105,7 @@ describe('SettingsService', () => {
     customFieldsService = TestBed.inject(CustomFieldsService)
     permissionService = TestBed.inject(PermissionsService)
     settingsService = TestBed.inject(SettingsService)
-    toastService = TestBed.inject(ToastService)
+    notificationService = TestBed.inject(NotificationService)
     // Normally done in app initializer
     settingsService.initializeSettings().subscribe()
   })
@@ -122,8 +122,8 @@ describe('SettingsService', () => {
     expect(req.request.method).toEqual('GET')
   })
 
-  it('should catch error and show toast on retrieve ui_settings error', fakeAsync(() => {
-    const toastSpy = jest.spyOn(toastService, 'showError')
+  it('should catch error and show notification on retrieve ui_settings error', fakeAsync(() => {
+    const notificationSpy = jest.spyOn(notificationService, 'showError')
     httpTestingController
       .expectOne(`${environment.apiBaseUrl}ui_settings/`)
       .flush(
@@ -131,7 +131,7 @@ describe('SettingsService', () => {
         { status: 403, statusText: 'Forbidden' }
       )
     tick(500)
-    expect(toastSpy).toHaveBeenCalled()
+    expect(notificationSpy).toHaveBeenCalled()
   }))
 
   it('calls ui_settings api endpoint with POST on store', () => {
diff --git a/src-ui/src/app/services/settings.service.ts b/src-ui/src/app/services/settings.service.ts
index 80e3b3474..5ad7a0d4a 100644
--- a/src-ui/src/app/services/settings.service.ts
+++ b/src-ui/src/app/services/settings.service.ts
@@ -26,13 +26,13 @@ import {
   UiSettings,
 } from '../data/ui-settings'
 import { User } from '../data/user'
+import { NotificationService } from './notification.service'
 import {
   PermissionAction,
   PermissionsService,
   PermissionType,
 } from './permissions.service'
 import { CustomFieldsService } from './rest/custom-fields.service'
-import { ToastService } from './toast.service'
 
 export interface LanguageOption {
   code: string
@@ -294,7 +294,7 @@ export class SettingsService {
     private meta: Meta,
     @Inject(LOCALE_ID) private localeId: string,
     protected http: HttpClient,
-    private toastService: ToastService,
+    private notificationService: NotificationService,
     private permissionsService: PermissionsService,
     private customFieldsService: CustomFieldsService
   ) {
@@ -307,7 +307,7 @@ export class SettingsService {
       first(),
       catchError((error) => {
         setTimeout(() => {
-          this.toastService.showError('Error loading settings', error)
+          this.notificationService.showError('Error loading settings', error)
         }, 500)
         return of({
           settings: {
@@ -601,7 +601,7 @@ export class SettingsService {
           this.cookieService.get(this.getLanguageCookieName())
         )
       } catch (error) {
-        this.toastService.showError(errorMessage)
+        this.notificationService.showError(errorMessage)
         console.log(error)
       }
 
@@ -610,10 +610,10 @@ export class SettingsService {
         .subscribe({
           next: () => {
             this.updateAppearanceSettings()
-            this.toastService.showInfo(successMessage)
+            this.notificationService.showInfo(successMessage)
           },
           error: (e) => {
-            this.toastService.showError(errorMessage)
+            this.notificationService.showError(errorMessage)
             console.log(e)
           },
         })
@@ -633,7 +633,7 @@ export class SettingsService {
         .pipe(first())
         .subscribe({
           error: (e) => {
-            this.toastService.showError(
+            this.notificationService.showError(
               'Error migrating update checking setting'
             )
             console.log(e)
@@ -663,7 +663,7 @@ export class SettingsService {
       this.storeSettings()
         .pipe(first())
         .subscribe(() => {
-          this.toastService.showInfo(
+          this.notificationService.showInfo(
             $localize`You can restart the tour from the settings page.`
           )
         })
diff --git a/src-ui/src/app/services/toast.service.spec.ts b/src-ui/src/app/services/toast.service.spec.ts
deleted file mode 100644
index ce50b165e..000000000
--- a/src-ui/src/app/services/toast.service.spec.ts
+++ /dev/null
@@ -1,109 +0,0 @@
-import { TestBed } from '@angular/core/testing'
-import { ToastService } from './toast.service'
-
-describe('ToastService', () => {
-  let toastService: ToastService
-
-  beforeEach(() => {
-    TestBed.configureTestingModule({
-      providers: [ToastService],
-    })
-
-    toastService = TestBed.inject(ToastService)
-  })
-
-  it('adds toast on show', () => {
-    const toast = {
-      title: 'Title',
-      content: 'content',
-      delay: 5000,
-    }
-    toastService.show(toast)
-
-    toastService.getToasts().subscribe((toasts) => {
-      expect(toasts).toContainEqual(toast)
-    })
-  })
-
-  it('adds a unique id to toast on show', () => {
-    const toast = {
-      title: 'Title',
-      content: 'content',
-      delay: 5000,
-    }
-    toastService.show(toast)
-
-    toastService.getToasts().subscribe((toasts) => {
-      expect(toasts[0].id).toBeDefined()
-    })
-  })
-
-  it('parses error string to object on show', () => {
-    const toast = {
-      title: 'Title',
-      content: 'content',
-      delay: 5000,
-      error: 'Error string',
-    }
-    toastService.show(toast)
-
-    toastService.getToasts().subscribe((toasts) => {
-      expect(toasts[0].error).toEqual('Error string')
-    })
-  })
-
-  it('creates toasts with defaults on showInfo and showError', () => {
-    toastService.showInfo('Info toast')
-    toastService.showError('Error toast')
-
-    toastService.getToasts().subscribe((toasts) => {
-      expect(toasts).toContainEqual({
-        content: 'Info toast',
-        delay: 5000,
-      })
-      expect(toasts).toContainEqual({
-        content: 'Error toast',
-        delay: 10000,
-      })
-    })
-  })
-
-  it('removes toast on close', () => {
-    const toast = {
-      title: 'Title',
-      content: 'content',
-      delay: 5000,
-    }
-    toastService.show(toast)
-    toastService.closeToast(toast)
-
-    toastService.getToasts().subscribe((toasts) => {
-      expect(toasts).toHaveLength(0)
-    })
-  })
-
-  it('clears all toasts on clearToasts', () => {
-    toastService.showInfo('Info toast')
-    toastService.showError('Error toast')
-    toastService.clearToasts()
-
-    toastService.getToasts().subscribe((toasts) => {
-      expect(toasts).toHaveLength(0)
-    })
-  })
-
-  it('suppresses popup toasts if suppressPopupToasts is true', (finish) => {
-    toastService.showToast.subscribe((toast) => {
-      expect(toast).not.toBeNull()
-    })
-    toastService.showInfo('Info toast')
-
-    toastService.showToast.subscribe((toast) => {
-      expect(toast).toBeNull()
-      finish()
-    })
-
-    toastService.suppressPopupToasts = true
-    toastService.showInfo('Info toast')
-  })
-})
diff --git a/src-ui/src/app/services/toast.service.ts b/src-ui/src/app/services/toast.service.ts
deleted file mode 100644
index b917bf94b..000000000
--- a/src-ui/src/app/services/toast.service.ts
+++ /dev/null
@@ -1,87 +0,0 @@
-import { Injectable } from '@angular/core'
-import { Subject } from 'rxjs'
-import { v4 as uuidv4 } from 'uuid'
-
-export interface Toast {
-  id?: string
-
-  content: string
-
-  delay: number
-
-  delayRemaining?: number
-
-  action?: any
-
-  actionName?: string
-
-  classname?: string
-
-  error?: any
-}
-
-@Injectable({
-  providedIn: 'root',
-})
-export class ToastService {
-  constructor() {}
-  _suppressPopupToasts: boolean
-
-  set suppressPopupToasts(value: boolean) {
-    this._suppressPopupToasts = value
-    this.showToast.next(null)
-  }
-
-  private toasts: Toast[] = []
-
-  private toastsSubject: Subject<Toast[]> = new Subject()
-
-  public showToast: Subject<Toast> = new Subject()
-
-  show(toast: Toast) {
-    if (!toast.id) {
-      toast.id = uuidv4()
-    }
-    if (typeof toast.error === 'string') {
-      try {
-        toast.error = JSON.parse(toast.error)
-      } catch (e) {}
-    }
-    this.toasts.unshift(toast)
-    if (!this._suppressPopupToasts) {
-      this.showToast.next(toast)
-    }
-    this.toastsSubject.next(this.toasts)
-  }
-
-  showError(content: string, error: any = null, delay: number = 10000) {
-    this.show({
-      content: content,
-      delay: delay,
-      classname: 'error',
-      error,
-    })
-  }
-
-  showInfo(content: string, delay: number = 5000) {
-    this.show({ content: content, delay: delay })
-  }
-
-  closeToast(toast: Toast) {
-    let index = this.toasts.findIndex((t) => t.id == toast.id)
-    if (index > -1) {
-      this.toasts.splice(index, 1)
-      this.toastsSubject.next(this.toasts)
-    }
-  }
-
-  getToasts() {
-    return this.toastsSubject
-  }
-
-  clearToasts() {
-    this.toasts = []
-    this.toastsSubject.next(this.toasts)
-    this.showToast.next(null)
-  }
-}
diff --git a/src-ui/src/styles.scss b/src-ui/src/styles.scss
index a3f385ed5..103b1b8ba 100644
--- a/src-ui/src/styles.scss
+++ b/src-ui/src/styles.scss
@@ -571,7 +571,7 @@ table.table {
 }
 
 .toast {
-  --bs-toast-max-width: var(--pngx-toast-max-width);
+  --bs-toast-max-width: var(--pngx-notification-max-width);
 }
 
 .alert-primary {
diff --git a/src-ui/src/theme.scss b/src-ui/src/theme.scss
index cc60d3851..94d78d3c4 100644
--- a/src-ui/src/theme.scss
+++ b/src-ui/src/theme.scss
@@ -24,11 +24,11 @@
   --pngx-bg-alt2: var(--bs-gray-200); // #e9ecef
   --pngx-bg-disabled: #f7f7f7;
   --pngx-focus-alpha: 0.3;
-  --pngx-toast-max-width: 360px;
+  --pngx-notification-max-width: 360px;
   --bs-info: var(--pngx-bg-alt2);
   --bs-info-rgb: 233, 236, 239;
   @media screen and (min-width: 1024px) {
-    --pngx-toast-max-width: 450px;
+    --pngx-notification-max-width: 450px;
   }
 }