diff --git a/CHANGELOG.md b/CHANGELOG.md
index 04eeea2a2..304d756b7 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -17,6 +17,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 - Added function for creating a new list and a test verifying list editing. [#112](https://github.com/verse-pbc/issues/issues/112)
 - Localized strings on the feed filter drop-down view.
 - Disabled automatic tracking in Sentry. [#126](https://github.com/verse-pbc/issues/issues/126)
+- Track TestFlight vs AppStore installations in Posthog. [#130](https://github.com/verse-pbc/issues/issues/130)
 - Added functionality to get follows notifications in the Notifications tab. [#127](https://github.com/verse-pbc/issues/issues/127)
 
 ## [1.1] - 2025-01-03Z
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index e14ab2151..7f36b5cb2 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -55,7 +55,15 @@ A maintainer will review your code and merge it when it has the required number
 
 ## Hot Reloading
 
-We make use of the [Inject](https://github.com/krzysztofzablocki/Inject) framework for hot reloading debug builds. To set up hot reloading, follow the [documentation](https://github.com/krzysztofzablocki/Inject?tab=readme-ov-file#individual-developer-setup-once-per-machine).
+We make use of the [Inject](https://github.com/krzysztofzablocki/Inject) framework for hot reloading debug builds. To set it up install the latest version of [InjectionIII](https://github.com/johnno1962/InjectionIII/releases). You can hot reload the app by:
+- Launching InjectionIII
+- Add `import Inject`, `@ObserveInjection var inject` to the top of the SwiftUI view you wish to reload, and add `.enableInjection()` as the last line in `body`.
+- Build and run the app. You should see something like `💉 InjectionIII connected /Users/you/nos/Nos.xcodeproj` in the console.
+- Change some code.
+- Hit command-S to save. You should see Inject recompile the file in the logs
+- For some reason our views don't update right away, but if you navigate away from the screen and back it should have reloaded.
+
+Full documentation is availabe [here](https://github.com/krzysztofzablocki/Inject?tab=readme-ov-file#workflow-integration)
 
 ## Dependency Management
 
diff --git a/Nos/AppController.swift b/Nos/AppController.swift
index f46b6846a..f920a2d2a 100644
--- a/Nos/AppController.swift
+++ b/Nos/AppController.swift
@@ -20,6 +20,7 @@ import Logger
     init() {
         currentState = .loading
         Log.info("App Version: \(Bundle.current.versionAndBuild)")
+        analytics.trackInstallationSourceIfNeeded()
     }
     
     func configureCurrentState() {
diff --git a/Nos/Assets/Localization/Localizable.xcstrings b/Nos/Assets/Localization/Localizable.xcstrings
index 7c5d65526..c494320da 100644
--- a/Nos/Assets/Localization/Localizable.xcstrings
+++ b/Nos/Assets/Localization/Localizable.xcstrings
@@ -14084,7 +14084,7 @@
         "en" : {
           "stringUnit" : {
             "state" : "translated",
-            "value" : "Your **@username** is your identity in the Nos community.
\nChoose a name that reflects you or your organization. Make it memorable and distinct!"
+            "value" : "Your **@username** is your identity in the Nos community.\u2028\nChoose a name that reflects you or your organization. Make it memorable and distinct!"
           }
         },
         "es" : {
@@ -19342,7 +19342,7 @@
         "en" : {
           "stringUnit" : {
             "state" : "translated",
-            "value" : "Well done, you've successfully claimed your **@username**!
\nYou can share this name with other people in the Nostr and Fediverse communities to make it easy to find you."
+            "value" : "Well done, you've successfully claimed your **@username**!\u2028\nYou can share this name with other people in the Nostr and Fediverse communities to make it easy to find you."
           }
         },
         "es" : {
diff --git a/Nos/Extensions/Bundle+Current.swift b/Nos/Extensions/Bundle+Current.swift
index 71d9177f8..f8b2fdf22 100644
--- a/Nos/Extensions/Bundle+Current.swift
+++ b/Nos/Extensions/Bundle+Current.swift
@@ -3,6 +3,11 @@ import Foundation
 private class CurrentBundle {}
 
 extension Bundle {
+    enum InstallationSource: String {
+        case testFlight = "TestFlight"
+        case appStore = "App Store"
+        case debug = "Debug"
+    }
 
     static let current = Bundle(for: CurrentBundle.self)
     
@@ -21,4 +26,23 @@ extension Bundle {
     var versionAndBuild: String {
         "\(self.version) (\(self.build))"
     }
+
+    /// > Warning: This method relies on undocumented implementation details to determine the installation source
+    /// and may break in future iOS releases.
+    /// https://gist.github.com/lukaskubanek/cbfcab29c0c93e0e9e0a16ab09586996
+    /// Checks the app's receipt URL to determine if it contains the TestFlight-specific
+    /// "sandboxReceipt" identifier.
+    /// - Returns: `true` if the app was installed through TestFlight, `false` otherwise.
+    private var isTestFlight: Bool {
+        Bundle.main.appStoreReceiptURL?.lastPathComponent == "sandboxReceipt"
+    }
+
+    /// Returns the app's installation source: debug, TestFlight, or App Store.
+    var installationSource: InstallationSource {
+    #if DEBUG
+        return .debug
+    #else
+        return isTestFlight ? .testFlight : .appStore
+    #endif
+    }
 }
diff --git a/Nos/Extensions/UIDevice+Simulator.swift b/Nos/Extensions/UIDevice+Simulator.swift
index 5ae8daea2..2bfdd9670 100644
--- a/Nos/Extensions/UIDevice+Simulator.swift
+++ b/Nos/Extensions/UIDevice+Simulator.swift
@@ -6,4 +6,12 @@ extension UIDevice {
     static var isSimulator: Bool {
         ProcessInfo.processInfo.environment["SIMULATOR_DEVICE_NAME"] != nil
     }
+
+    static var platformName: String {
+    #if os(iOS)
+        return UIDevice.current.systemName
+    #elseif os(macOS)
+        return "macOS"
+    #endif
+    }
 }
diff --git a/Nos/Service/Analytics.swift b/Nos/Service/Analytics.swift
index 945396d5e..c781eaa67 100644
--- a/Nos/Service/Analytics.swift
+++ b/Nos/Service/Analytics.swift
@@ -1,4 +1,4 @@
-import Foundation
+import UIKit
 import PostHog
 import Dependencies
 import Logger
@@ -155,6 +155,31 @@ class Analytics {
         postHog?.capture(eventName, properties: properties)
     }
 
+    /// Tracks the source of the app download when the user launches the app.
+    func trackInstallationSourceIfNeeded() {
+        let source = Bundle.main.installationSource
+        // Make sure we don't track in debug mode.
+        guard source != .debug else { return }
+
+        let installSourceKey = "TrackedAppInstallationSource"
+
+        // Check if we've already tracked this installation.
+        if UserDefaults.standard.bool(forKey: installSourceKey) {
+            return
+        }
+
+        track(
+            "Installation Source",
+            properties: [
+                "source": source.rawValue,
+                "platform": UIDevice.platformName,
+                "app_version": Bundle.current.versionAndBuild
+            ]
+        )
+        // Mark as tracked so we don't track again
+        UserDefaults.standard.set(true, forKey: installSourceKey)
+    }
+
     /// Tracks when the user submits a search on the Discover screen.
     func searchedDiscover() {
         track("Discover Search Started")