Initial project setup for PhaseFlow
Set up Next.js 16 project with TypeScript for a training decision app that integrates menstrual cycle phases with Garmin biometrics for Hashimoto's thyroiditis management. Stack: Next.js 16, React 19, Tailwind/shadcn, PocketBase, Drizzle, Zod, Resend, Vitest, Biome, Lefthook, Nix dev environment. Includes: - 7 page routes (dashboard, login, settings, calendar, history, plan) - 12 API endpoints (garmin, user, cycle, calendar, overrides, cron) - Core lib utilities (decision engine, cycle phases, nutrition, ICS) - Type definitions and component scaffolding - Python script for Garmin token bootstrapping - Initial unit tests for cycle utilities 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
83
src/lib/email.ts
Normal file
83
src/lib/email.ts
Normal file
@@ -0,0 +1,83 @@
|
||||
// ABOUTME: Email sending utilities using Resend.
|
||||
// ABOUTME: Sends daily training notifications and period confirmation emails.
|
||||
import { Resend } from "resend";
|
||||
|
||||
const resend = new Resend(process.env.RESEND_API_KEY);
|
||||
|
||||
const EMAIL_FROM = process.env.EMAIL_FROM || "phaseflow@example.com";
|
||||
|
||||
export interface DailyEmailData {
|
||||
to: string;
|
||||
cycleDay: number;
|
||||
phase: string;
|
||||
decision: {
|
||||
status: string;
|
||||
reason: string;
|
||||
icon: string;
|
||||
};
|
||||
bodyBatteryCurrent: number | null;
|
||||
bodyBatteryYesterdayLow: number | null;
|
||||
hrvStatus: string;
|
||||
weekIntensity: number;
|
||||
phaseLimit: number;
|
||||
remainingMinutes: number;
|
||||
seeds: string;
|
||||
carbRange: string;
|
||||
ketoGuidance: string;
|
||||
}
|
||||
|
||||
export async function sendDailyEmail(data: DailyEmailData): Promise<void> {
|
||||
const subject = `Today's Training: ${data.decision.icon} ${data.decision.status}`;
|
||||
|
||||
const body = `Good morning!
|
||||
|
||||
📅 CYCLE DAY: ${data.cycleDay} (${data.phase})
|
||||
|
||||
💪 TODAY'S PLAN:
|
||||
${data.decision.icon} ${data.decision.reason}
|
||||
|
||||
📊 YOUR DATA:
|
||||
• Body Battery Now: ${data.bodyBatteryCurrent ?? "N/A"}
|
||||
• Yesterday's Low: ${data.bodyBatteryYesterdayLow ?? "N/A"}
|
||||
• HRV Status: ${data.hrvStatus}
|
||||
• Week Intensity: ${data.weekIntensity} / ${data.phaseLimit} minutes
|
||||
• Remaining: ${data.remainingMinutes} minutes
|
||||
|
||||
🌱 SEEDS: ${data.seeds}
|
||||
|
||||
🍽️ MACROS: ${data.carbRange}
|
||||
🥑 KETO: ${data.ketoGuidance}
|
||||
|
||||
---
|
||||
Auto-generated by PhaseFlow`;
|
||||
|
||||
await resend.emails.send({
|
||||
from: EMAIL_FROM,
|
||||
to: data.to,
|
||||
subject,
|
||||
text: body,
|
||||
});
|
||||
}
|
||||
|
||||
export async function sendPeriodConfirmationEmail(
|
||||
to: string,
|
||||
lastPeriodDate: Date,
|
||||
cycleLength: number,
|
||||
): Promise<void> {
|
||||
const subject = "🔵 Period Tracking Updated";
|
||||
|
||||
const body = `Your cycle has been reset. Last period: ${lastPeriodDate.toLocaleDateString()}
|
||||
Phase calendar updated for next ${cycleLength} days.
|
||||
|
||||
Your calendar will update automatically within 24 hours.
|
||||
|
||||
---
|
||||
Auto-generated by PhaseFlow`;
|
||||
|
||||
await resend.emails.send({
|
||||
from: EMAIL_FROM,
|
||||
to,
|
||||
subject,
|
||||
text: body,
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user