[object Object]

← back to Govarbitrage

Subscriber model: double-opt-in newsletter (PENDING/CONFIRMED/UNSUBSCRIBED, unique tokens)

562ea5d2a8a8c7357cb415814eb5a388781d066d · 2026-07-13 01:19:22 -0700 · Steve Abrams

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

Files touched

Diff

commit 562ea5d2a8a8c7357cb415814eb5a388781d066d
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Mon Jul 13 01:19:22 2026 -0700

    Subscriber model: double-opt-in newsletter (PENDING/CONFIRMED/UNSUBSCRIBED, unique tokens)
    
    Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---
 prisma/schema.prisma | 25 +++++++++++++++++++++++++
 1 file changed, 25 insertions(+)

diff --git a/prisma/schema.prisma b/prisma/schema.prisma
index da4a4a2..870ab84 100644
--- a/prisma/schema.prisma
+++ b/prisma/schema.prisma
@@ -110,6 +110,15 @@ enum Tier {
   PREMIUM
 }
 
+// Double-opt-in newsletter lifecycle. PENDING until the confirm link is
+// clicked; UNSUBSCRIBED rows are kept (suppression list) so a re-subscribe
+// re-issues a fresh confirm rather than silently re-adding.
+enum SubscriberStatus {
+  PENDING
+  CONFIRMED
+  UNSUBSCRIBED
+}
+
 enum EventType {
   IMPORTED
   RESEARCH_STARTED
@@ -410,6 +419,22 @@ model User {
   auditLogs    AuditLog[]
 }
 
+// Public newsletter subscriber for the twice-daily Top-10 digest.
+// email is stored lowercase; tokens are single-purpose URL-safe secrets.
+model Subscriber {
+  id               String           @id @default(cuid())
+  email            String           @unique
+  status           SubscriberStatus @default(PENDING)
+  confirmToken     String           @unique
+  unsubscribeToken String           @unique
+  source           String           @default("newsletter_page")
+  createdAt        DateTime         @default(now())
+  confirmedAt      DateTime?
+  unsubscribedAt   DateTime?
+
+  @@index([status])
+}
+
 model ApiCredential {
   id        String   @id @default(cuid())
   userId    String

← 687fd82 Reprice tiers to research-backed audience-building points: S  ·  back to Govarbitrage  ·  Newsletter API: subscribe (honeypot+rate-limit, enumeration- d242730 →