payload-plugin-newsletter 0.20.6 → 0.21.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 +71 -0
- package/README.md +35 -18
- package/dist/admin.d.ts +3 -1
- package/dist/admin.js +157 -2
- package/dist/collections.cjs +131 -2
- package/dist/collections.cjs.map +1 -1
- package/dist/collections.js +131 -2
- package/dist/collections.js.map +1 -1
- package/dist/server.d.ts +6 -29
- package/dist/server.js +616 -133
- package/dist/types.d.cts +8 -30
- package/dist/types.d.ts +8 -30
- package/dist/utils.cjs +2 -1
- package/dist/utils.cjs.map +1 -1
- package/dist/utils.d.cts +2 -0
- package/dist/utils.d.ts +2 -0
- package/dist/utils.js +2 -1
- package/dist/utils.js.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,74 @@
|
|
|
1
|
+
## [0.21.0] - 2025-01-31
|
|
2
|
+
|
|
3
|
+
### Added
|
|
4
|
+
- **Webhook Support**: Real-time webhook integration for Broadcast provider
|
|
5
|
+
- Automatic updates for subscriber events (subscribed/unsubscribed)
|
|
6
|
+
- Real-time broadcast status updates (scheduled, in_progress, sent, etc.)
|
|
7
|
+
- HMAC-SHA256 signature verification for security
|
|
8
|
+
- Webhook configuration UI in Newsletter Settings
|
|
9
|
+
- Manual webhook registration with setup instructions
|
|
10
|
+
|
|
11
|
+
### Changed
|
|
12
|
+
- **BREAKING**: Removed polling-based unsubscribe synchronization
|
|
13
|
+
- Webhooks now provide real-time updates instead of scheduled polling
|
|
14
|
+
- Removed `unsubscribeSync` configuration option
|
|
15
|
+
- Removed `afterUnsubscribeSync` hook
|
|
16
|
+
- Removed sync job infrastructure
|
|
17
|
+
|
|
18
|
+
### Security
|
|
19
|
+
- Added webhook signature verification with timestamp validation
|
|
20
|
+
- Webhook secrets stored securely in Newsletter Settings
|
|
21
|
+
|
|
22
|
+
### Migration Guide
|
|
23
|
+
If you were using unsubscribe sync:
|
|
24
|
+
1. Remove any `unsubscribeSync` configuration from your plugin setup
|
|
25
|
+
2. Configure webhooks in your Broadcast dashboard (see README)
|
|
26
|
+
3. Add the webhook secret to your Newsletter Settings
|
|
27
|
+
|
|
28
|
+
## [0.20.7] - 2025-08-02
|
|
29
|
+
|
|
30
|
+
### Added
|
|
31
|
+
- **Comprehensive Email Wrapper System** - Unified email rendering that uses the same wrapper for previews and actual sends
|
|
32
|
+
- Preview component now passes ALL form fields to the preview endpoint as `documentData`
|
|
33
|
+
- Custom wrappers receive complete document data, not just content
|
|
34
|
+
- Consistent HTML output between preview and actual email sends
|
|
35
|
+
- Support for accessing any custom fields added to broadcasts collection
|
|
36
|
+
- Wrapper is applied during actual send operations, not just previews
|
|
37
|
+
|
|
38
|
+
### Enhanced
|
|
39
|
+
- **Email Preview Component** - Builds document data from all form fields for wrapper access
|
|
40
|
+
- Collects all field values using `useFormFields` hook
|
|
41
|
+
- Passes complete form state to preview endpoint
|
|
42
|
+
- Enables custom wrappers to access metadata like slug, issueNumber, custom fields
|
|
43
|
+
|
|
44
|
+
- **Preview Endpoint** - Updated to accept and forward documentData to email wrapper
|
|
45
|
+
- Receives documentData from preview component
|
|
46
|
+
- Passes through to convertToEmailSafeHtml function
|
|
47
|
+
- Maintains backward compatibility with existing implementations
|
|
48
|
+
|
|
49
|
+
- **Email Wrapper Interface** - Enhanced type definitions for better developer experience
|
|
50
|
+
- Added `EmailWrapperOptions` interface with documentData support
|
|
51
|
+
- Updated `CustomEmailWrapper` type for consistency
|
|
52
|
+
- Generic `Record<string, any>` allows any custom fields
|
|
53
|
+
|
|
54
|
+
- **Broadcast Sync Hooks** - Now apply wrapper during actual email sends
|
|
55
|
+
- Create operation uses wrapper when syncing to provider
|
|
56
|
+
- Update operation maintains wrapper consistency
|
|
57
|
+
- Deferred creates (from draft to published) include wrapper
|
|
58
|
+
- Same HTML template used for both preview and actual sends
|
|
59
|
+
|
|
60
|
+
### Technical Changes
|
|
61
|
+
- Updated `convertToEmailSafeHtml` to accept documentData in options
|
|
62
|
+
- Modified all sync operations in Broadcasts collection to use email preview config
|
|
63
|
+
- Wrapper customization now available throughout the email lifecycle
|
|
64
|
+
- No breaking changes - wrapper remains optional with sensible defaults
|
|
65
|
+
|
|
66
|
+
### Benefits
|
|
67
|
+
- **Flexibility** - Users can pass any custom fields without plugin modifications
|
|
68
|
+
- **Consistency** - Identical HTML in preview and actual email sends
|
|
69
|
+
- **Future Proof** - Add new fields to broadcasts without updating plugin code
|
|
70
|
+
- **No Breaking Changes** - Existing implementations continue to work unchanged
|
|
71
|
+
|
|
1
72
|
## [0.20.6] - 2025-08-01
|
|
2
73
|
|
|
3
74
|
### Fixed
|
package/README.md
CHANGED
|
@@ -17,7 +17,7 @@ A complete newsletter management plugin for [Payload CMS](https://github.com/pay
|
|
|
17
17
|
- 🌍 **Internationalization** - Multi-language support built-in
|
|
18
18
|
- 📊 **Analytics Ready** - UTM tracking and signup metadata collection
|
|
19
19
|
- ⚙️ **Admin UI Configuration** - Manage email settings through Payload admin panel
|
|
20
|
-
- 🔄 **
|
|
20
|
+
- 🔄 **Real-time Webhook Sync** - Receive subscriber and broadcast events from email services via webhooks
|
|
21
21
|
- 👁️ **Email Preview** - Real-time preview with desktop/mobile views (v0.9.0+)
|
|
22
22
|
- ✅ **Email Validation** - Built-in validation for email client compatibility (v0.9.0+)
|
|
23
23
|
- 📝 **Email-Safe Editor** - Rich text editor limited to email-compatible features (v0.9.0+)
|
|
@@ -654,27 +654,44 @@ This adds a "Newsletter Scheduling" group to your articles with:
|
|
|
654
654
|
- Audience segment selection
|
|
655
655
|
- Send status tracking
|
|
656
656
|
|
|
657
|
-
##
|
|
657
|
+
## Webhook Configuration (Broadcast)
|
|
658
658
|
|
|
659
|
-
The plugin supports
|
|
659
|
+
The plugin supports real-time webhook integration with Broadcast for instant updates:
|
|
660
660
|
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
661
|
+
### Automatic Updates
|
|
662
|
+
|
|
663
|
+
When configured, the plugin automatically receives and processes:
|
|
664
|
+
- **Subscriber Events**: `subscribed`, `unsubscribed`
|
|
665
|
+
- **Broadcast Events**: All status changes (`scheduled`, `in_progress`, `sent`, etc.)
|
|
666
|
+
|
|
667
|
+
### Setup Instructions
|
|
668
|
+
|
|
669
|
+
1. **Save your Newsletter Settings** in the Payload admin to generate a webhook URL
|
|
670
|
+
2. **Configure in Broadcast**:
|
|
671
|
+
- Go to your Broadcast dashboard → "Webhook Endpoints"
|
|
672
|
+
- Click "Add Webhook Endpoint"
|
|
673
|
+
- Paste the webhook URL from Payload
|
|
674
|
+
- Select events:
|
|
675
|
+
- Subscriber Events: `subscribed`, `unsubscribed`
|
|
676
|
+
- Broadcast Events: All
|
|
677
|
+
- Create the webhook and copy the webhook secret
|
|
678
|
+
3. **Add the webhook secret** to your Newsletter Settings in Payload
|
|
679
|
+
4. **Save and verify** the webhook connection
|
|
680
|
+
|
|
681
|
+
### Security
|
|
682
|
+
|
|
683
|
+
Webhooks are secured with:
|
|
684
|
+
- HMAC-SHA256 signature verification
|
|
685
|
+
- Timestamp validation (5-minute window)
|
|
686
|
+
- Secret key stored in Newsletter Settings
|
|
687
|
+
|
|
688
|
+
### Data Flow
|
|
670
689
|
|
|
671
|
-
|
|
672
|
-
-
|
|
673
|
-
-
|
|
674
|
-
- Supports both Broadcast and Resend providers
|
|
675
|
-
- Can run on a schedule or be triggered manually
|
|
690
|
+
- **Subscriber events** update the subscriber's status and metadata
|
|
691
|
+
- **Broadcast events** update the broadcast's status and delivery metrics
|
|
692
|
+
- All updates happen in real-time without polling
|
|
676
693
|
|
|
677
|
-
|
|
694
|
+
**Note**: Email engagement events (opens, clicks) remain in Broadcast for analytics.
|
|
678
695
|
|
|
679
696
|
## Email Providers
|
|
680
697
|
|
package/dist/admin.d.ts
CHANGED
|
@@ -22,4 +22,6 @@ interface EmailPreviewProps {
|
|
|
22
22
|
}
|
|
23
23
|
declare const EmailPreview: React.FC<EmailPreviewProps>;
|
|
24
24
|
|
|
25
|
-
|
|
25
|
+
declare const WebhookConfiguration: React.FC;
|
|
26
|
+
|
|
27
|
+
export { BroadcastInlinePreview, type BroadcastInlinePreviewProps, EmailPreview, type EmailPreviewProps, StatusBadge, type StatusBadgeProps, WebhookConfiguration };
|
package/dist/admin.js
CHANGED
|
@@ -22,10 +22,20 @@ var BroadcastInlinePreview = () => {
|
|
|
22
22
|
setLoading(false);
|
|
23
23
|
return;
|
|
24
24
|
}
|
|
25
|
+
const documentData = {};
|
|
26
|
+
Object.entries(fields || {}).forEach(([key, field]) => {
|
|
27
|
+
if (field && typeof field === "object" && "value" in field) {
|
|
28
|
+
documentData[key] = field.value;
|
|
29
|
+
}
|
|
30
|
+
});
|
|
25
31
|
const response = await fetch("/api/broadcasts/preview", {
|
|
26
32
|
method: "POST",
|
|
27
33
|
headers: { "Content-Type": "application/json" },
|
|
28
|
-
body: JSON.stringify({
|
|
34
|
+
body: JSON.stringify({
|
|
35
|
+
content: contentValue,
|
|
36
|
+
documentData
|
|
37
|
+
// Pass all form data
|
|
38
|
+
})
|
|
29
39
|
});
|
|
30
40
|
if (!response.ok) {
|
|
31
41
|
throw new Error(`Preview failed: ${response.statusText}`);
|
|
@@ -178,8 +188,153 @@ var EmailPreview = ({
|
|
|
178
188
|
] })
|
|
179
189
|
] });
|
|
180
190
|
};
|
|
191
|
+
|
|
192
|
+
// src/admin/components/WebhookConfiguration.tsx
|
|
193
|
+
import { useState as useState2 } from "react";
|
|
194
|
+
import { useFormFields as useFormFields2 } from "@payloadcms/ui";
|
|
195
|
+
import { Button } from "@payloadcms/ui";
|
|
196
|
+
import { toast } from "@payloadcms/ui";
|
|
197
|
+
import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
198
|
+
var WebhookConfiguration = () => {
|
|
199
|
+
const [showInstructions, setShowInstructions] = useState2(false);
|
|
200
|
+
const [verifying, setVerifying] = useState2(false);
|
|
201
|
+
const fields = useFormFields2(([fields2]) => ({
|
|
202
|
+
webhookUrl: fields2?.broadcastSettings?.webhookUrl,
|
|
203
|
+
webhookStatus: fields2?.broadcastSettings?.webhookStatus,
|
|
204
|
+
lastWebhookReceived: fields2?.broadcastSettings?.lastWebhookReceived
|
|
205
|
+
}));
|
|
206
|
+
const handleVerify = async () => {
|
|
207
|
+
setVerifying(true);
|
|
208
|
+
try {
|
|
209
|
+
const response = await fetch("/api/newsletter/webhooks/verify", {
|
|
210
|
+
method: "POST",
|
|
211
|
+
headers: {
|
|
212
|
+
"Content-Type": "application/json"
|
|
213
|
+
}
|
|
214
|
+
});
|
|
215
|
+
const data = await response.json();
|
|
216
|
+
if (data.success) {
|
|
217
|
+
toast.success(data.message || "Webhook verified");
|
|
218
|
+
} else {
|
|
219
|
+
toast.error(data.error || "Verification failed");
|
|
220
|
+
}
|
|
221
|
+
} catch (error) {
|
|
222
|
+
toast.error("Failed to verify webhook");
|
|
223
|
+
} finally {
|
|
224
|
+
setVerifying(false);
|
|
225
|
+
}
|
|
226
|
+
};
|
|
227
|
+
const getStatusColor = (status) => {
|
|
228
|
+
switch (status) {
|
|
229
|
+
case "verified":
|
|
230
|
+
return "green";
|
|
231
|
+
case "configured":
|
|
232
|
+
return "yellow";
|
|
233
|
+
case "error":
|
|
234
|
+
return "red";
|
|
235
|
+
default:
|
|
236
|
+
return "gray";
|
|
237
|
+
}
|
|
238
|
+
};
|
|
239
|
+
const getStatusLabel = (status) => {
|
|
240
|
+
switch (status) {
|
|
241
|
+
case "verified":
|
|
242
|
+
return "Verified \u2713";
|
|
243
|
+
case "configured":
|
|
244
|
+
return "Configured";
|
|
245
|
+
case "error":
|
|
246
|
+
return "Error";
|
|
247
|
+
default:
|
|
248
|
+
return "Not Configured";
|
|
249
|
+
}
|
|
250
|
+
};
|
|
251
|
+
return /* @__PURE__ */ jsx4("div", { className: "field-type", children: /* @__PURE__ */ jsxs3("div", { style: { marginBottom: "1rem" }, children: [
|
|
252
|
+
/* @__PURE__ */ jsx4("label", { className: "field-label", children: "Webhook Configuration" }),
|
|
253
|
+
/* @__PURE__ */ jsxs3("div", { style: {
|
|
254
|
+
background: "#f5f5f5",
|
|
255
|
+
padding: "1rem",
|
|
256
|
+
borderRadius: "4px",
|
|
257
|
+
marginTop: "0.5rem"
|
|
258
|
+
}, children: [
|
|
259
|
+
/* @__PURE__ */ jsxs3("div", { style: { marginBottom: "0.5rem" }, children: [
|
|
260
|
+
/* @__PURE__ */ jsx4("strong", { children: "Webhook URL:" }),
|
|
261
|
+
/* @__PURE__ */ jsx4("code", { style: {
|
|
262
|
+
display: "block",
|
|
263
|
+
padding: "0.5rem",
|
|
264
|
+
background: "#fff",
|
|
265
|
+
border: "1px solid #ddd",
|
|
266
|
+
borderRadius: "4px",
|
|
267
|
+
marginTop: "0.25rem",
|
|
268
|
+
fontSize: "0.875rem"
|
|
269
|
+
}, children: fields.webhookUrl || "Save settings to generate URL" })
|
|
270
|
+
] }),
|
|
271
|
+
/* @__PURE__ */ jsxs3("div", { style: { marginBottom: "0.5rem" }, children: [
|
|
272
|
+
/* @__PURE__ */ jsx4("strong", { children: "Status:" }),
|
|
273
|
+
" ",
|
|
274
|
+
/* @__PURE__ */ jsx4("span", { style: { color: getStatusColor(fields.webhookStatus || "not_configured") }, children: getStatusLabel(fields.webhookStatus || "not_configured") })
|
|
275
|
+
] }),
|
|
276
|
+
fields.lastWebhookReceived && /* @__PURE__ */ jsxs3("div", { style: { fontSize: "0.875rem", color: "#666" }, children: [
|
|
277
|
+
"Last event: ",
|
|
278
|
+
new Date(fields.lastWebhookReceived).toLocaleString()
|
|
279
|
+
] })
|
|
280
|
+
] }),
|
|
281
|
+
/* @__PURE__ */ jsxs3("div", { style: { marginTop: "1rem", display: "flex", gap: "0.5rem" }, children: [
|
|
282
|
+
/* @__PURE__ */ jsxs3(
|
|
283
|
+
Button,
|
|
284
|
+
{
|
|
285
|
+
onClick: () => setShowInstructions(!showInstructions),
|
|
286
|
+
buttonStyle: "secondary",
|
|
287
|
+
size: "small",
|
|
288
|
+
children: [
|
|
289
|
+
showInstructions ? "Hide" : "Show",
|
|
290
|
+
" Instructions"
|
|
291
|
+
]
|
|
292
|
+
}
|
|
293
|
+
),
|
|
294
|
+
/* @__PURE__ */ jsx4(
|
|
295
|
+
Button,
|
|
296
|
+
{
|
|
297
|
+
onClick: handleVerify,
|
|
298
|
+
buttonStyle: "primary",
|
|
299
|
+
size: "small",
|
|
300
|
+
disabled: verifying || !fields.webhookUrl,
|
|
301
|
+
children: verifying ? "Verifying..." : "Verify Webhook"
|
|
302
|
+
}
|
|
303
|
+
)
|
|
304
|
+
] }),
|
|
305
|
+
showInstructions && /* @__PURE__ */ jsxs3("div", { style: {
|
|
306
|
+
marginTop: "1rem",
|
|
307
|
+
padding: "1rem",
|
|
308
|
+
background: "#f0f8ff",
|
|
309
|
+
border: "1px solid #b0d4ff",
|
|
310
|
+
borderRadius: "4px"
|
|
311
|
+
}, children: [
|
|
312
|
+
/* @__PURE__ */ jsx4("h4", { style: { marginTop: 0 }, children: "Configure Broadcast Webhook" }),
|
|
313
|
+
/* @__PURE__ */ jsxs3("ol", { children: [
|
|
314
|
+
/* @__PURE__ */ jsx4("li", { children: "Copy the webhook URL above" }),
|
|
315
|
+
/* @__PURE__ */ jsx4("li", { children: "Go to your Broadcast dashboard" }),
|
|
316
|
+
/* @__PURE__ */ jsx4("li", { children: 'Navigate to "Webhook Endpoints"' }),
|
|
317
|
+
/* @__PURE__ */ jsx4("li", { children: 'Click "Add Webhook Endpoint"' }),
|
|
318
|
+
/* @__PURE__ */ jsx4("li", { children: "Paste the URL" }),
|
|
319
|
+
/* @__PURE__ */ jsxs3("li", { children: [
|
|
320
|
+
"Select these events:",
|
|
321
|
+
/* @__PURE__ */ jsxs3("ul", { children: [
|
|
322
|
+
/* @__PURE__ */ jsx4("li", { children: "Subscriber Events: subscribed, unsubscribed" }),
|
|
323
|
+
/* @__PURE__ */ jsx4("li", { children: "Broadcast Events: All" })
|
|
324
|
+
] })
|
|
325
|
+
] }),
|
|
326
|
+
/* @__PURE__ */ jsx4("li", { children: 'Click "Create Webhook"' }),
|
|
327
|
+
/* @__PURE__ */ jsx4("li", { children: "Copy the webhook secret shown" }),
|
|
328
|
+
/* @__PURE__ */ jsx4("li", { children: "Paste it in the Webhook Secret field below" }),
|
|
329
|
+
/* @__PURE__ */ jsx4("li", { children: "Save these settings" }),
|
|
330
|
+
/* @__PURE__ */ jsx4("li", { children: 'Click "Verify Webhook" to test the connection' })
|
|
331
|
+
] })
|
|
332
|
+
] })
|
|
333
|
+
] }) });
|
|
334
|
+
};
|
|
181
335
|
export {
|
|
182
336
|
BroadcastInlinePreview,
|
|
183
337
|
EmailPreview,
|
|
184
|
-
StatusBadge
|
|
338
|
+
StatusBadge,
|
|
339
|
+
WebhookConfiguration
|
|
185
340
|
};
|
package/dist/collections.cjs
CHANGED
|
@@ -979,7 +979,8 @@ async function convertToEmailSafeHtml(editorState, options) {
|
|
|
979
979
|
if (options.customWrapper) {
|
|
980
980
|
return await Promise.resolve(options.customWrapper(sanitizedHtml, {
|
|
981
981
|
preheader: options.preheader,
|
|
982
|
-
subject: options.subject
|
|
982
|
+
subject: options.subject,
|
|
983
|
+
documentData: options.documentData
|
|
983
984
|
}));
|
|
984
985
|
}
|
|
985
986
|
return wrapInEmailTemplate(sanitizedHtml, options.preheader);
|
|
@@ -1866,7 +1867,7 @@ var createBroadcastPreviewEndpoint = (config, _collectionSlug) => {
|
|
|
1866
1867
|
handler: async (req) => {
|
|
1867
1868
|
try {
|
|
1868
1869
|
const data = await (req.json?.() || Promise.resolve({}));
|
|
1869
|
-
const { content, preheader, subject } = data;
|
|
1870
|
+
const { content, preheader, subject, documentData } = data;
|
|
1870
1871
|
if (!content) {
|
|
1871
1872
|
return Response.json({
|
|
1872
1873
|
success: false,
|
|
@@ -1882,6 +1883,8 @@ var createBroadcastPreviewEndpoint = (config, _collectionSlug) => {
|
|
|
1882
1883
|
preheader,
|
|
1883
1884
|
subject,
|
|
1884
1885
|
mediaUrl,
|
|
1886
|
+
documentData,
|
|
1887
|
+
// Pass all document data
|
|
1885
1888
|
customBlockConverter: config.customizations?.broadcasts?.customBlockConverter,
|
|
1886
1889
|
customWrapper: emailPreviewConfig?.customWrapper
|
|
1887
1890
|
});
|
|
@@ -2151,6 +2154,14 @@ var createBroadcastsCollection = (pluginConfig) => {
|
|
|
2151
2154
|
condition: (data) => hasProviders && data?.providerId
|
|
2152
2155
|
}
|
|
2153
2156
|
},
|
|
2157
|
+
{
|
|
2158
|
+
name: "externalId",
|
|
2159
|
+
type: "text",
|
|
2160
|
+
admin: {
|
|
2161
|
+
readOnly: true,
|
|
2162
|
+
description: "External ID for webhook integration"
|
|
2163
|
+
}
|
|
2164
|
+
},
|
|
2154
2165
|
{
|
|
2155
2166
|
name: "providerData",
|
|
2156
2167
|
type: "json",
|
|
@@ -2159,6 +2170,77 @@ var createBroadcastsCollection = (pluginConfig) => {
|
|
|
2159
2170
|
condition: () => false
|
|
2160
2171
|
// Hidden by default
|
|
2161
2172
|
}
|
|
2173
|
+
},
|
|
2174
|
+
// Webhook tracking fields
|
|
2175
|
+
{
|
|
2176
|
+
name: "webhookData",
|
|
2177
|
+
type: "group",
|
|
2178
|
+
label: "Webhook Data",
|
|
2179
|
+
admin: {
|
|
2180
|
+
condition: () => false
|
|
2181
|
+
// Hidden by default, used for webhook tracking
|
|
2182
|
+
},
|
|
2183
|
+
fields: [
|
|
2184
|
+
{
|
|
2185
|
+
name: "lastWebhookEvent",
|
|
2186
|
+
type: "text",
|
|
2187
|
+
admin: {
|
|
2188
|
+
readOnly: true
|
|
2189
|
+
}
|
|
2190
|
+
},
|
|
2191
|
+
{
|
|
2192
|
+
name: "lastWebhookEventAt",
|
|
2193
|
+
type: "date",
|
|
2194
|
+
admin: {
|
|
2195
|
+
readOnly: true
|
|
2196
|
+
}
|
|
2197
|
+
},
|
|
2198
|
+
{
|
|
2199
|
+
name: "hasWarnings",
|
|
2200
|
+
type: "checkbox",
|
|
2201
|
+
defaultValue: false
|
|
2202
|
+
},
|
|
2203
|
+
{
|
|
2204
|
+
name: "failureReason",
|
|
2205
|
+
type: "text"
|
|
2206
|
+
},
|
|
2207
|
+
{
|
|
2208
|
+
name: "sentCount",
|
|
2209
|
+
type: "number"
|
|
2210
|
+
},
|
|
2211
|
+
{
|
|
2212
|
+
name: "totalCount",
|
|
2213
|
+
type: "number"
|
|
2214
|
+
},
|
|
2215
|
+
{
|
|
2216
|
+
name: "failedCount",
|
|
2217
|
+
type: "number"
|
|
2218
|
+
},
|
|
2219
|
+
{
|
|
2220
|
+
name: "remainingCount",
|
|
2221
|
+
type: "number"
|
|
2222
|
+
},
|
|
2223
|
+
{
|
|
2224
|
+
name: "sendingStartedAt",
|
|
2225
|
+
type: "date"
|
|
2226
|
+
},
|
|
2227
|
+
{
|
|
2228
|
+
name: "failedAt",
|
|
2229
|
+
type: "date"
|
|
2230
|
+
},
|
|
2231
|
+
{
|
|
2232
|
+
name: "abortedAt",
|
|
2233
|
+
type: "date"
|
|
2234
|
+
},
|
|
2235
|
+
{
|
|
2236
|
+
name: "abortReason",
|
|
2237
|
+
type: "text"
|
|
2238
|
+
},
|
|
2239
|
+
{
|
|
2240
|
+
name: "pausedAt",
|
|
2241
|
+
type: "date"
|
|
2242
|
+
}
|
|
2243
|
+
]
|
|
2162
2244
|
}
|
|
2163
2245
|
],
|
|
2164
2246
|
hooks: {
|
|
@@ -2181,7 +2263,14 @@ var createBroadcastsCollection = (pluginConfig) => {
|
|
|
2181
2263
|
const provider = new BroadcastApiProvider2(providerConfig);
|
|
2182
2264
|
req.payload.logger.info("Populating media fields and converting content to HTML...");
|
|
2183
2265
|
const populatedContent = await populateMediaFields(doc.contentSection?.content, req.payload, pluginConfig);
|
|
2266
|
+
const emailPreviewConfig = pluginConfig.customizations?.broadcasts?.emailPreview;
|
|
2184
2267
|
const htmlContent = await convertToEmailSafeHtml(populatedContent, {
|
|
2268
|
+
wrapInTemplate: emailPreviewConfig?.wrapInTemplate ?? true,
|
|
2269
|
+
customWrapper: emailPreviewConfig?.customWrapper,
|
|
2270
|
+
preheader: doc.contentSection?.preheader,
|
|
2271
|
+
subject: doc.subject,
|
|
2272
|
+
documentData: doc,
|
|
2273
|
+
// Pass entire document
|
|
2185
2274
|
customBlockConverter: pluginConfig.customizations?.broadcasts?.customBlockConverter
|
|
2186
2275
|
});
|
|
2187
2276
|
if (!htmlContent || htmlContent.trim() === "") {
|
|
@@ -2281,7 +2370,14 @@ var createBroadcastsCollection = (pluginConfig) => {
|
|
|
2281
2370
|
}
|
|
2282
2371
|
req.payload.logger.info("Creating broadcast in provider (deferred from initial create)...");
|
|
2283
2372
|
const populatedContent = await populateMediaFields(doc.contentSection?.content, req.payload, pluginConfig);
|
|
2373
|
+
const emailPreviewConfig = pluginConfig.customizations?.broadcasts?.emailPreview;
|
|
2284
2374
|
const htmlContent = await convertToEmailSafeHtml(populatedContent, {
|
|
2375
|
+
wrapInTemplate: emailPreviewConfig?.wrapInTemplate ?? true,
|
|
2376
|
+
customWrapper: emailPreviewConfig?.customWrapper,
|
|
2377
|
+
preheader: doc.contentSection?.preheader,
|
|
2378
|
+
subject: doc.subject,
|
|
2379
|
+
documentData: doc,
|
|
2380
|
+
// Pass entire document
|
|
2285
2381
|
customBlockConverter: pluginConfig.customizations?.broadcasts?.customBlockConverter
|
|
2286
2382
|
});
|
|
2287
2383
|
if (!htmlContent || htmlContent.trim() === "") {
|
|
@@ -2343,7 +2439,14 @@ var createBroadcastsCollection = (pluginConfig) => {
|
|
|
2343
2439
|
}
|
|
2344
2440
|
if (JSON.stringify(doc.contentSection?.content) !== JSON.stringify(previousDoc?.contentSection?.content)) {
|
|
2345
2441
|
const populatedContent = await populateMediaFields(doc.contentSection?.content, req.payload, pluginConfig);
|
|
2442
|
+
const emailPreviewConfig = pluginConfig.customizations?.broadcasts?.emailPreview;
|
|
2346
2443
|
updates.content = await convertToEmailSafeHtml(populatedContent, {
|
|
2444
|
+
wrapInTemplate: emailPreviewConfig?.wrapInTemplate ?? true,
|
|
2445
|
+
customWrapper: emailPreviewConfig?.customWrapper,
|
|
2446
|
+
preheader: doc.contentSection?.preheader,
|
|
2447
|
+
subject: doc.subject,
|
|
2448
|
+
documentData: doc,
|
|
2449
|
+
// Pass entire document
|
|
2347
2450
|
customBlockConverter: pluginConfig.customizations?.broadcasts?.customBlockConverter
|
|
2348
2451
|
});
|
|
2349
2452
|
}
|
|
@@ -2779,6 +2882,15 @@ var createSubscribersCollection = (pluginConfig) => {
|
|
|
2779
2882
|
type: "date",
|
|
2780
2883
|
hidden: true
|
|
2781
2884
|
},
|
|
2885
|
+
// External ID for webhook integration
|
|
2886
|
+
{
|
|
2887
|
+
name: "externalId",
|
|
2888
|
+
type: "text",
|
|
2889
|
+
admin: {
|
|
2890
|
+
description: "ID from email service provider",
|
|
2891
|
+
readOnly: true
|
|
2892
|
+
}
|
|
2893
|
+
},
|
|
2782
2894
|
// Subscription status
|
|
2783
2895
|
{
|
|
2784
2896
|
name: "subscriptionStatus",
|
|
@@ -2794,6 +2906,14 @@ var createSubscribersCollection = (pluginConfig) => {
|
|
|
2794
2906
|
description: "Current subscription status"
|
|
2795
2907
|
}
|
|
2796
2908
|
},
|
|
2909
|
+
{
|
|
2910
|
+
name: "subscribedAt",
|
|
2911
|
+
type: "date",
|
|
2912
|
+
admin: {
|
|
2913
|
+
description: "When the user subscribed",
|
|
2914
|
+
readOnly: true
|
|
2915
|
+
}
|
|
2916
|
+
},
|
|
2797
2917
|
{
|
|
2798
2918
|
name: "unsubscribedAt",
|
|
2799
2919
|
type: "date",
|
|
@@ -2803,6 +2923,15 @@ var createSubscribersCollection = (pluginConfig) => {
|
|
|
2803
2923
|
readOnly: true
|
|
2804
2924
|
}
|
|
2805
2925
|
},
|
|
2926
|
+
{
|
|
2927
|
+
name: "unsubscribeReason",
|
|
2928
|
+
type: "text",
|
|
2929
|
+
admin: {
|
|
2930
|
+
condition: (data) => data?.subscriptionStatus === "unsubscribed",
|
|
2931
|
+
description: "Reason for unsubscribing",
|
|
2932
|
+
readOnly: true
|
|
2933
|
+
}
|
|
2934
|
+
},
|
|
2806
2935
|
// Email preferences
|
|
2807
2936
|
{
|
|
2808
2937
|
name: "emailPreferences",
|