Files
freno-dev/vercel.json
Michael Freno f66fd43f62 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
2026-10-04 23:22:24 -04:00

63 lines
1.8 KiB
JSON

{
"rewrites": [
{
"source": "/api/(.*)",
"has": [{ "type": "host", "value": "gaze.freno.me" }],
"destination": "/api/$1"
},
{
"source": "/api/(.*)",
"has": [{ "type": "host", "value": "inputhalo.freno.me" }],
"destination": "/api/$1"
},
{
"source": "/api/(.*)",
"has": [{ "type": "host", "value": "nessa.freno.me" }],
"destination": "/api/$1"
},
{
"source": "/api/(.*)",
"has": [{ "type": "host", "value": "lineage.freno.me" }],
"destination": "/api/$1"
},
{ "source": "/(.*)", "has": [{ "type": "host", "value": "nessa.freno.me" }], "destination": "/nessa/$1" },
{ "source": "/(.*)", "has": [{ "type": "host", "value": "lineage.freno.me" }], "destination": "/lineage/$1" },
{ "source": "/(.*)", "has": [{ "type": "host", "value": "gaze.freno.me" }], "destination": "/gaze/$1" },
{ "source": "/(.*)", "has": [{ "type": "host", "value": "inputhalo.freno.me" }], "destination": "/inputhalo/$1" },
{ "source": "/api/(.*)", "has": [{ "type": "host", "value": "nook.freno.me" }], "destination": "/api/$1" },
{ "source": "/(.*)", "has": [{ "type": "host", "value": "nook.freno.me" }], "destination": "/nook/$1" }
],
"headers": [
{
"source": "/",
"headers": [
{
"key": "Cache-Control",
"value": "public, max-age=0, must-revalidate"
}
]
},
{
"source": "/(.*)",
"headers": [
{
"key": "Cache-Control",
"value": "public, max-age=0, must-revalidate"
}
]
},
{
"source": "/_build/(.*)",
"headers": [
{
"key": "Cache-Control",
"value": "public, max-age=31536000, immutable"
}
]
}
],
"crons": [
{ "path": "/api/the-nook/lifecycle", "schedule": "0 13 * * *" }
]
}