TinyKit Docs

Newsletter System

TinyKit SaaS includes a comprehensive newsletter subscription system with exit-intent popups, subscription management, and cookie-based tracking to avoid showing popups to existing subscribers.

๐ŸŽฏ Features

Exit-Intent & Timed Popups

  • Exit-Intent Detection: Shows newsletter popup when users attempt to leave the page
  • Timed Popup: Optional time-based popup that appears after a specified delay
  • Smart Triggers: Separate logic for exit-intent (immediate) vs. on-page (delayed) popups
  • Cookie-Based Tracking: Prevents showing popups to users who already subscribed

Admin Management

  • Subscription Management: View, export, and manage all newsletter subscribers
  • Settings Control: Enable/disable popups, customize text, and configure timing
  • Analytics: Track subscription sources, growth rates, and engagement
  • Bulk Operations: Import subscribers and manage subscription status

UI Components

  • Multiple Variants: Card, inline, and compact display options
  • Customizable Content: Custom titles, descriptions, and button text
  • Visual Effects: Border beams, confetti animations, loading states
  • Responsive Design: Works seamlessly across all device sizes

๐Ÿ—๏ธ Architecture

Database Schema

Important: Contact storage uses Resend Audiences API - there is no local contact table. The database stores only broadcast campaigns and settings.

// Newsletter broadcast campaigns (convex/mailingList/schema.ts)
broadcasts: defineTable({
  // Email content
  subject: v.string(),
  previewText: v.string(),
  content: v.string(), // JSON-stringified newsletter sections

  // Delivery status
  status: broadcastStatusValidator, // "draft", "scheduled", "sending", "sent", "failed"
  scheduledFor: v.optional(v.number()),
  sentAt: v.optional(v.number()),
  sentBy: v.string(), // Better Auth user ID

  // Resend API integration
  resendBroadcastId: v.optional(v.string()),
  audienceId: v.optional(v.string()),

  // Analytics
  recipientCount: v.optional(v.number()),
  errorMessage: v.optional(v.string()),

  // Timestamps
  createdAt: v.number(),
  updatedAt: v.number(),
})
  .index("by_status", ["status"])
  .index("by_sentBy", ["sentBy"])
  .index("by_sentAt", ["sentAt"])
  .index("by_createdAt", ["createdAt"]);

// Unified settings for newsletter and waitlist (single row table)
mailingListSettings: defineTable({
  // Newsletter feature toggles
  newsletterEnabled: v.boolean(),
  showSubscriberCount: v.boolean(),
  allowUnsubscribe: v.boolean(),

  // Exit-intent popup settings
  exitPopupEnabled: v.boolean(),
  exitPopupTitle: v.string(),
  exitPopupDescription: v.string(),
  exitPopupButtonText: v.string(),
  exitPopupCooldown: v.number(), // Days before re-showing

  // Time-delayed popup
  onPagePopupEnabled: v.optional(v.boolean()),
  onPagePopupDelay: v.optional(v.number()), // Milliseconds

  // Waitlist settings
  waitlistEnabled: v.boolean(),
  showWaitlistCount: v.boolean(),

  // Resend Audience API integration
  resendAudienceSyncEnabled: v.optional(v.boolean()),
  resendAudienceId: v.optional(v.string()),

  // Audit trail
  updatedBy: v.string(), // Better Auth user ID
  updatedAt: v.number(),
});

Component Architecture

src/components/organisms/
โ”œโ”€โ”€ NewsletterSignup.tsx          # Main signup form component
โ”œโ”€โ”€ NewsletterSignup.types.ts     # Type definitions and validation
โ””โ”€โ”€ NewsletterExitPopup.tsx       # Exit-intent and timed popup

src/app/admin/newsletter/
โ”œโ”€โ”€ page.tsx                      # Main admin interface
โ””โ”€โ”€ _components/
    โ”œโ”€โ”€ NewsletterStats.tsx       # Subscription statistics
    โ”œโ”€โ”€ NewsletterTable.tsx       # Subscriber management table
    โ”œโ”€โ”€ NewsletterSettings.tsx    # Admin settings panel
    โ””โ”€โ”€ AddSubscribersDialog.tsx  # Bulk import interface

src/lib/
โ””โ”€โ”€ newsletter-cookies.ts         # Cookie-based state management

๐Ÿ”ง Usage

Basic Newsletter Signup

import { NewsletterSignup } from "@/features/newsletter/newsletter-signup";

export function Footer() {
  return (
    <NewsletterSignup
      variant="compact"
      source="footer"
      showTitle={false}
      customDescription="Get updates on new features and releases"
    />
  );
}

Exit-Intent Popup

import { NewsletterExitPopup } from "@/features/newsletter/newsletter-exit-popup";

export function Layout({ children }) {
  return (
    <>
      {children}
      <NewsletterExitPopup />
    </>
  );
}

Admin Management

Access the newsletter admin interface at /admin/newsletter:

  • Subscribers Tab: Manage all newsletter subscriptions

    • View subscriber list with status, source, and dates
    • Search and filter subscribers
    • Bulk operations (approve, delete, export)
    • Add single or bulk subscribers
  • Settings Tab: Configure newsletter behavior

    • Enable/disable newsletter system
    • Configure popup settings and timing
    • Customize popup text and appearance
    • Set cooldown periods

