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
TypeScript
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 };