forked from Mike/freno-dev
nook: capture trial email and run the two lifecycle emails
Trials were anonymous — a 14-day trial you cannot talk to. trials gains an email column (idempotent ALTER), POST /api/the-nook/trial-email attaches the optional first-launch prompt's answer, and the address lands in a Brevo list when NOOK_TRIAL_LIST_ID is set. The lifecycle emails follow: a D+11 trial expiry reminder with a buy link, a purchase thank-you from the Stripe webhook, and scheduled testimonial (D+3) and review (D+14) asks via the daily /api/the-nook/lifecycle cron. Rows are marked before sending so retries never double-send; trial addresses that already bought get no buy reminder; the endpoint requires a Bearer secret (CRON_SECRET / NOOK_LIFECYCLE_SECRET) and fails closed when unconfigured — an open trigger would email real users. All sends are best-effort and logged, never thrown. Tests: trial-email (3) and lifecycle (6) suites; full run 539 pass. Issues: FRE-10, FRE-11
This commit is contained in:
9
src/env/server.ts
vendored
9
src/env/server.ts
vendored
@@ -73,7 +73,14 @@ const serverEnvSchema = z.object({
|
||||
NOOK_LICENSE_PRIVATE_KEY: z.string().min(1),
|
||||
NOOK_STRIPE_SK: z.string().min(1),
|
||||
NOOK_STRIPE_WEBHOOK_SECRET: z.string().min(1),
|
||||
NOOK_STRIPE_PRICE_ID: z.string().min(1)
|
||||
NOOK_STRIPE_PRICE_ID: z.string().min(1),
|
||||
// Brevo list the trial-start emails land in (the lifecycle mail's
|
||||
// audience). Optional: capture still stores the address when unset.
|
||||
NOOK_TRIAL_LIST_ID: z.coerce.number().int().positive().optional(),
|
||||
// Bearer secret for the daily lifecycle-email cron. Falls back to
|
||||
// Vercel's CRON_SECRET; unset + no CRON_SECRET = the endpoint denies all.
|
||||
NOOK_LIFECYCLE_SECRET: z.string().min(1).optional(),
|
||||
CRON_SECRET: z.string().min(1).optional()
|
||||
});
|
||||
|
||||
export type ServerEnv = z.infer<typeof serverEnvSchema>;
|
||||
|
||||
Reference in New Issue
Block a user