payload-plugin-newsletter 0.16.2 → 0.16.5
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 +50 -0
- package/dist/collections.cjs +90 -18
- package/dist/collections.cjs.map +1 -1
- package/dist/collections.js +90 -18
- package/dist/collections.js.map +1 -1
- package/dist/index.cjs +94 -22
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +94 -22
- package/dist/index.js.map +1 -1
- package/dist/types.cjs.map +1 -1
- package/dist/types.d.cts +1 -1
- package/dist/types.d.ts +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +1 -1
- package/plugin-api-key-recommendations.md +117 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,53 @@
|
|
|
1
|
+
## [0.16.5] - 2025-07-29
|
|
2
|
+
|
|
3
|
+
### Breaking Changes
|
|
4
|
+
- **Field Renaming** - Renamed `status` field to `sendStatus` throughout the codebase
|
|
5
|
+
- This avoids confusion with Payload's built-in `_status` field (draft/published)
|
|
6
|
+
- Database field is now `sendStatus` for email send status (draft, scheduled, sending, sent, etc.)
|
|
7
|
+
- All references in providers, endpoints, and types have been updated
|
|
8
|
+
- If you have existing broadcast data with a `status` field, you'll need to migrate it to `sendStatus`
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
- **Update Sync** - Fixed issue where broadcast updates made in Payload weren't syncing to the Broadcast provider
|
|
12
|
+
- The update hook now correctly checks `sendStatus` instead of the non-existent `status` field
|
|
13
|
+
- Provider can now properly determine if a broadcast is editable based on its send status
|
|
14
|
+
|
|
15
|
+
### Technical
|
|
16
|
+
- Updated `Broadcast` type interface to use `sendStatus` property
|
|
17
|
+
- Updated all provider implementations (Broadcast and Resend) to use `sendStatus`
|
|
18
|
+
- Updated send and schedule endpoints to set `sendStatus` field
|
|
19
|
+
- All TypeScript errors resolved
|
|
20
|
+
|
|
21
|
+
## [0.16.4] - 2025-07-27
|
|
22
|
+
|
|
23
|
+
### Added
|
|
24
|
+
- **Access Control for Broadcasts** - Added proper access control to the Broadcasts collection
|
|
25
|
+
- Public read access for all users
|
|
26
|
+
- Create, update, and delete operations require authenticated users
|
|
27
|
+
- Prevents unauthorized modifications to broadcast content
|
|
28
|
+
- Follows Payload's standard access control patterns
|
|
29
|
+
|
|
30
|
+
### Improved
|
|
31
|
+
- **Enhanced Update Sync Debugging** - Added detailed logging for broadcast update synchronization
|
|
32
|
+
- Logs when update hooks are triggered with operation details
|
|
33
|
+
- Shows what fields are being synced to the provider
|
|
34
|
+
- Helps diagnose why updates might not be syncing
|
|
35
|
+
- Added info logging for skipped updates due to status restrictions
|
|
36
|
+
- **Clearer Field Naming** - Renamed `status` field to `sendStatus` to avoid confusion with Payload's `_status`
|
|
37
|
+
- Database field is now `sendStatus` (draft, scheduled, sending, sent, etc.)
|
|
38
|
+
- Payload's versioning field remains `_status` (draft, published)
|
|
39
|
+
- Added virtual `status` field for API backward compatibility
|
|
40
|
+
- Makes it clear which status controls email sending vs content publishing
|
|
41
|
+
|
|
42
|
+
## [0.16.3] - 2025-07-27
|
|
43
|
+
|
|
44
|
+
### Improved
|
|
45
|
+
- **Enhanced Error Logging** - Improved error logging for broadcast operations
|
|
46
|
+
- Now logs full error details including message, stack trace, and any additional error properties
|
|
47
|
+
- Helps diagnose API connection issues, authentication failures, and validation errors
|
|
48
|
+
- Structured error logging makes it easier to identify root causes
|
|
49
|
+
- Applied to all broadcast hooks: create, update, delete, and send
|
|
50
|
+
|
|
1
51
|
## [0.16.2] - 2025-07-27
|
|
2
52
|
|
|
3
53
|
### Fixed
|
package/dist/collections.cjs
CHANGED
|
@@ -291,9 +291,9 @@ var init_broadcast2 = __esm({
|
|
|
291
291
|
async update(id, data) {
|
|
292
292
|
try {
|
|
293
293
|
const existing = await this.get(id);
|
|
294
|
-
if (!this.canEditInStatus(existing.
|
|
294
|
+
if (!this.canEditInStatus(existing.sendStatus)) {
|
|
295
295
|
throw new BroadcastProviderError(
|
|
296
|
-
`Cannot update broadcast in status: ${existing.
|
|
296
|
+
`Cannot update broadcast in status: ${existing.sendStatus}`,
|
|
297
297
|
"INVALID_STATUS" /* INVALID_STATUS */,
|
|
298
298
|
this.name
|
|
299
299
|
);
|
|
@@ -336,9 +336,9 @@ var init_broadcast2 = __esm({
|
|
|
336
336
|
async delete(id) {
|
|
337
337
|
try {
|
|
338
338
|
const existing = await this.get(id);
|
|
339
|
-
if (!this.canEditInStatus(existing.
|
|
339
|
+
if (!this.canEditInStatus(existing.sendStatus)) {
|
|
340
340
|
throw new BroadcastProviderError(
|
|
341
|
-
`Cannot delete broadcast in status: ${existing.
|
|
341
|
+
`Cannot delete broadcast in status: ${existing.sendStatus}`,
|
|
342
342
|
"INVALID_STATUS" /* INVALID_STATUS */,
|
|
343
343
|
this.name
|
|
344
344
|
);
|
|
@@ -497,7 +497,7 @@ var init_broadcast2 = __esm({
|
|
|
497
497
|
subject: broadcast.subject,
|
|
498
498
|
preheader: broadcast.preheader,
|
|
499
499
|
content: broadcast.body,
|
|
500
|
-
|
|
500
|
+
sendStatus: this.mapBroadcastStatus(broadcast.status),
|
|
501
501
|
trackOpens: broadcast.track_opens,
|
|
502
502
|
trackClicks: broadcast.track_clicks,
|
|
503
503
|
replyTo: broadcast.reply_to,
|
|
@@ -1191,6 +1191,19 @@ var createBroadcastsCollection = (pluginConfig) => {
|
|
|
1191
1191
|
const customizations = pluginConfig.customizations?.broadcasts;
|
|
1192
1192
|
return {
|
|
1193
1193
|
slug: "broadcasts",
|
|
1194
|
+
access: {
|
|
1195
|
+
read: () => true,
|
|
1196
|
+
// Public read access
|
|
1197
|
+
create: ({ req: { user } }) => {
|
|
1198
|
+
return Boolean(user);
|
|
1199
|
+
},
|
|
1200
|
+
update: ({ req: { user } }) => {
|
|
1201
|
+
return Boolean(user);
|
|
1202
|
+
},
|
|
1203
|
+
delete: ({ req: { user } }) => {
|
|
1204
|
+
return Boolean(user);
|
|
1205
|
+
}
|
|
1206
|
+
},
|
|
1194
1207
|
versions: {
|
|
1195
1208
|
drafts: {
|
|
1196
1209
|
autosave: true,
|
|
@@ -1204,7 +1217,7 @@ var createBroadcastsCollection = (pluginConfig) => {
|
|
|
1204
1217
|
admin: {
|
|
1205
1218
|
useAsTitle: "subject",
|
|
1206
1219
|
description: "Individual email campaigns sent to subscribers",
|
|
1207
|
-
defaultColumns: ["subject", "_status", "
|
|
1220
|
+
defaultColumns: ["subject", "_status", "sendStatus", "sentAt", "recipientCount"]
|
|
1208
1221
|
},
|
|
1209
1222
|
fields: [
|
|
1210
1223
|
{
|
|
@@ -1264,8 +1277,9 @@ var createBroadcastsCollection = (pluginConfig) => {
|
|
|
1264
1277
|
]
|
|
1265
1278
|
},
|
|
1266
1279
|
{
|
|
1267
|
-
name: "
|
|
1280
|
+
name: "sendStatus",
|
|
1268
1281
|
type: "select",
|
|
1282
|
+
label: "Send Status",
|
|
1269
1283
|
required: true,
|
|
1270
1284
|
defaultValue: "draft" /* DRAFT */,
|
|
1271
1285
|
options: [
|
|
@@ -1279,6 +1293,7 @@ var createBroadcastsCollection = (pluginConfig) => {
|
|
|
1279
1293
|
],
|
|
1280
1294
|
admin: {
|
|
1281
1295
|
readOnly: true,
|
|
1296
|
+
description: "The status of the email send operation",
|
|
1282
1297
|
components: {
|
|
1283
1298
|
Cell: "payload-plugin-newsletter/components#StatusBadge"
|
|
1284
1299
|
}
|
|
@@ -1335,7 +1350,7 @@ var createBroadcastsCollection = (pluginConfig) => {
|
|
|
1335
1350
|
type: "group",
|
|
1336
1351
|
admin: {
|
|
1337
1352
|
readOnly: true,
|
|
1338
|
-
condition: (data) => data?.
|
|
1353
|
+
condition: (data) => data?.sendStatus === "sent" /* SENT */
|
|
1339
1354
|
},
|
|
1340
1355
|
fields: [
|
|
1341
1356
|
{
|
|
@@ -1394,7 +1409,7 @@ var createBroadcastsCollection = (pluginConfig) => {
|
|
|
1394
1409
|
name: "scheduledAt",
|
|
1395
1410
|
type: "date",
|
|
1396
1411
|
admin: {
|
|
1397
|
-
condition: (data) => data?.
|
|
1412
|
+
condition: (data) => data?.sendStatus === "scheduled" /* SCHEDULED */,
|
|
1398
1413
|
date: {
|
|
1399
1414
|
displayFormat: "MMM d, yyyy h:mm a"
|
|
1400
1415
|
}
|
|
@@ -1459,7 +1474,17 @@ var createBroadcastsCollection = (pluginConfig) => {
|
|
|
1459
1474
|
providerData: providerBroadcast.providerData
|
|
1460
1475
|
};
|
|
1461
1476
|
} catch (error) {
|
|
1462
|
-
|
|
1477
|
+
if (error instanceof Error) {
|
|
1478
|
+
req.payload.logger.error("Failed to create broadcast in provider:", {
|
|
1479
|
+
message: error.message,
|
|
1480
|
+
stack: error.stack,
|
|
1481
|
+
name: error.name,
|
|
1482
|
+
// If it's a BroadcastProviderError, it might have additional details
|
|
1483
|
+
...error.details
|
|
1484
|
+
});
|
|
1485
|
+
} else {
|
|
1486
|
+
req.payload.logger.error("Failed to create broadcast in provider:", error);
|
|
1487
|
+
}
|
|
1463
1488
|
return doc;
|
|
1464
1489
|
}
|
|
1465
1490
|
},
|
|
@@ -1469,7 +1494,7 @@ var createBroadcastsCollection = (pluginConfig) => {
|
|
|
1469
1494
|
const wasUnpublished = !previousDoc?._status || previousDoc._status === "draft";
|
|
1470
1495
|
const isNowPublished = doc._status === "published";
|
|
1471
1496
|
if (wasUnpublished && isNowPublished && doc.providerId) {
|
|
1472
|
-
if (doc.
|
|
1497
|
+
if (doc.sendStatus === "sent" /* SENT */ || doc.sendStatus === "sending" /* SENDING */) {
|
|
1473
1498
|
return doc;
|
|
1474
1499
|
}
|
|
1475
1500
|
try {
|
|
@@ -1488,19 +1513,29 @@ var createBroadcastsCollection = (pluginConfig) => {
|
|
|
1488
1513
|
collection: "broadcasts",
|
|
1489
1514
|
id: doc.id,
|
|
1490
1515
|
data: {
|
|
1491
|
-
|
|
1516
|
+
sendStatus: "sending" /* SENDING */,
|
|
1492
1517
|
sentAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1493
1518
|
},
|
|
1494
1519
|
req
|
|
1495
1520
|
});
|
|
1496
1521
|
req.payload.logger.info(`Broadcast ${doc.id} sent successfully`);
|
|
1497
1522
|
} catch (error) {
|
|
1498
|
-
|
|
1523
|
+
if (error instanceof Error) {
|
|
1524
|
+
req.payload.logger.error(`Failed to send broadcast ${doc.id}:`, {
|
|
1525
|
+
message: error.message,
|
|
1526
|
+
stack: error.stack,
|
|
1527
|
+
name: error.name,
|
|
1528
|
+
// If it's a BroadcastProviderError, it might have additional details
|
|
1529
|
+
...error.details
|
|
1530
|
+
});
|
|
1531
|
+
} else {
|
|
1532
|
+
req.payload.logger.error(`Failed to send broadcast ${doc.id}:`, error);
|
|
1533
|
+
}
|
|
1499
1534
|
await req.payload.update({
|
|
1500
1535
|
collection: "broadcasts",
|
|
1501
1536
|
id: doc.id,
|
|
1502
1537
|
data: {
|
|
1503
|
-
|
|
1538
|
+
sendStatus: "failed" /* FAILED */
|
|
1504
1539
|
},
|
|
1505
1540
|
req
|
|
1506
1541
|
});
|
|
@@ -1513,6 +1548,14 @@ var createBroadcastsCollection = (pluginConfig) => {
|
|
|
1513
1548
|
beforeChange: [
|
|
1514
1549
|
async ({ data, originalDoc, operation, req }) => {
|
|
1515
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
|
+
});
|
|
1516
1559
|
try {
|
|
1517
1560
|
const providerConfig = await getBroadcastConfig(req, pluginConfig);
|
|
1518
1561
|
if (!providerConfig || !providerConfig.token) {
|
|
@@ -1522,7 +1565,9 @@ var createBroadcastsCollection = (pluginConfig) => {
|
|
|
1522
1565
|
const { BroadcastApiProvider: BroadcastApiProvider2 } = await Promise.resolve().then(() => (init_broadcast2(), broadcast_exports));
|
|
1523
1566
|
const provider = new BroadcastApiProvider2(providerConfig);
|
|
1524
1567
|
const capabilities = provider.getCapabilities();
|
|
1525
|
-
|
|
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}`);
|
|
1526
1571
|
return data;
|
|
1527
1572
|
}
|
|
1528
1573
|
const updates = {};
|
|
@@ -1547,10 +1592,27 @@ var createBroadcastsCollection = (pluginConfig) => {
|
|
|
1547
1592
|
updates.audienceIds = data.audienceIds?.map((a) => a.audienceId);
|
|
1548
1593
|
}
|
|
1549
1594
|
if (Object.keys(updates).length > 0) {
|
|
1595
|
+
req.payload.logger.info("Syncing broadcast updates to provider", {
|
|
1596
|
+
providerId: originalDoc.providerId,
|
|
1597
|
+
updates
|
|
1598
|
+
});
|
|
1550
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");
|
|
1551
1603
|
}
|
|
1552
1604
|
} catch (error) {
|
|
1553
|
-
|
|
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
|
+
}
|
|
1554
1616
|
}
|
|
1555
1617
|
return data;
|
|
1556
1618
|
}
|
|
@@ -1568,11 +1630,21 @@ var createBroadcastsCollection = (pluginConfig) => {
|
|
|
1568
1630
|
const { BroadcastApiProvider: BroadcastApiProvider2 } = await Promise.resolve().then(() => (init_broadcast2(), broadcast_exports));
|
|
1569
1631
|
const provider = new BroadcastApiProvider2(providerConfig);
|
|
1570
1632
|
const capabilities = provider.getCapabilities();
|
|
1571
|
-
if (capabilities.editableStatuses.includes(doc.
|
|
1633
|
+
if (capabilities.editableStatuses.includes(doc.sendStatus)) {
|
|
1572
1634
|
await provider.delete(doc.providerId);
|
|
1573
1635
|
}
|
|
1574
1636
|
} catch (error) {
|
|
1575
|
-
|
|
1637
|
+
if (error instanceof Error) {
|
|
1638
|
+
req.payload.logger.error("Failed to delete broadcast from provider:", {
|
|
1639
|
+
message: error.message,
|
|
1640
|
+
stack: error.stack,
|
|
1641
|
+
name: error.name,
|
|
1642
|
+
// If it's a BroadcastProviderError, it might have additional details
|
|
1643
|
+
...error.details
|
|
1644
|
+
});
|
|
1645
|
+
} else {
|
|
1646
|
+
req.payload.logger.error("Failed to delete broadcast from provider:", error);
|
|
1647
|
+
}
|
|
1576
1648
|
}
|
|
1577
1649
|
return doc;
|
|
1578
1650
|
}
|