S7 (a1): newsletter — THE VYNDR REPORT
Email capture + daily report assembly + operator-triggered Listmonk send.
- NewsletterCapture (dark terminal, mono) on landing (below FAQ) + /welcome
(the real signup success surface); double-opt-in note; 'Signups open soon'
when Listmonk env is unset.
- POST /api/newsletter/subscribe: public, 10/min IP limit, honeypot,
server-side email validation, forwards to Listmonk subscribers API with
preconfirm_subscriptions:false (Listmonk sends the confirmation).
No env -> calm 200 { ok:false, reason:'not configured' }. Next proxy
web/src/app/api/newsletter/subscribe/route.ts (S25 rule).
- newsletterService.buildDailyReport: signals from snapshot:{sport}:latest,
STREAK WATCH via rosterLogs -> streaksService -> streakLens, THE RECORD via
ledgerService.getModelAggregate (percentage only when hit_pct != null —
n>=20 gate — else 'RECORD BUILDING · N pending'). RG footer (21+,
1-800-GAMBLER, Listmonk-native {{ UnsubscribeURL }}) in html + text.
VOICE v1.1 lint locked by tests: no '!', no banned vocabulary, numbers
only from injected pipeline data.
- sendDailyReport: creates + starts a Listmonk campaign; env-gated no-op;
refuses an empty report. Deliberately UNSCHEDULED — only
POST /api/internal/newsletter/send (internal key) triggers it.
- docs/NEWSLETTER.md: box-side Listmonk runbook (install, double-opt-in
list, API user, Coolify env, test-send).
- Spec: specs/feature-a1-s7-newsletter.md.
Tests 2398 -> 2429 (207 suites, all green); next build exit 0.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -3,6 +3,10 @@
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { useAuth } from '@/contexts/AuthContext';
|
||||
import { useEffect, useRef } from 'react';
|
||||
// Session S7 (a1) — THE VYNDR REPORT capture on the free-signup success path.
|
||||
// This page (not the signup "done" card, which auto-redirects after 1.5s) is
|
||||
// where a new free user actually lingers — the honest place for the ask.
|
||||
import NewsletterCapture from '@/components/NewsletterCapture';
|
||||
|
||||
export default function WelcomePage() {
|
||||
const router = useRouter();
|
||||
@@ -84,6 +88,11 @@ export default function WelcomePage() {
|
||||
</button>
|
||||
<p className="lbl" style={{ color: 'var(--text-2)' }}>BUILT IN DETROIT</p>
|
||||
</div>
|
||||
|
||||
{/* Session S7 (a1) — the daily wire, by email. Double opt-in via Listmonk. */}
|
||||
<div style={{ marginTop: 32, width: '100%', maxWidth: 440, textAlign: 'left', zIndex: 1 }}>
|
||||
<NewsletterCapture compact />
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user