Skip to content

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.

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_email from 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 existing flush_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 UOW
prepared = 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.

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.

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.

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.