Purpose, scope, definitions, and stakeholders for this specification.
1.1 Purpose
This document specifies the software requirements for Vesta v1, a bilingual reproductive health platform targeting Bangladesh. It defines what the system must do (functional requirements), how well it must do it (non-functional requirements), external API contracts (BDApps OTP + CAAS), deployment architecture, and the acceptance criteria that must pass before the v1 production launch. This document is the primary reference for client sign-off and developer implementation.
1.2 Scope
Vesta v1 delivers: phone-number-based account management (BDApps OTP verification), multi-profile period tracking and cycle prediction, symptom logging, a monthly calendar, bilingual UI (English + Bengali), Pro subscription billing via BDApps CAAS direct debit, email notifications, a bilingual content platform, and an admin panel with CMS and analytics. The system is deployed on Vercel (frontend + API) and Supabase (PostgreSQL). SMS notifications, pregnancy mode, and native mobile apps are explicitly out of scope for v1.
1.3 Definitions & Acronyms
MSISDN
Mobile Subscriber ISDN number — the full international phone number (e.g. 8801XXXXXXXXX). Used as the primary user identifier in BDApps APIs.
OTP
One-Time Password — a 4-6 digit code sent to the user's phone by the BDApps OTP API to verify account ownership.
CAAS
Carrier Aggregated Application Services — BDApps billing system that charges a user's mobile balance directly via direct debit API.
RLS
Row Level Security — PostgreSQL feature that restricts which rows a database session can access, enforced per user_id at the Supabase level.
AES-256-GCM
Advanced Encryption Standard 256-bit in Galois/Counter Mode — symmetric encryption used for sensitive health fields at rest via prisma-field-encryption.
ARIMA
AutoRegressive Integrated Moving Average — Tier 2 statistical model for cycle prediction, unlocked after 6 logged cycles.
LSTM
Long Short-Term Memory neural network — Tier 3 prediction model stub (not in phase v1), unlocked after 12 logged cycles.
Grace Period
Configurable N-day window after a CAAS balance check failure during which Pro access is maintained before being revoked.
QStash
Upstash QStash — HTTP-based serverless job queue replacing BullMQ for async jobs (email delivery, prediction recalculation).
1.4 Stakeholders
| Role | Description | Primary Interactions |
| End User | Individual tracking their own cycle. Primary persona. Phone number account, free tier. | Onboarding, period logging, dashboard, calendar, symptom logging |
| Family Manager | User managing profiles for family members (wife, sister, mother, daughter, girlfriend). Up to 6 profiles per account. | Profile switcher, per-profile data isolation |
| Pro Subscriber | User who has upgraded to Pro via BDApps CAAS direct debit. Accesses advanced predictions and notifications. | Subscription flow, subscription status persisted (gating deferred to post-v1), grace period notifications |
| Super Admin | Technical owner with full system access — user management, subscription management, analytics, and bilingual content publishing. | Admin panel, session management, KPIs, Tiptap CMS, article publish/edit, phase tag assignment |
Product perspective, operating environment, and implementation constraints.
2.1 Product Perspective
Vesta is built with Next.js 16 (React 19) deployed on Vercel. User identity is verified via BDApps OTP API (phone-number auth, no email). Pro subscriptions are charged via BDApps CAAS direct debit. The backend connects to PostgreSQL 16 on Supabase (with RLS), Redis on Upstash (JWT blacklist + rate limits), and Upstash QStash (async jobs). Two Vercel Cron jobs handle daily reminders and daily CAAS subscription checks. All sensitive health fields are encrypted at rest with AES-256-GCM.
2.2 Tech Stack
Frontend
Next.js 16 + React 19 + TypeScript
Single Next.js app (App Router) deployed on Vercel. Tailwind CSS v4 (CSS-first config).
State Management
TanStack Query + Zustand
TQ for server state + caching. Zustand for UI state and locale.
Routing
Next.js App Router
File-based routing with nested layouts and server/client component boundaries.
Backend
Next.js Route Handlers + TypeScript
API endpoints implemented as Next.js route handlers and deployed on Vercel.
ORM + Encryption
Prisma 6.13 (pinned)
Pinned for prisma-field-encryption compat. AES-256-GCM on health fields.
Database
PostgreSQL 16 via Supabase
RLS enabled for profile isolation at DB level.
Auth + Sessions
JWT + BDApps OTP
Phone auth, HttpOnly cookies, Upstash Redis blacklist.
Job Queue + Crons
Upstash QStash + Vercel Cron
QStash for async jobs. Vercel Cron for 2 scheduled jobs.
Email
Resend + React Email
3K emails/mo free. Transactional templates in both languages.
Billing
BDApps CAAS API
Direct debit + balance query for Pro subscription lifecycle.
i18n
i18next + react-i18next
EN + BN from day one. Noto Sans Bengali font.
Hosting
Vercel Hobby + Supabase Free
$0/month launch stack. Clear upgrade triggers defined.
2.3 Constraints
| Constraint | Detail | Impact |
| BDApps MSISDN-only auth | Users must have a Bangladeshi mobile number. No email/OAuth fallback. | Sign-up flow limited to phone numbers in tel:8801XXXXXXXXX format |
| Vercel 10s function timeout | Hobby plan serverless functions time out at 10 seconds. | Prediction recalculation must be async via QStash; no synchronous long jobs |
| Supabase 500 MB storage | Free tier PostgreSQL limited to 500 MB before auto-pause. | Encrypted health blobs increase row size — monitor and upgrade proactively |
| BDT currency only | BDApps CAAS only processes Bangladeshi Taka. | All Pro subscription pricing must be in BDT |
| Prisma pinned to 6.13 | prisma-field-encryption requires Prisma 6.13.x. | Cannot upgrade Prisma without verifying encryption library compat |
| 2 Vercel Cron jobs max | Hobby plan allows exactly 2 scheduled cron jobs. | Daily reminders and CAAS check must share the 2-job budget |
2.4 Release Plan — 4-Week MoSCoW
v1 ships in four weeks. Scope is fixed by the buckets below. Anything in Won’t Have is explicitly out of v1 and queued for v2.
| Priority | Window | Features |
| P0 |
Weeks 1–2 |
- Phone auth + BDApps OTP verification
- Multi-profile accounts (self, wife, sister, mother, daughter, girlfriend)
- Bilingual UI (EN + BN, i18next)
- Period tracking (start/end, flow intensity, retroactive editing)
- Cycle dashboard (day hero, phase label, countdown)
- Monthly calendar (color-coded period/fertile/ovulation)
- Symptom logging (30+ symptoms, 4 categories)
- Three-tier prediction engine (Avg → ARIMA → LSTM stub)
- Onboarding flow (trust screens, goal, cycle history)
- Field-level encryption (AES-256-GCM)
- Profile switcher with data isolation
- Subscription schema (FR-GATE) — gating logic deferred
- Redis session store + JWT blacklist
- BDApps CAAS charging for Pro subscription
- Auto-disable Pro on charging lapse + grace period
|
| P1 |
Week 3 |
- Email notifications (period alert, fertile window, logging reminder)
- Content platform (bilingual articles, phase-based)
- Notification preferences per-profile
|
| P2 |
Week 4 |
- Admin dashboard + user management
- Content CMS (Tiptap)
- Population health analytics
- Prediction accuracy reports
- Single Super Admin role (Content Admin role removed)
- Admin session + subscription management
- Data export (CSV/Excel)
|
| Won’t Have |
v2 backlog |
- Partner sharing
- Pregnancy mode
- SMS notifications
- Native mobile apps
- AI health chatbot
- Doctor consultations
- Medicine / sleep / exercise tracker
- Community forum
|
All v1 functional requirements grouped by MoSCoW bucket (Must / Should / Could). Each block shows requirements on the left and testable acceptance criteria on the right; coloured tags identify the bucket.
Requirements
User registers with Bangladeshi mobile number (11-digit, MSISDN format tel:8801XXXXXXXXX)
System calls BDApps otp/request and sends OTP to phone
User enters OTP; system calls otp/verify to confirm; account created on success
OTP resend available after 60-second cooldown
Exponential backoff retry on BDApps API failures
JWT issued in HttpOnly cookie on successful verification
JWT blacklisted in Upstash Redis on logout
Acceptance Criteria
✓
New user can complete sign-up end-to-end in production using a real Bangladeshi number
✓
Invalid OTP entry shows error; correct OTP creates account and redirects to onboarding
✓
Resend button is disabled for 60s, then re-enables
✓
Logged-out JWT is rejected on subsequent API calls
✓
Rate limiter blocks >3 OTP requests/minute from same IP
Requirements
One user account supports up to 6 profiles: self, wife, sister, mother, daughter, girlfriend
Each profile has an independent cycle history, symptoms, and predictions
Profile switcher accessible from main navigation
Profiles can be created, renamed, and deleted
Data isolation enforced at both API middleware and PostgreSQL RLS levels
Acceptance Criteria
✓
User can create 6 profiles and switch between them; each shows independent dashboard data
✓
Direct DB query confirms user A's period_logs are not readable by user B's session
✓
Deleting a profile removes all associated period_logs, symptom_logs, and predictions
Requirements
User logs period start date, end date, and flow intensity (light / medium / heavy)
Retroactive editing of past period entries
Each log creation/update triggers async prediction recalculation via QStash
Acceptance Criteria
✓
Period log saved → prediction recalc complete → calendar updated in < 3 seconds end-to-end
✓
Editing a past entry updates predictions; dashboard reflects new values within 3s
Requirements
Hero display: current cycle day (e.g. "Day 14")
Current phase label: Menstrual / Follicular / Ovulatory / Luteal
Countdown to next predicted period
Last period summary (start date, duration, flow)
Prediction confidence percentage visible to user
Acceptance Criteria
✓
Dashboard loads; all 5 data points render correctly
✓
New user without history sees "estimate" disclaimer and Tier 1 prediction from onboarding data
Requirements
Month grid view with color-coded dates: period (pink), fertile window (teal), ovulation day (accent)
Month navigation (previous / next)
Calendar updates automatically after prediction recalculation
Acceptance Criteria
✓
Calendar shows correct color coding for logged and predicted periods across 3 future months
✓
Month navigation works; no layout breaks at 375px
Requirements
30+ symptoms across 4 categories: Physical, Mood, Discharge, Lifestyle
Icon-based tap-to-select UI (no text input required)
Date picker to log symptoms for any day
Acceptance Criteria
✓
All 30+ symptoms across all 4 categories render correctly and can be saved
✓
Decrypted value returned to correct user via API
Requirements
Tier 1 — Simple average: available from day 1, uses onboarding-provided cycle history
Tier 2 — ARIMA: unlocks automatically after 6 logged cycles; higher accuracy
Tier 3 — LSTM stub (planned for v2)
Recalculation is async (QStash job); UI shows "updating" state
Outputs: next_period_start, ovulation_day, fertile_start, fertile_end, confidence
Acceptance Criteria
✓
New user with 1 logged cycle sees Tier 1 predictions with "estimate" disclaimer
✓
User with 6+ cycles automatically receives Tier 2 predictions (verify predictions.tier = 'ARIMA')
✓
Confidence percentage displayed and updates after each new period log
Requirements
Trust/privacy screens explaining data handling before any personal input
Goal selection: period tracking / trying to conceive / perimenopause
Cycle history entry: typical cycle length, typical period length, last period date
Onboarding data used to seed Tier 1 predictions immediately
Acceptance Criteria
✓
New user completes onboarding and lands on dashboard with Tier 1 predictions pre-populated
✓
Onboarding can be skipped; user still reaches dashboard (predictions show "add your first period")
Requirements
User selects Pro plan → system calls BDApps caas/direct/debit
On success: subscription record created, Pro features unlocked immediately
Daily cron (02:00 UTC) calls caas/get/balance for all Pro subscribers
On balance check failure: increment consecutive_failures counter
After N consecutive failures (configurable): start grace period countdown
At grace period expiry: disable Pro, send email warning to user
Transient BDApps errors tolerated — only N consecutive failures trigger lapse
Acceptance Criteria
✓
Pro upgrade via CAAS charges mobile balance and gates features in < 1 request
✓
Daily CAAS cron runs at 02:00 UTC; subscriptions table updated (verify logs)
✓
Grace period activates on simulated N consecutive failures; Pro revoked at expiry
✓
BDApps CAAS charge verified with a real transaction in production
Requirements
Schema-only in v1 — gating logic deferred to post-v1; DB carries the field so it can be enabled later without migration
subscription_status column exists on the user/profile table with values free | pro | lapsed and default free
v1: all features available to all authenticated users — no middleware gating, no Pro-only endpoints, no upgrade prompts, no 403s based on subscription
CAAS billing flow updates subscription_status in DB so the field stays accurate for future gating
Acceptance Criteria
✓
Migration creates subscription_status column with default free
✓
No route returns 403 based on subscription in v1 — verified by smoke test against Pro and free accounts
✓
Field is queryable; future middleware can read it without a schema change
Requirements
All UI strings available in both English and Bengali from day one
Noto Sans Bengali font loaded for Bengali mode
Language preference persisted per user account
Bengali text is 20–40% longer than English — all components tested for overflow
Acceptance Criteria
✓
Every page renders without broken layout in Bengali at 375px viewport
✓
Language toggle works on all screens; preference survives page reload
Requirements
Sensitive health fields (symptom_logs.value, health notes) encrypted at rest using AES-256-GCM
Encryption handled transparently by prisma-field-encryption
Decryption only occurs in application layer; raw DB contains cipher text
Acceptance Criteria
✓
Direct SQL query of symptom_logs shows cipher text, not plain text
✓
Application API returns correct decrypted value to the authenticated user
✓
CI integration test: encrypt → store → retrieve → decrypt passes
Requirements
JWT issued on successful OTP verification; stored as HttpOnly cookie
Logout adds the JWT’s jti to an Upstash Redis blacklist until token expiry
Auth middleware rejects any token whose jti is on the blacklist
Rate-limit counters for OTP request and login attempts stored in Redis
Note: Robi/BDApps upstream rate limiting may trigger before (and override) app-level limits; users may see upstream throttling even below our thresholds
Acceptance Criteria
✓
Logged-out user’s old token returns 401 even before its exp claim
✓
Blacklist entry TTL matches remaining JWT lifetime; no permanent growth
✓
>3 OTP requests/min from same MSISDN returns 429
Requirements
Three notification types: period start alert, fertile-window opening, daily logging reminder
Daily cron /api/cron/daily-reminders evaluates each active profile and dispatches email jobs to QStash
QStash worker calls Resend with React Email templates in the user’s preferred language (EN or BN)
Per-profile toggles in notification_preferences let users enable/disable each type independently
Failed Resend deliveries are retried by QStash; permanent failures logged for admin review
Acceptance Criteria
✓
Period alert email arrives in inbox within 1 hour of cron run on the predicted day
✓
Toggling a notification preference off prevents future emails of that type for that profile
✓
BN-preference user receives Bengali-translated subject and body
✓
Daily volume stays within Resend free-tier limits (3K/mo, 100/day) at v1 scale
Requirements
Health articles published with both English and Bengali bodies in a single record
Articles tagged with one or more cycle phases (menstrual, follicular, ovulation, luteal) and category
Reader UI surfaces articles relevant to the user’s current phase first
Article body renders in the user’s active locale; falls back to the other language if not yet translated
Acceptance Criteria
✓
Article list filters by phase tag and renders correctly in both languages
✓
Switching language toggle updates body text without page reload
✓
Article missing a translation shows a clear locale-fallback indicator
Requirements
Single Super Admin role with full system access — user management, subscription management, content publishing, analytics
Admin dashboard shows top-line KPIs: total users, active profiles, Pro subscribers, daily logs, prediction accuracy summary
User management: list, search, view profile metadata, suspend, soft-delete, and inspect a user’s subscription state
Subscription management: view current Pro subscribers, recent CAAS results, grace-period users, manual revoke / re-grant
Admin session: separate login screen, short-lived JWT, all admin routes protected by Super Admin authentication middleware
Acceptance Criteria
✓
Non-Super-Admin user accessing any /admin route receives 403
✓
KPI dashboard reflects DB state within 1 minute of an event (signup, log, upgrade)
✓
Suspending a user immediately prevents their JWT from authenticating
✓
Manual Pro revoke updates subscription_status and stops next CAAS charge attempt
Requirements
Tiptap 3.x rich-text editor for creating and editing bilingual articles in the admin panel
Side-by-side EN/BN editing with phase tag and category selectors
Draft / publish lifecycle — only published articles surface in reader UI
Image uploads stored in Supabase storage; sanitized HTML output
Acceptance Criteria
✓
Article authored, saved as draft, then published — appears in reader feed within 1 minute
✓
Editor renders correctly for Bengali input including font and basic typography
✓
Sanitization strips <script> and other unsafe tags from saved HTML
Requirements
Anonymized population health charts: cycle length distribution, common symptoms by phase, retention curves — all rendered with Recharts 3.x
Prediction accuracy report: per-tier MAE between predicted next-period start and actual logged start, computed nightly
All analytics queries respect encryption boundaries — no plaintext export of symptom_logs.value
Admin-only; not exposed to end users
Acceptance Criteria
✓
Population charts load in < 3 s on the admin dashboard
✓
Accuracy MAE figures match a manually computed sample for at least one tier
✓
No analytics endpoint returns row-level user identifiers or decrypted symptom values
Requirements
Authenticated user can export their own profiles’ period and symptom logs as CSV or Excel (XLSX)
Export covers per-profile data only; cross-profile export is opt-in and clearly labeled
Sensitive fields are decrypted in-app before write — the file the user downloads contains plaintext
Generation under 10s; for large datasets, fall back to async QStash job + email link (post-v1 if needed)
Acceptance Criteria
✓
CSV download opens cleanly in Excel and Google Sheets with correct column headers
✓
User cannot trigger export of another user’s data even by manipulating the request
✓
Exported file contains decrypted symptom values, not cipher text
4
External Interface Requirements
All third-party API contracts Vesta v1 depends on. Includes endpoints, parameters, response fields, and error handling policies.
4.1 BDApps OTP API — User Verification
Base URL: https://developer.bdapps.com | Auth: applicationId + password in request body | All requests: POST, Content-Type: application/json
Sends an OTP to the subscriber's mobile number. Returns a referenceNo used in the verify step.
Request Parameters
applicationIdrequiredBDApps application identifier from env
passwordrequiredBDApps application password from env
subscriberIdrequiredPhone in format tel:8801XXXXXXXXX
applicationHashoptionalApp hash for Android SMS auto-fill
Success Response
statusCodeS1000 = success
referenceNoToken to pass to otp/verify
// Request body
{
"applicationId": "APP_123456",
"password": "secret",
"subscriberId": "tel:8801712345678"
}
// Success response
{
"statusCode": "S1000",
"statusDetail": "Success",
"referenceNo": "REF_ABC123XYZ"
}
Verifies the OTP entered by the user. On success, returns subscriptionStatus and subscriberId. Account creation proceeds only on success.
Request Parameters
applicationIdrequiredBDApps application identifier
passwordrequiredBDApps application password
referenceNorequiredToken returned by otp/request
otprequiredCode entered by the user
Success Response
statusCodeS1000 = verified
subscriptionStatusSUBSCRIBED / UNSUBSCRIBED
subscriberIdConfirmed MSISDN
// Request body
{
"applicationId": "APP_123456",
"password": "secret",
"referenceNo": "REF_ABC123XYZ",
"otp": "4821"
}
// Success response
{
"statusCode": "S1000",
"subscriptionStatus": "SUBSCRIBED",
"subscriberId": "tel:8801712345678"
}
OTP Sign-up Flow
User enters phone
→
POST otp/request
→
referenceNo returned
→
User enters OTP
→
POST otp/verify
→
Account created + JWT
BDApps error
→
Exponential backoff retry
→
Show user-friendly error after 3 attempts
60s resend cooldown
4.2 BDApps CAAS API — Subscription Billing
Base URL: https://developer.bdapps.com | Currency: BDT only | paymentInstrumentName must be pre-registered in BDApps dashboard
Charges the subscriber's mobile balance directly. Called when user selects a Pro plan. Use a unique externalTrxId per transaction for idempotency.
Request Parameters
applicationIdrequiredBDApps application identifier
passwordrequiredBDApps application password
externalTrxIdrequiredUnique transaction ID (UUID recommended)
subscriberIdrequiredUser MSISDN (tel:8801XXXXXXXXX)
paymentInstrumentNamerequiredPre-registered billing instrument name
amountrequiredCharge amount in BDT (integer)
Success Response
externalTrxIdEcho of the input transaction ID
internalTrxIdBDApps internal transaction reference
referenceIdAdditional BDApps reference
timeStampTransaction timestamp
Queries subscriber's chargeable balance and account status. Called by the daily CAAS cron job for all Pro subscribers. Used to detect lapses.
Request Parameters
applicationIdrequiredBDApps application identifier
passwordrequiredBDApps application password
subscriberIdrequiredUser MSISDN
paymentInstrumentNamerequiredBilling instrument name
Success Response
accountTypeSubscriber account type
accountStatusACTIVE / SUSPENDED
chargeableBalanceAvailable balance in BDT
Pro Subscription Lifecycle
1
User selects Pro plan
Frontend calls backend → backend calls caas/direct/debit → on success, subscriptions record created with status ACTIVE. Pro features unlocked immediately.
2
Daily balance check (02:00 UTC cron)
Vercel Cron calls /api/cron/caas-check. For each Pro subscriber: calls caas/get/balance. On success: resets consecutive_failures to 0. On failure: increments consecutive_failures.
3
Lapse detection
When consecutive_failures reaches the configured threshold N: set grace_start = now(). Pro access still active during grace period.
4
Grace period warning
Email sent to user warning that Pro access will expire at grace_start + N days if billing is not resolved.
5
Pro access revoked
At grace period expiry: subscriptions.status set to LAPSED. Feature gating middleware blocks Pro features. User sees upgrade prompt.
4.3 Resend Email API
| Template | Trigger | Recipient | Languages |
| Period Alert | Daily cron: predicted period starts in 2 days | Profile owner | EN + BN |
| Fertile Window | Daily cron: fertile window starts today | Profile owner | EN + BN |
| Logging Reminder | Daily cron: no log in past 3 days | Profile owner | EN + BN |
Free tier: 3,000 emails/month, 100/day. Notification preferences are per-profile and respect opt-out flags.
5
Non-Functional Requirements
Quality attributes the system must meet. Each NFR includes a measurable metric.
| ID | Category | Requirement | Metric / Verification |
| NFR-SEC-01 |
Security |
Sensitive health fields encrypted at rest using AES-256-GCM |
Direct SQL query returns cipher text for symptom_logs.value |
| NFR-SEC-04 |
Security |
Zero third-party analytics scripts touching health event data |
Network panel audit: no health event data sent to third-party domains |
| NFR-PERF-01 |
Performance |
Period log → prediction recalc → calendar updated in under 10 seconds |
E2E test: log period → measure time until calendar shows updated predictions |
| NFR-PERF-02 |
Performance |
All synchronous API routes complete within 10 seconds |
Vercel function logs show p99 < 10s; prediction recalc is async via QStash |
| NFR-I18N-01 |
i18n |
All UI strings translated in EN and BN from initial launch |
i18next translation audit: zero missing keys in BN namespace |
| NFR-I18N-02 |
i18n |
Bengali layout does not overflow at 375px viewport |
Visual regression test across all 21 screens in BN mode at 375px |
| NFR-RESP-01 |
Responsive |
Fully responsive at 375px, 768px, and 1280px breakpoints |
Manual test: no broken layouts at all 3 breakpoints in both languages |
| NFR-AVAIL-01 |
Availability |
Supabase free-tier database does not pause due to inactivity |
Keep-alive cron pings DB daily; verify DB accessible after 10+ days of low traffic |
| NFR-BILL-01 |
Billing |
Pro features gated within 1 API request of successful CAAS charge (deferred — post-v1) |
Deferred — post-v1. Schema is in place (FR-GATE); middleware enforcement is not part of v1 scope. |
| NFR-BILL-02 |
Billing |
Grace period length configurable without a code deploy |
DB config; change grace period, verify new value takes effect immediately |
Free-tier production stack targeting $0/month for the experimental launch phase, with defined upgrade triggers.
6.1 Free-Tier Service Stack
| Service | Provider | Free Limits | Upgrade Trigger |
| Frontend + API | Vercel Hobby | 100 GB bandwidth, 10s timeout, 1M invocations/mo | Team seats needed OR >100 GB BW OR functions >10s |
| Database | Supabase Free | 500 MB, pauses after 7 days inactivity | >500 MB data OR pause avoidance becomes critical |
| Sessions / Cache | Upstash Redis | 10,000 commands/day, 256 MB | >10,000 commands/day |
| Cron Jobs | Vercel Cron | 2 jobs, daily minimum frequency | Need >2 crons OR sub-daily scheduling |
| Job Queue | Upstash QStash | 500 messages/day | >500 async jobs/day |
| Transactional Email | Resend Free | 3,000 emails/month, 100/day | >3,000 emails/month |
6.2 Infrastructure Diagram
VERCEL (Hobby — $0/mo)
├── Next.js 16 (React 19) app
├── Route Handlers (API) via Next.js
├── Cron: /api/cron/daily-reminders (0 8 * * *)
└── Cron: /api/cron/caas-check (0 2 * * *)
SUPABASE (Free — $0/mo)
├── PostgreSQL 16
├── RLS policies (user_id isolation on period_logs, symptom_logs, profiles)
└── pgcrypto extension
UPSTASH (Free — $0/mo)
├── Redis — JWT blacklist, session store, rate-limit state
└── QStash — prediction recalc jobs, email delivery jobs
RESEND (Free — $0/mo)
└── Transactional email (period alerts, fertile window, logging reminders)
BDAPPS (Usage-based)
├── OTP API — /otp/request, /otp/verify
└── CAAS API — /caas/direct/debit, /caas/get/balance
6.3 Backend Deployment Model
Backend endpoints are implemented as Next.js route handlers and deployed on Vercel. Shared middleware concerns (JWT validation, RLS header injection, rate limiting) are enforced via Next.js middleware and route-level guards. BullMQ is replaced by: Vercel Cron for scheduled jobs and Upstash QStash for async jobs triggered on-demand. The 10-second Hobby timeout is sufficient for all synchronous API routes; prediction recalculation is deliberately offloaded to QStash.
6.4 Cron Job Schedule
| Path | Schedule | Purpose | Auth |
/api/cron/daily-reminders | 0 8 * * * (08:00 UTC daily) | Check notification preferences; dispatch email jobs to QStash for period alerts, fertile window, logging reminders | CRON_SECRET header |
/api/cron/caas-check | 0 2 * * * (02:00 UTC daily) | Query balance for all Pro subscribers; update consecutive_failures; trigger grace period or revoke Pro on lapse | CRON_SECRET header |
6.5 Upgrade Path
Trigger: Supabase pausing
Supabase Pro
+$25/month
8 GB database, daily backups, no auto-pause. Zero migration — same connection strings.
Trigger: Team access or functions >10s
Vercel Pro
+$20/month
60s timeout, 1 TB bandwidth, team seats, more cron jobs. Frontend stays on same URLs.
Trigger: Redis or QStash over limits
Upstash Pay-as-you-go
<$1/month at early scale
No migration required — same Redis/QStash endpoints, billing switches to PAYG.
Trigger: Persistent workers needed
Railway Worker Process
+$5/month
Add worker process only. Frontend + DB stay on Vercel + Supabase unchanged.
7
Acceptance Criteria — Definition of Done
All 16 gates must pass before v1 is considered production-ready. These are the client sign-off criteria.
Authentication & Accounts
□
Phone sign-up + BDApps OTP verified end-to-end in production with a real Bangladeshi number
□
Multi-profile create / switch / delete works with complete data isolation between profiles
Core Tracking
□
Period logging → prediction recalc → calendar update completes in under 3 seconds
□
Symptom logging: all 30+ symptoms across all 4 categories save and display correctly
Localisation
□
All UI strings translated in both EN and BN; Bengali layout unbroken at 375px on every screen
□
Email notifications delivered in the correct language per user preference
Content
□
Bilingual health articles render correctly in both languages with phase tags
Billing — BDApps CAAS
□
Pro upgrade via CAAS charges mobile balance and updates subscription_status to pro in DB (gating deferred — post-v1)
□
Daily CAAS cron runs; grace period activates on lapse; Pro revoked at grace expiry
□
BDApps CAAS charge verified with a real transaction in production environment
Admin Panel
□
Admin dashboard: KPIs, user management, and CMS are all operational
□
Super Admin authentication enforced on all admin routes
Security
□
Field encryption verified: sensitive symptom data is cipher text in the database
□
Zero third-party analytics scripts touching health event data
Production & Responsiveness
□
Responsive: 375px / 768px / 1280px — no broken layouts in either language
□
Production deploy: Vercel + Supabase live, stable, and all env vars set