Skip to content

PRD: Newsletter Subscribers

ModuleCustomer (CORE-09)PRD IDPRD-SUB-001
StatusShippedOwnerCustomer squad
Date2026-04-03Versionv1.0
Packages@nx/outreach · @nx/core · apps/boURDSUB

TL;DR

Lets a site visitor opt into the newsletter by email, with topics and locale, then unsubscribe in one click via a unique token - and gives the back-office a single, authenticated statistics view of how the list is growing (total, new-this-month, counts by status). The result: a clean, globally-unique mailing list with idempotent subscribe/re-subscribe and a health dashboard, instead of an opaque sign-up form with no admin visibility.

1. Context & Problem

The public Overture marketing site collects newsletter sign-ups, but the back-office (apps/bo) has no way to read the list or see how it is growing. A visitor can subscribe, yet an admin cannot answer basic questions - how many subscribers exist, how many joined this month, how many have opted out. Sign-ups also need to behave safely under repeats: the same email submitted twice must not create duplicates, and a previously opted-out address must be able to rejoin without manual cleanup. Without a unique-email guarantee, a one-click unsubscribe, and an authenticated statistics read, the list is opaque and error-prone - a blocker for the marketing-engagement layer Customer targets.

This feature builds the subscriber lifecycle (subscribe, unsubscribe, re-subscribe) on the @nx/outreach service and exposes a read-only statistics aggregate to the back-office.

2. Goals & Non-Goals

Goals

  • Subscribe by email with optional topics and locale, keeping the email globally unique among subscribers.
  • Idempotent subscribe: an already-active email returns the existing subscriber; a deactivated one is reactivated.
  • One-click unsubscribe via a unique token link, with no authentication required.
  • An authenticated statistics endpoint for the back-office - total, monthly-new, and counts grouped by activated/deactivated status.

Non-Goals

  • Email / SMS campaign engine - Planned (see URD §7).
  • Customer segmentation and targeting.
  • Subscriber lifetime-value analytics.
  • Email delivery / SMTP transport (owned outside this module).

3. Success Metrics

MetricTarget / signal
List integrityZero duplicate emails among subscribers; every subscriber has a unique unsubscribe token
Subscribe idempotencyRepeated subscribe of the same email yields no new rows; deactivated rows reactivate cleanly
Unsubscribe reliabilityOne-click token link deactivates on first request; invalid token returns 404
Admin visibilityBack-office statistics view returns total, monthly-new, and per-status counts on demand

4. Personas & Use Cases

PersonaGoal in this feature
Site VisitorJoin the newsletter by email; leave in one click
MarketingSee list size, monthly growth, and opt-out counts in the back-office
OwnerConfirm the mailing list is healthy and growing

Core scenarios: a visitor subscribes by email (topics + locale) → a repeat or previously-opted-out email is handled idempotently → the visitor unsubscribes via a token link → marketing opens the back-office and reads the subscriber statistics.

5. User Stories

  • As a site visitor, I want to subscribe with my email, topics, and locale, so that I receive the newsletter.
  • As a site visitor, I want subscribing twice to be safe, so that I never create duplicate sign-ups or errors.
  • As a previously-unsubscribed visitor, I want to subscribe again and be reactivated, so that I can rejoin without help.
  • As a site visitor, I want to unsubscribe in one click via a token link, so that opting out is effortless.
  • As marketing, I want an authenticated statistics view of the list, so that I can track growth and opt-outs.

6. Functional Requirements

#RequirementURD ref
FR-1Subscribe by email (POST /subscribe, no auth) with optional locale (vi/en) and optional topics[]; returns the subscriber idURD-SUB-001
FR-2Email is globally unique among subscribers; unsubscribe token is globally uniqueURD-SUB-002
FR-3Subscribe is idempotent - an already-active email returns the existing subscriber; a deactivated one is reactivated (subscribedAt reset, unsubscribedAt cleared)URD-SUB-004
FR-4Unsubscribe by token (GET /unsubscribe?token=…, no auth) deactivates the subscriber; an invalid token returns 404URD-SUB-003
FR-5Statistics endpoint (GET /statistics, JWT or Basic auth) returns total, monthlyNew, and byStatus { activated, deactivated }URD-SUB-005
FR-6Statistics are a single SQL aggregate over non-deleted rows: COUNT(*) FILTER (…) for monthly-new (subscribedAt >= startOfMonth) and per-status countsURD-SUB-005

