UNPKG

payload-plugin-newsletter

Version:

Complete newsletter management plugin for Payload CMS with subscriber management, magic link authentication, and email service integration

714 lines (697 loc) 19.7 kB
import { Field, Block, RichTextField, Config, Endpoint, CollectionConfig, GlobalConfig } from 'payload'; /** * Core types for broadcast management functionality */ /** * Represents a broadcast (individual email campaign) in the system */ interface Broadcast { id: string; name: string; subject: string; preheader?: string; content: string; sendStatus: BroadcastStatus; trackOpens: boolean; trackClicks: boolean; replyTo?: string; recipientCount?: number; sentAt?: Date; scheduledAt?: Date; createdAt: Date; updatedAt: Date; providerData?: Record<string, any>; providerId?: string; providerType?: 'broadcast' | 'resend'; } /** * Possible statuses for a broadcast */ declare enum BroadcastStatus { DRAFT = "draft", SCHEDULED = "scheduled", SENDING = "sending", SENT = "sent", FAILED = "failed", PAUSED = "paused", CANCELED = "canceled" } /** * Options for listing broadcasts */ interface ListBroadcastOptions { limit?: number; offset?: number; status?: BroadcastStatus; sortBy?: 'createdAt' | 'updatedAt' | 'sentAt' | 'name'; sortOrder?: 'asc' | 'desc'; } /** * Response from listing broadcasts */ interface ListBroadcastResponse<T = Broadcast> { items: T[]; total: number; limit: number; offset: number; hasMore: boolean; } /** * Input for creating a new broadcast */ interface CreateBroadcastInput { name: string; subject: string; preheader?: string; content: string; trackOpens?: boolean; trackClicks?: boolean; replyTo?: string; audienceIds?: string[]; } /** * Input for updating an existing broadcast */ interface UpdateBroadcastInput { name?: string; subject?: string; preheader?: string; content?: string; trackOpens?: boolean; trackClicks?: boolean; replyTo?: string; audienceIds?: string[]; } /** * Options for sending a broadcast */ interface SendBroadcastOptions { audienceIds?: string[]; testMode?: boolean; testRecipients?: string[]; } /** * Analytics data for a broadcast */ interface BroadcastAnalytics { sent: number; delivered: number; opened: number; clicked: number; bounced: number; complained: number; unsubscribed: number; deliveryRate?: number; openRate?: number; clickRate?: number; bounceRate?: number; } /** * Capabilities that a broadcast provider supports */ interface BroadcastProviderCapabilities { supportsScheduling: boolean; supportsSegmentation: boolean; supportsAnalytics: boolean; supportsABTesting: boolean; supportsTemplates: boolean; supportsPersonalization: boolean; maxRecipientsPerSend?: number; editableStatuses: BroadcastStatus[]; supportedContentTypes: ('html' | 'text' | 'react')[]; supportsMultipleChannels: boolean; supportsChannelSegmentation: boolean; } /** * Provider interfaces for broadcast management */ /** * Main interface for broadcast providers */ interface BroadcastProvider$1 { /** * Get the provider name */ readonly name: string; /** * List broadcasts with pagination */ list(options?: ListBroadcastOptions): Promise<ListBroadcastResponse<Broadcast>>; /** * Get a specific broadcast by ID */ get(id: string): Promise<Broadcast>; /** * Create a new broadcast */ create(data: CreateBroadcastInput): Promise<Broadcast>; /** * Update an existing broadcast */ update(id: string, data: UpdateBroadcastInput): Promise<Broadcast>; /** * Delete a broadcast */ delete(id: string): Promise<void>; /** * Send a broadcast immediately or to test recipients */ send(id: string, options?: SendBroadcastOptions): Promise<Broadcast>; /** * Schedule a broadcast for future sending */ schedule(id: string, scheduledAt: Date): Promise<Broadcast>; /** * Cancel a scheduled broadcast */ cancelSchedule(id: string): Promise<Broadcast>; /** * Get analytics for a broadcast */ getAnalytics(id: string): Promise<BroadcastAnalytics>; /** * Get provider capabilities */ getCapabilities(): BroadcastProviderCapabilities; /** * Validate that the provider is properly configured */ validateConfiguration(): Promise<boolean>; } interface EmailWrapperOptions { preheader?: string; subject?: string; documentData?: Record<string, any>; } interface BroadcastCustomizations { additionalFields?: Field[]; customBlocks?: Block[]; fieldOverrides?: { content?: (defaultField: RichTextField) => RichTextField; }; /** * Custom block email converter * @param node - The block node from Lexical editor state * @param mediaUrl - Base URL for media files * @returns Promise<string> - The email-safe HTML for the block */ customBlockConverter?: (node: any, mediaUrl?: string) => Promise<string>; /** * Fields to populate in custom blocks before email conversion * Can be an array of field names or a function that returns field names based on block type * This is useful for upload fields that need to be populated with full media objects * * @example * // Array of field names to always populate * populateFields: ['bannerImage', 'sponsorLogo'] * * @example * // Function to return fields based on block type * populateFields: (blockType) => { * if (blockType === 'newsletter-hero') return ['bannerImage', 'sponsorLogo'] * if (blockType === 'content-section') return ['featuredImage'] * return [] * } */ populateFields?: string[] | ((blockType: string) => string[]); /** * Email preview customization options */ emailPreview?: { /** * Whether to wrap preview content in default email template * @default true */ wrapInTemplate?: boolean; /** * Custom wrapper function for preview content * Receives the converted HTML and should return wrapped HTML */ customWrapper?: (content: string, options?: EmailWrapperOptions) => string | Promise<string>; /** * Custom preview component to replace the default one entirely * If provided, this component will be used instead of the default EmailPreview */ customPreviewComponent?: string; }; } interface NewsletterPluginConfig { /** * Enable or disable the plugin * @default true */ enabled?: boolean; /** * Slug for the subscribers collection * @default 'subscribers' */ subscribersSlug?: string; /** * Slug for the newsletter settings global * @default 'newsletter-settings' */ settingsSlug?: string; /** * Authentication configuration for magic links */ auth?: { /** * Enable magic link authentication * @default true */ enabled?: boolean; /** * Token expiration time * @default '7d' */ tokenExpiration?: string; /** * Path where magic link redirects * @default '/newsletter/verify' */ magicLinkPath?: string; /** * Allow unsubscribed users to sign in * @default false */ allowUnsubscribedSignin?: boolean; /** * Allow unsubscribed users to resubscribe * @default false */ allowResubscribe?: boolean; }; /** * Access control configuration */ access?: { /** * Custom function to determine if a user is an admin * @param user - The authenticated user object * @returns true if the user should have admin access */ isAdmin?: (user: any) => boolean; }; /** * Email provider configuration */ providers: { /** * Default provider to use */ default: 'resend' | 'broadcast' | string; /** * Resend provider configuration */ resend?: ResendProviderConfig; /** * Broadcast provider configuration */ broadcast?: BroadcastProviderConfig; }; /** * Field customization options */ fields?: { /** * Override default fields */ overrides?: (args: { defaultFields: Field[]; }) => Field[]; /** * Additional custom fields */ additional?: Field[]; }; /** * Email template components */ templates?: { /** * Welcome email template */ welcome?: React.ComponentType<WelcomeEmailProps>; /** * Magic link email template */ magicLink?: React.ComponentType<MagicLinkEmailProps>; }; /** * Plugin hooks */ hooks?: { beforeSubscribe?: (args: BeforeSubscribeArgs) => void | Promise<void>; afterSubscribe?: (args: AfterSubscribeArgs) => void | Promise<void>; beforeUnsubscribe?: (args: BeforeUnsubscribeArgs) => void | Promise<void>; afterUnsubscribe?: (args: AfterUnsubscribeArgs) => void | Promise<void>; }; /** * UI component overrides */ components?: { signupForm?: React.ComponentType<SignupFormProps>; preferencesForm?: React.ComponentType<PreferencesFormProps>; }; /** * Feature flags */ features?: { /** * Lead magnet configuration */ leadMagnets?: { enabled?: boolean; collection?: string; }; /** * Post-signup survey configuration */ surveys?: { enabled?: boolean; questions?: SurveyQuestion[]; }; /** * UTM tracking configuration */ utmTracking?: { enabled?: boolean; fields?: string[]; }; /** * Newsletter scheduling configuration */ newsletterScheduling?: { enabled?: boolean; /** * Collections to add newsletter fields to * Can be a string for single collection or array for multiple * @example 'articles' or ['articles', 'posts', 'updates'] */ collections?: string | string[]; /** * Field configuration */ fields?: { /** * Group name for newsletter fields * @default 'newsletterScheduling' */ groupName?: string; /** * Rich text field name to use for content * @default 'content' */ contentField?: string; /** * Whether to create a markdown companion field * @default true */ createMarkdownField?: boolean; }; }; /** * Newsletter management configuration */ newsletterManagement?: { /** * Enable newsletter management features * @default false */ enabled?: boolean; /** * Collection names for broadcast management */ collections?: { /** * Broadcasts collection slug * @default 'broadcasts' */ broadcasts?: string; }; /** * Optional: Custom broadcast provider implementation * If not provided, will use the default email provider */ provider?: BroadcastProvider$1; }; }; /** * Internationalization configuration */ i18n?: { defaultLocale?: string; locales?: string[]; }; /** * Custom email templates */ customTemplates?: { [key: string]: React.ComponentType<any>; }; /** * Customization options for plugin collections */ customizations?: { broadcasts?: BroadcastCustomizations; }; } interface ResendProviderConfig { apiKey: string; fromEmail?: string; fromAddress?: string; fromName?: string; replyTo?: string; audienceIds?: { [locale: string]: { production?: string; development?: string; }; }; } interface BroadcastProviderConfig { apiUrl: string; token: string; fromEmail?: string; fromAddress?: string; fromName?: string; replyTo?: string; } interface Subscriber { id: string; email: string; name?: string; locale?: string; subscriptionStatus: 'active' | 'unsubscribed' | 'pending'; emailPreferences?: { newsletter?: boolean; announcements?: boolean; [key: string]: boolean | undefined; }; source?: string; importedFromProvider?: boolean; utmParameters?: { source?: string; medium?: string; campaign?: string; content?: string; term?: string; }; signupMetadata?: { ipAddress?: string; userAgent?: string; referrer?: string; signupPage?: string; }; leadMagnet?: string; unsubscribedAt?: string; magicLinkToken?: string; magicLinkTokenExpiry?: string; createdAt: string; updatedAt: string; } interface WelcomeEmailProps { subscriber: Subscriber; unsubscribeUrl: string; preferencesUrl: string; } interface MagicLinkEmailProps { magicLinkUrl: string; subscriber: Subscriber; } interface SignupFormProps { onSuccess?: (subscriber: Subscriber) => void; onError?: (error: Error) => void; showName?: boolean; showPreferences?: boolean; leadMagnet?: { id: string; title: string; description?: string; }; className?: string; styles?: { form?: React.CSSProperties; inputGroup?: React.CSSProperties; label?: React.CSSProperties; input?: React.CSSProperties; button?: React.CSSProperties; buttonDisabled?: React.CSSProperties; error?: React.CSSProperties; success?: React.CSSProperties; checkbox?: React.CSSProperties; checkboxInput?: React.CSSProperties; checkboxLabel?: React.CSSProperties; }; apiEndpoint?: string; buttonText?: string; loadingText?: string; successMessage?: string; placeholders?: { email?: string; name?: string; }; labels?: { email?: string; name?: string; newsletter?: string; announcements?: string; }; } interface PreferencesFormProps { subscriber?: Subscriber; onSuccess?: (subscriber: Subscriber) => void; onError?: (error: Error) => void; className?: string; styles?: { container?: React.CSSProperties; heading?: React.CSSProperties; form?: React.CSSProperties; section?: React.CSSProperties; sectionTitle?: React.CSSProperties; inputGroup?: React.CSSProperties; label?: React.CSSProperties; input?: React.CSSProperties; select?: React.CSSProperties; checkbox?: React.CSSProperties; checkboxInput?: React.CSSProperties; checkboxLabel?: React.CSSProperties; buttonGroup?: React.CSSProperties; button?: React.CSSProperties; primaryButton?: React.CSSProperties; secondaryButton?: React.CSSProperties; dangerButton?: React.CSSProperties; error?: React.CSSProperties; success?: React.CSSProperties; info?: React.CSSProperties; }; sessionToken?: string; apiEndpoint?: string; showUnsubscribe?: boolean; locales?: string[]; labels?: { title?: string; personalInfo?: string; emailPreferences?: string; name?: string; language?: string; newsletter?: string; announcements?: string; saveButton?: string; unsubscribeButton?: string; saving?: string; saved?: string; unsubscribeConfirm?: string; }; } interface BeforeSubscribeArgs { data: Partial<Subscriber>; req: any; } interface AfterSubscribeArgs { doc: Subscriber; req: any; } interface BeforeUnsubscribeArgs { email: string; req: any; } interface AfterUnsubscribeArgs { doc: Subscriber; req: any; } interface SurveyQuestion { id: string; question: string; type: 'text' | 'select' | 'multiselect' | 'radio'; options?: string[]; required?: boolean; } declare module 'payload' { interface BasePayload { newsletterEmailService?: any; broadcastProvider?: BroadcastProvider$1; newsletterProvider?: BroadcastProvider$1; } } declare const newsletterPlugin: (pluginConfig: NewsletterPluginConfig) => (incomingConfig: Config) => Config; declare const createSubscribeEndpoint: (config: NewsletterPluginConfig) => Endpoint; declare const createUnsubscribeEndpoint: (config: NewsletterPluginConfig) => Endpoint; declare const createPreferencesEndpoint: (config: NewsletterPluginConfig) => Endpoint; declare const createVerifyMagicLinkEndpoint: (config: NewsletterPluginConfig) => Endpoint; declare const createSubscribersCollection: (pluginConfig: NewsletterPluginConfig) => CollectionConfig; declare const createBroadcastsCollection: (pluginConfig: NewsletterPluginConfig) => CollectionConfig; declare const createNewsletterSettingsGlobal: (pluginConfig: NewsletterPluginConfig) => GlobalConfig; interface EmailProvider { send(params: SendEmailParams): Promise<void>; addContact(contact: Subscriber): Promise<void>; updateContact(contact: Subscriber): Promise<void>; removeContact(email: string): Promise<void>; getProvider(): string; } interface SendEmailParams { to: string | string[]; subject: string; html?: string; text?: string; react?: React.ReactElement; from?: { email: string; name?: string; }; replyTo?: string; } declare class ResendProvider implements EmailProvider { private client; private audienceIds; private fromAddress; private fromName; private isDevelopment; constructor(config: ResendProviderConfig & { fromAddress: string; fromName: string; }); getProvider(): string; send(params: SendEmailParams): Promise<void>; addContact(contact: Subscriber): Promise<void>; updateContact(contact: Subscriber): Promise<void>; removeContact(email: string): Promise<void>; private getAudienceId; } declare class BroadcastProvider implements EmailProvider { private apiUrl; private token; private fromAddress; private fromName; private replyTo?; constructor(config: BroadcastProviderConfig & { fromAddress: string; fromName: string; }); getProvider(): string; send(params: SendEmailParams): Promise<void>; addContact(contact: Subscriber): Promise<void>; updateContact(contact: Subscriber): Promise<void>; removeContact(email: string): Promise<void>; } export { BroadcastProvider, type NewsletterPluginConfig, ResendProvider, type Subscriber, createBroadcastsCollection, createNewsletterSettingsGlobal, createPreferencesEndpoint, createSubscribeEndpoint, createSubscribersCollection, createUnsubscribeEndpoint, createVerifyMagicLinkEndpoint, newsletterPlugin };