Skip to content

feat(newsletters): add superuser audience targeting - #6069

Open
BillLeoutsakosvl346 wants to merge 5 commits into
stagingfrom
admin-newsletter-targeting
Open

feat(newsletters): add superuser audience targeting#6069
BillLeoutsakosvl346 wants to merge 5 commits into
stagingfrom
admin-newsletter-targeting

Conversation

@BillLeoutsakosvl346

Copy link
Copy Markdown
Contributor

Summary

  • add a superuser-only newsletter audience builder with allowlisted natural-language targeting and immutable recipient snapshots
  • export consent-filtered CSVs or sync recipients to Resend Segments through resumable background jobs
  • add audited persistence, safety limits, recovery handling, and newsletter history/status UI

Type of Change

  • New feature

Testing

  • Focused Vitest suite (44 tests)
  • bun run type-check
  • bun run check:api-validation:strict
  • bun run check:migrations origin/staging
  • Full pre-ship audit suite

Checklist

  • Code follows project style guidelines
  • Self-reviewed my changes
  • Tests added/updated and passing
  • No new warnings introduced
  • I confirm that I have read and agree to the terms outlined in the Contributor License Agreement (CLA)

@vercel

vercel Bot commented Jul 29, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
docs Ready Ready Preview Jul 30, 2026 10:55am

Request Review

@cursor

cursor Bot commented Jul 29, 2026

Copy link
Copy Markdown

PR Summary

High Risk
Touches bulk user email data, marketing consent filters, and external Resend sync with new superuser APIs; incorrect targeting or sync bugs could affect many recipients or leak audience data.

Overview
Adds a superuser-only Newsletters settings area (gated by effective superuser mode, not just platform admin) for building marketing audience runs from allowlisted natural-language targeting prompts, with preview counts and immutable snapshots after finalize.

New /api/superuser/newsletters endpoints support listing/creating runs, finalizing audiences (Resend suppressions + safety cap), streaming consent-filtered CSV export, enqueueing Resend segment sync, and polling background job status. Persistence uses new newsletter_audience_runs / newsletter_audience_recipients tables and a Resend client with pagination, retries, and typed service errors mapped to 503 where appropriate.

Resend push runs as newsletter-resend-sync (Trigger.dev or inline database queue). Enqueue/resume logic reconciles stuck or duplicate provider jobs. The database async job backend gains inline claim tokens, lease heartbeats, and safer duplicate/retry behavior so newsletter sync can run inline without double-processing.

Account settings navigation fixes Mothership routing (no longer the default fallback for unknown sections).

Reviewed by Cursor Bugbot for commit 493375b. Bugbot is set up for automated code reviews on this repo. Configure here.

Comment thread apps/sim/lib/newsletters/resend.ts Outdated
Comment thread apps/sim/lib/newsletters/push-resend.ts
Comment thread apps/sim/components/settings/account-settings-renderer.tsx
@greptile-apps

greptile-apps Bot commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

Superuser newsletter audience targeting with immutable recipient snapshots, consent-filtered CSV export, and Resend segment sync via resumable background jobs.

  • Adds newsletter runs schema/migration, targeting, finalize/export/push APIs, and audited run status UI under account settings.
  • Resend suppressions/contacts paginate fail-closed; Trigger.dev resume fences completed/failed/active/missing provider jobs and restarts attempts from durable run state.
  • Account settings renderer branches newsletters explicitly and fails closed on unknown sections.

Confidence Score: 5/5

Prior Greptile findings are addressed at HEAD; the PR appears safe to merge from those resume, pagination, and settings-renderer issues.

Suppression pagination, Trigger.dev resume recovery (including completed-but-still-pushing), and account-settings fail-closed behavior are present with tests; no remaining blocking failure from the prior threads.

Important Files Changed

Filename Overview
apps/sim/lib/newsletters/push-resend.ts Resume treats durable run status as source of truth; completed/failed/missing/active Trigger.dev jobs are fenced and restarted with new attempt keys.
apps/sim/lib/newsletters/resend.ts Suppression and contact exclusion lists paginate with cursors, validation, and safety caps.
apps/sim/components/settings/account-settings-renderer.tsx Explicit newsletters branch; unknown account sections render null.
apps/sim/lib/newsletters/runs.ts Attempt claiming, job/segment fencing, and pushed/failed transitions back resume recovery.

Sequence Diagram

