Address 21 previously undefined behaviors across specs: - Authentication: Replace email/password with OIDC (Pocket-ID) - Cycle tracking: Add fixed-luteal phase scaling formula with examples - Calendar: Document period logging behavior (preserve predictions) - Garmin: Clarify connection is required (no phase-only mode) - Dashboard: Add UI states, dark mode, onboarding, accessibility - Notifications: Document timezone batching approach - New specs: observability.md (health, metrics, logging) - New specs: testing.md (unit + integration strategy) - Main spec: Add backup/recovery, known limitations, API updates Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
130 lines
3.4 KiB
Markdown
130 lines
3.4 KiB
Markdown
# Notifications Specification
|
|
|
|
## Job to Be Done
|
|
|
|
When I wake up each morning, I want to receive an email with my training decision, so that I don't need to open the app to know if I should train.
|
|
|
|
## Email Notifications
|
|
|
|
### Daily Training Email
|
|
|
|
Sent at user's preferred `notificationTime` (default: 07:00).
|
|
|
|
**Subject:**
|
|
```
|
|
PhaseFlow: [STATUS] - Day [cycleDay] ([phase])
|
|
```
|
|
|
|
Example: `PhaseFlow: ✅ TRAIN - Day 12 (FOLLICULAR)`
|
|
|
|
**Body:**
|
|
```
|
|
Good morning!
|
|
|
|
Today's Decision: [STATUS]
|
|
Reason: [reason]
|
|
|
|
Current Metrics:
|
|
- Cycle Day: [cycleDay] ([phase])
|
|
- Body Battery: [bbCurrent]
|
|
- HRV: [hrvStatus]
|
|
- Week Intensity: [weekIntensity]/[phaseLimit] min
|
|
|
|
Nutrition Today:
|
|
- Seeds: [seeds]
|
|
- Carbs: [carbRange]
|
|
- Keto: [ketoGuidance]
|
|
|
|
[Optional: Seed switch alert if day 15]
|
|
[Optional: Token expiration warning]
|
|
|
|
---
|
|
PhaseFlow - Training with your cycle, not against it
|
|
```
|
|
|
|
### Token Expiration Warnings
|
|
|
|
**14 Days Before:**
|
|
Subject: `⚠️ PhaseFlow: Garmin tokens expire in 14 days`
|
|
|
|
**7 Days Before:**
|
|
Subject: `🚨 PhaseFlow: Garmin tokens expire in 7 days - action required`
|
|
|
|
## Email Provider
|
|
|
|
Using Resend via `resend` npm package.
|
|
|
|
**Configuration:**
|
|
- `RESEND_API_KEY` - API key for Resend
|
|
- `EMAIL_FROM` - Sender address (e.g., `PhaseFlow <noreply@phaseflow.app>`)
|
|
|
|
## Email Utilities (`src/lib/email.ts`)
|
|
|
|
**Functions:**
|
|
- `sendDailyNotification(user, decision, dailyData)` - Send morning email
|
|
- `sendTokenExpirationWarning(user, daysUntilExpiry)` - Send warning email
|
|
|
|
## Cron Jobs
|
|
|
|
### `/api/cron/notifications`
|
|
|
|
Protected by `CRON_SECRET` header.
|
|
|
|
**Trigger:** Daily at notification times
|
|
|
|
**Process:**
|
|
1. Find all users with `notificationTime` matching current hour
|
|
2. For each user:
|
|
- Fetch current decision from decision engine
|
|
- Send email via Resend
|
|
- Update `DailyLog.notificationSentAt`
|
|
|
|
### Timezone Handling
|
|
|
|
Users are batched into hourly timezone windows for efficient scheduling.
|
|
|
|
**Implementation:**
|
|
1. Cron job runs 24 times daily (once per hour, on the hour)
|
|
2. Each run queries users whose `notificationTime` hour matches the current UTC hour adjusted for their timezone
|
|
3. Users are processed in batches within their timezone window
|
|
|
|
**Example:**
|
|
- User A: timezone `America/New_York`, notificationTime `07:00`
|
|
- User B: timezone `America/Los_Angeles`, notificationTime `07:00`
|
|
- User C: timezone `Europe/London`, notificationTime `07:00`
|
|
|
|
When cron runs at 12:00 UTC:
|
|
- User A receives notification (12:00 UTC = 07:00 EST)
|
|
- User B skipped (12:00 UTC = 04:00 PST)
|
|
- User C skipped (12:00 UTC = 12:00 GMT)
|
|
|
|
**Query Logic:**
|
|
```sql
|
|
SELECT * FROM users
|
|
WHERE EXTRACT(HOUR FROM NOW() AT TIME ZONE timezone) = CAST(SUBSTRING(notificationTime, 1, 2) AS INT)
|
|
```
|
|
|
|
## Rate Limiting
|
|
|
|
- Max 1 notification per user per day
|
|
- Check `DailyLog.notificationSentAt` before sending
|
|
- Only send if null or different date
|
|
|
|
## Success Criteria
|
|
|
|
1. Email arrives within 5 minutes of scheduled time
|
|
2. Email contains all relevant metrics and guidance
|
|
3. Token warnings sent at 14 and 7 day thresholds
|
|
4. No duplicate notifications on same day
|
|
|
|
## Acceptance Tests
|
|
|
|
- [ ] Daily email contains decision status and reason
|
|
- [ ] Daily email includes nutrition guidance
|
|
- [ ] Seed switch alert included on day 15
|
|
- [ ] Token warning email sent at 14-day threshold
|
|
- [ ] Token warning email sent at 7-day threshold
|
|
- [ ] Duplicate notifications prevented
|
|
- [ ] Timezone conversion correct for notification time
|
|
- [ ] CRON_SECRET required for endpoint access
|