- Docs
- QuadKit
- Packages
- Events & Notifications
- Notification
- Inbox Delivery
Trusted, retry-safe local inbox delivery
The native runtime now supports real account resolution, pinned recipients, durable inbox deduplication and worker restart recovery. This does not itself wire a Playground graph, mount an HTTP send route or authorize a publisher.
A runnable trusted Notification Center host now demonstrates authenticated publication, atomic enqueue, a separate worker, owner-scoped HTTP reads and explicit terminal redrive. It is application-owned reference composition, not automatic Playground graph wiring.
The native use-case generator also has an explicit atomic inbox intent primitive. Its generated SQL transition and enqueue share one transaction. It is not yet selected or authenticated/mounted by the Playground graph writer.
Compose native services explicitly
Section titled “Compose native services explicitly”Application-owned composition supplies:
InboxService(DatabaseInboxStore(database))on a trusted native SQL provider;- an existing account store’s lookup function, such as
SQLUserStore.get_user_by_emailfrom the host’s Auth composition; - a server-configured recipient email, never action/query/body input;
SqlDeliveryStore(database, table="editorial_inbox_delivery")for an isolated queue sharing the business database provider;NotificationDeliveryDispatcher([channel])and the existingflush_retries.
The notification extension imports only the user contract, not the Auth extension. There is no fake default user, permissive resolver, broadcast API or implicit global registration. Separate queues prevent an inbox-only dispatcher from consuming unrelated email deliveries.
from quadkit.notification.delivery import ( InboxDeliveryChannel, NotificationDeliveryDispatcher, SqlDeliveryStore, flush_retries,)
# inbox, users, database and recipient_email come from trusted host composition.channel = InboxDeliveryChannel( inbox, users.get_user_by_email, recipient_email=recipient_email)queue = SqlDeliveryStore(database, table="editorial_inbox_delivery")await queue.initialize() # committed schema bootstrap, before the business UOWprepared = await channel.prepare( f"post-published:v1:{post_id}", "Post published", f"Post {post_id} was published", post_id=post_id, status="published",)if prepared.is_err(): # Refuse the business operation; do not substitute a fixture recipient. raise RuntimeError("Configured notification recipient unavailable")
# Application-owned conditional business write AND enqueue share this provider.async with database.transaction(): updated = await database.execute_query( "UPDATE posts SET status = ? WHERE id = ? AND status = ? RETURNING id", ["published", post_id, "draft"], ) if not updated.success or not updated.rows: raise RuntimeError("Publication missing or not in draft state") await queue.create_pending(prepared.unwrap())
# Outside the business transaction. Hosts decide when/how to run the worker.await flush_retries(queue, NotificationDeliveryDispatcher([channel]))This is a native API composition example, not generated HTTP wiring. The
host owns HTTP authentication/authorization, status mapping, transactions and
worker scheduling. It must not expose prepare, send_once, queue creation or
recipient configuration as arbitrary-recipient endpoints. Copy only explicitly
selected committed fields; do not forward arbitrary details or routing inputs.
Recovery contract
Section titled “Recovery contract”prepare resolves an existing active account and pins its real user ID in the
persisted envelope. send resolves it again and refuses mismatches/inactive or
missing accounts. Email reassignment cannot redirect an already queued message.
Routing fields cannot be overridden through prepare metadata.
InboxService.send_once(effect_key, user_id, title, body, **metadata) returns a
native MessageDeliveryReceipt. The effect key identifies one immutable
recipient/content intent. Changed intent under the same key fails closed;
never automatically rotate keys to bypass a conflict. Legacy send still means
an ordinary new message and is unchanged.
Stores explicitly advertise the versioned optional
IdempotentInboxStoreProtocol capability; a coincidentally named save_once
method is not implicitly trusted. SQL receipt creation and inbox insertion
commit or roll back together. If inbox
insertion fails, no successful receipt remains, so the durable worker can retry.
If the process dies after inbox commit but before queue acknowledgement, replay
returns the same receipt/message ID without inserting another message. The
existing lease/backoff/max-attempt/dead-letter policy remains authoritative.
Business publication is not retried just because notification delivery failed.
The SQL implementation refuses active/unobservable/unsupported transaction scopes before sending. It is a post-commit delivery operation, not permission to call inbox sending inside the business transaction. Native SQL enqueue can share that business transaction; actual delivery cannot. Memory receipts last only for that process and do not provide durable restart guarantees.
Sent hooks run only for a first insertion, remain error-isolated/best-effort and are not a retryable transport. No exactly-once hook or external delivery claim.
Deletion and retention
Section titled “Deletion and retention”Read/delete/clear never resurrect a message when a delivery is replayed. The receipt table retains only effect key, intent digest, message ID and timestamp, not another copy of private title/body/metadata. Digests distinguish JSON scalar types and use backend-independent numeric representation. Invalid/nonfinite or cyclic JSON is rejected rather than silently coerced.
Receipt identity/digest retention is necessary for that replay guarantee and needs an explicit operator retention/erasure policy. The pre-existing delivery queue also persists its envelope; inbox deletion is not complete erasure of queue history or all application data. No automatic expiry/cleanup is implied.
Verified scope
Section titled “Verified scope”Real SQLite account/store/queue tests cover failed insertion and receipt rollback, explicit retry, cold connections/restart, expired worker lease after lost acknowledgement, duplicate producer envelopes, business enqueue rollback, owner predicates, deletion and recipient spoof/reassignment refusal. This is local source runtime evidence, not PostgreSQL, released-wheel, deployment or owner/ security acceptance. The Playground editorial starter remains blocked until its actual generated producer, DI/worker composition and inbox handoff are wired and verified. Existing graph/action/auth/effect gates were not loosened by this API.