payload-plugin-newsletter 0.25.12 → 0.26.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/CHANGELOG.md +6 -0
- package/dist/collections.cjs +27 -0
- package/dist/collections.cjs.map +1 -1
- package/dist/collections.js +27 -0
- package/dist/collections.js.map +1 -1
- package/dist/server.d.ts +93 -2
- package/dist/server.js +81 -19
- package/package.json +1 -1
package/dist/server.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { Field, Block, RichTextField, Config, Endpoint, CollectionConfig, GlobalConfig } from 'payload';
|
|
1
|
+
import { Field, Block, RichTextField, Payload, Config, Endpoint, CollectionConfig, GlobalConfig } from 'payload';
|
|
2
|
+
import { SerializedEditorState } from 'lexical';
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* Core types for broadcast management functionality
|
|
@@ -632,6 +633,96 @@ interface SurveyQuestion {
|
|
|
632
633
|
required?: boolean;
|
|
633
634
|
}
|
|
634
635
|
|
|
636
|
+
/**
|
|
637
|
+
* Recursively populates media fields in Lexical content.
|
|
638
|
+
* Resolves Media IDs to full media objects with URLs.
|
|
639
|
+
*
|
|
640
|
+
* @param content - Lexical editor state content
|
|
641
|
+
* @param payload - Payload instance for database queries
|
|
642
|
+
* @param config - Newsletter plugin configuration
|
|
643
|
+
* @returns Populated content with resolved media objects
|
|
644
|
+
*/
|
|
645
|
+
declare function populateMediaFields(content: unknown, payload: Payload, config: NewsletterPluginConfig): Promise<unknown>;
|
|
646
|
+
|
|
647
|
+
/**
|
|
648
|
+
* Options for generating broadcast preview HTML
|
|
649
|
+
*/
|
|
650
|
+
interface PreviewOptions {
|
|
651
|
+
/** Email subject line */
|
|
652
|
+
subject: string;
|
|
653
|
+
/** Email preheader text (preview text in inbox) */
|
|
654
|
+
preheader?: string;
|
|
655
|
+
/** Whether to wrap content in email template (default: true) */
|
|
656
|
+
wrapInTemplate?: boolean;
|
|
657
|
+
/** Base URL for media files (optional, derived from payload.config.serverURL if not provided) */
|
|
658
|
+
mediaUrl?: string;
|
|
659
|
+
/** Additional document data to pass to custom wrapper */
|
|
660
|
+
documentData?: Record<string, unknown>;
|
|
661
|
+
}
|
|
662
|
+
/**
|
|
663
|
+
* Result from preview generation
|
|
664
|
+
*/
|
|
665
|
+
interface PreviewResult {
|
|
666
|
+
/** Generated HTML content */
|
|
667
|
+
html: string;
|
|
668
|
+
/** Subject line used */
|
|
669
|
+
subject: string;
|
|
670
|
+
/** Preheader text used (null if not provided) */
|
|
671
|
+
preheader: string | null;
|
|
672
|
+
}
|
|
673
|
+
/**
|
|
674
|
+
* Generates preview HTML from broadcast content.
|
|
675
|
+
*
|
|
676
|
+
* This utility handles the full preview generation pipeline:
|
|
677
|
+
* 1. Populates media fields (resolves Media IDs to full objects with URLs)
|
|
678
|
+
* 2. Converts Lexical content to email-safe HTML
|
|
679
|
+
* 3. Optionally wraps in email template
|
|
680
|
+
*
|
|
681
|
+
* @param content - Lexical editor state content from broadcast
|
|
682
|
+
* @param payload - Payload instance for database queries
|
|
683
|
+
* @param config - Newsletter plugin configuration
|
|
684
|
+
* @param options - Preview generation options
|
|
685
|
+
* @returns Preview result with HTML, subject, and preheader
|
|
686
|
+
*
|
|
687
|
+
* @example
|
|
688
|
+
* ```typescript
|
|
689
|
+
* import { generateBroadcastPreviewHtml } from 'payload-plugin-newsletter'
|
|
690
|
+
*
|
|
691
|
+
* const preview = await generateBroadcastPreviewHtml(
|
|
692
|
+
* broadcast.content,
|
|
693
|
+
* payload,
|
|
694
|
+
* pluginConfig,
|
|
695
|
+
* {
|
|
696
|
+
* subject: broadcast.subject,
|
|
697
|
+
* preheader: broadcast.preheader,
|
|
698
|
+
* wrapInTemplate: true,
|
|
699
|
+
* }
|
|
700
|
+
* )
|
|
701
|
+
*
|
|
702
|
+
* // preview.html contains the rendered email HTML
|
|
703
|
+
* ```
|
|
704
|
+
*/
|
|
705
|
+
declare function generateBroadcastPreviewHtml(content: SerializedEditorState | null | undefined, payload: Payload, config: NewsletterPluginConfig, options: PreviewOptions): Promise<PreviewResult>;
|
|
706
|
+
|
|
707
|
+
/**
|
|
708
|
+
* Converts Lexical editor state to email-safe HTML
|
|
709
|
+
*/
|
|
710
|
+
declare function convertToEmailSafeHtml(editorState: SerializedEditorState | undefined | null, options?: {
|
|
711
|
+
wrapInTemplate?: boolean;
|
|
712
|
+
preheader?: string;
|
|
713
|
+
mediaUrl?: string;
|
|
714
|
+
customBlockConverter?: (node: any, mediaUrl?: string) => Promise<string>;
|
|
715
|
+
payload?: any;
|
|
716
|
+
populateFields?: string[] | ((blockType: string) => string[]);
|
|
717
|
+
customWrapper?: (content: string, options?: {
|
|
718
|
+
preheader?: string;
|
|
719
|
+
subject?: string;
|
|
720
|
+
documentData?: Record<string, any>;
|
|
721
|
+
}) => string | Promise<string>;
|
|
722
|
+
subject?: string;
|
|
723
|
+
documentData?: Record<string, any>;
|
|
724
|
+
}): Promise<string>;
|
|
725
|
+
|
|
635
726
|
declare module 'payload' {
|
|
636
727
|
interface BasePayload {
|
|
637
728
|
newsletterEmailService?: any;
|
|
@@ -710,4 +801,4 @@ declare class BroadcastProvider implements EmailProvider {
|
|
|
710
801
|
removeContact(email: string): Promise<void>;
|
|
711
802
|
}
|
|
712
803
|
|
|
713
|
-
export { BroadcastProvider, type NewsletterPluginConfig, ResendProvider, type Subscriber, createBroadcastsCollection, createNewsletterSettingsGlobal, createPreferencesEndpoint, createSubscribeEndpoint, createSubscribersCollection, createUnsubscribeEndpoint, createVerifyMagicLinkEndpoint, newsletterPlugin };
|
|
804
|
+
export { BroadcastProvider, type NewsletterPluginConfig, type PreviewOptions, type PreviewResult, ResendProvider, type Subscriber, convertToEmailSafeHtml, createBroadcastsCollection, createNewsletterSettingsGlobal, createPreferencesEndpoint, createSubscribeEndpoint, createSubscribersCollection, createUnsubscribeEndpoint, createVerifyMagicLinkEndpoint, generateBroadcastPreviewHtml, newsletterPlugin, populateMediaFields };
|
package/dist/server.js
CHANGED
|
@@ -2763,10 +2763,20 @@ async function updateBroadcastStatus(event, status, payload, broadcastsSlug) {
|
|
|
2763
2763
|
return;
|
|
2764
2764
|
}
|
|
2765
2765
|
const broadcast = existing.docs[0];
|
|
2766
|
+
const newWebhookEvent = {
|
|
2767
|
+
eventType: event.type,
|
|
2768
|
+
receivedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2769
|
+
eventPayload: event.data
|
|
2770
|
+
};
|
|
2771
|
+
const existingEvents = broadcast.webhookData?.webhookEvents || [];
|
|
2772
|
+
const webhookEvents = [...existingEvents.slice(-9), newWebhookEvent];
|
|
2766
2773
|
const updateData = {
|
|
2767
|
-
status,
|
|
2768
|
-
|
|
2769
|
-
|
|
2774
|
+
sendStatus: status,
|
|
2775
|
+
// CRITICAL: Top-level field - 'sendStatus' not 'status'
|
|
2776
|
+
"webhookData.lastWebhookEvent": event.type,
|
|
2777
|
+
"webhookData.lastWebhookEventAt": event.occurred_at,
|
|
2778
|
+
"webhookData.webhookEvents": webhookEvents
|
|
2779
|
+
// Log event for debugging
|
|
2770
2780
|
};
|
|
2771
2781
|
switch (event.type) {
|
|
2772
2782
|
case WEBHOOK_EVENT_TYPES.BROADCAST_SCHEDULED:
|
|
@@ -2774,42 +2784,42 @@ async function updateBroadcastStatus(event, status, payload, broadcastsSlug) {
|
|
|
2774
2784
|
break;
|
|
2775
2785
|
case WEBHOOK_EVENT_TYPES.BROADCAST_SENDING: {
|
|
2776
2786
|
const sendingEvent = event;
|
|
2777
|
-
updateData.sentCount = sendingEvent.data.sent_count;
|
|
2778
|
-
updateData.totalCount = sendingEvent.data.total_count;
|
|
2787
|
+
updateData["webhookData.sentCount"] = sendingEvent.data.sent_count;
|
|
2788
|
+
updateData["webhookData.totalCount"] = sendingEvent.data.total_count;
|
|
2779
2789
|
break;
|
|
2780
2790
|
}
|
|
2781
2791
|
case WEBHOOK_EVENT_TYPES.BROADCAST_SENT: {
|
|
2782
2792
|
const sentEvent = event;
|
|
2783
|
-
updateData.sentCount = sentEvent.data.sent_count;
|
|
2793
|
+
updateData["webhookData.sentCount"] = sentEvent.data.sent_count;
|
|
2784
2794
|
updateData.sentAt = sentEvent.data.completed_at;
|
|
2785
|
-
updateData.sendingStartedAt = sentEvent.data.started_at;
|
|
2795
|
+
updateData["webhookData.sendingStartedAt"] = sentEvent.data.started_at;
|
|
2786
2796
|
break;
|
|
2787
2797
|
}
|
|
2788
2798
|
case WEBHOOK_EVENT_TYPES.BROADCAST_FAILED: {
|
|
2789
2799
|
const failedEvent = event;
|
|
2790
|
-
updateData.failureReason = failedEvent.data.error;
|
|
2791
|
-
updateData.failedAt = failedEvent.data.failed_at;
|
|
2800
|
+
updateData["webhookData.failureReason"] = failedEvent.data.error;
|
|
2801
|
+
updateData["webhookData.failedAt"] = failedEvent.data.failed_at;
|
|
2792
2802
|
break;
|
|
2793
2803
|
}
|
|
2794
2804
|
case WEBHOOK_EVENT_TYPES.BROADCAST_PARTIAL_FAILURE: {
|
|
2795
2805
|
const partialFailureEvent = event;
|
|
2796
|
-
updateData.sentCount = partialFailureEvent.data.sent_count;
|
|
2797
|
-
updateData.failedCount = partialFailureEvent.data.failed_count;
|
|
2798
|
-
updateData.totalCount = partialFailureEvent.data.total_count;
|
|
2799
|
-
updateData.hasWarnings = true;
|
|
2806
|
+
updateData["webhookData.sentCount"] = partialFailureEvent.data.sent_count;
|
|
2807
|
+
updateData["webhookData.failedCount"] = partialFailureEvent.data.failed_count;
|
|
2808
|
+
updateData["webhookData.totalCount"] = partialFailureEvent.data.total_count;
|
|
2809
|
+
updateData["webhookData.hasWarnings"] = true;
|
|
2800
2810
|
break;
|
|
2801
2811
|
}
|
|
2802
2812
|
case WEBHOOK_EVENT_TYPES.BROADCAST_ABORTED: {
|
|
2803
2813
|
const abortedEvent = event;
|
|
2804
|
-
updateData.abortedAt = abortedEvent.data.aborted_at;
|
|
2805
|
-
updateData.abortReason = abortedEvent.data.reason;
|
|
2814
|
+
updateData["webhookData.abortedAt"] = abortedEvent.data.aborted_at;
|
|
2815
|
+
updateData["webhookData.abortReason"] = abortedEvent.data.reason;
|
|
2806
2816
|
break;
|
|
2807
2817
|
}
|
|
2808
2818
|
case WEBHOOK_EVENT_TYPES.BROADCAST_PAUSED: {
|
|
2809
2819
|
const pausedEvent = event;
|
|
2810
|
-
updateData.pausedAt = pausedEvent.data.paused_at;
|
|
2811
|
-
updateData.sentCount = pausedEvent.data.sent_count;
|
|
2812
|
-
updateData.remainingCount = pausedEvent.data.remaining_count;
|
|
2820
|
+
updateData["webhookData.pausedAt"] = pausedEvent.data.paused_at;
|
|
2821
|
+
updateData["webhookData.sentCount"] = pausedEvent.data.sent_count;
|
|
2822
|
+
updateData["webhookData.remainingCount"] = pausedEvent.data.remaining_count;
|
|
2813
2823
|
break;
|
|
2814
2824
|
}
|
|
2815
2825
|
}
|
|
@@ -4821,6 +4831,33 @@ var createBroadcastsCollection = (pluginConfig) => {
|
|
|
4821
4831
|
{
|
|
4822
4832
|
name: "pausedAt",
|
|
4823
4833
|
type: "date"
|
|
4834
|
+
},
|
|
4835
|
+
{
|
|
4836
|
+
name: "webhookEvents",
|
|
4837
|
+
type: "array",
|
|
4838
|
+
label: "Webhook Event Log",
|
|
4839
|
+
maxRows: 10,
|
|
4840
|
+
admin: {
|
|
4841
|
+
readOnly: true,
|
|
4842
|
+
description: "Recent webhook events for debugging"
|
|
4843
|
+
},
|
|
4844
|
+
fields: [
|
|
4845
|
+
{
|
|
4846
|
+
name: "eventType",
|
|
4847
|
+
type: "text",
|
|
4848
|
+
admin: { readOnly: true }
|
|
4849
|
+
},
|
|
4850
|
+
{
|
|
4851
|
+
name: "receivedAt",
|
|
4852
|
+
type: "date",
|
|
4853
|
+
admin: { readOnly: true }
|
|
4854
|
+
},
|
|
4855
|
+
{
|
|
4856
|
+
name: "eventPayload",
|
|
4857
|
+
type: "json",
|
|
4858
|
+
admin: { readOnly: true }
|
|
4859
|
+
}
|
|
4860
|
+
]
|
|
4824
4861
|
}
|
|
4825
4862
|
]
|
|
4826
4863
|
}
|
|
@@ -5289,6 +5326,28 @@ var setPluginConfig = (config) => {
|
|
|
5289
5326
|
// src/utilities/session.ts
|
|
5290
5327
|
import jwt2 from "jsonwebtoken";
|
|
5291
5328
|
|
|
5329
|
+
// src/utils/preview.ts
|
|
5330
|
+
async function generateBroadcastPreviewHtml(content, payload, config, options) {
|
|
5331
|
+
const mediaUrl = options.mediaUrl ?? (payload.config.serverURL ? `${payload.config.serverURL}/api/media` : "/api/media");
|
|
5332
|
+
const emailPreviewConfig = config.customizations?.broadcasts?.emailPreview;
|
|
5333
|
+
payload.logger?.info("Populating media fields for preview generation...");
|
|
5334
|
+
const populatedContent = await populateMediaFields(content, payload, config);
|
|
5335
|
+
const html = await convertToEmailSafeHtml(populatedContent, {
|
|
5336
|
+
wrapInTemplate: options.wrapInTemplate ?? emailPreviewConfig?.wrapInTemplate ?? true,
|
|
5337
|
+
preheader: options.preheader,
|
|
5338
|
+
subject: options.subject,
|
|
5339
|
+
mediaUrl,
|
|
5340
|
+
documentData: options.documentData,
|
|
5341
|
+
customBlockConverter: config.customizations?.broadcasts?.customBlockConverter,
|
|
5342
|
+
customWrapper: emailPreviewConfig?.customWrapper
|
|
5343
|
+
});
|
|
5344
|
+
return {
|
|
5345
|
+
html,
|
|
5346
|
+
subject: options.subject,
|
|
5347
|
+
preheader: options.preheader ?? null
|
|
5348
|
+
};
|
|
5349
|
+
}
|
|
5350
|
+
|
|
5292
5351
|
// src/index.ts
|
|
5293
5352
|
var newsletterPlugin = (pluginConfig) => (incomingConfig) => {
|
|
5294
5353
|
const config = {
|
|
@@ -5443,6 +5502,7 @@ var newsletterPlugin = (pluginConfig) => (incomingConfig) => {
|
|
|
5443
5502
|
export {
|
|
5444
5503
|
BroadcastProvider,
|
|
5445
5504
|
ResendProvider,
|
|
5505
|
+
convertToEmailSafeHtml,
|
|
5446
5506
|
createBroadcastsCollection,
|
|
5447
5507
|
createNewsletterSettingsGlobal,
|
|
5448
5508
|
createPreferencesEndpoint,
|
|
@@ -5450,5 +5510,7 @@ export {
|
|
|
5450
5510
|
createSubscribersCollection,
|
|
5451
5511
|
createUnsubscribeEndpoint,
|
|
5452
5512
|
createVerifyMagicLinkEndpoint,
|
|
5453
|
-
|
|
5513
|
+
generateBroadcastPreviewHtml,
|
|
5514
|
+
newsletterPlugin,
|
|
5515
|
+
populateMediaFields
|
|
5454
5516
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "payload-plugin-newsletter",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.26.0",
|
|
4
4
|
"description": "Complete newsletter management plugin for Payload CMS with subscriber management, magic link authentication, and email service integration",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|