Files
phaseflow/specs/notifications.md
Petru Paler 6a8d55c0b9 Document spec gaps: auth, phase scaling, observability, testing
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>
2026-01-11 07:49:56 +00:00

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