Setup Ralph.
This commit is contained in:
110
specs/notifications.md
Normal file
110
specs/notifications.md
Normal file
@@ -0,0 +1,110 @@
|
||||
# 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 store preferred timezone (e.g., `America/New_York`).
|
||||
|
||||
Cron runs every hour. Check if current hour in user's timezone matches their `notificationTime`.
|
||||
|
||||
## 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
|
||||
Reference in New Issue
Block a user