PRD: Người đăng ký bản tin
| Module | Khách hàng | PRD ID | PRD-SUB-001 |
| Trạng thái | Sẵn sàng dev | FEAT | SUB |
| Epic | — | Plane | BANA-1536 |
| Ngày | 2026-04-03 | Phiên bản | v1.1 |
| Gói | @nx/outreach | URD | SUB |
| Surface | BO · Vận hành | ||
| Phụ trách | Phát Nguyễn | ||
TL;DR
Khách truy cập đăng ký bản tin bằng email (kèm chủ đề và ngôn ngữ) và rời đi chỉ với một cú nhấp qua token duy nhất - vòng đời này đã chạy. Back-office hiện có màn danh sách người đăng ký chỉ-đọc (5 cột: email, trạng thái, chủ đề, ngôn ngữ, ngày đăng ký) - đã dựng. Trang thống kê (tổng số, mới trong tháng, đang hoạt động, đã hủy đăng ký) là forward-spec: nền số liệu đã sẵn sàng phía hệ thống, màn hiển thị trên Back Office chưa dựng (🚧). Kết quả: một danh sách gửi thư sạch, email duy nhất, đăng ký/đăng ký lại idempotent.
1. Bối cảnh & Vấn đề
Trang công khai Overture thu thập đăng ký bản tin, nhưng đội vận hành cần trả lời được những câu cơ bản - có bao nhiêu người đăng ký, bao nhiêu người tham gia trong tháng, bao nhiêu người đã rời đi. Việc đăng ký cũng phải an toàn khi lặp lại: gửi cùng một email hai lần không được tạo bản trùng, và địa chỉ đã rời đi trước đó phải tham gia lại được mà không cần dọn dẹp thủ công.
Tính năng này xây dựng vòng đời người đăng ký (đăng ký, hủy đăng ký, đăng ký lại - đã chạy), màn danh sách chỉ-đọc ở back-office (đã dựng), và một trang thống kê chỉ-đọc (chưa dựng - phần tính toán số liệu đã sẵn sàng phía hệ thống, còn thiếu màn hiển thị).
2. Mục tiêu & Loại trừ
Mục tiêu
- Đăng ký bằng email kèm chủ đề và ngôn ngữ tùy chọn; giữ email duy nhất trong danh sách người đăng ký.
- Đăng ký idempotent: email đang hoạt động trả về người đăng ký hiện có; email đã vô hiệu hóa sẽ được kích hoạt lại.
- Hủy đăng ký một cú nhấp qua liên kết token duy nhất - không cần xác thực.
- Màn danh sách người đăng ký chỉ-đọc ở back-office - xem và lọc, không sửa.
- Một trang thống kê có xác thực cho back-office - tổng số, mới trong tháng, đang hoạt động, đã hủy đăng ký (🚧 chưa dựng).
Loại trừ
- Engine chiến dịch Email / SMS - Dự kiến (xem URD §7).
- Tạo / sửa / xóa người đăng ký từ back-office - danh sách chỉ-đọc có chủ đích; dữ liệu chỉ vào qua form công khai.
- Phân khúc và nhắm mục tiêu khách hàng.
- Phân tích giá trị vòng đời của người đăng ký.
- Gửi email / vận chuyển thư (thuộc phạm vi ngoài module này).
3. Thước đo thành công
| Thước đo | Mục tiêu / tín hiệu |
|---|---|
| Tính toàn vẹn của danh sách | Không có email trùng trong số người đăng ký; mỗi người đăng ký có một token hủy đăng ký duy nhất |
| Tính idempotent của đăng ký | Đăng ký lặp cùng một email không tạo mục mới; mục đã vô hiệu hóa được kích hoạt lại sạch sẽ |
| Độ tin cậy của hủy đăng ký | Liên kết token một cú nhấp vô hiệu hóa ngay ở lần gọi đầu; token không hợp lệ bị từ chối là không-tìm-thấy |
| Khả năng quan sát | Danh sách back-office xem và lọc được (đã có); trang thống kê trả về 4 con số khi yêu cầu (khi dựng xong) |
4. Persona & Tình huống
| Persona | Mục tiêu trong tính năng này |
|---|---|
| Khách truy cập | Tham gia bản tin bằng email; rời đi chỉ với một cú nhấp |
| Đội vận hành (NEXPANDO & VNPAY) | Xem và lọc danh sách người đăng ký; theo dõi quy mô, tăng trưởng theo tháng, và số lượng rời đi |
Luồng cốt lõi: khách truy cập đăng ký bằng email (chủ đề + ngôn ngữ) → email lặp lại hoặc đã từng rời đi được xử lý idempotent → khách hủy đăng ký qua liên kết token → đội vận hành xem danh sách người đăng ký trong back-office; khi trang thống kê dựng xong, đọc thêm 4 con số tăng trưởng.
5. Câu chuyện người dùng
- Là một khách truy cập, tôi muốn đăng ký bằng email, chủ đề, và ngôn ngữ của mình, để tôi nhận được bản tin.
- Là một khách truy cập, tôi muốn đăng ký hai lần vẫn an toàn, để không bao giờ tạo đăng ký trùng hay gặp lỗi.
- Là một khách đã hủy đăng ký trước đó, tôi muốn đăng ký lại và được kích hoạt lại, để tham gia lại mà không cần trợ giúp.
- Là một khách truy cập, tôi muốn hủy đăng ký chỉ với một cú nhấp qua liên kết token, để việc rời đi không tốn công.
- Là đội vận hành, tôi muốn xem và lọc danh sách người đăng ký trong back-office, để biết ai đang trong danh sách.
- Là đội vận hành, tôi muốn một trang thống kê về danh sách, để theo dõi tăng trưởng và số lượng rời đi mà không phải đếm tay.
6. Yêu cầu chức năng
| # | Yêu cầu | Trạng thái | URD ref |
|---|---|---|---|
| FR-1 | Đăng ký bằng email (công khai, không xác thực) với ngôn ngữ và chủ đề tùy chọn | ✅ đã dựng | URD-SUB-001 |
| FR-2 | Email duy nhất trong số người đăng ký đang hoạt động; token hủy đăng ký sinh duy nhất và khó đoán | ✅ đã dựng | URD-SUB-002 |
| FR-3 | Đăng ký idempotent - email đang hoạt động trả về người đăng ký hiện có; email đã vô hiệu hóa được kích hoạt lại | ✅ đã dựng | URD-SUB-004 |
| FR-4 | Hủy đăng ký bằng token (công khai) vô hiệu hóa người đăng ký; token không hợp lệ bị từ chối là không-tìm-thấy | ✅ đã dựng | URD-SUB-003 |
| FR-5 | Màn danh sách người đăng ký chỉ-đọc ở back-office - 5 cột, 4 cột lọc được | ✅ đã dựng | URD-SUB-005 |
| FR-6 | Trang thống kê có xác thực - 4 con số: tổng số, mới trong tháng, đang hoạt động, đã hủy đăng ký | 🚧 chưa dựng | URD-SUB-005 |
FR-1 - Đăng ký bằng email (công khai)
- Field: email (bắt buộc, đúng định dạng), chủ đề quan tâm (tùy chọn, nhiều giá trị), ngôn ngữ ưu tiên (tùy chọn - Tiếng Việt / English).
- Không cần đăng nhập; trả về định danh của người đăng ký.
- Mốc thời gian đăng ký và token hủy đăng ký được cấp lúc tạo.
FR-2 - Email duy nhất & token
- Email duy nhất trong số người đăng ký đang hoạt động, bảo đảm bằng một quy tắc duy nhất - không bao giờ có hai bản ghi active cùng email.
- Token hủy đăng ký sinh ra duy nhất và khó đoán (entropy cao), nhưng không bị ràng buộc duy nhất ở tầng lưu trữ.
FR-3 - Đăng ký idempotent
- Email đang hoạt động → trả về người đăng ký hiện có, không tạo mới, không lỗi.
- Email đã vô hiệu hóa → kích hoạt lại: thời điểm đăng ký đặt lại về hiện tại, thời điểm hủy đăng ký xóa, trạng thái về hoạt động.
- Chuyển trạng thái là tất định - cùng đầu vào luôn cho cùng kết quả.
FR-4 - Hủy đăng ký bằng token
- Một cú nhấp qua liên kết mang token, không cần xác thực → người đăng ký chuyển sang đã hủy đăng ký, thời điểm hủy được ghi.
- Token không hợp lệ → từ chối là không-tìm-thấy; không lộ thông tin.
- Hủy đăng ký trên mục đã vô hiệu hóa là idempotent - không lỗi, không đổi gì thêm.
FR-5 - Danh sách người đăng ký ở back-office (chỉ-đọc)
Màn duy nhất đang có của tính năng này trên Back Office.
| Cột | Lọc được | Hiển thị |
|---|---|---|
| ✔ | ||
| Trạng thái | ✔ | Nhãn màu: Đang hoạt động / Đã hủy đăng ký |
| Chủ đề | - | Danh sách nhãn, một nhãn mỗi chủ đề |
| Ngôn ngữ | ✔ | Nhãn: Tiếng Việt / English |
| Ngày đăng ký | ✔ | Ngày giờ |
- Chỉ-đọc: không có nút tạo, không sửa, không xóa - người đăng ký chỉ vào danh sách qua form công khai.
- Màn yêu cầu đăng nhập back-office.
FR-6 - Trang thống kê (🚧 chưa dựng)
Hiện trạng: phần tính toán số liệu đã sẵn sàng phía hệ thống; còn thiếu màn hiển thị trên Back Office.
Đặc tả khi dựng:
- 4 con số: tổng số người đăng ký · mới trong tháng (tham gia kể từ đầu tháng hiện tại) · đang hoạt động · đã hủy đăng ký.
- Tính trong một lượt tổng hợp duy nhất trên các mục chưa xóa (nhận biết soft-delete); mục đã xóa không được đếm.
- Yêu cầu phiên đăng nhập; không chặn thêm theo quyền - ai vào được back-office đều đọc được.
- Nơi hiển thị dự kiến: khối số liệu ngay phía trên bảng danh sách người đăng ký (FR-5), để danh sách và sức khỏe danh sách đọc cùng một chỗ.
Tiêu chí nghiệm thu
Nghiệm thu được ngay (phần đã dựng):
- Đăng ký một email mới → người đăng ký xuất hiện trong danh sách back-office với trạng thái Đang hoạt động và ngày đăng ký đúng.
- Đăng ký cùng email lần hai → danh sách vẫn chỉ có một dòng cho email đó.
- Hủy đăng ký qua liên kết token → dòng tương ứng chuyển nhãn thành Đã hủy đăng ký.
- Đăng ký lại email đã hủy → dòng đó trở về Đang hoạt động với ngày đăng ký mới; không sinh dòng thứ hai.
- Mở liên kết hủy đăng ký với token sai → nhận phản hồi không-tìm-thấy.
- Danh sách back-office hiện đủ 5 cột; lọc theo Email, Trạng thái, Ngôn ngữ, Ngày đăng ký trả về đúng kết quả; cột Chủ đề không có bộ lọc.
- Màn danh sách không có nút tạo / sửa / xóa.
Chờ dựng màn (phần thống kê 🚧):
- Trang thống kê hiển thị đúng 4 con số; tổng = đang hoạt động + đã hủy (trên các mục chưa xóa).
- "Mới trong tháng" chỉ đếm người tham gia kể từ 00:00 ngày đầu tháng hiện tại.
- Người đăng ký đã xóa (soft-delete) không xuất hiện trong bất kỳ con số nào.
- Truy cập trang thống kê khi chưa đăng nhập → bị từ chối.
7. Yêu cầu phi chức năng
| Khía cạnh | Yêu cầu |
|---|---|
| Tính toàn vẹn dữ liệu | Email duy nhất trong số người đăng ký đang hoạt động; đăng ký không bao giờ tạo bản trùng; mọi mục có thể soft-delete |
| Tính idempotent | Đăng ký lặp lại là an toàn (trả về hiện có hoặc kích hoạt lại); các chuyển trạng thái là tất định |
| Phạm vi & phân quyền | Đăng ký/hủy đăng ký công khai không yêu cầu xác thực; danh sách và thống kê back-office yêu cầu phiên đăng nhập; lượt đọc thống kê không chặn thêm theo quyền |
| Hiệu năng / quy mô | Thống kê tính bằng một lượt tổng hợp duy nhất, không đếm từng dòng |
| i18n | Ngôn ngữ ghi nhận theo từng người đăng ký; nhãn hiển thị song ngữ (EN/VI) |
8. UX & Luồng
Màn hình chính: form đăng ký bản tin công khai trên trang Overture, và màn danh sách người đăng ký trong back-office (đã dựng). Trang thống kê dự kiến là khối số liệu phía trên danh sách (🚧).
9. Dữ liệu & Miền nghiệp vụ
| Khái niệm | Vai trò |
|---|---|
| Người đăng ký | Mục trong danh sách gửi thư - email (duy nhất), trạng thái (đang hoạt động / đã hủy đăng ký), ngôn ngữ, chủ đề, thời điểm đăng ký và hủy đăng ký, và một token hủy đăng ký duy nhất |
| Tóm tắt thống kê | Một bản đọc chỉ-đọc trên người đăng ký: tổng số, mới trong tháng, đếm theo trạng thái |
Chỉ ở mức khái niệm - mô hình dữ liệu và bất biến đầy đủ nằm trong Outreach domain model.
10. Phụ thuộc & Giả định
Phụ thuộc vào
- Năng lực outreach - chứa vòng đời người đăng ký và số liệu thống kê.
- Back-office - bề mặt hiển thị danh sách (đã có) và thống kê (chưa dựng).
- Trang công khai Overture - nguồn các đăng ký bản tin.
Giả định
- Trang công khai có thể gọi các hành động đăng ký/hủy đăng ký không xác thực.
- Người dùng back-office xác thực để xem danh sách và thống kê.
11. Rủi ro & Câu hỏi mở
| Rủi ro / câu hỏi | Giảm thiểu / trạng thái |
|---|---|
| Đăng ký trùng do gửi lặp lại | Đăng ký là idempotent; email duy nhất toàn cục |
| Đoán / phát lại token hủy đăng ký | Token entropy cao, khó đoán; token không hợp lệ bị từ chối; hủy đăng ký idempotent trên mục đã vô hiệu hóa |
| Màn thống kê chưa dựng - đội vận hành chưa tự đếm được tăng trưởng | Forward-spec FR-6 giữ nguyên yêu cầu; nền số liệu đã sẵn phía hệ thống, chỉ còn dựng màn hiển thị |
| Chưa có engine chiến dịch để hành động trên danh sách | Ngoài phạm vi; ghi nhận là Dự kiến trong URD |
| Chi phí thống kê khi danh sách lớn dần | Một lượt tổng hợp duy nhất; xem lại caching nếu back-office truy vấn dày |
12. Kế hoạch phát hành & Tiêu chí ra mắt
| Khía cạnh | Kế hoạch |
|---|---|
| Phase | P2 - tính năng Khách hàng SUB trong danh mục tính năng URD; FR-1..FR-5 đã dựng, FR-6 chưa |
| Rollout | Mọi deployment; không có feature flag |
| Migration | Không (mục mới; không backfill dữ liệu) |
| Tiêu chí ra mắt | Nhóm "nghiệm thu được ngay" (§6, mục 1-7) đạt toàn bộ; nhóm thống kê (mục 8-11) nghiệm thu khi màn FR-6 dựng xong |
| Giám sát | Tăng trưởng người đăng ký (mới trong tháng), tỷ lệ rời đi, tỷ lệ lỗi hủy đăng ký |
13. FAQ
Điều gì xảy ra nếu cùng một email đăng ký hai lần? Đăng ký là idempotent - một email đang hoạt động trả về người đăng ký hiện có, và một email đã vô hiệu hóa trước đó được kích hoạt lại. Không có mục trùng nào được tạo.
Hủy đăng ký có cần đăng nhập không? Không - đó là một liên kết công khai một cú nhấp mang token duy nhất. Ngược lại, danh sách và thống kê ở back-office thì yêu cầu đăng nhập.
Back-office hiện xem được gì? Màn danh sách người đăng ký chỉ-đọc: email, trạng thái, chủ đề, ngôn ngữ, ngày đăng ký - lọc được theo 4 cột (trừ chủ đề). Không tạo/sửa/xóa từ đây.
Trang thống kê đã có chưa? Chưa - đây là forward-spec (🚧). Phần tính toán số liệu đã sẵn sàng phía hệ thống; màn hiển thị trên Back Office sẽ dựng theo FR-6: 4 con số, dự kiến đặt ngay phía trên danh sách.
Người đăng ký đã xóa có được đếm không? Không - thống kê chỉ tổng hợp trên các mục chưa xóa (nhận biết soft-delete).
Tham chiếu
- URD: Khách hàng - Người đăng ký bản tin
- PRD liên quan: PRD-CAP-001 - Thu thập lead & người đăng ký
- Module: Khách hàng - URD
- Developer: @nx/outreach · Outreach domain model