@volchoklv/newsletter-kit 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +505 -0
- package/dist/adapters/email/index.d.ts +119 -0
- package/dist/adapters/email/index.js +417 -0
- package/dist/adapters/email/index.js.map +1 -0
- package/dist/adapters/storage/index.d.ts +215 -0
- package/dist/adapters/storage/index.js +415 -0
- package/dist/adapters/storage/index.js.map +1 -0
- package/dist/components/index.d.ts +198 -0
- package/dist/components/index.js +505 -0
- package/dist/components/index.js.map +1 -0
- package/dist/index.d.ts +73 -0
- package/dist/index.js +1762 -0
- package/dist/index.js.map +1 -0
- package/dist/server/index.d.ts +77 -0
- package/dist/server/index.js +530 -0
- package/dist/server/index.js.map +1 -0
- package/dist/types-BmajlhNp.d.ts +226 -0
- package/package.json +95 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/utils/crypto.ts","../src/utils/validation.ts","../src/utils/rate-limit.ts","../src/adapters/storage/memory.ts","../src/server/handlers.ts","../src/components/newsletter-form.tsx","../src/utils/cn.ts","../src/components/newsletter-blocks.tsx","../src/hooks/use-newsletter.ts","../src/adapters/email/resend.ts","../src/adapters/email/nodemailer.ts","../src/adapters/email/mailchimp.ts","../src/adapters/storage/prisma.ts","../src/adapters/storage/supabase.ts"],"sourcesContent":["import { randomBytes } from 'crypto';\n\n/**\n * Generate a secure random token for email confirmation\n */\nexport function generateToken(length: number = 32): string {\n return randomBytes(length).toString('hex');\n}\n\n/**\n * Generate a URL-safe token\n */\nexport function generateUrlSafeToken(length: number = 32): string {\n return randomBytes(length)\n .toString('base64')\n .replace(/\\+/g, '-')\n .replace(/\\//g, '_')\n .replace(/=/g, '');\n}\n","/**\n * Validate email format\n * Uses a reasonable regex that catches most issues without being overly strict\n */\nexport function isValidEmail(email: string): boolean {\n if (!email || typeof email !== 'string') return false;\n\n // Trim and check length\n const trimmed = email.trim();\n if (trimmed.length === 0 || trimmed.length > 254) return false;\n\n // Basic format check\n const emailRegex = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/;\n if (!emailRegex.test(trimmed)) return false;\n\n // Check for common typos/issues\n const [localPart, domain] = trimmed.split('@');\n\n // Local part checks\n if (!localPart || localPart.length > 64) return false;\n if (localPart.startsWith('.') || localPart.endsWith('.')) return false;\n if (localPart.includes('..')) return false;\n\n // Domain checks\n if (!domain || domain.length > 253) return false;\n if (domain.startsWith('.') || domain.endsWith('.')) return false;\n if (domain.startsWith('-') || domain.endsWith('-')) return false;\n if (!domain.includes('.')) return false;\n\n // TLD should be at least 2 chars\n const tld = domain.split('.').pop();\n if (!tld || tld.length < 2) return false;\n\n return true;\n}\n\n/**\n * Normalize email address\n * - Lowercase\n * - Trim whitespace\n * - Remove dots from Gmail local part (optional)\n */\nexport function normalizeEmail(\n email: string,\n options: { normalizeGmail?: boolean } = {}\n): string {\n let normalized = email.toLowerCase().trim();\n\n if (options.normalizeGmail) {\n const [localPart, domain] = normalized.split('@');\n if (domain === 'gmail.com' || domain === 'googlemail.com') {\n // Remove dots and everything after + in local part\n const cleanLocal = localPart.split('+')[0].replace(/\\./g, '');\n normalized = `${cleanLocal}@gmail.com`;\n }\n }\n\n return normalized;\n}\n\n/**\n * Check if email is from a disposable domain\n * This is a basic list - for production, consider using a service\n */\nconst DISPOSABLE_DOMAINS = new Set([\n 'tempmail.com',\n 'throwaway.email',\n 'guerrillamail.com',\n 'sharklasers.com',\n 'mailinator.com',\n 'yopmail.com',\n '10minutemail.com',\n 'temp-mail.org',\n 'fakeinbox.com',\n 'trashmail.com',\n 'getnada.com',\n 'maildrop.cc',\n 'dispostable.com',\n 'tempail.com',\n 'mohmal.com',\n]);\n\nexport function isDisposableEmail(email: string): boolean {\n const domain = email.toLowerCase().split('@')[1];\n return DISPOSABLE_DOMAINS.has(domain);\n}\n\n/**\n * Sanitize string input to prevent XSS\n */\nexport function sanitizeString(input: string): string {\n return input\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n .replace(/\"/g, '"')\n .replace(/'/g, ''');\n}\n","import type { RateLimitConfig } from '../types';\n\ninterface RateLimitEntry {\n count: number;\n resetAt: number;\n}\n\n/**\n * Simple in-memory rate limiter\n * \n * For production with multiple server instances, consider:\n * - Redis-based rate limiting\n * - Upstash Ratelimit\n * - Arcjet\n */\nexport function createRateLimiter(config: RateLimitConfig) {\n const { max, windowSeconds, identifier } = config;\n const store = new Map<string, RateLimitEntry>();\n\n // Cleanup old entries periodically\n setInterval(() => {\n const now = Date.now();\n for (const [key, entry] of store.entries()) {\n if (entry.resetAt < now) {\n store.delete(key);\n }\n }\n }, windowSeconds * 1000);\n\n return {\n /**\n * Check if request should be rate limited\n * Returns true if request is allowed, false if rate limited\n */\n async check(req: Request): Promise<{ allowed: boolean; remaining: number; resetAt: Date }> {\n const id = identifier ? identifier(req) : getClientIP(req);\n const now = Date.now();\n const windowMs = windowSeconds * 1000;\n\n let entry = store.get(id);\n\n // If no entry or window expired, create new entry\n if (!entry || entry.resetAt < now) {\n entry = {\n count: 0,\n resetAt: now + windowMs,\n };\n }\n\n entry.count++;\n store.set(id, entry);\n\n const allowed = entry.count <= max;\n const remaining = Math.max(0, max - entry.count);\n\n return {\n allowed,\n remaining,\n resetAt: new Date(entry.resetAt),\n };\n },\n\n /**\n * Get current limit status without incrementing\n */\n async status(req: Request): Promise<{ remaining: number; resetAt: Date | null }> {\n const id = identifier ? identifier(req) : getClientIP(req);\n const entry = store.get(id);\n\n if (!entry || entry.resetAt < Date.now()) {\n return { remaining: max, resetAt: null };\n }\n\n return {\n remaining: Math.max(0, max - entry.count),\n resetAt: new Date(entry.resetAt),\n };\n },\n\n /**\n * Reset rate limit for an identifier\n */\n async reset(req: Request): Promise<void> {\n const id = identifier ? identifier(req) : getClientIP(req);\n store.delete(id);\n },\n };\n}\n\n/**\n * Extract client IP from request\n */\nexport function getClientIP(req: Request): string {\n // Check common headers set by proxies/load balancers\n const headers = req.headers;\n\n // Cloudflare\n const cfConnectingIP = headers.get('cf-connecting-ip');\n if (cfConnectingIP) return cfConnectingIP;\n\n // Standard proxy headers\n const xForwardedFor = headers.get('x-forwarded-for');\n if (xForwardedFor) {\n // Take the first IP (original client)\n return xForwardedFor.split(',')[0].trim();\n }\n\n const xRealIP = headers.get('x-real-ip');\n if (xRealIP) return xRealIP;\n\n // Vercel\n const xVercelForwardedFor = headers.get('x-vercel-forwarded-for');\n if (xVercelForwardedFor) return xVercelForwardedFor.split(',')[0].trim();\n\n // Fallback - this will be the server's IP in most proxy scenarios\n return 'unknown';\n}\n\n/**\n * Create rate limit headers for response\n */\nexport function createRateLimitHeaders(\n limit: number,\n remaining: number,\n resetAt: Date\n): Headers {\n const headers = new Headers();\n headers.set('X-RateLimit-Limit', limit.toString());\n headers.set('X-RateLimit-Remaining', remaining.toString());\n headers.set('X-RateLimit-Reset', Math.floor(resetAt.getTime() / 1000).toString());\n return headers;\n}\n\n/**\n * Default rate limit config\n */\nexport const defaultRateLimitConfig: RateLimitConfig = {\n max: 5,\n windowSeconds: 60, // 5 requests per minute\n};\n","import type { StorageAdapter, Subscriber, SubscribeInput, SubscriptionStatus } from '../../types';\nimport { generateToken } from '../../utils/crypto';\n\n/**\n * In-memory storage adapter\n * \n * Useful for:\n * - Development and testing\n * - Simple sites that don't need persistence\n * - When you only want to send emails without storing subscribers\n * \n * WARNING: Data is lost when the server restarts!\n * \n * @example\n * ```ts\n * import { createMemoryAdapter } from '@volchok/newsletter-kit/adapters/storage';\n * \n * const storageAdapter = createMemoryAdapter();\n * ```\n */\nexport function createMemoryAdapter(): StorageAdapter {\n const subscribers = new Map<string, Subscriber>();\n const tokenIndex = new Map<string, string>(); // token -> email\n\n return {\n async createSubscriber(input: SubscribeInput, token?: string): Promise<Subscriber> {\n const email = input.email.toLowerCase();\n const confirmToken = token || generateToken();\n const now = new Date();\n\n // Remove old token if exists\n const existing = subscribers.get(email);\n if (existing?.id) {\n // Find and remove old token\n for (const [t, e] of tokenIndex.entries()) {\n if (e === email) {\n tokenIndex.delete(t);\n break;\n }\n }\n }\n\n const subscriber: Subscriber = {\n id: existing?.id || crypto.randomUUID(),\n email,\n status: 'pending',\n source: input.source,\n tags: input.tags || [],\n metadata: input.metadata,\n consentIp: input.ip,\n consentAt: now,\n confirmedAt: undefined,\n unsubscribedAt: undefined,\n createdAt: existing?.createdAt || now,\n updatedAt: now,\n };\n\n subscribers.set(email, subscriber);\n tokenIndex.set(confirmToken, email);\n\n // Return with token attached (hack for internal use)\n return { ...subscriber, id: confirmToken } as Subscriber;\n },\n\n async getSubscriberByEmail(email: string): Promise<Subscriber | null> {\n return subscribers.get(email.toLowerCase()) || null;\n },\n\n async getSubscriberByToken(token: string): Promise<Subscriber | null> {\n const email = tokenIndex.get(token);\n if (!email) return null;\n return subscribers.get(email) || null;\n },\n\n async confirmSubscriber(token: string): Promise<Subscriber | null> {\n const email = tokenIndex.get(token);\n if (!email) return null;\n\n const subscriber = subscribers.get(email);\n if (!subscriber) return null;\n\n const updated: Subscriber = {\n ...subscriber,\n status: 'confirmed',\n confirmedAt: new Date(),\n updatedAt: new Date(),\n };\n\n subscribers.set(email, updated);\n tokenIndex.delete(token);\n\n return updated;\n },\n\n async unsubscribe(email: string): Promise<boolean> {\n const subscriber = subscribers.get(email.toLowerCase());\n if (!subscriber) return false;\n\n const updated: Subscriber = {\n ...subscriber,\n status: 'unsubscribed',\n unsubscribedAt: new Date(),\n updatedAt: new Date(),\n };\n\n subscribers.set(email.toLowerCase(), updated);\n return true;\n },\n\n async listSubscribers(options?: {\n status?: SubscriptionStatus;\n source?: string;\n tags?: string[];\n limit?: number;\n offset?: number;\n }): Promise<{ subscribers: Subscriber[]; total: number }> {\n let results = Array.from(subscribers.values());\n\n if (options?.status) {\n results = results.filter((s) => s.status === options.status);\n }\n if (options?.source) {\n results = results.filter((s) => s.source === options.source);\n }\n if (options?.tags?.length) {\n results = results.filter((s) =>\n options.tags!.some((tag) => s.tags?.includes(tag))\n );\n }\n\n // Sort by createdAt desc\n results.sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime());\n\n const total = results.length;\n const offset = options?.offset || 0;\n const limit = options?.limit || 100;\n\n return {\n subscribers: results.slice(offset, offset + limit),\n total,\n };\n },\n\n async deleteSubscriber(email: string): Promise<boolean> {\n const normalizedEmail = email.toLowerCase();\n const existed = subscribers.has(normalizedEmail);\n subscribers.delete(normalizedEmail);\n\n // Clean up token index\n for (const [token, e] of tokenIndex.entries()) {\n if (e === normalizedEmail) {\n tokenIndex.delete(token);\n break;\n }\n }\n\n return existed;\n },\n\n async updateSubscriber(email: string, data: Partial<Subscriber>): Promise<Subscriber | null> {\n const normalizedEmail = email.toLowerCase();\n const subscriber = subscribers.get(normalizedEmail);\n if (!subscriber) return null;\n\n const updated: Subscriber = {\n ...subscriber,\n ...(data.source !== undefined && { source: data.source }),\n ...(data.tags !== undefined && { tags: data.tags }),\n ...(data.metadata !== undefined && { metadata: data.metadata }),\n updatedAt: new Date(),\n };\n\n subscribers.set(normalizedEmail, updated);\n return updated;\n },\n };\n}\n\n/**\n * No-op storage adapter\n * \n * Use when you don't want to store subscribers at all,\n * only send emails (e.g., when using Mailchimp which handles storage)\n */\nexport function createNoopAdapter(): StorageAdapter {\n return {\n async createSubscriber(input: SubscribeInput): Promise<Subscriber> {\n return {\n id: 'noop',\n email: input.email,\n status: 'pending',\n source: input.source,\n tags: input.tags,\n metadata: input.metadata,\n consentIp: input.ip,\n consentAt: new Date(),\n createdAt: new Date(),\n updatedAt: new Date(),\n };\n },\n async getSubscriberByEmail(): Promise<Subscriber | null> {\n return null;\n },\n async getSubscriberByToken(): Promise<Subscriber | null> {\n return null;\n },\n async confirmSubscriber(): Promise<Subscriber | null> {\n return null;\n },\n async unsubscribe(): Promise<boolean> {\n return true;\n },\n };\n}\n","import type {\n NewsletterConfig,\n SubscribeRequest,\n SubscribeResult,\n ConfirmResult,\n UnsubscribeResult,\n APIResponse,\n Subscriber,\n} from '../types';\nimport { generateToken } from '../utils/crypto';\nimport { isValidEmail, normalizeEmail } from '../utils/validation';\nimport { createRateLimiter, getClientIP, createRateLimitHeaders, defaultRateLimitConfig } from '../utils/rate-limit';\nimport { createMemoryAdapter } from '../adapters/storage/memory';\n\n/**\n * Create newsletter API handlers for Next.js App Router\n * \n * @example\n * ```ts\n * // lib/newsletter.ts\n * import { createNewsletterHandlers } from '@volchok/newsletter-kit/server';\n * import { createResendAdapter } from '@volchok/newsletter-kit/adapters/email';\n * import { createPrismaAdapter } from '@volchok/newsletter-kit/adapters/storage';\n * \n * export const newsletter = createNewsletterHandlers({\n * emailAdapter: createResendAdapter({\n * apiKey: process.env.RESEND_API_KEY!,\n * from: 'newsletter@example.com',\n * }),\n * storageAdapter: createPrismaAdapter({ prisma }),\n * baseUrl: process.env.NEXT_PUBLIC_URL!,\n * doubleOptIn: true,\n * });\n * \n * // app/api/newsletter/subscribe/route.ts\n * import { newsletter } from '@/lib/newsletter';\n * \n * export const POST = newsletter.subscribe;\n * ```\n */\nexport function createNewsletterHandlers(config: NewsletterConfig) {\n const {\n emailAdapter,\n storageAdapter = createMemoryAdapter(),\n doubleOptIn = true,\n baseUrl,\n confirmPath = '/api/newsletter/confirm',\n unsubscribePath = '/api/newsletter/unsubscribe',\n honeypotField = 'website',\n rateLimit = defaultRateLimitConfig,\n validateEmail: customValidateEmail,\n allowedSources,\n defaultTags = [],\n onSubscribe,\n onConfirm,\n onUnsubscribe,\n onError,\n } = config;\n\n const rateLimiter = rateLimit ? createRateLimiter(rateLimit) : null;\n\n /**\n * Helper to create JSON response\n */\n function jsonResponse<T>(\n data: APIResponse<T>,\n status: number = 200,\n headers?: Headers\n ): Response {\n const responseHeaders = new Headers(headers);\n responseHeaders.set('Content-Type', 'application/json');\n\n return new Response(JSON.stringify(data), {\n status,\n headers: responseHeaders,\n });\n }\n\n /**\n * Handle subscription request\n */\n async function handleSubscribe(req: Request): Promise<SubscribeResult> {\n const body = (await req.json()) as SubscribeRequest;\n const { email, source, tags = [], metadata } = body;\n\n // Honeypot check\n if (honeypotField && body[honeypotField]) {\n // Bot detected - return success to not reveal detection\n return {\n success: true,\n message: doubleOptIn\n ? 'Please check your email to confirm your subscription.'\n : 'You have been subscribed!',\n };\n }\n\n // Validate email\n if (!email || !isValidEmail(email)) {\n return {\n success: false,\n message: 'Please enter a valid email address.',\n };\n }\n\n // Custom validation\n if (customValidateEmail) {\n const isValid = await customValidateEmail(email);\n if (!isValid) {\n return {\n success: false,\n message: 'This email address is not allowed.',\n };\n }\n }\n\n // Validate source\n if (source && allowedSources && !allowedSources.includes(source)) {\n return {\n success: false,\n message: 'Invalid subscription source.',\n };\n }\n\n const normalizedEmail = normalizeEmail(email);\n const ip = getClientIP(req);\n\n try {\n // Check if already subscribed\n const existing = await storageAdapter.getSubscriberByEmail(normalizedEmail);\n\n if (existing?.status === 'confirmed') {\n return {\n success: true,\n message: 'You are already subscribed!',\n subscriber: existing,\n };\n }\n\n // Create/update subscriber\n const token = generateToken();\n const subscriber = await storageAdapter.createSubscriber(\n {\n email: normalizedEmail,\n source,\n tags: [...defaultTags, ...tags],\n metadata,\n ip,\n },\n token\n );\n\n if (doubleOptIn) {\n // Send confirmation email\n const confirmUrl = `${baseUrl}${confirmPath}?token=${token}`;\n await emailAdapter.sendConfirmation(normalizedEmail, token, confirmUrl);\n\n // Notify admin if configured\n if (emailAdapter.notifyAdmin) {\n await emailAdapter.notifyAdmin(subscriber);\n }\n\n // Callback\n if (onSubscribe) {\n await onSubscribe(subscriber);\n }\n\n return {\n success: true,\n message: 'Please check your email to confirm your subscription.',\n subscriber,\n requiresConfirmation: true,\n };\n } else {\n // Direct subscription (no double opt-in)\n const confirmed = await storageAdapter.confirmSubscriber(token);\n\n // Send welcome email\n await emailAdapter.sendWelcome(normalizedEmail);\n\n // Notify admin if configured\n if (emailAdapter.notifyAdmin && confirmed) {\n await emailAdapter.notifyAdmin(confirmed);\n }\n\n // Callback\n if (onSubscribe && confirmed) {\n await onSubscribe(confirmed);\n }\n\n return {\n success: true,\n message: 'You have been subscribed!',\n subscriber: confirmed || subscriber,\n requiresConfirmation: false,\n };\n }\n } catch (error) {\n if (onError) {\n await onError(error as Error, 'subscribe');\n }\n console.error('[newsletter-kit] Subscribe error:', error);\n return {\n success: false,\n message: 'Something went wrong. Please try again.',\n };\n }\n }\n\n /**\n * Handle confirmation request\n */\n async function handleConfirm(token: string): Promise<ConfirmResult> {\n if (!token) {\n return {\n success: false,\n message: 'Invalid confirmation link.',\n };\n }\n\n try {\n const subscriber = await storageAdapter.confirmSubscriber(token);\n\n if (!subscriber) {\n return {\n success: false,\n message: 'This confirmation link is invalid or has expired.',\n };\n }\n\n // Send welcome email\n await emailAdapter.sendWelcome(subscriber.email);\n\n // Callback\n if (onConfirm) {\n await onConfirm(subscriber);\n }\n\n return {\n success: true,\n message: 'Your subscription has been confirmed!',\n subscriber,\n };\n } catch (error) {\n if (onError) {\n await onError(error as Error, 'confirm');\n }\n console.error('[newsletter-kit] Confirm error:', error);\n return {\n success: false,\n message: 'Something went wrong. Please try again.',\n };\n }\n }\n\n /**\n * Handle unsubscribe request\n */\n async function handleUnsubscribe(email: string): Promise<UnsubscribeResult> {\n if (!email || !isValidEmail(email)) {\n return {\n success: false,\n message: 'Invalid email address.',\n };\n }\n\n const normalizedEmail = normalizeEmail(email);\n\n try {\n const success = await storageAdapter.unsubscribe(normalizedEmail);\n\n if (!success) {\n return {\n success: false,\n message: 'Email not found in our list.',\n };\n }\n\n // Send unsubscribe confirmation if adapter supports it\n if (emailAdapter.sendUnsubscribed) {\n const resubscribeUrl = `${baseUrl}`;\n await emailAdapter.sendUnsubscribed(normalizedEmail, resubscribeUrl);\n }\n\n // Callback\n if (onUnsubscribe) {\n await onUnsubscribe(normalizedEmail);\n }\n\n return {\n success: true,\n message: 'You have been unsubscribed.',\n };\n } catch (error) {\n if (onError) {\n await onError(error as Error, 'unsubscribe');\n }\n console.error('[newsletter-kit] Unsubscribe error:', error);\n return {\n success: false,\n message: 'Something went wrong. Please try again.',\n };\n }\n }\n\n // Return Next.js route handlers\n return {\n /**\n * POST /api/newsletter/subscribe\n */\n subscribe: async (req: Request): Promise<Response> => {\n // Rate limiting\n if (rateLimiter) {\n const { allowed, remaining, resetAt } = await rateLimiter.check(req);\n const headers = createRateLimitHeaders(rateLimit!.max, remaining, resetAt);\n\n if (!allowed) {\n return jsonResponse(\n {\n success: false,\n message: 'Too many requests. Please try again later.',\n },\n 429,\n headers\n );\n }\n }\n\n const result = await handleSubscribe(req);\n return jsonResponse(result, result.success ? 200 : 400);\n },\n\n /**\n * GET /api/newsletter/confirm?token=xxx\n * \n * Can also return a redirect for better UX\n */\n confirm: async (req: Request): Promise<Response> => {\n const url = new URL(req.url);\n const token = url.searchParams.get('token');\n\n const result = await handleConfirm(token || '');\n\n // Check if client wants JSON or redirect\n const acceptsJson = req.headers.get('accept')?.includes('application/json');\n\n if (acceptsJson) {\n return jsonResponse(result, result.success ? 200 : 400);\n }\n\n // Redirect to success/error page\n const redirectUrl = new URL(baseUrl);\n redirectUrl.searchParams.set('confirmed', result.success ? 'true' : 'false');\n if (!result.success) {\n redirectUrl.searchParams.set('error', result.message);\n }\n\n return Response.redirect(redirectUrl.toString(), 302);\n },\n\n /**\n * POST /api/newsletter/unsubscribe\n * Also supports GET with email query param\n */\n unsubscribe: async (req: Request): Promise<Response> => {\n let email: string;\n\n if (req.method === 'GET') {\n const url = new URL(req.url);\n email = url.searchParams.get('email') || '';\n } else {\n const body = await req.json();\n email = body.email;\n }\n\n const result = await handleUnsubscribe(email);\n return jsonResponse(result, result.success ? 200 : 400);\n },\n\n /**\n * Internal handlers for custom implementations\n */\n handlers: {\n subscribe: handleSubscribe,\n confirm: handleConfirm,\n unsubscribe: handleUnsubscribe,\n },\n\n /**\n * Access to storage adapter for admin features\n */\n storage: storageAdapter,\n\n /**\n * Get subscriber by email (for admin/API use)\n */\n getSubscriber: async (email: string): Promise<Subscriber | null> => {\n return storageAdapter.getSubscriberByEmail(normalizeEmail(email));\n },\n\n /**\n * List subscribers (for admin/export use)\n */\n listSubscribers: storageAdapter.listSubscribers,\n };\n}\n\nexport type NewsletterHandlers = ReturnType<typeof createNewsletterHandlers>;\n","'use client';\n\nimport * as React from 'react';\nimport { useState, useCallback, FormEvent } from 'react';\nimport type { NewsletterFormProps, NewsletterFormState } from '../types';\nimport { cn } from '../utils/cn';\n\n/**\n * Newsletter subscription form component\n * \n * Designed to work with shadcn/ui but includes default Tailwind styles.\n * \n * @example\n * ```tsx\n * // Basic usage\n * <NewsletterForm endpoint=\"/api/newsletter/subscribe\" />\n * \n * // With all options\n * <NewsletterForm\n * endpoint=\"/api/newsletter/subscribe\"\n * source=\"footer\"\n * tags={['marketing']}\n * placeholder=\"Enter your email\"\n * buttonText=\"Subscribe\"\n * successMessage=\"Check your inbox!\"\n * onSuccess={(email) => console.log('Subscribed:', email)}\n * onError={(error) => console.error('Error:', error)}\n * className=\"max-w-md\"\n * />\n * ```\n */\nexport function NewsletterForm({\n endpoint = '/api/newsletter/subscribe',\n source,\n tags,\n honeypotField = 'website',\n className,\n formClassName,\n inputClassName,\n buttonClassName,\n messageClassName,\n placeholder = 'Enter your email',\n buttonText = 'Subscribe',\n loadingText = 'Subscribing...',\n successMessage,\n errorMessage,\n showMessage = true,\n onSuccess,\n onError,\n onSubmit,\n disabled = false,\n metadata,\n}: NewsletterFormProps) {\n const [state, setState] = useState<NewsletterFormState>({\n status: 'idle',\n message: '',\n email: '',\n });\n\n const handleSubmit = useCallback(\n async (e: FormEvent<HTMLFormElement>) => {\n e.preventDefault();\n\n const formData = new FormData(e.currentTarget);\n const email = formData.get('email') as string;\n const honeypot = formData.get(honeypotField) as string;\n\n // If honeypot is filled, silently \"succeed\" (bot detection)\n if (honeypot) {\n setState({\n status: 'success',\n message: successMessage || 'Thanks for subscribing!',\n email: '',\n });\n return;\n }\n\n if (!email || !email.includes('@')) {\n setState({\n status: 'error',\n message: 'Please enter a valid email address.',\n email,\n });\n onError?.('Please enter a valid email address.');\n return;\n }\n\n setState({ status: 'loading', message: '', email });\n onSubmit?.(email);\n\n try {\n const response = await fetch(endpoint, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n email,\n source,\n tags,\n metadata,\n }),\n });\n\n const data = await response.json();\n\n if (response.ok && data.success) {\n const message = successMessage || data.message || 'Thanks for subscribing!';\n setState({\n status: 'success',\n message,\n email: '',\n });\n onSuccess?.(email, message);\n } else {\n const message = errorMessage || data.message || 'Something went wrong. Please try again.';\n setState({\n status: 'error',\n message,\n email,\n });\n onError?.(message);\n }\n } catch (error) {\n const message = errorMessage || 'Network error. Please try again.';\n setState({\n status: 'error',\n message,\n email,\n });\n onError?.(message);\n }\n },\n [endpoint, source, tags, metadata, honeypotField, successMessage, errorMessage, onSuccess, onError, onSubmit]\n );\n\n const isLoading = state.status === 'loading';\n const isDisabled = disabled || isLoading;\n\n return (\n <div className={cn('w-full', className)}>\n <form\n onSubmit={handleSubmit}\n className={cn('flex flex-col sm:flex-row gap-2', formClassName)}\n >\n {/* Honeypot field - hidden from users, visible to bots */}\n <input\n type=\"text\"\n name={honeypotField}\n autoComplete=\"off\"\n tabIndex={-1}\n aria-hidden=\"true\"\n className=\"absolute -left-[9999px] opacity-0 pointer-events-none\"\n />\n\n <input\n type=\"email\"\n name=\"email\"\n placeholder={placeholder}\n required\n disabled={isDisabled}\n defaultValue={state.email}\n aria-label=\"Email address\"\n aria-describedby={state.message ? 'newsletter-message' : undefined}\n className={cn(\n 'flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm',\n 'ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium',\n 'placeholder:text-muted-foreground',\n 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2',\n 'disabled:cursor-not-allowed disabled:opacity-50',\n inputClassName\n )}\n />\n\n <button\n type=\"submit\"\n disabled={isDisabled}\n className={cn(\n 'inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium',\n 'ring-offset-background transition-colors',\n 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2',\n 'disabled:pointer-events-none disabled:opacity-50',\n 'bg-primary text-primary-foreground hover:bg-primary/90',\n 'h-10 px-4 py-2',\n 'min-w-[120px]',\n buttonClassName\n )}\n >\n {isLoading ? (\n <>\n <svg\n className=\"mr-2 h-4 w-4 animate-spin\"\n xmlns=\"http://www.w3.org/2000/svg\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n >\n <circle\n className=\"opacity-25\"\n cx=\"12\"\n cy=\"12\"\n r=\"10\"\n stroke=\"currentColor\"\n strokeWidth=\"4\"\n />\n <path\n className=\"opacity-75\"\n fill=\"currentColor\"\n d=\"M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z\"\n />\n </svg>\n {loadingText}\n </>\n ) : (\n buttonText\n )}\n </button>\n </form>\n\n {showMessage && state.message && (\n <p\n id=\"newsletter-message\"\n role={state.status === 'error' ? 'alert' : 'status'}\n className={cn(\n 'mt-2 text-sm',\n state.status === 'success' && 'text-green-600 dark:text-green-400',\n state.status === 'error' && 'text-red-600 dark:text-red-400',\n messageClassName\n )}\n >\n {state.message}\n </p>\n )}\n </div>\n );\n}\n","/**\n * Utility for merging class names\n * Compatible with shadcn/ui's cn function\n */\nexport function cn(...inputs: (string | undefined | null | false)[]): string {\n return inputs.filter(Boolean).join(' ');\n}\n","'use client';\n\nimport * as React from 'react';\nimport { NewsletterForm } from './newsletter-form';\nimport type { NewsletterFormProps } from '../types';\nimport { cn } from '../utils/cn';\n\n// ============================================================================\n// Newsletter Block - Full width section\n// ============================================================================\n\ninterface NewsletterBlockProps extends NewsletterFormProps {\n title?: string;\n description?: string;\n containerClassName?: string;\n titleClassName?: string;\n descriptionClassName?: string;\n}\n\n/**\n * Full-width newsletter subscription block\n * \n * @example\n * ```tsx\n * <NewsletterBlock\n * endpoint=\"/api/newsletter/subscribe\"\n * title=\"Stay updated\"\n * description=\"Get the latest news delivered to your inbox.\"\n * source=\"footer\"\n * />\n * ```\n */\nexport function NewsletterBlock({\n title = 'Subscribe to our newsletter',\n description = 'Get the latest updates delivered directly to your inbox.',\n containerClassName,\n titleClassName,\n descriptionClassName,\n ...formProps\n}: NewsletterBlockProps) {\n return (\n <section\n className={cn(\n 'w-full py-12 md:py-16 lg:py-20',\n 'bg-muted/50',\n containerClassName\n )}\n >\n <div className=\"container mx-auto px-4 md:px-6\">\n <div className=\"flex flex-col items-center justify-center space-y-4 text-center\">\n <div className=\"space-y-2\">\n <h2\n className={cn(\n 'text-2xl font-bold tracking-tight sm:text-3xl md:text-4xl',\n titleClassName\n )}\n >\n {title}\n </h2>\n <p\n className={cn(\n 'max-w-[600px] text-muted-foreground md:text-lg',\n descriptionClassName\n )}\n >\n {description}\n </p>\n </div>\n <div className=\"w-full max-w-md\">\n <NewsletterForm {...formProps} />\n </div>\n </div>\n </div>\n </section>\n );\n}\n\n// ============================================================================\n// Newsletter Card - Contained card variant\n// ============================================================================\n\ninterface NewsletterCardProps extends NewsletterFormProps {\n title?: string;\n description?: string;\n cardClassName?: string;\n titleClassName?: string;\n descriptionClassName?: string;\n}\n\n/**\n * Card-style newsletter subscription component\n * \n * @example\n * ```tsx\n * <NewsletterCard\n * endpoint=\"/api/newsletter/subscribe\"\n * title=\"Join the community\"\n * description=\"Be the first to know about new features.\"\n * source=\"sidebar\"\n * />\n * ```\n */\nexport function NewsletterCard({\n title = 'Newsletter',\n description = 'Subscribe to get updates.',\n cardClassName,\n titleClassName,\n descriptionClassName,\n ...formProps\n}: NewsletterCardProps) {\n return (\n <div\n className={cn(\n 'rounded-lg border bg-card text-card-foreground shadow-sm',\n 'p-6',\n cardClassName\n )}\n >\n <div className=\"space-y-4\">\n <div className=\"space-y-1.5\">\n <h3\n className={cn(\n 'text-lg font-semibold leading-none tracking-tight',\n titleClassName\n )}\n >\n {title}\n </h3>\n <p\n className={cn(\n 'text-sm text-muted-foreground',\n descriptionClassName\n )}\n >\n {description}\n </p>\n </div>\n <NewsletterForm {...formProps} />\n </div>\n </div>\n );\n}\n\n// ============================================================================\n// Newsletter Inline - Minimal inline variant\n// ============================================================================\n\ninterface NewsletterInlineProps extends NewsletterFormProps {\n label?: string;\n labelClassName?: string;\n}\n\n/**\n * Inline newsletter subscription component\n * \n * @example\n * ```tsx\n * // In a footer\n * <NewsletterInline\n * endpoint=\"/api/newsletter/subscribe\"\n * label=\"Subscribe:\"\n * source=\"footer\"\n * />\n * ```\n */\nexport function NewsletterInline({\n label,\n labelClassName,\n ...formProps\n}: NewsletterInlineProps) {\n return (\n <div className=\"flex flex-col gap-2\">\n {label && (\n <label\n className={cn(\n 'text-sm font-medium leading-none',\n labelClassName\n )}\n >\n {label}\n </label>\n )}\n <NewsletterForm {...formProps} />\n </div>\n );\n}\n\n// ============================================================================\n// Newsletter Modal - Dialog/modal variant (controlled externally)\n// ============================================================================\n\ninterface NewsletterModalContentProps extends NewsletterFormProps {\n title?: string;\n description?: string;\n titleClassName?: string;\n descriptionClassName?: string;\n}\n\n/**\n * Newsletter content for use inside a modal/dialog\n * \n * Use with shadcn/ui Dialog component:\n * \n * @example\n * ```tsx\n * import { Dialog, DialogContent, DialogTrigger } from '@/components/ui/dialog';\n * import { NewsletterModalContent } from '@volchok/newsletter-kit/components';\n * \n * <Dialog>\n * <DialogTrigger asChild>\n * <Button>Subscribe</Button>\n * </DialogTrigger>\n * <DialogContent>\n * <NewsletterModalContent\n * endpoint=\"/api/newsletter/subscribe\"\n * title=\"Join our newsletter\"\n * description=\"Get weekly updates on the latest news.\"\n * source=\"popup\"\n * onSuccess={() => {\n * // Close modal after success\n * setTimeout(() => setOpen(false), 2000);\n * }}\n * />\n * </DialogContent>\n * </Dialog>\n * ```\n */\nexport function NewsletterModalContent({\n title = 'Subscribe to our newsletter',\n description = \"We'll send you the best content, no spam.\",\n titleClassName,\n descriptionClassName,\n ...formProps\n}: NewsletterModalContentProps) {\n return (\n <div className=\"space-y-4\">\n <div className=\"space-y-2 text-center\">\n <h2\n className={cn(\n 'text-lg font-semibold leading-none tracking-tight',\n titleClassName\n )}\n >\n {title}\n </h2>\n <p\n className={cn(\n 'text-sm text-muted-foreground',\n descriptionClassName\n )}\n >\n {description}\n </p>\n </div>\n <NewsletterForm\n {...formProps}\n formClassName=\"flex-col\"\n buttonClassName=\"w-full\"\n />\n </div>\n );\n}\n\n// ============================================================================\n// Newsletter Footer - Optimized for site footers\n// ============================================================================\n\ninterface NewsletterFooterProps extends NewsletterFormProps {\n title?: string;\n description?: string;\n containerClassName?: string;\n titleClassName?: string;\n descriptionClassName?: string;\n privacyText?: string;\n privacyLink?: string;\n}\n\n/**\n * Footer-optimized newsletter subscription component\n * \n * @example\n * ```tsx\n * <footer>\n * <NewsletterFooter\n * endpoint=\"/api/newsletter/subscribe\"\n * title=\"Newsletter\"\n * description=\"Stay up to date with our latest news.\"\n * source=\"footer\"\n * privacyText=\"We respect your privacy.\"\n * privacyLink=\"/privacy\"\n * />\n * </footer>\n * ```\n */\nexport function NewsletterFooter({\n title = 'Newsletter',\n description,\n containerClassName,\n titleClassName,\n descriptionClassName,\n privacyText,\n privacyLink,\n ...formProps\n}: NewsletterFooterProps) {\n return (\n <div className={cn('space-y-4', containerClassName)}>\n {title && (\n <h3\n className={cn(\n 'text-sm font-semibold uppercase tracking-wider',\n titleClassName\n )}\n >\n {title}\n </h3>\n )}\n {description && (\n <p\n className={cn(\n 'text-sm text-muted-foreground',\n descriptionClassName\n )}\n >\n {description}\n </p>\n )}\n <NewsletterForm {...formProps} />\n {privacyText && (\n <p className=\"text-xs text-muted-foreground\">\n {privacyLink ? (\n <>\n {privacyText.replace(/\\.$/, '')}.{' '}\n <a href={privacyLink} className=\"underline hover:text-foreground\">\n Privacy Policy\n </a>\n </>\n ) : (\n privacyText\n )}\n </p>\n )}\n </div>\n );\n}\n","'use client';\n\nimport { useState, useCallback } from 'react';\n\ninterface UseNewsletterOptions {\n endpoint?: string;\n source?: string;\n tags?: string[];\n metadata?: Record<string, unknown>;\n onSuccess?: (email: string, message: string) => void;\n onError?: (error: string) => void;\n}\n\ninterface UseNewsletterState {\n status: 'idle' | 'loading' | 'success' | 'error';\n message: string;\n email: string | null;\n}\n\ninterface UseNewsletterReturn extends UseNewsletterState {\n subscribe: (email: string) => Promise<boolean>;\n reset: () => void;\n isLoading: boolean;\n isSuccess: boolean;\n isError: boolean;\n}\n\n/**\n * React hook for programmatic newsletter subscription\n * \n * @example\n * ```tsx\n * const { subscribe, isLoading, isSuccess, message } = useNewsletter({\n * endpoint: '/api/newsletter/subscribe',\n * source: 'custom-form',\n * onSuccess: (email) => {\n * analytics.track('newsletter_subscribed', { email });\n * },\n * });\n * \n * const handleSubmit = async (email: string) => {\n * const success = await subscribe(email);\n * if (success) {\n * // Show confetti or something\n * }\n * };\n * ```\n */\nexport function useNewsletter(options: UseNewsletterOptions = {}): UseNewsletterReturn {\n const {\n endpoint = '/api/newsletter/subscribe',\n source,\n tags,\n metadata,\n onSuccess,\n onError,\n } = options;\n\n const [state, setState] = useState<UseNewsletterState>({\n status: 'idle',\n message: '',\n email: null,\n });\n\n const subscribe = useCallback(\n async (email: string): Promise<boolean> => {\n if (!email || !email.includes('@')) {\n const errorMsg = 'Please enter a valid email address.';\n setState({\n status: 'error',\n message: errorMsg,\n email,\n });\n onError?.(errorMsg);\n return false;\n }\n\n setState({ status: 'loading', message: '', email });\n\n try {\n const response = await fetch(endpoint, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n email,\n source,\n tags,\n metadata,\n }),\n });\n\n const data = await response.json();\n\n if (response.ok && data.success) {\n const message = data.message || 'Thanks for subscribing!';\n setState({\n status: 'success',\n message,\n email,\n });\n onSuccess?.(email, message);\n return true;\n } else {\n const message = data.message || 'Something went wrong. Please try again.';\n setState({\n status: 'error',\n message,\n email,\n });\n onError?.(message);\n return false;\n }\n } catch (error) {\n const message = 'Network error. Please try again.';\n setState({\n status: 'error',\n message,\n email,\n });\n onError?.(message);\n return false;\n }\n },\n [endpoint, source, tags, metadata, onSuccess, onError]\n );\n\n const reset = useCallback(() => {\n setState({\n status: 'idle',\n message: '',\n email: null,\n });\n }, []);\n\n return {\n ...state,\n subscribe,\n reset,\n isLoading: state.status === 'loading',\n isSuccess: state.status === 'success',\n isError: state.status === 'error',\n };\n}\n","import type { EmailAdapter, EmailAdapterConfig, EmailTemplates, Subscriber } from '../../types';\n\ninterface ResendAdapterConfig extends EmailAdapterConfig {\n apiKey: string;\n}\n\n/**\n * Email adapter for Resend (https://resend.com)\n * \n * @example\n * ```ts\n * import { createResendAdapter } from '@volchok/newsletter-kit/adapters/email';\n * \n * const emailAdapter = createResendAdapter({\n * apiKey: process.env.RESEND_API_KEY!,\n * from: 'newsletter@yourdomain.com',\n * adminEmail: 'you@yourdomain.com', // optional\n * });\n * ```\n */\nexport function createResendAdapter(config: ResendAdapterConfig): EmailAdapter {\n const { apiKey, from, replyTo, templates, adminEmail } = config;\n\n async function sendEmail(params: {\n to: string;\n subject: string;\n html: string;\n text?: string;\n }) {\n // Dynamic import to avoid requiring resend as a hard dependency\n const { Resend } = await import('resend');\n const resend = new Resend(apiKey);\n\n await resend.emails.send({\n from,\n replyTo,\n to: params.to,\n subject: params.subject,\n html: params.html,\n text: params.text,\n });\n }\n\n const defaultTemplates: Required<EmailTemplates> = {\n confirmation: {\n subject: 'Confirm your subscription',\n text: undefined,\n html: ({ confirmUrl, email }: { confirmUrl: string; email: string }) => `\n <!DOCTYPE html>\n <html>\n <head>\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n </head>\n <body style=\"font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; line-height: 1.6; color: #333; max-width: 600px; margin: 0 auto; padding: 20px;\">\n <h1 style=\"color: #111; font-size: 24px; margin-bottom: 20px;\">Confirm your subscription</h1>\n <p>Thanks for subscribing! Please confirm your email address by clicking the button below:</p>\n <a href=\"${confirmUrl}\" style=\"display: inline-block; background-color: #000; color: #fff; padding: 12px 24px; text-decoration: none; border-radius: 6px; margin: 20px 0;\">\n Confirm Subscription\n </a>\n <p style=\"color: #666; font-size: 14px; margin-top: 30px;\">\n If you didn't subscribe to this newsletter, you can safely ignore this email.\n </p>\n <p style=\"color: #999; font-size: 12px; margin-top: 20px;\">\n This confirmation was requested for ${email}\n </p>\n </body>\n </html>\n `,\n },\n welcome: {\n subject: 'Welcome to our newsletter!',\n text: undefined,\n html: ({ email }: { email: string }) => `\n <!DOCTYPE html>\n <html>\n <head>\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n </head>\n <body style=\"font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; line-height: 1.6; color: #333; max-width: 600px; margin: 0 auto; padding: 20px;\">\n <h1 style=\"color: #111; font-size: 24px; margin-bottom: 20px;\">You're subscribed! 🎉</h1>\n <p>Thanks for confirming your subscription. You'll now receive our updates at <strong>${email}</strong>.</p>\n <p>We're excited to have you!</p>\n </body>\n </html>\n `,\n },\n unsubscribed: {\n subject: 'You have been unsubscribed',\n text: undefined,\n html: ({ email, resubscribeUrl }: { email: string; resubscribeUrl?: string }) => `\n <!DOCTYPE html>\n <html>\n <head>\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n </head>\n <body style=\"font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; line-height: 1.6; color: #333; max-width: 600px; margin: 0 auto; padding: 20px;\">\n <h1 style=\"color: #111; font-size: 24px; margin-bottom: 20px;\">Unsubscribed</h1>\n <p>You have been unsubscribed from our newsletter. We're sorry to see you go!</p>\n ${resubscribeUrl ? `<p><a href=\"${resubscribeUrl}\">Changed your mind? Resubscribe here.</a></p>` : ''}\n <p style=\"color: #999; font-size: 12px; margin-top: 20px;\">\n This email was sent to ${email}\n </p>\n </body>\n </html>\n `,\n },\n };\n\n return {\n async sendConfirmation(email: string, token: string, confirmUrl: string) {\n const template = templates?.confirmation ?? defaultTemplates.confirmation;\n const subject = template.subject ?? defaultTemplates.confirmation.subject!;\n const html = template.html ?? defaultTemplates.confirmation.html!;\n await sendEmail({\n to: email,\n subject,\n html: html({ confirmUrl, email }),\n text: template.text?.({ confirmUrl, email }),\n });\n },\n\n async sendWelcome(email: string) {\n const template = templates?.welcome ?? defaultTemplates.welcome;\n const subject = template.subject ?? defaultTemplates.welcome.subject!;\n const html = template.html ?? defaultTemplates.welcome.html!;\n await sendEmail({\n to: email,\n subject,\n html: html({ email }),\n text: template.text?.({ email }),\n });\n },\n\n async sendUnsubscribed(email: string, resubscribeUrl?: string) {\n const template = templates?.unsubscribed ?? defaultTemplates.unsubscribed;\n const subject = template.subject ?? defaultTemplates.unsubscribed.subject!;\n const html = template.html ?? defaultTemplates.unsubscribed.html!;\n await sendEmail({\n to: email,\n subject,\n html: html({ email, resubscribeUrl }),\n text: template.text?.({ email, resubscribeUrl }),\n });\n },\n\n async notifyAdmin(subscriber: Subscriber) {\n if (!adminEmail) return;\n\n await sendEmail({\n to: adminEmail,\n subject: `New newsletter subscriber: ${subscriber.email}`,\n html: `\n <!DOCTYPE html>\n <html>\n <body style=\"font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; line-height: 1.6; color: #333; padding: 20px;\">\n <h2>New Newsletter Subscriber</h2>\n <p><strong>Email:</strong> ${subscriber.email}</p>\n <p><strong>Status:</strong> ${subscriber.status}</p>\n ${subscriber.source ? `<p><strong>Source:</strong> ${subscriber.source}</p>` : ''}\n ${subscriber.tags?.length ? `<p><strong>Tags:</strong> ${subscriber.tags.join(', ')}</p>` : ''}\n <p><strong>Subscribed at:</strong> ${subscriber.createdAt.toISOString()}</p>\n </body>\n </html>\n `,\n });\n },\n };\n}\n","import type { EmailAdapter, EmailAdapterConfig, EmailTemplates, Subscriber } from '../../types';\n\ninterface SMTPConfig {\n host: string;\n port: number;\n secure?: boolean;\n auth?: {\n user: string;\n pass: string;\n };\n}\n\ninterface NodemailerAdapterConfig extends EmailAdapterConfig {\n smtp: SMTPConfig;\n}\n\n/**\n * Email adapter for Nodemailer/SMTP\n * \n * @example\n * ```ts\n * import { createNodemailerAdapter } from '@volchok/newsletter-kit/adapters/email';\n * \n * const emailAdapter = createNodemailerAdapter({\n * smtp: {\n * host: 'smtp.example.com',\n * port: 587,\n * secure: false,\n * auth: {\n * user: process.env.SMTP_USER!,\n * pass: process.env.SMTP_PASS!,\n * },\n * },\n * from: 'newsletter@yourdomain.com',\n * adminEmail: 'you@yourdomain.com', // optional\n * });\n * ```\n */\nexport function createNodemailerAdapter(config: NodemailerAdapterConfig): EmailAdapter {\n const { smtp, from, replyTo, templates, adminEmail } = config;\n\n async function sendEmail(params: {\n to: string;\n subject: string;\n html: string;\n text?: string;\n }) {\n // Dynamic import to avoid requiring nodemailer as a hard dependency\n const nodemailer = await import('nodemailer');\n const transporter = nodemailer.createTransport(smtp);\n\n await transporter.sendMail({\n from,\n replyTo,\n to: params.to,\n subject: params.subject,\n html: params.html,\n text: params.text,\n });\n }\n\n const defaultTemplates: Required<EmailTemplates> = {\n confirmation: {\n subject: 'Confirm your subscription',\n text: undefined,\n html: ({ confirmUrl, email }: { confirmUrl: string; email: string }) => `\n <!DOCTYPE html>\n <html>\n <head>\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n </head>\n <body style=\"font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; line-height: 1.6; color: #333; max-width: 600px; margin: 0 auto; padding: 20px;\">\n <h1 style=\"color: #111; font-size: 24px; margin-bottom: 20px;\">Confirm your subscription</h1>\n <p>Thanks for subscribing! Please confirm your email address by clicking the button below:</p>\n <a href=\"${confirmUrl}\" style=\"display: inline-block; background-color: #000; color: #fff; padding: 12px 24px; text-decoration: none; border-radius: 6px; margin: 20px 0;\">\n Confirm Subscription\n </a>\n <p style=\"color: #666; font-size: 14px; margin-top: 30px;\">\n If you didn't subscribe to this newsletter, you can safely ignore this email.\n </p>\n <p style=\"color: #999; font-size: 12px; margin-top: 20px;\">\n This confirmation was requested for ${email}\n </p>\n </body>\n </html>\n `,\n },\n welcome: {\n subject: 'Welcome to our newsletter!',\n text: undefined,\n html: ({ email }: { email: string }) => `\n <!DOCTYPE html>\n <html>\n <head>\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n </head>\n <body style=\"font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; line-height: 1.6; color: #333; max-width: 600px; margin: 0 auto; padding: 20px;\">\n <h1 style=\"color: #111; font-size: 24px; margin-bottom: 20px;\">You're subscribed! 🎉</h1>\n <p>Thanks for confirming your subscription. You'll now receive our updates at <strong>${email}</strong>.</p>\n <p>We're excited to have you!</p>\n </body>\n </html>\n `,\n },\n unsubscribed: {\n subject: 'You have been unsubscribed',\n text: undefined,\n html: ({ email, resubscribeUrl }: { email: string; resubscribeUrl?: string }) => `\n <!DOCTYPE html>\n <html>\n <head>\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n </head>\n <body style=\"font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; line-height: 1.6; color: #333; max-width: 600px; margin: 0 auto; padding: 20px;\">\n <h1 style=\"color: #111; font-size: 24px; margin-bottom: 20px;\">Unsubscribed</h1>\n <p>You have been unsubscribed from our newsletter. We're sorry to see you go!</p>\n ${resubscribeUrl ? `<p><a href=\"${resubscribeUrl}\">Changed your mind? Resubscribe here.</a></p>` : ''}\n <p style=\"color: #999; font-size: 12px; margin-top: 20px;\">\n This email was sent to ${email}\n </p>\n </body>\n </html>\n `,\n },\n };\n\n return {\n async sendConfirmation(email: string, token: string, confirmUrl: string) {\n const template = templates?.confirmation ?? defaultTemplates.confirmation;\n const subject = template.subject ?? defaultTemplates.confirmation.subject!;\n const html = template.html ?? defaultTemplates.confirmation.html!;\n await sendEmail({\n to: email,\n subject,\n html: html({ confirmUrl, email }),\n text: template.text?.({ confirmUrl, email }),\n });\n },\n\n async sendWelcome(email: string) {\n const template = templates?.welcome ?? defaultTemplates.welcome;\n const subject = template.subject ?? defaultTemplates.welcome.subject!;\n const html = template.html ?? defaultTemplates.welcome.html!;\n await sendEmail({\n to: email,\n subject,\n html: html({ email }),\n text: template.text?.({ email }),\n });\n },\n\n async sendUnsubscribed(email: string, resubscribeUrl?: string) {\n const template = templates?.unsubscribed ?? defaultTemplates.unsubscribed;\n const subject = template.subject ?? defaultTemplates.unsubscribed.subject!;\n const html = template.html ?? defaultTemplates.unsubscribed.html!;\n await sendEmail({\n to: email,\n subject,\n html: html({ email, resubscribeUrl }),\n text: template.text?.({ email, resubscribeUrl }),\n });\n },\n\n async notifyAdmin(subscriber: Subscriber) {\n if (!adminEmail) return;\n\n await sendEmail({\n to: adminEmail,\n subject: `New newsletter subscriber: ${subscriber.email}`,\n html: `\n <!DOCTYPE html>\n <html>\n <body style=\"font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; line-height: 1.6; color: #333; padding: 20px;\">\n <h2>New Newsletter Subscriber</h2>\n <p><strong>Email:</strong> ${subscriber.email}</p>\n <p><strong>Status:</strong> ${subscriber.status}</p>\n ${subscriber.source ? `<p><strong>Source:</strong> ${subscriber.source}</p>` : ''}\n ${subscriber.tags?.length ? `<p><strong>Tags:</strong> ${subscriber.tags.join(', ')}</p>` : ''}\n <p><strong>Subscribed at:</strong> ${subscriber.createdAt.toISOString()}</p>\n </body>\n </html>\n `,\n });\n },\n };\n}\n","import type { EmailAdapter, EmailAdapterConfig, Subscriber } from '../../types';\n\ninterface MailchimpAdapterConfig extends EmailAdapterConfig {\n apiKey: string;\n server: string; // e.g., 'us1', 'us2', etc.\n listId: string;\n /** If true, use Mailchimp for storage too (adds to list on subscribe) */\n useAsStorage?: boolean;\n}\n\n/**\n * Email adapter for Mailchimp\n * \n * Note: Mailchimp works differently from other adapters. It manages its own\n * subscriber list, so this adapter can optionally act as both email AND storage.\n * \n * @example\n * ```ts\n * import { createMailchimpAdapter } from '@volchok/newsletter-kit/adapters/email';\n * \n * const emailAdapter = createMailchimpAdapter({\n * apiKey: process.env.MAILCHIMP_API_KEY!,\n * server: 'us1',\n * listId: 'your-list-id',\n * from: 'newsletter@yourdomain.com',\n * useAsStorage: true, // Use Mailchimp's list as storage\n * });\n * ```\n */\nexport function createMailchimpAdapter(config: MailchimpAdapterConfig): EmailAdapter {\n const { apiKey, server, listId, from, adminEmail, useAsStorage } = config;\n\n async function getClient() {\n // Dynamic import\n const mailchimp = await import('@mailchimp/mailchimp_marketing');\n mailchimp.default.setConfig({\n apiKey,\n server,\n });\n return mailchimp.default;\n }\n\n async function addOrUpdateMember(email: string, status: 'subscribed' | 'pending') {\n const client = await getClient();\n const subscriberHash = await hashEmail(email);\n\n try {\n await client.lists.setListMember(listId, subscriberHash, {\n email_address: email,\n status_if_new: status,\n });\n } catch (error) {\n // If member doesn't exist, add them\n await client.lists.addListMember(listId, {\n email_address: email,\n status,\n });\n }\n }\n\n async function hashEmail(email: string): Promise<string> {\n const crypto = await import('crypto');\n return crypto.createHash('md5').update(email.toLowerCase()).digest('hex');\n }\n\n // Mailchimp handles its own confirmation emails when status is 'pending'\n // We provide fallback implementations that work with Mailchimp's system\n\n return {\n async sendConfirmation(email: string, _token: string, _confirmUrl: string) {\n if (useAsStorage) {\n // Add to Mailchimp with 'pending' status - Mailchimp sends its own confirmation\n await addOrUpdateMember(email, 'pending');\n }\n // If not using as storage, the storage adapter handles this\n // and we don't send duplicate confirmation emails\n },\n\n async sendWelcome(email: string) {\n // Mailchimp can be configured to send welcome emails automatically\n // via automations. If useAsStorage is true, we update status to subscribed.\n if (useAsStorage) {\n await addOrUpdateMember(email, 'subscribed');\n }\n },\n\n async sendUnsubscribed(email: string) {\n if (useAsStorage) {\n const client = await getClient();\n const subscriberHash = await hashEmail(email);\n await client.lists.updateListMember(listId, subscriberHash, {\n status: 'unsubscribed',\n });\n }\n },\n\n async notifyAdmin(subscriber: Subscriber) {\n if (!adminEmail) return;\n\n // Mailchimp doesn't have a built-in way to notify admins\n // You'd typically set up a webhook or automation for this\n // For now, we log a warning\n console.warn(\n `[newsletter-kit] Mailchimp adapter: Admin notification not implemented. ` +\n `Consider setting up a Mailchimp webhook or automation. ` +\n `New subscriber: ${subscriber.email}`\n );\n },\n };\n}\n\n/**\n * Create a combined Mailchimp adapter that handles both email and storage\n * \n * This is useful when you want Mailchimp to be your single source of truth\n * for subscribers.\n */\nexport function createMailchimpStorageAdapter(config: MailchimpAdapterConfig) {\n const { apiKey, server, listId } = config;\n\n async function getClient() {\n const mailchimp = await import('@mailchimp/mailchimp_marketing');\n mailchimp.default.setConfig({\n apiKey,\n server,\n });\n return mailchimp.default;\n }\n\n async function hashEmail(email: string): Promise<string> {\n const crypto = await import('crypto');\n return crypto.createHash('md5').update(email.toLowerCase()).digest('hex');\n }\n\n return {\n async createSubscriber(input: { email: string; source?: string; tags?: string[] }) {\n const client = await getClient();\n\n await client.lists.addListMember(listId, {\n email_address: input.email,\n status: 'pending', // Mailchimp handles double opt-in\n tags: input.tags,\n merge_fields: {\n SOURCE: input.source || '',\n },\n });\n\n return {\n id: await hashEmail(input.email),\n email: input.email,\n status: 'pending' as const,\n source: input.source,\n tags: input.tags,\n createdAt: new Date(),\n updatedAt: new Date(),\n };\n },\n\n async getSubscriberByEmail(email: string) {\n const client = await getClient();\n const hash = await hashEmail(email);\n\n try {\n const member = await client.lists.getListMember(listId, hash) as {\n email_address: string;\n status: string;\n tags?: Array<{ name: string }>;\n timestamp_signup?: string;\n timestamp_opt?: string;\n last_changed?: string;\n };\n return {\n id: hash,\n email: member.email_address,\n status: member.status === 'subscribed' ? 'confirmed' : member.status,\n tags: member.tags?.map((t) => t.name),\n createdAt: new Date(member.timestamp_signup || member.timestamp_opt || Date.now()),\n updatedAt: new Date(member.last_changed || Date.now()),\n };\n } catch {\n return null;\n }\n },\n\n // Note: Mailchimp doesn't use tokens - it has its own confirmation system\n async getSubscriberByToken() {\n console.warn('[newsletter-kit] Mailchimp uses its own confirmation system');\n return null;\n },\n\n async confirmSubscriber(_token: string) {\n console.warn('[newsletter-kit] Mailchimp handles confirmation automatically');\n return null;\n },\n\n async unsubscribe(email: string) {\n const client = await getClient();\n const hash = await hashEmail(email);\n\n await client.lists.updateListMember(listId, hash, {\n status: 'unsubscribed',\n });\n\n return true;\n },\n };\n}\n","import type { StorageAdapter, Subscriber, SubscribeInput, SubscriptionStatus } from '../../types';\nimport { generateToken } from '../../utils/crypto';\n\n/**\n * Prisma schema required for this adapter:\n * \n * **PostgreSQL / Neon / MySQL:**\n * ```prisma\n * model NewsletterSubscriber {\n * id String @id @default(cuid())\n * email String @unique\n * status String @default(\"pending\") // pending, confirmed, unsubscribed\n * token String? @unique\n * source String?\n * tags String[] @default([])\n * metadata Json?\n * consentIp String?\n * consentAt DateTime?\n * confirmedAt DateTime?\n * unsubscribedAt DateTime?\n * createdAt DateTime @default(now())\n * updatedAt DateTime @updatedAt\n * \n * @@index([status])\n * @@index([source])\n * }\n * ```\n * \n * **MongoDB:**\n * ```prisma\n * model NewsletterSubscriber {\n * id String @id @default(auto()) @map(\"_id\") @db.ObjectId\n * email String @unique\n * status String @default(\"pending\") // pending, confirmed, unsubscribed\n * token String? @unique\n * source String?\n * tags String[] @default([])\n * metadata Json?\n * consentIp String?\n * consentAt DateTime?\n * confirmedAt DateTime?\n * unsubscribedAt DateTime?\n * createdAt DateTime @default(now())\n * updatedAt DateTime @updatedAt\n * \n * @@index([status])\n * @@index([source])\n * }\n * ```\n */\n\ninterface PrismaClient {\n newsletterSubscriber: {\n upsert: (args: unknown) => Promise<unknown>;\n findUnique: (args: unknown) => Promise<unknown>;\n findFirst: (args: unknown) => Promise<unknown>;\n findMany: (args: unknown) => Promise<unknown[]>;\n update: (args: unknown) => Promise<unknown>;\n delete: (args: unknown) => Promise<unknown>;\n count: (args?: unknown) => Promise<number>;\n };\n}\n\ninterface PrismaAdapterConfig {\n /** Your Prisma client instance */\n prisma: PrismaClient;\n /** Model name if different from 'newsletterSubscriber' */\n modelName?: string;\n}\n\nfunction mapToSubscriber(record: Record<string, unknown>): Subscriber {\n return {\n id: record.id as string,\n email: record.email as string,\n status: record.status as SubscriptionStatus,\n source: record.source as string | undefined,\n tags: record.tags as string[] | undefined,\n metadata: record.metadata as Record<string, unknown> | undefined,\n consentIp: record.consentIp as string | undefined,\n consentAt: record.consentAt ? new Date(record.consentAt as string) : undefined,\n confirmedAt: record.confirmedAt ? new Date(record.confirmedAt as string) : undefined,\n unsubscribedAt: record.unsubscribedAt ? new Date(record.unsubscribedAt as string) : undefined,\n createdAt: new Date(record.createdAt as string),\n updatedAt: new Date(record.updatedAt as string),\n };\n}\n\n/**\n * Storage adapter for Prisma ORM\n * \n * @example\n * ```ts\n * import { createPrismaAdapter } from '@volchok/newsletter-kit/adapters/storage';\n * import { prisma } from '@/lib/prisma';\n * \n * const storageAdapter = createPrismaAdapter({ prisma });\n * ```\n */\nexport function createPrismaAdapter(config: PrismaAdapterConfig): StorageAdapter {\n const { prisma } = config;\n const model = prisma.newsletterSubscriber;\n\n return {\n async createSubscriber(input: SubscribeInput, token?: string): Promise<Subscriber> {\n const confirmToken = token || generateToken();\n\n const record = await model.upsert({\n where: { email: input.email.toLowerCase() },\n update: {\n token: confirmToken,\n source: input.source,\n tags: input.tags || [],\n metadata: input.metadata,\n consentIp: input.ip,\n consentAt: new Date(),\n // Reset to pending if resubscribing\n status: 'pending',\n unsubscribedAt: null,\n },\n create: {\n email: input.email.toLowerCase(),\n token: confirmToken,\n status: 'pending',\n source: input.source,\n tags: input.tags || [],\n metadata: input.metadata,\n consentIp: input.ip,\n consentAt: new Date(),\n },\n });\n\n return mapToSubscriber(record as Record<string, unknown>);\n },\n\n async getSubscriberByEmail(email: string): Promise<Subscriber | null> {\n const record = await model.findUnique({\n where: { email: email.toLowerCase() },\n });\n\n return record ? mapToSubscriber(record as Record<string, unknown>) : null;\n },\n\n async getSubscriberByToken(token: string): Promise<Subscriber | null> {\n const record = await model.findFirst({\n where: { token },\n });\n\n return record ? mapToSubscriber(record as Record<string, unknown>) : null;\n },\n\n async confirmSubscriber(token: string): Promise<Subscriber | null> {\n try {\n const record = await model.update({\n where: { token },\n data: {\n status: 'confirmed',\n confirmedAt: new Date(),\n token: null, // Clear token after use\n },\n });\n\n return mapToSubscriber(record as Record<string, unknown>);\n } catch {\n // Token not found\n return null;\n }\n },\n\n async unsubscribe(email: string): Promise<boolean> {\n try {\n await model.update({\n where: { email: email.toLowerCase() },\n data: {\n status: 'unsubscribed',\n unsubscribedAt: new Date(),\n },\n });\n return true;\n } catch {\n return false;\n }\n },\n\n async listSubscribers(options?: {\n status?: SubscriptionStatus;\n source?: string;\n tags?: string[];\n limit?: number;\n offset?: number;\n }): Promise<{ subscribers: Subscriber[]; total: number }> {\n const where: Record<string, unknown> = {};\n\n if (options?.status) {\n where.status = options.status;\n }\n if (options?.source) {\n where.source = options.source;\n }\n if (options?.tags?.length) {\n where.tags = { hasSome: options.tags };\n }\n\n const [records, total] = await Promise.all([\n model.findMany({\n where,\n take: options?.limit || 100,\n skip: options?.offset || 0,\n orderBy: { createdAt: 'desc' },\n }),\n model.count({ where }),\n ]);\n\n return {\n subscribers: records.map((r) => mapToSubscriber(r as Record<string, unknown>)),\n total,\n };\n },\n\n async deleteSubscriber(email: string): Promise<boolean> {\n try {\n await model.delete({\n where: { email: email.toLowerCase() },\n });\n return true;\n } catch {\n return false;\n }\n },\n\n async updateSubscriber(email: string, data: Partial<Subscriber>): Promise<Subscriber | null> {\n try {\n const record = await model.update({\n where: { email: email.toLowerCase() },\n data: {\n ...(data.source !== undefined && { source: data.source }),\n ...(data.tags !== undefined && { tags: data.tags }),\n ...(data.metadata !== undefined && { metadata: data.metadata }),\n },\n });\n\n return mapToSubscriber(record as Record<string, unknown>);\n } catch {\n return null;\n }\n },\n };\n}\n","import type { StorageAdapter, Subscriber, SubscribeInput, SubscriptionStatus } from '../../types';\nimport { generateToken } from '../../utils/crypto';\n\n/**\n * Supabase table schema (SQL):\n * \n * ```sql\n * CREATE TABLE newsletter_subscribers (\n * id UUID DEFAULT gen_random_uuid() PRIMARY KEY,\n * email TEXT UNIQUE NOT NULL,\n * status TEXT DEFAULT 'pending' CHECK (status IN ('pending', 'confirmed', 'unsubscribed')),\n * token TEXT UNIQUE,\n * source TEXT,\n * tags TEXT[] DEFAULT '{}',\n * metadata JSONB,\n * consent_ip TEXT,\n * consent_at TIMESTAMPTZ,\n * confirmed_at TIMESTAMPTZ,\n * unsubscribed_at TIMESTAMPTZ,\n * created_at TIMESTAMPTZ DEFAULT NOW(),\n * updated_at TIMESTAMPTZ DEFAULT NOW()\n * );\n * \n * -- Indexes\n * CREATE INDEX idx_newsletter_subscribers_status ON newsletter_subscribers(status);\n * CREATE INDEX idx_newsletter_subscribers_source ON newsletter_subscribers(source);\n * CREATE INDEX idx_newsletter_subscribers_token ON newsletter_subscribers(token);\n * \n * -- Updated at trigger\n * CREATE OR REPLACE FUNCTION update_updated_at()\n * RETURNS TRIGGER AS $$\n * BEGIN\n * NEW.updated_at = NOW();\n * RETURN NEW;\n * END;\n * $$ LANGUAGE plpgsql;\n * \n * CREATE TRIGGER newsletter_subscribers_updated_at\n * BEFORE UPDATE ON newsletter_subscribers\n * FOR EACH ROW\n * EXECUTE FUNCTION update_updated_at();\n * ```\n */\n\ninterface SupabaseClient {\n from: (table: string) => {\n upsert: (data: unknown, options?: unknown) => Promise<{ data: unknown; error: unknown }>;\n select: (columns?: string) => {\n eq: (column: string, value: unknown) => {\n single: () => Promise<{ data: unknown; error: unknown }>;\n range: (from: number, to: number) => Promise<{ data: unknown[]; error: unknown }>;\n };\n contains: (column: string, value: unknown) => {\n range: (from: number, to: number) => Promise<{ data: unknown[]; error: unknown }>;\n };\n order: (column: string, options?: unknown) => {\n range: (from: number, to: number) => Promise<{ data: unknown[]; error: unknown }>;\n };\n };\n update: (data: unknown) => {\n eq: (column: string, value: unknown) => Promise<{ data: unknown; error: unknown }>;\n };\n delete: () => {\n eq: (column: string, value: unknown) => Promise<{ data: unknown; error: unknown }>;\n };\n };\n}\n\ninterface SupabaseAdapterConfig {\n /** Your Supabase client instance */\n supabase: SupabaseClient;\n /** Table name if different from 'newsletter_subscribers' */\n tableName?: string;\n}\n\ninterface SupabaseRecord {\n id: string;\n email: string;\n status: SubscriptionStatus;\n token?: string;\n source?: string;\n tags?: string[];\n metadata?: Record<string, unknown>;\n consent_ip?: string;\n consent_at?: string;\n confirmed_at?: string;\n unsubscribed_at?: string;\n created_at: string;\n updated_at: string;\n}\n\nfunction mapToSubscriber(record: SupabaseRecord): Subscriber {\n return {\n id: record.id,\n email: record.email,\n status: record.status,\n source: record.source,\n tags: record.tags,\n metadata: record.metadata,\n consentIp: record.consent_ip,\n consentAt: record.consent_at ? new Date(record.consent_at) : undefined,\n confirmedAt: record.confirmed_at ? new Date(record.confirmed_at) : undefined,\n unsubscribedAt: record.unsubscribed_at ? new Date(record.unsubscribed_at) : undefined,\n createdAt: new Date(record.created_at),\n updatedAt: new Date(record.updated_at),\n };\n}\n\n/**\n * Storage adapter for Supabase\n * \n * @example\n * ```ts\n * import { createSupabaseAdapter } from '@volchok/newsletter-kit/adapters/storage';\n * import { createClient } from '@supabase/supabase-js';\n * \n * const supabase = createClient(\n * process.env.SUPABASE_URL!,\n * process.env.SUPABASE_SERVICE_KEY!\n * );\n * \n * const storageAdapter = createSupabaseAdapter({ supabase });\n * ```\n */\nexport function createSupabaseAdapter(config: SupabaseAdapterConfig): StorageAdapter {\n const { supabase, tableName = 'newsletter_subscribers' } = config;\n\n return {\n async createSubscriber(input: SubscribeInput, token?: string): Promise<Subscriber> {\n const confirmToken = token || generateToken();\n const email = input.email.toLowerCase();\n\n const { data, error } = await supabase\n .from(tableName)\n .upsert(\n {\n email,\n token: confirmToken,\n status: 'pending',\n source: input.source,\n tags: input.tags || [],\n metadata: input.metadata,\n consent_ip: input.ip,\n consent_at: new Date().toISOString(),\n unsubscribed_at: null,\n },\n { onConflict: 'email' }\n );\n\n if (error) throw error;\n\n // Fetch the created/updated record\n const { data: record } = await supabase\n .from(tableName)\n .select()\n .eq('email', email)\n .single();\n\n return mapToSubscriber(record as SupabaseRecord);\n },\n\n async getSubscriberByEmail(email: string): Promise<Subscriber | null> {\n const { data, error } = await supabase\n .from(tableName)\n .select()\n .eq('email', email.toLowerCase())\n .single();\n\n if (error || !data) return null;\n return mapToSubscriber(data as SupabaseRecord);\n },\n\n async getSubscriberByToken(token: string): Promise<Subscriber | null> {\n const { data, error } = await supabase\n .from(tableName)\n .select()\n .eq('token', token)\n .single();\n\n if (error || !data) return null;\n return mapToSubscriber(data as SupabaseRecord);\n },\n\n async confirmSubscriber(token: string): Promise<Subscriber | null> {\n const { error } = await supabase\n .from(tableName)\n .update({\n status: 'confirmed',\n confirmed_at: new Date().toISOString(),\n token: null,\n })\n .eq('token', token);\n\n if (error) return null;\n\n // Fetch updated record by getting any confirmed subscriber updated recently\n // This is a bit hacky - ideally we'd get the email from the token first\n const { data } = await supabase\n .from(tableName)\n .select()\n .eq('token', null)\n .single();\n\n // Better approach: get by email after updating\n return data ? mapToSubscriber(data as SupabaseRecord) : null;\n },\n\n async unsubscribe(email: string): Promise<boolean> {\n const { error } = await supabase\n .from(tableName)\n .update({\n status: 'unsubscribed',\n unsubscribed_at: new Date().toISOString(),\n })\n .eq('email', email.toLowerCase());\n\n return !error;\n },\n\n async listSubscribers(options?: {\n status?: SubscriptionStatus;\n source?: string;\n tags?: string[];\n limit?: number;\n offset?: number;\n }): Promise<{ subscribers: Subscriber[]; total: number }> {\n const limit = options?.limit || 100;\n const offset = options?.offset || 0;\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n let query: any = supabase.from(tableName).select();\n\n if (options?.status) {\n query = query.eq('status', options.status);\n }\n if (options?.source) {\n query = query.eq('source', options.source);\n }\n if (options?.tags?.length) {\n query = query.contains('tags', options.tags);\n }\n\n const { data, error } = await query\n .order('created_at', { ascending: false })\n .range(offset, offset + limit - 1);\n\n if (error) throw error;\n\n return {\n subscribers: (data || []).map((r: SupabaseRecord) => mapToSubscriber(r)),\n total: (data || []).length, // Note: Supabase doesn't return total count easily\n };\n },\n\n async deleteSubscriber(email: string): Promise<boolean> {\n const { error } = await supabase\n .from(tableName)\n .delete()\n .eq('email', email.toLowerCase());\n\n return !error;\n },\n\n async updateSubscriber(email: string, data: Partial<Subscriber>): Promise<Subscriber | null> {\n const updateData: Record<string, unknown> = {};\n if (data.source !== undefined) updateData.source = data.source;\n if (data.tags !== undefined) updateData.tags = data.tags;\n if (data.metadata !== undefined) updateData.metadata = data.metadata;\n\n const { error } = await supabase\n .from(tableName)\n .update(updateData)\n .eq('email', email.toLowerCase());\n\n if (error) return null;\n\n return this.getSubscriberByEmail(email);\n },\n };\n}\n"],"mappings":"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","names":["Fragment","jsx","jsxs","useState","useCallback","crypto","mapToSubscriber"]}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { N as NewsletterConfig, g as SubscribeResult, C as ConfirmResult, U as UnsubscribeResult, S as StorageAdapter, h as Subscriber, i as SubscriptionStatus } from '../types-BmajlhNp.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Create newsletter API handlers for Next.js App Router
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* ```ts
|
|
8
|
+
* // lib/newsletter.ts
|
|
9
|
+
* import { createNewsletterHandlers } from '@volchok/newsletter-kit/server';
|
|
10
|
+
* import { createResendAdapter } from '@volchok/newsletter-kit/adapters/email';
|
|
11
|
+
* import { createPrismaAdapter } from '@volchok/newsletter-kit/adapters/storage';
|
|
12
|
+
*
|
|
13
|
+
* export const newsletter = createNewsletterHandlers({
|
|
14
|
+
* emailAdapter: createResendAdapter({
|
|
15
|
+
* apiKey: process.env.RESEND_API_KEY!,
|
|
16
|
+
* from: 'newsletter@example.com',
|
|
17
|
+
* }),
|
|
18
|
+
* storageAdapter: createPrismaAdapter({ prisma }),
|
|
19
|
+
* baseUrl: process.env.NEXT_PUBLIC_URL!,
|
|
20
|
+
* doubleOptIn: true,
|
|
21
|
+
* });
|
|
22
|
+
*
|
|
23
|
+
* // app/api/newsletter/subscribe/route.ts
|
|
24
|
+
* import { newsletter } from '@/lib/newsletter';
|
|
25
|
+
*
|
|
26
|
+
* export const POST = newsletter.subscribe;
|
|
27
|
+
* ```
|
|
28
|
+
*/
|
|
29
|
+
declare function createNewsletterHandlers(config: NewsletterConfig): {
|
|
30
|
+
/**
|
|
31
|
+
* POST /api/newsletter/subscribe
|
|
32
|
+
*/
|
|
33
|
+
subscribe: (req: Request) => Promise<Response>;
|
|
34
|
+
/**
|
|
35
|
+
* GET /api/newsletter/confirm?token=xxx
|
|
36
|
+
*
|
|
37
|
+
* Can also return a redirect for better UX
|
|
38
|
+
*/
|
|
39
|
+
confirm: (req: Request) => Promise<Response>;
|
|
40
|
+
/**
|
|
41
|
+
* POST /api/newsletter/unsubscribe
|
|
42
|
+
* Also supports GET with email query param
|
|
43
|
+
*/
|
|
44
|
+
unsubscribe: (req: Request) => Promise<Response>;
|
|
45
|
+
/**
|
|
46
|
+
* Internal handlers for custom implementations
|
|
47
|
+
*/
|
|
48
|
+
handlers: {
|
|
49
|
+
subscribe: (req: Request) => Promise<SubscribeResult>;
|
|
50
|
+
confirm: (token: string) => Promise<ConfirmResult>;
|
|
51
|
+
unsubscribe: (email: string) => Promise<UnsubscribeResult>;
|
|
52
|
+
};
|
|
53
|
+
/**
|
|
54
|
+
* Access to storage adapter for admin features
|
|
55
|
+
*/
|
|
56
|
+
storage: StorageAdapter;
|
|
57
|
+
/**
|
|
58
|
+
* Get subscriber by email (for admin/API use)
|
|
59
|
+
*/
|
|
60
|
+
getSubscriber: (email: string) => Promise<Subscriber | null>;
|
|
61
|
+
/**
|
|
62
|
+
* List subscribers (for admin/export use)
|
|
63
|
+
*/
|
|
64
|
+
listSubscribers: ((options?: {
|
|
65
|
+
status?: SubscriptionStatus;
|
|
66
|
+
source?: string;
|
|
67
|
+
tags?: string[];
|
|
68
|
+
limit?: number;
|
|
69
|
+
offset?: number;
|
|
70
|
+
}) => Promise<{
|
|
71
|
+
subscribers: Subscriber[];
|
|
72
|
+
total: number;
|
|
73
|
+
}>) | undefined;
|
|
74
|
+
};
|
|
75
|
+
type NewsletterHandlers = ReturnType<typeof createNewsletterHandlers>;
|
|
76
|
+
|
|
77
|
+
export { NewsletterConfig, type NewsletterHandlers, createNewsletterHandlers };
|