Full requirement text and acceptance criteria live in the Customer URD. This PRD references them rather than restating them.

7. Non-Functional Requirements

AreaRequirement
Data integrityEmail and unsubscribe token are globally unique; subscribe never creates duplicates; all rows use soft-delete
IdempotencyRepeated subscribe is safe (returns existing or reactivates); state transitions are deterministic
Tenancy & authzPublic subscribe/unsubscribe require no auth; statistics requires JWT or BASIC strategy; permissions declared via crudPermissions(Subscriber.AUTHORIZATION_SUBJECT, …)
Performance / scaleStatistics computed in a single SQL aggregate (COUNT(*) FILTER), not row-by-row counting
i18nLocale captured per subscriber (vi/en); user-facing labels are bilingual ({ en, vi })

8. UX & Flows

Key screens: the public newsletter sign-up on the Overture site, and the back-office subscriber list + statistics view in apps/bo.

9. Data & Domain

EntityRole
SubscriberThe mailing-list entry - email (unique), status (activated/deactivated), locale, topics[], subscribedAt, unsubscribedAt, unique unsubscribeToken
Statistics aggregateA read-only projection over Subscriber: total, monthlyNew, and byStatus counts

Conceptual only - full schema and invariants in the Outreach domain model.

10. Dependencies & Assumptions

Depends on

  • @nx/outreach - hosts the subscriber service, repository, and routes.
  • @nx/core - provides the Subscriber model, schema, and crudPermissions.
  • apps/bo - the back-office surface that reads the statistics endpoint.
  • Overture public site - the source of newsletter sign-ups.

Assumptions

  • The public site can call the unauthenticated subscribe/unsubscribe routes.
  • Back-office users authenticate via JWT or BASIC to read statistics.

11. Risks & Open Questions

Risk / questionMitigation / status
Duplicate sign-ups from repeated submitsSubscribe is idempotent; email is globally unique
Unsubscribe token guessing / replayToken is globally unique; invalid token returns 404; unsubscribe is idempotent on deactivated rows
No campaign engine to act on the listOut of scope; documented as Planned in the URD
Statistics cost as the list growsSingle SQL aggregate; revisit caching if the back-office polls heavily

12. Release Plan & Launch Criteria

AspectPlan
PhaseP2 - Customer feature SUB in the URD feature catalog
RolloutAll deployments; no feature flag
MigrationNone (new entity; no data backfill)
Launch criteriaSubscribe/unsubscribe/re-subscribe verified idempotent end-to-end; statistics endpoint returns correct total, monthly-new, and per-status counts; back-office list screen renders the data
MonitoringSubscriber growth (monthly-new), opt-out rate (deactivated count), unsubscribe error rate

13. FAQ

What happens if the same email subscribes twice? Subscribe is idempotent - an already-active email returns the existing subscriber, and a previously deactivated one is reactivated. No duplicate row is created.

Does unsubscribe require login? No - unsubscribe is a public one-click link carrying a unique token. Statistics, by contrast, requires JWT or Basic auth.

What does the statistics endpoint return? total subscribers, monthlyNew (joined since the start of the current month), and byStatus counts split into activated and deactivated.

Does this feature send emails? No - it manages the subscriber list and its statistics. The campaign/delivery engine is Planned and lives outside this module.

Are deleted subscribers counted? No - statistics aggregate over non-deleted (soft-delete-aware) rows only.

References

Proprietary and Confidential. Unauthorized copying, distribution, or use of this software is strictly prohibited.