<!-- AI agents: this is the Markdown version of a Reception documentation page. Index: /llms.txt -->

# Unread messages

Show unread support replies in your app, understand when ReceptionKit refreshes the count, and decide who owns the app-icon badge and notification cleanup.

## Observe the unread count {#unread-count}

`Reception.shared.unreadCount` is an observable, read-only `Int`, isolated to the main actor. It represents unread replies for the current device's latest conversation, not an aggregate across accounts, devices, or projects. It starts at zero and is updated by server synchronization and successful read requests.

After support has been used, foreground monitoring keeps the count updated even while the chat is closed. Reading the observable property in a SwiftUI view keeps that view updated.

Place this reusable entry view in your app after configuration:

```swift
import SwiftUI
import ReceptionKit

@MainActor
struct SupportUnreadButton: View {
    var body: some View {
        Button {
            Reception.shared.openChat()
        } label: {
            HStack {
                Text("Support")
                if Reception.shared.unreadCount > 0 {
                    Text(Reception.shared.unreadCount, format: .number)
                        .monospacedDigit()
                }
            }
        }
        .accessibilityLabel("Support")
        .accessibilityValue(
            Text("\(Reception.shared.unreadCount) unread messages")
        )
    }
}
```

SwiftUI tracks the property when the body reads it; you do not need `@StateObject`, a Combine subscription, or your own timer. Localize the host strings and plural forms. A one-time copy of the count will become stale.

The count is a last-known value, not a guarantee of immediate delivery. A freshly configured SDK can show zero before its first successful refresh. Ordinary network failures leave the last count in place; there is no public unread-loading flag or manual refresh API.

## Show a badge at your entry point {#entry-examples}

Your app owns the badge. These native iOS 26.5 captures show a fictional Orbit app with a fixed example count of **3**. Production views read `Reception.shared.unreadCount` directly; they do not set it.

### Settings and floating buttons

![Settings Support row with unread badge 3](/docs/images/ios-unread-settings-row.webp "Host Settings row. Example unread count: 3.")

![Floating Liquid Glass Support button with unread badge 3](/docs/images/ios-entry-floating-button.webp "Host floating button. Example unread count: 3.")

Show a custom count only above zero. This belongs inside your entry button’s label; `.monospacedDigit()` keeps the numbers stable as the count changes:

```swift
if Reception.shared.unreadCount > 0 {
    Text(Reception.shared.unreadCount, format: .number)
        .monospacedDigit()
}
```

