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
Popup Settings
| Setting | Description | Default |
|---|---|---|
exitPopupEnabled | Enable exit-intent popup | false |
exitPopupTitle | Popup headline text | "Before You Go!" |
exitPopupDescription | Popup description | "Join our newsletter..." |
exitPopupButtonText | Subscribe button text | "Subscribe" |
exitPopupCooldown | Days before reshowing | 7 |
onPagePopupEnabled | Enable time-based popup | false |
onPagePopupDelay | Delay in milliseconds | 30000 |
Newsletter Settings
| Setting | Description | Default |
|---|---|---|
newsletterEnabled | Enable newsletter system | true |
showSubscriberCount | Show public subscriber count | false |
allowUnsubscribe | Allow users to unsubscribe | true |
๐ 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
- Navigate to
/admin/newsletter - Go to the "Settings" tab
- Enable desired popup types
- Customize text and timing
- 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
๐ช Cookie-Based Tracking
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
}Cookie Functions
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 formexit-popup- Exit-intent popuplanding-page- Landing page formadmin_import- Bulk imported by admin- Custom sources via
sourceprop
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
classNameprop - 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 settingsgetNewsletterStatsAdmin- Get subscription statistics (admin)getAllNewsletterSubscriptionsAdmin- Get all subscriptions (admin)searchNewsletterSubscriptionsAdmin- Search subscriptions (admin)
Public Mutations:
subscribeToNewsletter- Subscribe to newsletterunsubscribeFromNewsletter- Unsubscribe from newsletterupdateNewsletterSettings- 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.