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

Relationships

↑ child of #2008⊘ blocks #2016

#2013 feat(nurture): suppression — opt-out, address-grain frequency caps, one-click unsubscribe

Opened by keeb · 9/4/2026

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

Problem

Three separate holes, and all three have to be closed before the first nurture message leaves:

  1. There is no per-operative product-email preference. The only opt-out that exists is newsletter_subscriber.unsubscribedAt, which covers the marketing list. An operative has no way to say "stop sending me lifecycle mail".
  2. Resend suppression does not apply to what we are about to do. That unsubscribedAt is mirrored back from Resend broadcasts via /api/resend/webhook. ResendMailer.send POSTs /emails directly — a transactional path Resend does not suppress. Sending nurture through it silently mails people who have already unsubscribed.
  3. One human can hold several operatives. accountLinking.disableImplicitLinking is true, so a social sign-in whose email does not match mints a new account. Per-operative dedupe is correct and still mails the same person twice. The Attio person grain (#1665) resolved the identical problem by keying on the address.

What ships

A nurture preference on the operative, defaulting to opted-in, with an /settings-reachable control and an unsubscribe route that works from a mail footer without a session (signed token, one click, no login wall).

One suppression check, evaluated at delivery time, returning a reason, not a boolean — the panel renders it:

  • explicit nurture opt-out
  • newsletter_subscriber.unsubscribedAt set for this address (we honour it even though Resend will not enforce it here)
  • banned account
  • address-grain frequency cap — at most one nurture email per address per N days (propose 14), across all flows and all accounts sharing that address
  • account has no username yet — hold, do not send, the same rule the announce path applies to an unnamed operative

Recomputed at delivery, never at identification, so a preference change between the projector run and the send is honoured — the same level-triggered shape the Attio and Resend queues use.

Every mail carries the footer link. No exceptions, no per-flow opt-out from the opt-out.

Verification

  • tests/app/nurture_suppression_test.ts — one case per reason, plus the two-accounts-one-address case asserting the second is capped.
  • tests/routes/nurture_unsubscribe_test.ts — a valid token unsubscribes with no session; a forged or expired one 400s and changes nothing.
  • The panel shows suppressed rows with their reason, which is how this is verified against the real population before anything is armed.

Out of scope

The transport itself (B2) and the copy (B3).

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

In Progress

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

Click a lifecycle step above to view its details.

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

Sign in to post a ripple.