PRD: Lead & subscriber capture
| Module | Marketing | PRD ID | PRD-CAP-001 |
| Status | Ready to dev | FEAT | CAP |
| Epic | — | Plane | BANA-1510 |
| Date | 2026-04-03 | Version | v1.1 |
| Packages | @nx/outreach | URD | CAP |
| Surface | BO · Ops | ||
| Owner | Phát Nguyễn | ||
TL;DR
Lets the public marketing site collect leads - consult/contact/sales/demo/partner inquiries and newsletter subscribers. Subscribe is idempotent by email, unsubscribe is honored via a one-click token, and every new inquiry notifies our operations team (NEXPANDO & VNPAY) in real time - a clean, opt-out-respecting audience ready for the Phase 2 campaigns. The back-office read (subscriber list + statistics) is specified in PRD-SUB-001.
1. Context & Problem
Merchants want to grow an audience from their public site - visitors who request a demo, contact sales, or opt into the newsletter. Without a capture surface those signals are lost or scattered: no deduplicated list, no enforced unsubscribe, no read on how the audience is growing. That gap blocks any future outbound campaign, which depends on a clean, opt-out-respecting base.
This feature lays the capture foundation: it records inquiries and newsletter opt-ins from the public site, keeps the subscriber list idempotent and opt-out-safe, and notifies the operations team the moment a new lead arrives.
2. Goals & Non-Goals
Goals
- Capture inquiries from the public site with 5 types (consult, contact, sales, demo, partner), with a real-time notification to the operations team.
- Make newsletter subscribe idempotent by email - re-subscribing reactivates a previously unsubscribed record rather than duplicating it.
- Honor token-based unsubscribe before any reactivation or send.
Non-Goals
- The back-office subscriber list and statistics - specified in PRD-SUB-001 (single source).
- Working inquiries in the back office (assign, reply, convert) - specified in PRD-CAP-002.
- Campaigns, drip automation, and A/B testing - owned by Campaigns & Automation (
CMP, P2). - Email/SMS delivery infrastructure - provided by external providers.
- Promotional pricing - owned by the Campaign module.
3. Success Metrics
| Metric | Target / signal |
|---|---|
| Deduplication | Zero duplicate active subscribers per email |
| Opt-out integrity | Every unsubscribe token resolves to a deactivated record; no send to deactivated subscribers |
| Capture latency | Inquiry submit → operations-team notification is near real-time |
4. Personas & Use Cases
| Persona | Goal in this feature |
|---|---|
| Visitor | Submit an inquiry or subscribe to the newsletter from the public site |
| Subscriber | Unsubscribe at any time via a one-click token link |
| Operations team (NEXPANDO & VNPAY) | Be told the moment a new inquiry arrives, to respond quickly |
Core scenario: a visitor submits an inquiry (operations team notified in real time) or subscribes to the newsletter (idempotent by email) → a subscriber later clicks the unsubscribe link (token-resolved, deactivated) → a previously opted-out address subscribes again and is reactivated, with no duplicate.
5. User Stories
- As a visitor, I want to submit a consult/contact/sales/demo/partner inquiry from the public site, so the business can follow up.
- As a visitor, I want to subscribe to the newsletter with my email, so I receive updates - and subscribing twice never creates a duplicate.
- As a subscriber, I want to unsubscribe via a token link, so I stop receiving messages.
- As a previously-unsubscribed visitor, I want re-subscribing to reactivate my record, so I rejoin without a duplicate entry.
- As the operations team, I want a real-time notification when an inquiry arrives, so we can respond quickly.
6. Functional Requirements
| # | Requirement | Status | URD ref |
|---|---|---|---|
| FR-1 | Capture an inquiry from the public site - a full form with fields and validation (detailed below) | ✅ built | URD-CAP-001 |
| FR-2 | Newsletter subscribe is idempotent by email; an active email returns the existing record, no duplicate | ✅ built | URD-CAP-002 |
| FR-3 | Re-subscribing a previously unsubscribed email reactivates it (subscribe timestamp reset, unsubscribe timestamp cleared, status set to active) | ✅ built | URD-CAP-002 |
| FR-4 | Token-based unsubscribe resolves a subscriber by their token and deactivates it; an invalid token is rejected | ✅ built | URD-CAP-003 |
| FR-5 | Back-office read of the subscriber list and statistics - specified, with build status, in PRD-SUB-001 (single source; this PRD does not restate it) | - | URD-CAP-004 |
| FR-6 | A new inquiry submission emits a real-time notification to the operations team | ✅ built | URD-CAP-001 |
FR-1 - Inquiry capture (public form)
| Field | Required | Rule |
|---|---|---|
| Inquiry type | ✔ | One of 5: Consult · Contact · Sales · Demo · Partner |
| First name | ✔ | Must not be empty |
| Last name | - | |
| ✔ | Valid email format | |
| Phone | ✔ | Valid phone format |
| Business name | - | |
| Business type | - | |
| Location count | - | Free entry |
| Estimated revenue | - | Free entry |
| Subject | - | |
| Message | - | Multiline |
- The form is open to anonymous visitors - no sign-in required.
- A new inquiry enters the system with status New and appears immediately in the operations team's workbench (PRD-CAP-002).
FR-2 / FR-3 - Newsletter subscribe (idempotent)
The public newsletter form collects: email (required, valid format), topics of interest (optional), preferred language (optional - Vietnamese / English).
Idempotency rules, by email:
- Email not seen before → create a new subscriber (status active) with a unique unsubscribe token.
- Email already active → return the existing record; no duplicate, no error.
- Email previously unsubscribed → reactivate: reset the subscribe timestamp to now, clear the unsubscribe timestamp, set status to active.
FR-4 - Token-based unsubscribe
- The unsubscribe link carries a unique, hard-to-guess token issued at sign-up; one click deactivates - no sign-in needed.
- An invalid token → rejected as not-found; no subscriber information is revealed.
- A deactivated record is honored before any future send (constraint C-01).
FR-6 - Real-time notification
- When a new inquiry is submitted from the public site, operations users with the Back Office open see it immediately: the menu badge increments (counting New + In review inquiries) and a toast notification appears - no page reload.
Acceptance criteria
- Submitting the inquiry form with First name, Email, Phone, or Type missing → a field-level error, nothing recorded.
- A malformed email or phone number on the inquiry form → a field-level error.
- A valid inquiry submission → the inquiry appears in the Back Office list with status New; the badge and toast fire for operators online.
- Subscribing the same email twice in a row → exactly one active record exists.
- Re-subscribing a previously unsubscribed email → the record is reactivated (active status, fresh subscribe timestamp, unsubscribe timestamp cleared) - no second record.
- Clicking a valid unsubscribe link → the record deactivates on the first click.
- Opening an unsubscribe link with an invalid token → a not-found response.
7. Non-Functional Requirements
| Area | Requirement |
|---|---|
| Data integrity | Subscribe is idempotent by email - one active record per email |
| Opt-out integrity | Unsubscribe is honored before any reactivation or send; deactivation is via token only |
| Tenancy | Capture data is global (not confined to a single merchant) per module constraint C-02 |
| Authz | Public capture entry points are open to anonymous visitors; back-office reads are permission-gated (see PRD-SUB-001) |
| i18n | A subscriber's preferred language is captured so future sends can localize; user-facing labels are bilingual (EN/VI) |
8. UX & Flows
Key screens: the public-site capture forms (inquiry + newsletter). The back-office read (subscriber list, statistics) belongs to PRD-SUB-001; the inquiry workbench belongs to PRD-CAP-002.
9. Data & Domain
| Concept | Role |
|---|---|
| Inquiry | A consult/contact/sales/demo/partner request captured from the public site - contact (first name, last name, email, phone), business (name, type, location count, estimated revenue), content (subject, message) |
| Subscriber | A newsletter opt-in - email, preferred language, topics, status (active/deactivated), subscribe timestamp, unsubscribe timestamp, unsubscribe token |
Conceptual only - full schema and invariants in the outreach developer docs.
10. Dependencies & Assumptions
Depends on
- The capture capability - hosts inquiry/subscriber capture and lookup.
- Real-time notification channel - pushes new-inquiry events to the operations team.
Assumptions
- Capture data is global (constraint C-02); no per-merchant confinement on subscribers/inquiries.
- Each subscriber holds a unique unsubscribe token issued at capture time for one-click unsubscribe.
- External providers (out of scope) handle eventual email/SMS delivery.
11. Risks & Open Questions
| Risk / question | Mitigation / status |
|---|---|
| Duplicate subscribers on rapid double-submit | Subscribe is idempotent by email - existing active record returned, no insert |
| Sending to an unsubscribed address | Unsubscribe deactivates the record; constraint C-01 requires opt-out be honored before any send |
| Global (non-confined) data across merchants | Accepted per constraint C-02 |
| Spam through the public form | No dedicated anti-spam yet; monitor inquiry volume, add protection if abused |
12. Release Plan & Launch Criteria
| Aspect | Plan |
|---|---|
| Phase | P1 (foundation) - marketing/CAP in the URD feature catalog |
| Rollout | All sites; no feature flag |
| Migration | None (new entities; global data) |
| Launch criteria | All §6 acceptance criteria pass |
| Monitoring | Subscribe/unsubscribe volume, duplicate-subscriber rate (expect 0), inquiry notification reliability |
13. FAQ
What if the same email subscribes twice? Nothing duplicates - an already-active email returns the existing record; a previously unsubscribed email is reactivated.
How does unsubscribe work? Via a token link: the unsubscribe token resolves to the subscriber, which is then deactivated. Opt-out is always honored before any send.
Are subscribers scoped per merchant? No - capture data is global per constraint C-02.
Where do I see the subscriber list or statistics? In the back-office read - specified in PRD-SUB-001.
Does this send any campaigns? No - sending is out of scope here and owned by Campaigns & Automation (CMP, P2). This feature only captures the audience.
References
- URD: Marketing - Lead & Subscriber Capture
- Related PRDs: PRD-CAP-002 - Inquiry Management (Back Office) · PRD-SUB-001 - Newsletter subscribers · Campaigns & Automation (planned)
- Module: Marketing - URD
- Developer: @nx/outreach