diff --git a/web/src/app/Connection.js b/web/src/app/Connection.js
index d5ec7900..6d1b9ac7 100644
--- a/web/src/app/Connection.js
+++ b/web/src/app/Connection.js
@@ -1,18 +1,31 @@
+import {shortTopicUrl, topicUrlWs} from "./utils";
+
+const retryBackoffSeconds = [5, 10, 15, 20, 30, 45, 60, 120];
+
 class Connection {
-    constructor(wsUrl, subscriptionId, onNotification) {
-        this.wsUrl = wsUrl;
+    constructor(subscriptionId, baseUrl, topic, since, onNotification) {
         this.subscriptionId = subscriptionId;
+        this.baseUrl = baseUrl;
+        this.topic = topic;
+        this.since = since;
+        this.shortUrl = shortTopicUrl(baseUrl, topic);
         this.onNotification = onNotification;
         this.ws = null;
+        this.retryCount = 0;
+        this.retryTimeout = null;
     }
 
     start() {
-        const socket = new WebSocket(this.wsUrl);
-        socket.onopen = (event) => {
-            console.log(`[Connection] [${this.subscriptionId}] Connection established`);
+        const since = (this.since === 0) ? "all" : this.since.toString();
+        const wsUrl = topicUrlWs(this.baseUrl, this.topic, since);
+        console.log(`[Connection, ${this.shortUrl}] Opening connection to ${wsUrl}`);
+        this.ws = new WebSocket(wsUrl);
+        this.ws.onopen = (event) => {
+            console.log(`[Connection, ${this.shortUrl}] Connection established`, event);
+            this.retryCount = 0;
         }
-        socket.onmessage = (event) => {
-            console.log(`[Connection] [${this.subscriptionId}] Message received from server: ${event.data}`);
+        this.ws.onmessage = (event) => {
+            console.log(`[Connection, ${this.shortUrl}] Message received from server: ${event.data}`);
             try {
                 const data = JSON.parse(event.data);
                 const relevantAndValid =
@@ -21,31 +34,43 @@ class Connection {
                     'time' in data &&
                     'message' in data;
                 if (!relevantAndValid) {
+                    console.log(`[Connection, ${this.shortUrl}] Message irrelevant or invalid. Ignoring.`);
                     return;
                 }
+                this.since = data.time;
                 this.onNotification(this.subscriptionId, data);
             } catch (e) {
-                console.log(`[Connection] [${this.subscriptionId}] Error handling message: ${e}`);
+                console.log(`[Connection, ${this.shortUrl}] Error handling message: ${e}`);
             }
         };
-        socket.onclose = (event) => {
+        this.ws.onclose = (event) => {
             if (event.wasClean) {
-                console.log(`[Connection] [${this.subscriptionId}] Connection closed cleanly, code=${event.code} reason=${event.reason}`);
+                console.log(`[Connection, ${this.shortUrl}] Connection closed cleanly, code=${event.code} reason=${event.reason}`);
+                this.ws = null;
             } else {
-                console.log(`[Connection] [${this.subscriptionId}] Connection died`);
+                const retrySeconds = retryBackoffSeconds[Math.min(this.retryCount, retryBackoffSeconds.length-1)];
+                this.retryCount++;
+                console.log(`[Connection, ${this.shortUrl}] Connection died, retrying in ${retrySeconds} seconds`);
+                this.retryTimeout = setTimeout(() => this.start(), retrySeconds * 1000);
             }
         };
-        socket.onerror = (event) => {
-            console.log(this.subscriptionId, `[Connection] [${this.subscriptionId}] ${event.message}`);
+        this.ws.onerror = (event) => {
+            console.log(`[Connection, ${this.shortUrl}] Error occurred: ${event}`, event);
         };
-        this.ws = socket;
     }
 
-    cancel() {
-        if (this.ws !== null) {
-            this.ws.close();
-            this.ws = null;
+    close() {
+        console.log(`[Connection, ${this.shortUrl}] Closing connection`);
+        const socket = this.ws;
+        const retryTimeout = this.retryTimeout;
+        if (socket !== null) {
+            socket.close();
         }
+        if (retryTimeout !== null) {
+            clearTimeout(retryTimeout);
+        }
+        this.retryTimeout = null;
+        this.ws = null;
     }
 }
 
diff --git a/web/src/app/ConnectionManager.js b/web/src/app/ConnectionManager.js
index 9018c612..374bba2b 100644
--- a/web/src/app/ConnectionManager.js
+++ b/web/src/app/ConnectionManager.js
@@ -14,10 +14,12 @@ export class ConnectionManager {
         subscriptions.forEach((id, subscription) => {
             const added = !this.connections.get(id)
             if (added) {
-                const wsUrl = subscription.wsUrl();
-                const connection = new Connection(wsUrl, id, onNotification);
+                const baseUrl = subscription.baseUrl;
+                const topic = subscription.topic;
+                const since = 0;
+                const connection = new Connection(id, baseUrl, topic, since, onNotification);
                 this.connections.set(id, connection);
-                console.log(`[ConnectionManager] Starting new connection ${id} using URL ${wsUrl}`);
+                console.log(`[ConnectionManager] Starting new connection ${id}`);
                 connection.start();
             }
         });
@@ -27,7 +29,7 @@ export class ConnectionManager {
             console.log(`[ConnectionManager] Closing connection ${id}`);
             const connection = this.connections.get(id);
             this.connections.delete(id);
-            connection.cancel();
+            connection.close();
         });
     }
 }
diff --git a/web/src/app/Subscription.js b/web/src/app/Subscription.js
index 8046cffa..3a1268f1 100644
--- a/web/src/app/Subscription.js
+++ b/web/src/app/Subscription.js
@@ -1,24 +1,15 @@
 import {topicUrl, shortTopicUrl, topicUrlWs} from './utils';
 
 export default class Subscription {
-    id = '';
-    baseUrl = '';
-    topic = '';
-    notifications = [];
-    lastActive = null;
-
     constructor(baseUrl, topic) {
         this.id = topicUrl(baseUrl, topic);
         this.baseUrl = baseUrl;
         this.topic = topic;
+        this.notifications = new Map();
     }
 
     addNotification(notification) {
-        if (notification.time === null) {
-            return this;
-        }
-        this.notifications.push(notification);
-        this.lastActive = notification.time;
+        this.notifications.set(notification.id, notification);
         return this;
     }
 
@@ -27,12 +18,8 @@ export default class Subscription {
         return this;
     }
 
-    url() {
-        return this.id;
-    }
-
-    wsUrl() {
-        return topicUrlWs(this.baseUrl, this.topic);
+    getNotifications() {
+        return Array.from(this.notifications.values());
     }
 
     shortUrl() {
diff --git a/web/src/app/utils.js b/web/src/app/utils.js
index 794af812..3f8573c7 100644
--- a/web/src/app/utils.js
+++ b/web/src/app/utils.js
@@ -1,5 +1,5 @@
 export const topicUrl = (baseUrl, topic) => `${baseUrl}/${topic}`;
-export const topicUrlWs = (baseUrl, topic) => `${topicUrl(baseUrl, topic)}/ws`
+export const topicUrlWs = (baseUrl, topic, since) => `${topicUrl(baseUrl, topic)}/ws?since=${since}`
     .replaceAll("https://", "wss://")
     .replaceAll("http://", "ws://");
 export const topicUrlJson = (baseUrl, topic) => `${topicUrl(baseUrl, topic)}/json`;
diff --git a/web/src/components/AddDialog.js b/web/src/components/AddDialog.js
index 837133ce..c1b1fe21 100644
--- a/web/src/components/AddDialog.js
+++ b/web/src/components/AddDialog.js
@@ -9,7 +9,8 @@ import DialogTitle from '@mui/material/DialogTitle';
 import {useState} from "react";
 import Subscription from "../app/Subscription";
 
-const defaultBaseUrl = "https://ntfy.sh"
+const defaultBaseUrl = "http://127.0.0.1"
+//const defaultBaseUrl = "https://ntfy.sh"
 
 const AddDialog = (props) => {
     const [topic, setTopic] = useState("");
diff --git a/web/src/components/App.js b/web/src/components/App.js
index 1181b38e..d63b928d 100644
--- a/web/src/components/App.js
+++ b/web/src/components/App.js
@@ -101,7 +101,7 @@ const SubscriptionNavItem = (props) => {
 }
 
 const App = () => {
-    console.log("Launching App component");
+    console.log(`[App] Rendering main view`);
 
     const [drawerOpen, setDrawerOpen] = useState(true);
     const [subscriptions, setSubscriptions] = useState(new Subscriptions());
@@ -114,6 +114,7 @@ const App = () => {
         });
     };
     const handleSubscribeSubmit = (subscription) => {
+        console.log(`[App] New subscription: ${subscription.id}`);
         setSubscribeDialogOpen(false);
         setSubscriptions(prev => prev.add(subscription).clone());
         setSelectedSubscription(subscription);
@@ -126,10 +127,11 @@ const App = () => {
             });
     };
     const handleSubscribeCancel = () => {
-        console.log(`Cancel clicked`);
+        console.log(`[App] Cancel clicked`);
         setSubscribeDialogOpen(false);
     };
     const handleUnsubscribe = (subscriptionId) => {
+        console.log(`[App] Unsubscribing from ${subscriptionId}`);
         setSubscriptions(prev => {
             const newSubscriptions = prev.remove(subscriptionId).clone();
             setSelectedSubscription(newSubscriptions.firstOrNull());
@@ -137,10 +139,10 @@ const App = () => {
         });
     };
     const handleSubscriptionClick = (subscriptionId) => {
-        console.log(`Selected subscription ${subscriptionId}`);
+        console.log(`[App] Selected ${subscriptionId}`);
         setSelectedSubscription(subscriptions.get(subscriptionId));
     };
-    const notifications = (selectedSubscription !== null) ? selectedSubscription.notifications : [];
+    const notifications = (selectedSubscription !== null) ? selectedSubscription.getNotifications() : [];
     const toggleDrawer = () => {
         setDrawerOpen(!drawerOpen);
     };