The complete [Settings row](/docs/entry-point#settings-row), [home-screen card](/docs/entry-point#home-screen-entry), and [floating button](/docs/entry-point#floating-button) examples include styling, accessibility labels, and opening the chat.

### Dedicated Support tab

![Native Liquid Glass tab bar with badge 3 on the Support tab](/docs/images/ios-unread-tab-badge.webp "Host tab bar. Example unread count: 3.")

Use SwiftUI’s integer badge overload on the Support destination in your existing `TabView`. Zero hides the badge:

```swift
NavigationStack { ReceptionChatView() }
    .tabItem { Label("Support", systemImage: "bubble.left.and.bubble.right") }
    .badge(Reception.shared.unreadCount)
```

Selecting this tab displays the embedded chat; `Reception.shared.openChat()` presents the SDK sheet instead. Do not call it for the same action that selects the tab. See the complete [tab and notification routing example](/docs/entry-point#support-tab).

Choosing a tab or opening a sheet does not itself clear the count. It updates after the visible chat successfully marks loaded replies read. Newer replies can remain unread. An offline badge can remain visible.

## When the count refreshes {#refresh-timing}

Reception remains inactive until the first valid send attempt. For an unused session, configuration, opening an empty chat, and forwarding push callbacks do not register a device or fetch unread state. Public [remote appearance](/docs/remote-appearance) may still be fetched separately.

After support has started, the count refreshes when the app becomes active, chat closes, or your app forwards a support notification. Updating the push token can also refresh the count.

Unread requests fetch the count without loading messages or advancing delivered/read receipts. They do not update the dashboard's Last active in chat timestamp.

### Periodic checks while chat is closed

ReceptionKit checks for replies while the app is active and the chat is hidden. Checks can pause for older conversations.

Checks pause when push is configured for the device and notification permission is available. If permission is revoked, foreground checks can resume. Push configuration alone does not confirm delivery.

There are no periodic checks for an unused or resolved conversation, or while the app is in the background. Returning to the foreground still refreshes an activated support session.

### While the chat is visible

While chat is visible, ReceptionKit loads new messages automatically. Successful reads update `unreadCount` with the remaining unread replies reported by Reception. Replies arriving after the loaded messages remain unread until processed. This is server-backed read handling: simply calling `Reception.shared.openChat()` does not immediately clear the count, and an offline chat can retain its last unread value.

Leaving the chat refreshes unread state again.

## App-icon badge {#updates-app-badge}

`Reception.shared.updatesAppBadge` defaults to `true`. Whenever the SDK synchronizes its unread count, it attempts to set the app-icon badge to that count, including when the number has not changed. It does not add the count to a badge managed by another feature. iOS notification settings govern whether badges appear, and badge-setting errors are ignored by the SDK.

Configuration initializes the in-memory count to zero without changing the app-icon badge. The first server synchronization supplies the authoritative value; a launch should not erase a badge just because the SDK has not refreshed yet.

If your app owns a combined badge for several features, disable SDK badge management during main-actor app initialization:

```swift
Reception.shared.updatesAppBadge = false
```

The host remains responsible for its count and icon updates; ReceptionKit does not maintain a combined counter. Observe `unreadCount` changes rather than repeatedly reading a captured initial value. Keep your startup synchronization policy explicit: zero immediately after configuration does not prove that the server has no unread replies.

> **Important:** `updatesAppBadge = false` disables both SDK app-icon writes and SDK cleanup of delivered support notifications. It does not disable in-app unread observation, server read handling, or unread refreshes.

Changing the flag does not clear or recalculate an existing badge. Registration includes the preference, and active support sessions synchronize changes asynchronously with retries after failures and on foreground. Once the server saves `false`, subsequent support pushes omit `aps.badge` entirely, without disabling alerts or configured sound. Offline, the last successfully saved value still applies; already prepared or delivered pushes are not changed. Choosing this setting does not register an unused device. Set it on every app launch, since the default is `true`. See [Push notifications](/docs/push-notifications).

## Notification Center cleanup {#notification-cleanup}

With badge management enabled, every SDK unread synchronization to zero also removes delivered notifications whose `userInfo` contains a `reception` key. Other delivered notifications remain. Cleanup is asynchronous and is not restricted to a single conversation ID. It removes delivered notifications; it does not cancel pending notification requests.

Cleanup can follow a successful read, a zero unread response, or a local session reset. It is separate from deleting support data on the server.

If your app disables SDK badge management, it also owns any desired cleanup. This helper belongs in the host notification coordinator; call it when your own synchronization policy determines that support notifications should be removed:

```swift
import UserNotifications

func removeDeliveredSupportNotifications() async {
    let center = UNUserNotificationCenter.current()
    let notifications = await center.deliveredNotifications()
    let identifiers = notifications.compactMap { notification in
        notification.request.content.userInfo["reception"] != nil
            ? notification.request.identifier
            : nil
    }
    center.removeDeliveredNotifications(withIdentifiers: identifiers)
}
```

Do not call it solely because the SDK's initial count is zero. Preserve delivered notifications belonging to other app features.

## Forward push notifications {#push-notifications}

For a foreground notification, forward its payload with `openChat: false`. For a notification tap, the default `openChat: true` requests the SDK sheet. Both recognized cases request an unread refresh after support activation. The Boolean return indicates a recognized support payload, not successful network synchronization or completed presentation.

This belongs in your existing notification handler on the main actor; `userInfo` is the received notification's payload:

```swift
let handled = Reception.shared.handlePushNotification(
    userInfo: userInfo,
    openChat: false
)

if handled && Reception.shared.isChatOpen {
    // Apply your host's policy to suppress a redundant foreground banner.
}
```

The SDK does not install or replace your notification delegate, request authorization, or choose foreground presentation options. Implement those decisions in the host, preserving handlers for other notification types. Full delegate examples and tap routing are in [Push notifications](/docs/push-notifications).

`Reception.shared.isChatOpen` reports chat visibility, not connection health. Use it to inform foreground presentation policy, not as a substitute for forwarding the notification.

## Account changes and resets

`Reception.shared.logout()` resets local support state, including unread count. When `Reception.shared.deleteData()` completes for the current session, it resets the count. Completion can be local only and does not prove server history was deleted. Await the call before discarding the session so failures can be retried. See [Login and logout](/docs/accounts#sign-out) and [Delete data](/docs/accounts#delete-data).

A server support reset, or a session that Reception no longer accepts, can also clear the session's unread state. With badge management enabled, resetting the count to zero triggers app-icon synchronization and delivered-support-notification cleanup. With it disabled, the host must reconcile its own badge and cleanup policy.

## Verify unread behavior {#verify-unread-behavior}

Use a development app and a test conversation:

1. Send a valid message to activate support, then leave the chat with the app still active.
2. Reply from the dashboard. If push is unavailable, allow the automatic unread check; otherwise forward received support notifications through the host delegate.
3. Check that your entry point updates without navigating away and back. With badge management enabled and badges allowed in iOS settings, check the app icon too.
4. Open the chat with a working connection. Once all replies are processed, confirm the count returns to zero and delivered support notifications are removed.
5. Background and foreground the app with chat closed. Confirm a refresh occurs even when periodic checks are paused.
6. Repeat with `Reception.shared.updatesAppBadge = false`. The in-app count should still update while SDK icon writes and notification cleanup remain disabled. After successful device synchronization, check that a new support push still appears and leaves the host badge unchanged. Repeat offline to confirm that the server retains its last saved preference until synchronization succeeds.

Use a real device for end-to-end APNs verification. A simulator chat test does not verify push registration or delivery.

## Troubleshooting

| Symptom | Check |
| --- | --- |
| Count stays zero after a fresh install | Send the first valid message. Opening an unused chat does not activate support. |
| The row never updates | Configure Reception first, read `Reception.shared.unreadCount` inside the SwiftUI body, and check network access. |
| A reply is not reflected immediately | Check push forwarding, foreground state, conversation status, and automatic unread checks. |
| Count does not clear on opening | A successful server read is required; check the chat connection. |
| In-app badge works but the app icon does not | Check `updatesAppBadge`, iOS badge permission, and other host badge writers. |
| Notifications remain at zero | Check whether SDK badge management is disabled; cleanup is asynchronous and requires a `reception` payload marker. |
| Another feature's badge count disappears | The SDK writes its support count directly. Disable its badge management and use one host coordinator. |

See [Troubleshooting](/docs/troubleshooting) for configuration, connectivity, and push checks.
