DataTalksClub / DataTalksClub/website

Build the durable Relay delivery intent and status lifecycle

Open
#49 6 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

email integration operations P0 security testing
Dominant language
Python
Stars
0
Forks
0
PR merge metrics
No merged PRs in 30d

Description

Parent epic: #6
Recorded provider decision: #21
Cross-repository contract: #106

Normative product behavior: _docs/specs/05-events-registration-email.md#durable-delivery-model, with provider execution moved from the website to Relay by #21/#106.

Outcome

Implement one durable website EmailDelivery intent and redacted status projection backed by Relay's authoritative transport lifecycle. Harden Relay where its current API/state cannot meet the required idempotency, crash, suppression, callback, and reconciliation guarantees. The website never runs an SES or Datamailer sender.

Website scope

  • Commit business state and one unique logical delivery intent in the same transaction. The intent pins purpose, business references, recipient/reference snapshot per retention policy, Relay template key/version, minimal context snapshot, and stable idempotency key.
  • Submit to Relay only after commit through a durable website job. An HTTP timeout/retry replays the identical request/key; Relay's idempotency result converges it to one message.
  • Store Relay message ID plus a redacted authoritative-status projection, callback/reconciliation freshness, safe reason, attempts summary, and audit links. Relay remains source of truth.
  • Authenticate, timestamp-check, deduplicate, and reorder-tolerantly apply generic Relay callbacks. Reconcile nonterminal items every five minutes with bounded backoff, recently terminal items daily for seven days, and one item on operator request.
  • Provide shared services used by Studio/admin API for redacted inspect, safe retry, reconcile, ambiguous resolution, and audited manual resend. Manual resend creates a new intent/key linked to the original; it is not an automatic retry.

Required Relay hardening

  • Require an idempotency key and canonical SHA-256 request hash. Exact replay returns the original; same key/different delivery-affecting request returns 409 idempotency_conflict; concurrent first inserts recover through the unique constraint and create one queue item.
  • Require immutable template_key + template_version and snapshot rendered content/sender before queueing, using #48's safe Relay renderer.
  • Implement guarded queued, leased, provider_accepted, delivered, retryable, ambiguous, suppressed, dead, hard_bounced, and complained states. Accepted is never displayed as delivered.
  • Claim with owner/token/expiry and record attempts. Recover an expired pre-provider lease; never auto-retry an uncertain provider call. Ambiguous items reconcile or await explicit operator resolution.
  • Recheck hard-bounce/complaint and applicable transactional suppression atomically at claim immediately before provider call.
  • Replace the CMP-only bearer callback assumption with a generic tenant HMAC/timestamp callback outbox and redacted versioned event payload; retry delivery with backoff.
  • Deduplicate provider event IDs, retain valid unmatched redacted events for seven days, retry correlation, and expose unresolved items to operators.
  • Use separate tenant-scoped, expiring runtime/template-management API keys and a separate callback signing secret, with overlap rotation and immediate revocation.

Acceptance criteria

  • Business state and one unique delivery intent commit atomically; rollback creates neither, and no network request happens in the transaction.
  • After-commit submission, request timeout, replay, and concurrent submission converge to one website intent, one Relay message, and one provider submission unless an operator explicitly creates a manual resend.
  • Same key with changed recipient/template version/context/sender/options returns 409; exact replay returns the original, including insert races.
  • State transitions, lease token/expiry, bounded retry/dead behavior, provider-accepted versus delivered, and ambiguous reconciliation are explicit and guarded.
  • Suppression recheck at claim prevents a send when bounce/complaint arrives after queueing.
  • Generic callbacks are HMAC/timestamp authenticated, tenant-scoped, redacted, deduplicated, reorder-tolerant, durably retried, and recoverable through reconciliation.
  • Valid unmatched provider events are retained/retried for seven days and become operator-visible without leaking raw payload/PII.
  • Studio/admin API delivery capabilities have parity, least-privilege roles, revision/idempotency/confirmation/audit, and masked recipient/body/provider details.
  • Development allowlist/simulation prevents broad sends; wrong-scope/expired/revoked credentials fail closed.
  • Metrics/alerts/runbooks cover queue age, expired leases, retry/dead/ambiguous counts, callback failure/lag, reconciliation lag, suppression, unmatched events, and provider health.

Mandatory failure tests

  1. Rollback; crash before submission; timeout before Relay commit; response loss after Relay commit; concurrent replay; callback loss; and reconciliation recovery.
  2. Worker crash before claim, after claim/before provider, during uncertain acknowledgement, after provider acceptance, and after local acknowledgement/ callback enqueue.
  3. Exact replay versus changed request hash; insert race; expired lease; retry exhaustion; and forbidden automatic retry from ambiguous.
  4. Valid/invalid signature, replayed timestamp, duplicate/out-of-order callback, late bounce/complaint, unmatched provider event, quota/throttle, permanent reject, and suppression change between queue and claim.
  5. Tenant isolation, wrong endpoint scope, key expiry/revocation/rotation, redacted logs/audit, and no direct SES/Datamailer call from website code.
Playwright

Inspect queued/leased/provider-accepted/delivered/retryable/ambiguous/suppressed/dead/bounced/complained projections; exercise authorized reconcile/retry/resolve/manual resend with confirmation and stale/double-submit protection; verify role denials, safe Relay-unavailable/lagging state, masked PII, and desktop/mobile screenshots.

Dependencies

Depends on #21, specification reconciliation #124, #31–#33, #48's immutable template contract, the idempotent delivery/lease/state contract in DataTalksClub/relay#2, and the generic callback/unmatched-event reconciliation contract in DataTalksClub/relay#3. AWS resources and secrets follow #9/infrastructure-repository boundaries.

The restore-generation reconciliation portion additionally depends on accepted #284. It must implement #284's exact merged contract rather than letting #258 define an email adapter or state-to-safe mapping. Ordinary delivery remains independent of privacy contracts #281/#283.

Non-goals

  • A website SES/Datamailer adapter, sender worker, provider-event stack, template store, or second authoritative delivery state.
  • Marketing email, blind retry of ambiguity, claiming accepted means delivered, or exposing body/token/address/provider payload.
  • Production Relay rollout.

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with the normative durable-delivery model in _docs/specs/05-events-registration-email.md and the Relay contracts in issues #2 and #3, then reconcile dependencies #21, #48, #124, and #284. Use the mandatory failure tests and Playwright checklist as the definition of done, including lifecycle safety, callback recovery, redaction, operator controls, and no direct provider calls from website code.

Written by the indexing model from the issue text.

Assessment

Tech stack
python
Domain
api, backend, databases, security, testing
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Active
Clarity
Needs clarification
Newbie friendliness
25/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.