You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Close the remaining gap left by #42 / PR #99: when the email provider may already have accepted a message but PostKit did not observe a clear success (timeout, transport drop, or similar), retries must not casually produce a second delivery.
Today the application Idempotency-Key ledger prevents a second successful acknowledgement for the same key and gates internal retries of clear HTTP transient failures (statusCode present). It does not provide provider-side deduplication. Ambiguous outcomes are not retried internally, but a caller retry of the same key after release can still double-deliver if the first POST was accepted.
Background
Raised on PR #99 (CodeRabbit); declined for #42 because that issue forbids new queues/durable orchestration and the current provider has no documented send idempotency key. Track the follow-up here instead of leaving an unresolved PR thread as the only record.
Scope (options to refine before implementation)
Pick one primary approach (or a sequenced combination) in refinement; do not implement all at once without a written decision:
Provider-side deduplication — if/when the outbound provider supports an idempotency or dedupe key, pass the application Idempotency-Key (or a derived stable key) on the provider request and document the contract.
Ambiguous-outcome claim policy — on timeout/transport (no HTTP status), do not release the in-progress claim immediately; keep 409 IDEMPOTENCY_IN_PROGRESS (or a dedicated status) until TTL/reconciliation so callers cannot re-POST blindly. Document caller guidance and ops escape hatches.
Reconciliation / outbox — only if (1) and (2) are insufficient: durable ambiguous state and a later resolve path. If this needs a queue or durable orchestration, document why and stop for human design approval (same constraint spirit as Define timeout policy, failure classification, and safe retry for sends #42).
Also update:
docs/architecture/send-timeout-retry.md
docs/architecture/send-idempotency.md
docs/operations/troubleshooting.md
…so operators can tell replay-safe failures from ambiguous ones.
Out of scope (for the first cut unless refinement says otherwise)
Changing the public send API shape beyond what the chosen approach requires
Provider-specific DTOs leaking into @singleton-sd/post-kit-types
Silent “always safe” claims in docs without an enforceable mechanism
Constraints
Public-repo safety: no secrets, customer data, or real tenant identifiers in issues/PRs
Prefer the smallest mechanism that materially cuts duplicate risk
Do not invent a queue just to look complete — justify any durable orchestration
Acceptance criteria
Written decision on approach (1), (2), and/or (3) with trade-offs
Caller-retry guidance after ambiguous outcomes is explicit and matches implementation (no “always safe” wording)
If provider dedupe is chosen: the provider request carries a stable dedupe key derived from the application idempotency model, with tests
If claim-hold is chosen: timeout/transport failures do not release the claim in a way that invites immediate duplicate send; TTL/ops behaviour is documented
Docs updated for operators and consumers
pnpm -r --if-present run test passes
Agent implementation notes
Do not start coding until this issue is refined to agent-ready (section 4 of docs/github-source-of-truth.md) — especially a single chosen approach. Read apps/api/src/send-delivery.ts, docs/architecture/send-timeout-retry.md, and the PR #99 review thread that declined the heavy lift before planning.
Parent: #6
Depends on: #42
Goal
Close the remaining gap left by #42 / PR #99: when the email provider may already have accepted a message but PostKit did not observe a clear success (timeout, transport drop, or similar), retries must not casually produce a second delivery.
Today the application
Idempotency-Keyledger prevents a second successful acknowledgement for the same key and gates internal retries of clear HTTP transient failures (statusCodepresent). It does not provide provider-side deduplication. Ambiguous outcomes are not retried internally, but a caller retry of the same key after release can still double-deliver if the first POST was accepted.Background
Raised on PR #99 (CodeRabbit); declined for #42 because that issue forbids new queues/durable orchestration and the current provider has no documented send idempotency key. Track the follow-up here instead of leaving an unresolved PR thread as the only record.
Scope (options to refine before implementation)
Pick one primary approach (or a sequenced combination) in refinement; do not implement all at once without a written decision:
Idempotency-Key(or a derived stable key) on the provider request and document the contract.409 IDEMPOTENCY_IN_PROGRESS(or a dedicated status) until TTL/reconciliation so callers cannot re-POST blindly. Document caller guidance and ops escape hatches.Also update:
docs/architecture/send-timeout-retry.mddocs/architecture/send-idempotency.mddocs/operations/troubleshooting.md…so operators can tell replay-safe failures from ambiguous ones.
Out of scope (for the first cut unless refinement says otherwise)
@singleton-sd/post-kit-typesConstraints
Acceptance criteria
pnpm -r --if-present run testpassesAgent implementation notes
Do not start coding until this issue is refined to agent-ready (section 4 of
docs/github-source-of-truth.md) — especially a single chosen approach. Readapps/api/src/send-delivery.ts,docs/architecture/send-timeout-retry.md, and the PR #99 review thread that declined the heavy lift before planning.Related: #37 (idempotency ledger), #42 (timeouts / classification / safe internal retry).