AppGroupMailbox

0.2.1

A reliable, bounded, file-backed mailbox for Apple App Group processes
adamtheturtle/AppGroupMailbox

What's New

AppGroupMailbox 0.2.1

2026-08-05T12:08:46Z

Fixes

  • Post recovery notifications even when later mailbox maintenance fails.
  • Quarantine malformed pending files before applying mailbox overflow eviction.
  • Preserve valid FIFO messages when malformed files consume active capacity.

AppGroupMailbox

A reliable, bounded, file-backed mailbox for passing messages between Apple app processes that share an App Group container.

Documentation | Swift Package Index

Installation

.package(url: "https://github.com/adamtheturtle/AppGroupMailbox.git", from: "0.1.0")

Add the AppGroupMailbox product to the app, widget, intent, extension, or helper targets that exchange messages.

Usage

Define a caller-owned message type:

import AppGroupMailbox

enum WidgetAction: Codable, Sendable {
    case refresh
    case selectItem(id: UUID)
}

let mailbox = try AppGroupMailbox<WidgetAction>(
    appGroupIdentifier: "group.com.example.product",
    namespace: "widget-actions",
    limits: .init(maxMessages: 100, maxPayloadBytes: 65_536),
    notificationName: "group.com.example.product.widget-action-enqueued"
)

The producer writes atomically and can optionally nudge an already-running consumer with a payload-free Darwin notification:

try mailbox.enqueue(.selectItem(id: itemID))

When importing another durable queue, supply its stable record ID. Retrying the import succeeds without writing a duplicate while that ID is pending or claimed:

try mailbox.enqueue(action, id: legacyRecordID)

Pass the legacy enqueue date as well to preserve its chronological position relative to messages already in the mailbox. Records with equal dates retain their mailbox insertion order:

try mailbox.enqueue(action, id: legacyRecordID, enqueuedAt: legacyEnqueuedAt)

The consumer claims in FIFO order, handles each value, and acknowledges success:

for claim in try mailbox.claimPending() {
    do {
        try await handle(claim.message)
        try claim.acknowledge()
    } catch {
        try claim.release()
    }
}

Delivery and recovery

Pending messages are atomically renamed into unique claims. Two concurrent consumers cannot claim the same file. A successful acknowledgement deletes the claim; release restores its original queue position. If a process terminates while holding a claim, the next maintenance or claim operation restores it after claimTimeout.

This is at-least-once delivery, so handlers should be idempotent. Stable claim IDs let an application deduplicate effects when required.

Queue depth, encoded payload size, message age, claim timeout, and quarantine size are bounded. When full, a mailbox can reject the newest enqueue (the default) or discard its oldest pending message.

Security

  • Namespaces are validated and cannot traverse out of the mailbox directory.
  • Only regular, non-symbolic-link queue files are accepted.
  • File size is checked before bytes are read.
  • Malformed, unsafe, and oversized inputs are rejected or quarantined.
  • Quarantine storage is bounded.
  • Diagnostics describe outcomes but never contain message contents.

The App Group container is the trust boundary. Every target that uses the mailbox must have the same App Group entitlement.

Requirements

  • Swift 6.2+
  • macOS 14+, iOS 17+, tvOS 17+, watchOS 10+, or visionOS 1+
  • No dependencies

License

MIT. See LICENSE.

Description

  • Swift Tools 6.2.0
View More Packages from this Author

Dependencies

Last updated: Sun Aug 09 2026 09:02:51 GMT-0900 (Hawaii-Aleutian Daylight Time)