Skip to main content
← Back to list
01Issue
FeatureOpenSwamp ClubPublic
AssigneesNone

Relationships

↑ child of #2008

#2021 feat(nurture): deliver to the swamp-club inbox on the same dedupe decision as email

Opened by keeb · 9/4/2026

Parent: the product-nurture epic. Adds the second channel.

The whiteboard says these go out as emails and broadcast messages in the app. The in-app half is almost entirely built already — this issue wires nurture to it without giving the dedupe decision a second owner.

The terminology trap, first

"Broadcast" on the whiteboard means "in-app message". In this codebase a BroadcastMessage goes to everyone. Nurture is targeted. The correct call is:

sendSystemMessage(deps, {
  sender: { service: "web" },
  kind: "nurture",
  audience: { type: "users", userIds: [...] },   // NOT { type: "broadcast" }
  dedupKey: nurtureRow._id,
  ...
})

Using the broadcast arm would put a dormancy nudge on the site for every operative at once.

What ships

One decision, two deliveries. The nurture_send row created in Phase A is the single decision record. The inbox delivery is a channel on that row, not a second producer. Concretely, the ledger's _id becomes the dedupKey passed to sendSystemMessage — and since the notifications collection carries a unique (recipientId, dedupKey) index, the inbox side is idempotent by construction even if the email side retries.

Per-flow channel matrix, controlled from /admin/nurture (A4). Proposed defaults: celebratory and behavioural flows go to both; the two dormancy flows go to email only at first, since an in-app message reaches someone who by definition is not visiting.

Channel outcomes are independent. An email that defers does not block the inbox delivery, and vice versa — the row records both, and the panel renders both. A flow is "sent" when its enabled channels have each reached a terminal state.

No self-notification, no unknown recipients. sendSystemMessage already drops both and reports the counts; surface those in the panel rather than treating them as errors.

Verification

  • tests/app/nurture_inbox_channel_test.ts — one nurture row produces exactly one notification; a re-delivery produces zero; an email failure still leaves the inbox delivery sent.
  • An assertion that the broadcast arm is never reachable from the nurture producer.
  • On the local stack: arm a flow with both channels, trigger it, and read the message in /inbox as the recipient.

Out of scope

The CLI hint (E1/E2), which reads the inbox this issue fills.

02Bog Flow
OPENTRIAGEDIN PROGRESSSHIPPED

Open

9/4/2026, 7:40:53 PM

No activity in this phase yet.

03Sludge Pulse
keeb linked parent of #20089/4/2026, 7:41:17 PM

Sign in to post a ripple.