Skip to main content
← Back to list
01Issue
FeatureIn ProgressSwamp ClubPublic
Assigneeskeeb

Relationships

↑ child of #2008⊘ blocks #2016

#2014 feat(nurture): an outbound transport that reports its outcome, and per-channel send status

Opened by keeb · 9/4/2026

Parent: the product-nurture epic. Blocks every send.

Problem

The ask includes send status. It cannot be recorded through the transport we have:

// lib/infrastructure/resend-mailer.ts
if (!res.ok) {
  logger.error("Resend API error", { ... });   // and then returns void
}

A Resend 4xx — a suppressed address, an invalid recipient, a rate limit — is logged and reported to the caller as success. A ledger fed by that transport would show every nurture send as sent, including the ones that never arrived. The same swallow means a retryable failure is indistinguishable from a permanent one, so a naive retry loop would either never retry or retry forever.

What ships

An outcome-returning send. A result type distinguishing:

  • delivered — Resend accepted it, with the provider message id recorded
  • rejected — a 4xx we must not retry (bad address, suppressed); terminal
  • deferred — a 429 or 5xx; retryable with backoff
  • skipped — no RESEND_API_KEY, which is the local-dev and CI default and must stay a first-class outcome rather than a fake success

Introduced alongside the existing Mailer rather than by changing it — lib/auth.ts and the trial mails have their own posture, and widening this change into them is how a small fix becomes a risky one.

Per-channel status on the nurture_send row, with attempt count, last error, and next-attempt instant. A deferred row is retried by the projector with backoff; a rejected row is terminal and rendered as such in the panel.

The claim happens before the send, and it stays claimed on failure. This is at-most-once, deliberately, and it is the same call sendWelcomeEmail documents: a duplicate nurture email is worse than a missing one. A deferred retry is the single exception, bounded by attempt count.

Verification

  • tests/infrastructure/nurture_mailer_test.ts — a stub Resend answering 200, 422, 429, 500 and a network throw; assert each maps to the right outcome and that a 4xx is never reported as delivered.
  • tests/app/nurture_delivery_test.ts — a deferred send retries with backoff and eventually gives up; a rejected send never retries; a claimed row is never re-claimed.
  • Drive one real send to a real address on the local stack with a live key, and confirm the recorded provider message id matches the Resend dashboard.

Out of scope

Which flows send, and what they say.

02Bog Flow
OPENTRIAGEDIN PROGRESSSHIPPED+ 1 MOREASSIGNED+ 5 MOREREVIEW+ 2 MORECODE_CONFORMANCE_REVIEW

In Progress

9/5/2026, 12:36:35 AM

Click a lifecycle step above to view its details.

03Sludge Pulse
keeb assigned keeb9/4/2026, 11:19:23 PM
keeb linked parent of #20089/4/2026, 7:41:09 PM
keeb linked blocked by #20169/4/2026, 7:41:46 PM

Sign in to post a ripple.