diff --git a/package.json b/package.json
index 3a75bac5944682d07020942a1316ca12892dc128..ed7155dc92be823c5965aaca0a5fa2bdceea30e6 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "chatterbox",
-  "version": "0.2.2",
+  "version": "0.2.3",
   "scripts": {
     "start": "vite",
     "build": "tsc && vite build && vite build --config parent-vite.config.js && scripts/after-build.sh",
diff --git a/src/main.ts b/src/main.ts
index 9c3c83e1a26c778ca955733e81ba0c32ad4a9340..cd2338aad2b43582e593c32f474b5430e98c25cc 100644
--- a/src/main.ts
+++ b/src/main.ts
@@ -29,6 +29,10 @@ async function fetchConfig(): Promise<IChatterboxConfig> {
     return config;
 }
 
+function shouldStartMinimized(): boolean {
+    return !!new URLSearchParams(window.location.search).get("minimized");
+}
+
 async function main() {
     const root = document.querySelector(rootDivId) as HTMLDivElement;
     if (!root) {
@@ -40,7 +44,8 @@ async function main() {
     const navigation = new Navigation(allowsChild);
     platform.setNavigation(navigation);
     const urlRouter = createRouter({ navigation, history: platform.history });
-    const rootViewModel = new RootViewModel(config, {platform, navigation, urlCreator: urlRouter});
+    const startMinimized = shouldStartMinimized();
+    const rootViewModel = new RootViewModel(config, {platform, navigation, urlCreator: urlRouter, startMinimized});
     rootViewModel.start();
     const rootView = new RootView(rootViewModel);
     root.appendChild(rootView.mount());
@@ -67,4 +72,8 @@ function allowsChild(parent, child) {
     window.parent?.postMessage({ action: "minimize" }, "*");
 };
 
+(window as any).sendNotificationCount = function (count: number) {
+    window.parent?.postMessage({ action: "unread-message", count }, "*");
+};
+
 main();
diff --git a/src/observables/MessageFromParent.ts b/src/observables/MessageFromParent.ts
new file mode 100644
index 0000000000000000000000000000000000000000..7b5446525e5be993224f14b59002512c5c9159ed
--- /dev/null
+++ b/src/observables/MessageFromParent.ts
@@ -0,0 +1,11 @@
+import { EventEmitter } from "hydrogen-view-sdk";
+
+export class MessageFromParent extends EventEmitter {
+    constructor() {
+        super();
+        window.addEventListener("message", (event) => {
+            const { action } = event.data;
+            this.emit(action, event.data);
+        });
+    }
+}
diff --git a/src/parent/iframe.ts b/src/parent/iframe.ts
index 28880132b3440415a843c4e125475befcc96c657..5b35ac74873419f2bf6efae0aaa5ff965511b559 100644
--- a/src/parent/iframe.ts
+++ b/src/parent/iframe.ts
@@ -17,11 +17,13 @@ export function toggleIframe() {
     if (iframeElement.style.display !== "none") {
         iframeElement.style.display = "none";
         document.querySelector(".start-chat-btn").classList.remove("start-background-minimized");
+        iframeElement.contentWindow.postMessage({ action: "minimize" }, "*");;
         if (isMobile()) {
             startButtonDiv.style.display = "block";
         }
     }
     else {
+        iframeElement.contentWindow.postMessage({ action: "maximize" }, "*");;
         iframeElement.style.display = "block";
         document.querySelector(".start-chat-btn").classList.add("start-background-minimized");
         if (isMobile()) {
diff --git a/src/parent/load.ts b/src/parent/load.ts
index a4ec55526aaa067331ceaa02d7e333a4e3e4d3fd..59d7c3d4a9874737273bb8f49c49a54d89ffd2c5 100644
--- a/src/parent/load.ts
+++ b/src/parent/load.ts
@@ -8,11 +8,31 @@ export function loadStartButton() {
     loadCSS();
     const container = document.createElement("div");
     container.className = "start";
+    const button = createStartButton();
+    container.appendChild(button);
+    document.body.appendChild(container);
+    if (window.localStorage.getItem("chatterbox-should-load-in-background")) {
+        /**
+         * If chatterbox made it to the timeline before, load the chatterbox app in background.
+         * This will let us watch for new messages and show a notification badge as needed.
+         */
+        loadIframe(true);
+        toggleIframe();
+    }
+}
+
+function createStartButton() {
     const button = document.createElement("button");
     button.className = "start-chat-btn";
     button.onclick = () => (window as any).isIframeLoaded? toggleIframe() : loadIframe();
-    container.appendChild(button);
-    document.body.appendChild(container);
+    button.appendChild(createNotificationBadge());
+    return button;
+}
+
+function createNotificationBadge() {
+    const notificationBadge = document.createElement("span");
+    notificationBadge.className = "notification-badge hidden";
+    return notificationBadge;
 }
 
 function loadCSS() {
@@ -22,22 +42,20 @@ function loadCSS() {
     document.head.appendChild(linkElement);
 }
 
-function loadIframe() {
+function loadIframe(minimized = false) {
     const iframe = document.createElement("iframe");
     const configLocation = (window as any).CHATTERBOX_CONFIG_LOCATION;
     if (!configLocation) {
         throw new Error("CHATTERBOX_CONFIG_LOCATION is not set");
     }
     iframe.src = new URL(
-        "../chatterbox.html?config=" + configLocation,
+        `../chatterbox.html?config=${configLocation}${minimized? "&minimized=true": ""}`,
         hostRoot
     ).href;
     iframe.className = "chatterbox-iframe";
     document.body.appendChild(iframe);
     (window as any).isIframeLoaded = true;
-    document
-        .querySelector(".start-chat-btn")
-        .classList.add("start-background-minimized");
+    document .querySelector(".start-chat-btn") .classList.add("start-background-minimized");
     if (isMobile()) {
         (document.querySelector(".start") as HTMLDivElement).style.display =
             "none";
diff --git a/src/parent/parent-style.css b/src/parent/parent-style.css
index a6eaa88954bde09942c8136bfc8c26a8a77fed01..ec905c358f6a0428c941c13048990565012f86e3 100644
--- a/src/parent/parent-style.css
+++ b/src/parent/parent-style.css
@@ -39,3 +39,24 @@
 .start-background-minimized {
     background: no-repeat center url("../ui/res/chevron-down-button.svg"), linear-gradient(180deg, #7657F2 0%, #5C56F5 100%);
 }
+
+.notification-badge {
+    position: absolute;
+    width: 20px;
+    height: 20px;
+    color: white;
+    background-color: #FF5B55;
+    left: 47px;
+    bottom: 49px;
+    border-radius: 100%;
+    font-size: 12px;
+    font-weight: bold;
+    display: flex;
+    justify-content: center;
+    align-items: center;
+    box-shadow: 0px 1px 2px rgba(0, 0, 0, 0.25);
+}
+
+.hidden {
+    display: none;
+}
diff --git a/src/parent/parent.ts b/src/parent/parent.ts
index ab7b50725fe6bc9d89314920365a2c521b941a11..5836df9b89e699f949182bfdc9412b32003d5bb1 100644
--- a/src/parent/parent.ts
+++ b/src/parent/parent.ts
@@ -4,15 +4,35 @@ import "./parent-style.css";
 
 (window as any).isIframeLoaded = false;
 
+function setUnreadCount(count) {
+    const notification = document.querySelector(".notification-badge") as HTMLSpanElement;
+    if (count === 0) {
+        notification.classList.add("hidden");
+    }
+    else {
+        notification.innerText = count;
+        notification.classList.remove("hidden");
+    }
+}
+
 window.addEventListener("message", event => {
     const { action } = event.data;
     switch (action) {
         case "resize-iframe":
+            if (event.data.view === "timeline") {
+                // Chatterbox has made it to the timeline!
+                // Store this is info in localStorage so that we know to load chatterbox in background
+                // in subsequent visits.
+                window.localStorage.setItem("chatterbox-should-load-in-background", "true");
+            }
             resizeIframe(event.data);
             break;
         case "minimize":
             toggleIframe();
             break;
+        case "unread-message":
+            setUnreadCount(event.data.count);
+            break;
     }
 });
 
diff --git a/src/ui/views/ChatterboxView.ts b/src/ui/views/ChatterboxView.ts
index ac40e13a57af3f49ea240a40966a5596d0476d4b..c7e12bb280096e13529642ea206207208ae82a50 100644
--- a/src/ui/views/ChatterboxView.ts
+++ b/src/ui/views/ChatterboxView.ts
@@ -10,7 +10,8 @@ export class ChatterboxView extends TemplateView<ChatterboxViewModel> {
     }
 
     render(t) {
-        return t.div({ className: "ChatterboxView" }, [
+        return t.div({ className: "ChatterboxView", },
+            [
             t.mapView(
                 (vm) => (vm.roomViewModel ? vm : null),
                 (vm) => (vm ? new RoomHeaderView(vm) : null)
@@ -39,7 +40,11 @@ class RoomHeaderView extends TemplateView<ChatterboxViewModel> {
             avatar,
             t.div({ className: "RoomHeaderView_name" }, vm => vm.roomName),
             t.div({ className: "RoomHeaderView_menu" }, [
-                t.button({ className: "RoomHeaderView_menu_minimize", onClick: () => (window as any).sendMinimizeToParent() })
+                t.button({
+                    className: "RoomHeaderView_menu_minimize", onClick: () => {
+                        vm.minimize();
+                    }
+                })
             ]),
         ]);
     }
diff --git a/src/viewmodels/ChatterboxViewModel.ts b/src/viewmodels/ChatterboxViewModel.ts
index 55caf2053df667bc08f20bb06586456debedde37..e8fcd0180118db75dab395bf2efc948994145d58 100644
--- a/src/viewmodels/ChatterboxViewModel.ts
+++ b/src/viewmodels/ChatterboxViewModel.ts
@@ -3,11 +3,13 @@ import { RoomViewModel, ViewModel, RoomStatus} from "hydrogen-view-sdk";
 export class ChatterboxViewModel extends ViewModel {
     private _roomViewModel?: typeof RoomViewModel;
     private _loginPromise: Promise<void>;
+    private _minimize: () => void;
 
     constructor(options) {
         super(options);
         this._client = options.client;
         this._loginPromise = options.loginPromise;
+        this._minimize = options.minimize;
     }
 
     async load() {
@@ -22,13 +24,13 @@ export class ChatterboxViewModel extends ViewModel {
         else {
             throw new Error("ConfigError: You must either specify 'invite_user' or 'auto_join_room'");
         }
-        this._roomViewModel = new RoomViewModel({
+        this._roomViewModel = this.track(new RoomViewModel({
             room,
             ownUserId: this._session.userId,
             platform: this.platform,
             urlCreator: this.urlCreator,
             navigation: this.navigation,
-        });
+        }));
         await this._roomViewModel.load();
         this.emitChange("roomViewModel");
     }
@@ -87,6 +89,11 @@ export class ChatterboxViewModel extends ViewModel {
         return promise;
     }
 
+    minimize() {
+        (window as any).sendMinimizeToParent();
+        this._minimize();
+    }
+
     get timelineViewModel() {
         return this._roomViewModel?.timelineViewModel;
     }
diff --git a/src/viewmodels/RootViewModel.ts b/src/viewmodels/RootViewModel.ts
index b4f5484c6fd5058fcbc1622dade8ad366c3f5412..ae2f3463e9fb31fbda513a7012dab6a13f7fd4de 100644
--- a/src/viewmodels/RootViewModel.ts
+++ b/src/viewmodels/RootViewModel.ts
@@ -1,10 +1,11 @@
-import { ViewModel, Client, Navigation, createRouter, Platform  } from "hydrogen-view-sdk";
+import { ViewModel, Client, Navigation, createRouter, Platform } from "hydrogen-view-sdk";
 import { IChatterboxConfig } from "../types/IChatterboxConfig";
 import { ChatterboxViewModel } from "./ChatterboxViewModel";
 import "hydrogen-view-sdk/style.css";
 import { AccountSetupViewModel } from "./AccountSetupViewModel";
+import { MessageFromParent } from "../observables/MessageFromParent";
 
-type Options = { platform: typeof Platform, navigation: typeof Navigation, urlCreator: ReturnType<typeof createRouter> };
+type Options = { platform: typeof Platform, navigation: typeof Navigation, urlCreator: ReturnType<typeof createRouter>, startMinimized: boolean };
 
 export class RootViewModel extends ViewModel {
     private _config: IChatterboxConfig;
@@ -12,12 +13,19 @@ export class RootViewModel extends ViewModel {
     private _chatterBoxViewModel?: ChatterboxViewModel;
     private _accountSetupViewModel?: AccountSetupViewModel;
     private _activeSection?: string;
+    private _messageFromParent: MessageFromParent = new MessageFromParent();
+    private _startMinimized: boolean;
+    private _isWatchingNotificationCount: boolean;
 
     constructor(config: IChatterboxConfig, options: Options) {
         super(options);
+        this._startMinimized = options.startMinimized;
         this._config = config;
         this._client = new Client(this.platform);
         this._setupNavigation();
+        this._messageFromParent.on("maximize", () => this._showTimeline(Promise.resolve()));
+        // Chatterbox can be minimized via the start button on the parent page!
+        this._messageFromParent.on("minimize", () => this.minimizeChatterbox());
     }
 
     private _setupNavigation() {
@@ -28,6 +36,10 @@ export class RootViewModel extends ViewModel {
     async start() {
         const sessionAlreadyExists = await this.attemptStartWithExistingSession();
         if (sessionAlreadyExists) {
+            this._watchNotificationCount();
+            if (this._startMinimized) {
+                return;
+            }
             this.navigation.push("timeline");
             return;
         }
@@ -43,9 +55,14 @@ export class RootViewModel extends ViewModel {
                     config: this._config,
                     state: this._state,
                     loginPromise,
+                    minimize: () => this.minimizeChatterbox()
                 })
             ));
-            this._chatterBoxViewModel.load();
+            await this._chatterBoxViewModel.load();
+            if (!this._isWatchingNotificationCount) {
+                // for when chatterbox is loaded initially
+                this._watchNotificationCount();
+            }
         }
         this.emitChange("activeSection");
     }
@@ -77,6 +94,38 @@ export class RootViewModel extends ViewModel {
         return false;
     }
 
+    private _watchNotificationCount() {
+        const [room] = this._client.session.rooms.values();
+        let previousCount = room.notificationCount;
+        (window as any).sendNotificationCount(previousCount);
+        const subscription = {
+            onUpdate(_: unknown, room) {
+                const newCount = room.notificationCount;
+                if (newCount !== previousCount) {
+                    if (!room.isUnread && newCount !== 0) {
+                        /*
+                        when chatterbox is maximized and there are previous unread messages,
+                        this condition is hit but we still want to send the notification count so that 
+                        the badge zeroes out.
+                        */
+                        room.clearUnread();
+                        return;
+                    }
+                    (window as any).sendNotificationCount(newCount);
+                    previousCount = newCount;
+                }
+            },
+        };
+        this.track(this._client.session.rooms.subscribe(subscription));
+        this._isWatchingNotificationCount = true;
+    }
+    
+    minimizeChatterbox() {
+        this._chatterBoxViewModel = this.disposeTracked(this._chatterBoxViewModel);
+        this._activeSection = "";
+        this.emitChange("chatterboxViewModel");
+    }
+
     get chatterboxViewModel() {
         return this._chatterBoxViewModel;
     }