patch notes · #253 October

#25 · Mail attempts retain uncertainty and campaigns require an owner test

The operator's sender now binds each idempotency key to its exact payload and logical mail. A lost response, ambiguous server failure or concurrent-key reply retains the same key within a bounded window. A later refusal cannot erase an earlier uncertain attempt, including within the same claim. Claims carry an ownership token so a delayed refusal cannot release a newer sender's claim. Quota accounting includes delayed acceptance and outstanding uncertainty. These controls do not claim exactly-once delivery.

The ledger limits dewfpga's own allocation. A live sender observes the existing Resend account's usage before each ledger claim; unavailable usage or insufficient operator-chosen headroom defers work without creating a claim. External senders can still consume capacity between that observation and a request. Provider quota refusal stops the run; no guaranteed Auth reservation is claimed.

An owner test requires the owner's explicit confirmed subscription, a usable unsubscribe link and a receipt note with measured or explicitly attested unsubscribe evidence before the matching campaign can run. Acceptance by the provider is not inbox receipt. The campaign rechecks subscriber eligibility before each dispatch. If fresh consent changes a previous campaign payload, that recipient is reported as conflicted without changing the old intent or sending again; unrelated recipients continue. The operator guide is mail/README.md.

Before/after evidence. Earlier immutable candidates reproduced duplicate-key retry, stale-claim, accounting and token-lifecycle failures. The final integrated local gates use the shipped migrations rather than substitute SQL: SQL 173/0, mail flow 60/0, sender fault scenarios 27 passed, lifecycle 112/0, account-usage unit tests 10/0, and usage/sender integration 8/0. Counts are checks within their suites, not distinct defects. Provider replies are modeled; no real mail was sent. An intermediate same-claim snapshot failed two root expectations; the final sender retains uncertainty and the integration regression passes. A worker mutation removing that rule made the mail test fail as intended.

Before activation (4 October). A review against Resend's and Supabase's own documentation found gaps that local tests could not show. A sending-only Resend key is refused by the usage endpoint (401 restricted_api_key): the worker used to defer every run with "account usage unavailable" and no cause; it now names the status and error and says a full-access key is needed, without echoing the key or the response text. After a 429 rate-limit refusal the sender now waits before its next derived key (the provider's retry-after when sane, else 1 s then 3 s, at most 60 s); before, all three keys were spent within milliseconds and that recipient missed the issue. The limit is per team and shared with dewsletter. Re-applying 002 no longer deletes a consumer row the operator configured, and the ledger's id sequence is revoked from anon and authenticated, which existing Supabase projects grant on every new public sequence.

Two operator scripts prepare the first live apply. mail/ops/preflight_readonly.sql reports the shared project in one read-only transaction: dewfpga objects and grants, other applications' tables tied to auth.users, triggers, default privileges, foreign callers and which migration parts already exist. mail/ops/rollback_dewfpga.sql removes only dewfpga's objects and drops nothing when another application uses them. Both live outside mail/sql: in the first draft a loop over that folder ran the rollback right after the migrations, which a sender test caught. Local gates on the integrated head: activation scripts 142/0, SQL 187/0, mail flow 60/0, every sender scenario including the 429 wait, lifecycle 112/0, usage 13 tests OK, pages 137/0, Chromium and WebKit 158/0. Provider and Auth replies are modeled; nothing was applied to the live project and no mail was sent.


previous: #24 Newsletter consent and working management links
next: #26 Optional accounts with local session handling and scoped deletion
This entry in the full list
Every SystemVerilog construct measured so far, next to what Vivado does: the probes, stage by stage