payload-plugin-newsletter 0.16.5 → 0.16.7
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 +45 -0
- package/dist/collections.cjs +184 -134
- package/dist/collections.cjs.map +1 -1
- package/dist/collections.js +184 -134
- package/dist/collections.js.map +1 -1
- package/dist/index.cjs +184 -134
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +184 -134
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,48 @@
|
|
|
1
|
+
## [0.16.7] - 2025-07-29
|
|
2
|
+
|
|
3
|
+
### Added
|
|
4
|
+
- **Comprehensive Diagnostic Logging** - Added extensive logging to diagnose broadcast sync issues
|
|
5
|
+
- Logs the HTML content conversion process
|
|
6
|
+
- Shows exactly what data is being sent to the Broadcast API (with preview)
|
|
7
|
+
- Displays request URL, method, and body structure
|
|
8
|
+
- Shows API response status and headers
|
|
9
|
+
- Captures and logs all error types (Error objects, strings, JSON responses)
|
|
10
|
+
- Logs raw errors to identify unexpected error formats
|
|
11
|
+
|
|
12
|
+
### Improved
|
|
13
|
+
- **Better Error Visibility** - Enhanced error handling to capture more details
|
|
14
|
+
- Raw error logging to see the actual error structure
|
|
15
|
+
- Multiple error format handlers (Error, string, object, unknown)
|
|
16
|
+
- Response body parsing for API errors
|
|
17
|
+
- Document context logging when errors occur
|
|
18
|
+
- API request/response details in provider logs
|
|
19
|
+
|
|
20
|
+
### Technical
|
|
21
|
+
- Added pre-API call logging in afterChange hook
|
|
22
|
+
- Added comprehensive error logging in BroadcastApiProvider
|
|
23
|
+
- Logs help identify if issues are with content, API format, or authentication
|
|
24
|
+
|
|
25
|
+
## [0.16.6] - 2025-07-29
|
|
26
|
+
|
|
27
|
+
### Fixed
|
|
28
|
+
- **Critical: Update Sync Now Works** - Fixed the afterChange hook that was blocking update operations
|
|
29
|
+
- Removed the `operation !== 'create'` check that prevented the afterChange hook from running on updates
|
|
30
|
+
- Moved update sync logic from beforeChange to afterChange for proper architectural pattern
|
|
31
|
+
- Updates are now synced AFTER they're saved to Payload, ensuring consistency
|
|
32
|
+
- Provider sync failures no longer block Payload updates
|
|
33
|
+
|
|
34
|
+
### Improved
|
|
35
|
+
- **Better Hook Architecture** - Sync operations now happen in the correct lifecycle stage
|
|
36
|
+
- beforeChange was architecturally wrong - if provider sync failed, data would be inconsistent
|
|
37
|
+
- afterChange ensures Payload data is saved first, then syncs to provider
|
|
38
|
+
- More resilient to network failures and API errors
|
|
39
|
+
|
|
40
|
+
### Technical
|
|
41
|
+
- Consolidated create and update logic in a single afterChange hook
|
|
42
|
+
- Added comprehensive content change detection before syncing
|
|
43
|
+
- Enhanced logging for update sync operations
|
|
44
|
+
- Removed redundant beforeChange hook logic
|
|
45
|
+
|
|
1
46
|
## [0.16.5] - 2025-07-29
|
|
2
47
|
|
|
3
48
|
### Breaking Changes
|
package/dist/collections.cjs
CHANGED
|
@@ -252,29 +252,46 @@ var init_broadcast2 = __esm({
|
|
|
252
252
|
async create(data) {
|
|
253
253
|
try {
|
|
254
254
|
this.validateRequiredFields(data, ["name", "subject", "content"]);
|
|
255
|
+
const requestBody = {
|
|
256
|
+
broadcast: {
|
|
257
|
+
name: data.name,
|
|
258
|
+
subject: data.subject,
|
|
259
|
+
preheader: data.preheader,
|
|
260
|
+
body: data.content,
|
|
261
|
+
html_body: true,
|
|
262
|
+
track_opens: data.trackOpens ?? true,
|
|
263
|
+
track_clicks: data.trackClicks ?? true,
|
|
264
|
+
reply_to: data.replyTo,
|
|
265
|
+
segment_ids: data.audienceIds
|
|
266
|
+
}
|
|
267
|
+
};
|
|
268
|
+
console.log("[BroadcastApiProvider] Creating broadcast:", {
|
|
269
|
+
url: `${this.apiUrl}/api/v1/broadcasts`,
|
|
270
|
+
method: "POST",
|
|
271
|
+
hasToken: !!this.token,
|
|
272
|
+
tokenLength: this.token?.length,
|
|
273
|
+
body: JSON.stringify(requestBody, null, 2)
|
|
274
|
+
});
|
|
255
275
|
const response = await fetch(`${this.apiUrl}/api/v1/broadcasts`, {
|
|
256
276
|
method: "POST",
|
|
257
277
|
headers: {
|
|
258
278
|
"Authorization": `Bearer ${this.token}`,
|
|
259
279
|
"Content-Type": "application/json"
|
|
260
280
|
},
|
|
261
|
-
body: JSON.stringify(
|
|
262
|
-
broadcast: {
|
|
263
|
-
name: data.name,
|
|
264
|
-
subject: data.subject,
|
|
265
|
-
preheader: data.preheader,
|
|
266
|
-
body: data.content,
|
|
267
|
-
html_body: true,
|
|
268
|
-
track_opens: data.trackOpens ?? true,
|
|
269
|
-
track_clicks: data.trackClicks ?? true,
|
|
270
|
-
reply_to: data.replyTo,
|
|
271
|
-
segment_ids: data.audienceIds
|
|
272
|
-
}
|
|
273
|
-
})
|
|
281
|
+
body: JSON.stringify(requestBody)
|
|
274
282
|
});
|
|
283
|
+
console.log("[BroadcastApiProvider] Response status:", response.status);
|
|
284
|
+
console.log("[BroadcastApiProvider] Response headers:", Object.fromEntries(response.headers.entries()));
|
|
275
285
|
if (!response.ok) {
|
|
276
|
-
const
|
|
277
|
-
|
|
286
|
+
const errorText = await response.text();
|
|
287
|
+
console.error("[BroadcastApiProvider] Error response body:", errorText);
|
|
288
|
+
let errorDetails;
|
|
289
|
+
try {
|
|
290
|
+
errorDetails = JSON.parse(errorText);
|
|
291
|
+
console.error("[BroadcastApiProvider] Parsed error:", errorDetails);
|
|
292
|
+
} catch {
|
|
293
|
+
}
|
|
294
|
+
throw new Error(`Broadcast API error: ${response.status} - ${errorText}`);
|
|
278
295
|
}
|
|
279
296
|
const result = await response.json();
|
|
280
297
|
return this.get(result.id.toString());
|
|
@@ -1435,58 +1452,162 @@ var createBroadcastsCollection = (pluginConfig) => {
|
|
|
1435
1452
|
}
|
|
1436
1453
|
],
|
|
1437
1454
|
hooks: {
|
|
1438
|
-
// Sync with provider on create
|
|
1455
|
+
// Sync with provider on create and update
|
|
1439
1456
|
afterChange: [
|
|
1440
|
-
async ({ doc, operation, req }) => {
|
|
1441
|
-
if (!hasProviders
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1457
|
+
async ({ doc, operation, req, previousDoc }) => {
|
|
1458
|
+
if (!hasProviders) return doc;
|
|
1459
|
+
if (operation === "create") {
|
|
1460
|
+
try {
|
|
1461
|
+
const providerConfig = await getBroadcastConfig(req, pluginConfig);
|
|
1462
|
+
if (!providerConfig || !providerConfig.token) {
|
|
1463
|
+
req.payload.logger.error("Broadcast provider not configured in Newsletter Settings or environment variables");
|
|
1464
|
+
return doc;
|
|
1465
|
+
}
|
|
1466
|
+
const { BroadcastApiProvider: BroadcastApiProvider2 } = await Promise.resolve().then(() => (init_broadcast2(), broadcast_exports));
|
|
1467
|
+
const provider = new BroadcastApiProvider2(providerConfig);
|
|
1468
|
+
req.payload.logger.info("Converting content to HTML...");
|
|
1469
|
+
const htmlContent = await convertToEmailSafeHtml(doc.contentSection?.content);
|
|
1470
|
+
const createData = {
|
|
1471
|
+
name: doc.subject,
|
|
1472
|
+
// Use subject as name since we removed the name field
|
|
1473
|
+
subject: doc.subject,
|
|
1474
|
+
preheader: doc.contentSection?.preheader,
|
|
1475
|
+
content: htmlContent,
|
|
1476
|
+
trackOpens: doc.settings?.trackOpens,
|
|
1477
|
+
trackClicks: doc.settings?.trackClicks,
|
|
1478
|
+
replyTo: doc.settings?.replyTo || providerConfig.replyTo,
|
|
1479
|
+
audienceIds: doc.audienceIds?.map((a) => a.audienceId)
|
|
1480
|
+
};
|
|
1481
|
+
req.payload.logger.info("Creating broadcast with data:", {
|
|
1482
|
+
name: createData.name,
|
|
1483
|
+
subject: createData.subject,
|
|
1484
|
+
preheader: createData.preheader || "NONE",
|
|
1485
|
+
contentLength: htmlContent ? htmlContent.length : 0,
|
|
1486
|
+
contentPreview: htmlContent ? htmlContent.substring(0, 100) + "..." : "EMPTY",
|
|
1487
|
+
trackOpens: createData.trackOpens,
|
|
1488
|
+
trackClicks: createData.trackClicks,
|
|
1489
|
+
replyTo: createData.replyTo,
|
|
1490
|
+
audienceIds: createData.audienceIds || [],
|
|
1491
|
+
apiUrl: providerConfig.apiUrl,
|
|
1492
|
+
hasToken: !!providerConfig.token
|
|
1493
|
+
});
|
|
1494
|
+
const providerBroadcast = await provider.create(createData);
|
|
1495
|
+
await req.payload.update({
|
|
1496
|
+
collection: "broadcasts",
|
|
1497
|
+
id: doc.id,
|
|
1498
|
+
data: {
|
|
1499
|
+
providerId: providerBroadcast.id,
|
|
1500
|
+
providerData: providerBroadcast.providerData
|
|
1501
|
+
},
|
|
1502
|
+
req
|
|
1503
|
+
});
|
|
1504
|
+
return {
|
|
1505
|
+
...doc,
|
|
1466
1506
|
providerId: providerBroadcast.id,
|
|
1467
1507
|
providerData: providerBroadcast.providerData
|
|
1468
|
-
}
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1508
|
+
};
|
|
1509
|
+
} catch (error) {
|
|
1510
|
+
req.payload.logger.error("Raw error from broadcast provider:");
|
|
1511
|
+
req.payload.logger.error(error);
|
|
1512
|
+
if (error instanceof Error) {
|
|
1513
|
+
req.payload.logger.error("Error is instance of Error:", {
|
|
1514
|
+
message: error.message,
|
|
1515
|
+
stack: error.stack,
|
|
1516
|
+
name: error.name,
|
|
1517
|
+
// If it's a BroadcastProviderError, it might have additional details
|
|
1518
|
+
...error.details,
|
|
1519
|
+
// Check if it's a fetch response error
|
|
1520
|
+
...error.response,
|
|
1521
|
+
...error.data,
|
|
1522
|
+
...error.status,
|
|
1523
|
+
...error.statusText
|
|
1524
|
+
});
|
|
1525
|
+
} else if (typeof error === "string") {
|
|
1526
|
+
req.payload.logger.error("Error is a string:", error);
|
|
1527
|
+
} else if (error && typeof error === "object") {
|
|
1528
|
+
req.payload.logger.error("Error is an object:", JSON.stringify(error, null, 2));
|
|
1529
|
+
} else {
|
|
1530
|
+
req.payload.logger.error("Unknown error type:", typeof error);
|
|
1531
|
+
}
|
|
1532
|
+
req.payload.logger.error("Failed broadcast document:", {
|
|
1533
|
+
id: doc.id,
|
|
1534
|
+
subject: doc.subject,
|
|
1535
|
+
hasContent: !!doc.contentSection?.content,
|
|
1536
|
+
contentType: doc.contentSection?.content ? typeof doc.contentSection.content : "none"
|
|
1484
1537
|
});
|
|
1485
|
-
|
|
1486
|
-
req.payload.logger.error("Failed to create broadcast in provider:", error);
|
|
1538
|
+
return doc;
|
|
1487
1539
|
}
|
|
1488
|
-
return doc;
|
|
1489
1540
|
}
|
|
1541
|
+
if (operation === "update" && doc.providerId) {
|
|
1542
|
+
req.payload.logger.info("Broadcast afterChange update hook triggered", {
|
|
1543
|
+
operation,
|
|
1544
|
+
hasProviderId: !!doc.providerId,
|
|
1545
|
+
sendStatus: doc.sendStatus,
|
|
1546
|
+
publishStatus: doc._status
|
|
1547
|
+
});
|
|
1548
|
+
try {
|
|
1549
|
+
const providerConfig = await getBroadcastConfig(req, pluginConfig);
|
|
1550
|
+
if (!providerConfig || !providerConfig.token) {
|
|
1551
|
+
req.payload.logger.error("Broadcast provider not configured in Newsletter Settings or environment variables");
|
|
1552
|
+
return doc;
|
|
1553
|
+
}
|
|
1554
|
+
const { BroadcastApiProvider: BroadcastApiProvider2 } = await Promise.resolve().then(() => (init_broadcast2(), broadcast_exports));
|
|
1555
|
+
const provider = new BroadcastApiProvider2(providerConfig);
|
|
1556
|
+
const capabilities = provider.getCapabilities();
|
|
1557
|
+
const sendStatus = doc.sendStatus || "draft" /* DRAFT */;
|
|
1558
|
+
if (!capabilities.editableStatuses.includes(sendStatus)) {
|
|
1559
|
+
req.payload.logger.info(`Skipping sync for broadcast in status: ${sendStatus}`);
|
|
1560
|
+
return doc;
|
|
1561
|
+
}
|
|
1562
|
+
const contentChanged = doc.subject !== previousDoc?.subject || doc.contentSection?.preheader !== previousDoc?.contentSection?.preheader || JSON.stringify(doc.contentSection?.content) !== JSON.stringify(previousDoc?.contentSection?.content) || doc.settings?.trackOpens !== previousDoc?.settings?.trackOpens || doc.settings?.trackClicks !== previousDoc?.settings?.trackClicks || doc.settings?.replyTo !== previousDoc?.settings?.replyTo || JSON.stringify(doc.audienceIds) !== JSON.stringify(previousDoc?.audienceIds);
|
|
1563
|
+
if (contentChanged) {
|
|
1564
|
+
const updates = {};
|
|
1565
|
+
if (doc.subject !== previousDoc?.subject) {
|
|
1566
|
+
updates.name = doc.subject;
|
|
1567
|
+
updates.subject = doc.subject;
|
|
1568
|
+
}
|
|
1569
|
+
if (doc.contentSection?.preheader !== previousDoc?.contentSection?.preheader) {
|
|
1570
|
+
updates.preheader = doc.contentSection?.preheader;
|
|
1571
|
+
}
|
|
1572
|
+
if (JSON.stringify(doc.contentSection?.content) !== JSON.stringify(previousDoc?.contentSection?.content)) {
|
|
1573
|
+
updates.content = await convertToEmailSafeHtml(doc.contentSection?.content);
|
|
1574
|
+
}
|
|
1575
|
+
if (doc.settings?.trackOpens !== previousDoc?.settings?.trackOpens) {
|
|
1576
|
+
updates.trackOpens = doc.settings.trackOpens;
|
|
1577
|
+
}
|
|
1578
|
+
if (doc.settings?.trackClicks !== previousDoc?.settings?.trackClicks) {
|
|
1579
|
+
updates.trackClicks = doc.settings.trackClicks;
|
|
1580
|
+
}
|
|
1581
|
+
if (doc.settings?.replyTo !== previousDoc?.settings?.replyTo) {
|
|
1582
|
+
updates.replyTo = doc.settings.replyTo || providerConfig.replyTo;
|
|
1583
|
+
}
|
|
1584
|
+
if (JSON.stringify(doc.audienceIds) !== JSON.stringify(previousDoc?.audienceIds)) {
|
|
1585
|
+
updates.audienceIds = doc.audienceIds?.map((a) => a.audienceId);
|
|
1586
|
+
}
|
|
1587
|
+
req.payload.logger.info("Syncing broadcast updates to provider", {
|
|
1588
|
+
providerId: doc.providerId,
|
|
1589
|
+
updates
|
|
1590
|
+
});
|
|
1591
|
+
await provider.update(doc.providerId, updates);
|
|
1592
|
+
req.payload.logger.info(`Broadcast ${doc.id} synced to provider successfully`);
|
|
1593
|
+
} else {
|
|
1594
|
+
req.payload.logger.info("No content changes to sync to provider");
|
|
1595
|
+
}
|
|
1596
|
+
} catch (error) {
|
|
1597
|
+
if (error instanceof Error) {
|
|
1598
|
+
req.payload.logger.error("Failed to sync broadcast update to provider:", {
|
|
1599
|
+
message: error.message,
|
|
1600
|
+
stack: error.stack,
|
|
1601
|
+
name: error.name,
|
|
1602
|
+
// If it's a BroadcastProviderError, it might have additional details
|
|
1603
|
+
...error.details
|
|
1604
|
+
});
|
|
1605
|
+
} else {
|
|
1606
|
+
req.payload.logger.error("Failed to sync broadcast update to provider:", error);
|
|
1607
|
+
}
|
|
1608
|
+
}
|
|
1609
|
+
}
|
|
1610
|
+
return doc;
|
|
1490
1611
|
},
|
|
1491
1612
|
// Hook to send when published
|
|
1492
1613
|
async ({ doc, operation, previousDoc, req }) => {
|
|
@@ -1544,79 +1665,8 @@ var createBroadcastsCollection = (pluginConfig) => {
|
|
|
1544
1665
|
return doc;
|
|
1545
1666
|
}
|
|
1546
1667
|
],
|
|
1547
|
-
//
|
|
1548
|
-
beforeChange: [
|
|
1549
|
-
async ({ data, originalDoc, operation, req }) => {
|
|
1550
|
-
if (!hasProviders || !originalDoc?.providerId || operation !== "update") return data;
|
|
1551
|
-
req.payload.logger.info("Broadcast beforeChange update hook triggered", {
|
|
1552
|
-
operation,
|
|
1553
|
-
hasProviderId: !!originalDoc?.providerId,
|
|
1554
|
-
originalSendStatus: originalDoc?.sendStatus,
|
|
1555
|
-
originalPublishStatus: originalDoc?._status,
|
|
1556
|
-
newSendStatus: data?.sendStatus,
|
|
1557
|
-
newPublishStatus: data?._status
|
|
1558
|
-
});
|
|
1559
|
-
try {
|
|
1560
|
-
const providerConfig = await getBroadcastConfig(req, pluginConfig);
|
|
1561
|
-
if (!providerConfig || !providerConfig.token) {
|
|
1562
|
-
req.payload.logger.error("Broadcast provider not configured in Newsletter Settings or environment variables");
|
|
1563
|
-
return data;
|
|
1564
|
-
}
|
|
1565
|
-
const { BroadcastApiProvider: BroadcastApiProvider2 } = await Promise.resolve().then(() => (init_broadcast2(), broadcast_exports));
|
|
1566
|
-
const provider = new BroadcastApiProvider2(providerConfig);
|
|
1567
|
-
const capabilities = provider.getCapabilities();
|
|
1568
|
-
const sendStatus = originalDoc.sendStatus || "draft" /* DRAFT */;
|
|
1569
|
-
if (!capabilities.editableStatuses.includes(sendStatus)) {
|
|
1570
|
-
req.payload.logger.info(`Skipping sync for broadcast in status: ${sendStatus}`);
|
|
1571
|
-
return data;
|
|
1572
|
-
}
|
|
1573
|
-
const updates = {};
|
|
1574
|
-
if (data.subject !== originalDoc.subject) {
|
|
1575
|
-
updates.name = data.subject;
|
|
1576
|
-
updates.subject = data.subject;
|
|
1577
|
-
}
|
|
1578
|
-
if (data.contentSection?.preheader !== originalDoc.contentSection?.preheader) updates.preheader = data.contentSection?.preheader;
|
|
1579
|
-
if (data.contentSection?.content !== originalDoc.contentSection?.content) {
|
|
1580
|
-
updates.content = await convertToEmailSafeHtml(data.contentSection?.content);
|
|
1581
|
-
}
|
|
1582
|
-
if (data.settings?.trackOpens !== originalDoc.settings?.trackOpens) {
|
|
1583
|
-
updates.trackOpens = data.settings.trackOpens;
|
|
1584
|
-
}
|
|
1585
|
-
if (data.settings?.trackClicks !== originalDoc.settings?.trackClicks) {
|
|
1586
|
-
updates.trackClicks = data.settings.trackClicks;
|
|
1587
|
-
}
|
|
1588
|
-
if (data.settings?.replyTo !== originalDoc.settings?.replyTo) {
|
|
1589
|
-
updates.replyTo = data.settings.replyTo || providerConfig.replyTo;
|
|
1590
|
-
}
|
|
1591
|
-
if (JSON.stringify(data.audienceIds) !== JSON.stringify(originalDoc.audienceIds)) {
|
|
1592
|
-
updates.audienceIds = data.audienceIds?.map((a) => a.audienceId);
|
|
1593
|
-
}
|
|
1594
|
-
if (Object.keys(updates).length > 0) {
|
|
1595
|
-
req.payload.logger.info("Syncing broadcast updates to provider", {
|
|
1596
|
-
providerId: originalDoc.providerId,
|
|
1597
|
-
updates
|
|
1598
|
-
});
|
|
1599
|
-
await provider.update(originalDoc.providerId, updates);
|
|
1600
|
-
req.payload.logger.info("Successfully synced broadcast updates to provider");
|
|
1601
|
-
} else {
|
|
1602
|
-
req.payload.logger.info("No broadcast updates to sync to provider");
|
|
1603
|
-
}
|
|
1604
|
-
} catch (error) {
|
|
1605
|
-
if (error instanceof Error) {
|
|
1606
|
-
req.payload.logger.error("Failed to update broadcast in provider:", {
|
|
1607
|
-
message: error.message,
|
|
1608
|
-
stack: error.stack,
|
|
1609
|
-
name: error.name,
|
|
1610
|
-
// If it's a BroadcastProviderError, it might have additional details
|
|
1611
|
-
...error.details
|
|
1612
|
-
});
|
|
1613
|
-
} else {
|
|
1614
|
-
req.payload.logger.error("Failed to update broadcast in provider:", error);
|
|
1615
|
-
}
|
|
1616
|
-
}
|
|
1617
|
-
return data;
|
|
1618
|
-
}
|
|
1619
|
-
],
|
|
1668
|
+
// beforeChange hooks can be added here if needed
|
|
1669
|
+
beforeChange: [],
|
|
1620
1670
|
// Handle deletion
|
|
1621
1671
|
afterDelete: [
|
|
1622
1672
|
async ({ doc, req }) => {
|