โš™๏ธ Configuration

SettingDescriptionDefault
exitPopupEnabledEnable exit-intent popupfalse
exitPopupTitlePopup headline text"Before You Go!"
exitPopupDescriptionPopup description"Join our newsletter..."
exitPopupButtonTextSubscribe button text"Subscribe"
exitPopupCooldownDays before reshowing7
onPagePopupEnabledEnable time-based popupfalse
onPagePopupDelayDelay in milliseconds30000

Newsletter Settings

SettingDescriptionDefault
newsletterEnabledEnable newsletter systemtrue
showSubscriberCountShow public subscriber countfalse
allowUnsubscribeAllow users to unsubscribetrue

๐Ÿš€ Implementation Guide

1. Add Newsletter Signup to Your App

// In your landing page or footer
<NewsletterSignup
  variant="card"
  source="landing-page"
  customTitle="Stay in the Loop"
  customDescription="Get notified about new features and updates"
  showBorderEffects={true}
  showConfetti={true}
/>

2. Enable Exit-Intent Popup

// Add to your main layout
import { NewsletterExitPopup } from "@/features/newsletter/newsletter-exit-popup";

export default function RootLayout({ children }) {
  return (
    <html>
      <body>
        {children}
        <NewsletterExitPopup />
      </body>
    </html>
  );
}

3. Configure Settings

  1. Navigate to /admin/newsletter
  2. Go to the "Settings" tab
  3. Enable desired popup types
  4. Customize text and timing
  5. Save changes

4. Manage Subscribers

  • View Subscribers: See all subscriptions with status and source tracking
  • Export Data: Download subscriber list as CSV
  • Bulk Import: Add multiple subscribers at once
  • Search & Filter: Find specific subscribers quickly

The system uses a single JSON cookie (newsletter_state) to track:

interface NewsletterState {
  subscribed?: boolean; // User has subscribed
  dismissedAt?: number; // When popup was last dismissed
  shownCount?: number; // How many times popup was shown
}
import { newsletterCookies } from "@/lib/newsletter-cookies";

// Check if should show popup
const canShow = newsletterCookies.shouldShowPopup();

// Mark user as subscribed
newsletterCookies.markAsSubscribed();

// Track dismissal
newsletterCookies.markAsDismissed();

// Check subscription status
const isSubscribed = newsletterCookies.isSubscribed();

๐Ÿ“Š Analytics & Insights

Subscription Sources

Track where subscribers come from:

  • footer - Footer signup form
  • exit-popup - Exit-intent popup
  • landing-page - Landing page form
  • admin_import - Bulk imported by admin
  • Custom sources via source prop

Admin Dashboard Metrics

  • Total Subscribers: Active subscription count
  • Growth Rate: Recent subscription velocity
  • Source Breakdown: Subscription origin analysis
  • Status Distribution: Active vs. unsubscribed ratios

๐Ÿ”’ Privacy & Compliance

Data Handling

  • Minimal Data Collection: Only email addresses and metadata
  • Unsubscribe Support: One-click unsubscribe functionality
  • Cookie Consent: Respects user privacy preferences
  • Data Export: CSV export for data portability

Security Features

  • Email Validation: Server-side email format validation
  • Duplicate Prevention: Automatic duplicate email detection
  • Rate Limiting: Built-in protection against spam
  • Admin-Only Access: Settings protected by role-based access

๐ŸŽจ Customization

Styling Options

All components use semantic CSS variables and can be customized via:

  • Theme Colors: Automatically adapts to your color scheme
  • Custom Classes: Add custom styling via className prop
  • Variant Support: Multiple display modes (card, inline, compact)
  • Animation Control: Enable/disable visual effects

Text Customization

Fully customizable text content:

<NewsletterSignup
  customTitle="Join Our Community"
  customDescription="Get exclusive updates and early access"
  customButtonText="Count Me In!"
  source="custom-cta"
/>

๐Ÿงช Testing

Manual Testing Checklist

  • Newsletter signup form works
  • Exit-intent popup triggers correctly
  • Timed popup appears after delay
  • Cookie tracking prevents duplicate popups
  • Admin settings save and apply
  • Subscriber management functions work
  • CSV export contains correct data
  • Unsubscribe flow works properly

๐Ÿ“š API Reference

Convex Functions

Public Queries:

  • getNewsletterSettings - Get public newsletter settings
  • getNewsletterStatsAdmin - Get subscription statistics (admin)
  • getAllNewsletterSubscriptionsAdmin - Get all subscriptions (admin)
  • searchNewsletterSubscriptionsAdmin - Search subscriptions (admin)

Public Mutations:

  • subscribeToNewsletter - Subscribe to newsletter
  • unsubscribeFromNewsletter - Unsubscribe from newsletter
  • updateNewsletterSettings - Update settings (admin)
  • updateNewsletterStatus - Change subscription status (admin)
  • deleteNewsletterSubscription - Delete subscription (admin)
  • bulkImportNewsletterEmails - Import multiple subscribers (admin)

See the API Reference for detailed function signatures and examples.

On this page

Ship your startup faster. In minutes.

Get TinyKit SaaS