PRD: SMS campaigns
| Module | Marketing (EXT-02) | PRD ID | PRD-CMP-001 |
| Status | Planned | Owner | Marketing squad |
| Date | 2026-03-13 | Version | v0.1 |
| Packages | mq-sms · @nx/identity · @nx/core | URD | CMP |
TL;DR
Gives Marketing a real SMS delivery channel so messages reach customers' phones, not just a queue. A standalone
mq-smsmodule wraps the VNPAY SMS provider, anApplicationSMSComponentwires it into identity, and the first consumer - OTP request and verification - proves the channel end to end. This is the load-bearing send path that the planned campaign-to-segment send (URD-CMP-001) will reuse.
1. Context & Problem
The Marketing module's Campaigns & Automation feature needs a way to actually deliver messages to customers. Per the module Non-Goals, KICKO does not build its own delivery infrastructure - it integrates an external provider. Today there is no SMS send path at all: nothing in the platform can hand a text message to a carrier, so neither authentication (OTP) nor any future campaign can reach a customer's phone.
This increment stands up the SMS-delivery groundwork - a third-party provider integration, the application component that uses it, and provider/credential configuration - and exercises it with the first concrete consumer (OTP). The campaign-to-segment send itself is layered on top later; building the channel first unblocks both auth and marketing.
2. Goals & Non-Goals
Goals
- Stand up an
mq-smsthird-party module wrapping the VNPAY SMS provider (request helpers, request/response models, controller). - Add an
ApplicationSMSComponentto identity and integrate SMS sending into the application. - Load SMS provider configuration (credentials, environment) through the configuration service, with idempotent (upsert) seeding and tolerant migration handling.
- Build the first SMS consumer - OTP request and verification - with an SMS OTP sender and a log-only sender for non-prod.
Non-Goals
- The campaign-to-segment send itself (URD-CMP-001) - sending a planned campaign to a customer segment is layered on top of this channel, not in this increment.
- Triggered / drip automation (URD-CMP-002) and A/B testing & reporting (URD-CMP-003).
- Building delivery infrastructure beyond integrating the external VNPAY provider (per module Non-Goals).
- Email / other-channel delivery.
3. Success Metrics
| Metric | Target / signal |
|---|---|
| Channel availability | SMS send path returns provider acceptance for valid requests; failures surfaced, not silently dropped |
| OTP deliverability | OTP request → SMS delivered → verify succeeds end to end |
| Config safety | Missing/invalid SMS config does not crash startup; seeding is idempotent across restarts |
| Reuse readiness | The same channel is callable by a future campaign sender with no provider rework |
4. Personas & Use Cases
| Persona | Goal in this feature |
|---|---|
| Customer (recipient) | Receive an OTP (and later, campaign messages) on their phone |
| End user authenticating | Request and verify an OTP to complete sign-in |
| Owner / Manager (Marketing) | Eventually reuse this channel to reach segments via campaigns |
| Platform operator | Configure VNPAY credentials/environment without redeploying code |
Core scenarios: configure the VNPAY SMS provider once → an application flow (OTP request) asks the SMS channel to send → mq-sms builds an encrypted VNPAY request and dispatches it → the recipient gets the text → OTP verify confirms the code.
5. User Stories
- As an end user authenticating, I want to receive an OTP by SMS and verify it, so that I can complete sign-in securely.
- As a platform operator, I want SMS provider credentials and environment loaded from configuration, so that I can switch providers/environments without a code change.
- As a platform operator, I want missing SMS config to be tolerated at startup, so that environments without SMS don't break boot.
- As a Marketing owner, I want a reusable SMS send channel, so that the planned campaign-to-segment send can reach customers without new provider work.
- As a developer, I want a log-only sender in non-prod, so that I can exercise SMS flows without spending real messages.
6. Functional Requirements
| # | Requirement | URD ref |
|---|---|---|
| FR-1 | Provide an mq-sms third-party module exposing an SMS send path (controller + request/response models) | URD-CMP-001 |
| FR-2 | Integrate the VNPAY SMS provider via request helpers and VNPAY constants/types | URD-CMP-001 |
| FR-3 | Encrypt SMS payloads before dispatch to the provider | URD-CMP-001 |
| FR-4 | Expose an ApplicationSMSComponent in identity that the application calls to send SMS | URD-CMP-001 |
| FR-5 | Load SMS provider config (credentials, environment) via the configuration service, seeded with upsert (idempotent) | URD-CMP-001 |
| FR-6 | Tolerate missing SMS configuration at startup/migration without failing boot | URD-CMP-001 |
| FR-7 | Provide OTP request and verification on top of the SMS channel, with an SMS OTP sender and a log-only sender for non-prod | URD-CMP-001 |
| FR-8 | Centralize OTP configuration in the configuration service; hash OTP secrets via Bun.password | URD-CMP-001 |
Full requirement text and acceptance criteria live in the Marketing URD. This PRD references them rather than restating them.
7. Non-Functional Requirements
| Area | Requirement |
|---|---|
| Security | SMS payloads are encrypted before reaching the provider; OTP secrets are hashed (Bun.password), never stored in clear |
| Configurability | Provider credentials/environment live in configuration, not code; switchable without redeploy |
| Resilience | Missing/invalid SMS config is tolerated at startup; failures surface rather than crashing the application |
| Idempotency | Config seeding uses upsert - repeated startups/migrations converge to one record |
| Observability | A log-only sender exists for non-prod so flows are testable without real sends |
| i18n | User-facing OTP messaging supports bilingual content ({ en, vi }) |
8. UX & Flows
This channel has no merchant-facing screen of its own in this increment; it is invoked by application flows (OTP today, campaign send later). Provider credentials are managed through configuration.
9. Data & Domain
| Entity | Role |
|---|---|
| SMS request / response models | Shape of an outbound SMS and its provider acknowledgement (mq-sms) |
TMQSMSClientConfig | Provider client configuration type (credentials, endpoint) |
| SMS provider config | Per-environment provider settings in the INTEGRATION config group, with environment field |
| OTP config | Centralized OTP settings in the configuration service |
| OTP record | The issued/hashed OTP used by request + verify |
Conceptual only - full schema and provider detail live in the developer outreach docs.
10. Dependencies & Assumptions
Depends on
- VNPAY SMS provider - the external carrier integration the channel wraps.
@nx/coreconfiguration service - provider/OTP config loading, upsert seeding, migrations.@nx/identity- hosts theApplicationSMSComponentand the OTP controller/service.
Assumptions
- Valid VNPAY SMS credentials and an
environmentare provided per deployment (or SMS is intentionally disabled). Bun.passwordis available for OTP hashing.- A future campaign sender will reuse this channel rather than integrating a provider of its own.
11. Risks & Open Questions
| Risk / question | Mitigation / status |
|---|---|
| Campaign-to-segment send (URD-CMP-001) is not yet built on this channel | Channel + OTP shipped first; campaign send is the next increment - PRD stays planned until then |
| Single provider (VNPAY) coupling | Channel abstracts the provider behind mq-sms; a second provider can slot in behind the same component |
| Missing SMS config could break boot | Startup/migration made tolerant of absent SMS config |
| Provider outage / delivery failure | Failures surfaced to callers; non-prod uses log-only sender |
| Cost of real sends during testing | Log-only sender for non-prod environments |
12. Release Plan & Launch Criteria
| Aspect | Plan |
|---|---|
| Phase | P2 - SMS delivery channel for Campaigns & Automation |
| Rollout | Channel available where VNPAY config is present; OTP consumer enabled in identity |
| Migration | SMS provider config seeded into the INTEGRATION config group via upsert; environment field added; tolerant of missing config |
| Launch criteria | OTP request → SMS delivered → verify verified end to end; config seeding idempotent across restarts; missing config does not break startup |
| Monitoring | SMS send success/failure rate, OTP verify success rate, provider error responses |
13. FAQ
Does this send marketing campaigns to customers yet? No. This increment delivers the SMS channel and its first consumer (OTP). The campaign-to-segment send (URD-CMP-001) is layered on top in a later increment - which is why this PRD is still planned.
Why is OTP here if this is a Marketing PRD? OTP is auth-facing, but it is the first and load-bearing consumer of the exact SMS channel that campaigns will reuse. Proving the channel through OTP de-risks the marketing send.
Which provider does it use? VNPAY SMS, wrapped by the mq-sms third-party module. The provider sits behind the channel so it can be swapped or extended.
How are credentials managed? Through the configuration service (INTEGRATION group), with an environment field and idempotent upsert seeding - no redeploy needed to change them.
What happens in non-prod or when SMS isn't configured? A log-only sender stands in for real sends, and missing SMS config is tolerated at startup so boot doesn't fail.
References
- URD: Marketing - Campaigns & Automation - requirements (CMP area)
- Module: Marketing - overview + traceability
- Developer: @nx/outreach - capture-side package