sequenceDiagram
  participant Admin as Superuser UI
  participant API as Newsletter APIs
  participant Runs as runs.ts
  participant Queue as Job queue
  participant Worker as runNewsletterResendSync
  participant Resend as Resend API

  Admin->>API: finalize run
  API->>Runs: snapshot recipients
  Admin->>API: push / resume
  API->>Runs: claimNewsletterRunResendAttempt
  alt stored job terminal or active (non-DB)
    API->>Queue: getJob / cancelJob
    API->>Runs: mark failed attempt + re-claim
  end
  API->>Queue: enqueue newsletter-resend-sync
  API->>Runs: setNewsletterRunResendJob
  Queue->>Worker: payload + attempt
  Worker->>Resend: segment, properties, contacts
  Worker->>Runs: mark pushed or failed
Loading

Reviews (7): Last reviewed commit: "fix(newsletters): fence resend recovery ..." | Re-trigger Greptile

Comment thread apps/sim/lib/newsletters/resend.ts
@BillLeoutsakosvl346

Copy link
Copy Markdown
Contributor Author

@greptile

@BillLeoutsakosvl346

Copy link
Copy Markdown
Contributor Author

@cursor review

Comment thread apps/sim/lib/newsletters/push-resend.ts Outdated
@BillLeoutsakosvl346
BillLeoutsakosvl346 force-pushed the admin-newsletter-targeting branch from 4fbd67c to 849323e Compare July 30, 2026 00:17
@BillLeoutsakosvl346

Copy link
Copy Markdown
Contributor Author

@greptile

@BillLeoutsakosvl346

Copy link
Copy Markdown
Contributor Author

@cursor review

Comment thread apps/sim/lib/newsletters/push-resend.ts
@BillLeoutsakosvl346
BillLeoutsakosvl346 force-pushed the admin-newsletter-targeting branch from 849323e to 4e522cd Compare July 30, 2026 01:01
@BillLeoutsakosvl346

Copy link
Copy Markdown
Contributor Author

@greptile

@BillLeoutsakosvl346

Copy link
Copy Markdown
Contributor Author

@cursor review

Comment thread apps/sim/background/newsletter-resend-sync.ts Outdated
@BillLeoutsakosvl346

Copy link
Copy Markdown
Contributor Author

@greptile

@BillLeoutsakosvl346

Copy link
Copy Markdown
Contributor Author

@cursor review

Comment thread apps/sim/lib/newsletters/runs.ts
Comment thread apps/sim/lib/core/async-jobs/backends/database.ts
@BillLeoutsakosvl346
BillLeoutsakosvl346 force-pushed the admin-newsletter-targeting branch from f8c79bf to c56913a Compare July 30, 2026 01:49
@BillLeoutsakosvl346

Copy link
Copy Markdown
Contributor Author

@greptile

@BillLeoutsakosvl346

Copy link
Copy Markdown
Contributor Author

@cursor review

Comment thread apps/sim/lib/newsletters/push-resend.ts
Comment thread apps/sim/lib/newsletters/push-resend.ts
Comment thread apps/sim/app/api/superuser/newsletters/runs/[id]/finalize/route.ts
>
{finalizePending ? 'Finalizing...' : 'Finalize'}
</Chip>
)}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stuck finalizing runs unreclaimable

Medium Severity

canFinalize only allows draft, so a run left in finalizing after a crashed or timed-out finalize never shows a Finalize action. The API can reclaim stale finalizing rows, but the UI never offers that path, leaving operators unable to recover without a manual API call.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 493375b. Configure here.

AND cc.deleted_at IS NULL
AND cm.deleted_at IS NULL
AND cm.role = 'user'
AND lower(cm.content::text) LIKE ${pattern}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Chat targeting scans full JSON

Medium Severity

chat_mentions matching runs LIKE over cm.content::text, but copilot_messages.content stores the full PersistedMessage JSON. Users can enter the audience via attachment names, context blockType values, or other metadata containing the term, not only message text, so Instagram chat targeting can over-include recipients.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 493375b. Configure here.

@BillLeoutsakosvl346

Copy link
Copy Markdown
Contributor Author

@greptile

@BillLeoutsakosvl346

Copy link
Copy Markdown
Contributor Author

@cursor review

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.

There are 3 total unresolved issues (including 2 from previous reviews).

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 493375b. Configure here.

{ cause: error }
)
}
return { run: updatedRun, jobId: updatedRun.resendSyncJobId ?? jobId }

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Resume can attach a finished job

Medium Severity

When shouldEnqueue is true because tracking was never persisted, enqueue reuses the attempt idempotency key. If that provider job already finished, Trigger/DB return the terminal job without running again, then setNewsletterRunResendJob stores it and the API succeeds while the run stays pushing with no worker. Recovery needs another resume after reconciliation sees the completed/failed job.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 493375b. Configure here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant