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 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
@@ -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.status)) {
294
+ if (!this.canEditInStatus(existing.sendStatus)) {
295
295
  throw new BroadcastProviderError(
296
- `Cannot update broadcast in status: ${existing.status}`,
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.status)) {
339
+ if (!this.canEditInStatus(existing.sendStatus)) {
340
340
  throw new BroadcastProviderError(
341
- `Cannot delete broadcast in status: ${existing.status}`,
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
- status: this.mapBroadcastStatus(broadcast.status),
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", "status", "sentAt", "recipientCount"]
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: "status",
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?.status === "sent" /* SENT */
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?.status === "scheduled" /* SCHEDULED */,
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
- req.payload.logger.error("Failed to create broadcast in provider:", error);
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.status === "sent" || doc.status === "sending") {
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
- status: "sending" /* SENDING */,
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
- req.payload.logger.error(`Failed to send broadcast ${doc.id}:`, error);
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
- status: "failed" /* FAILED */
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
- if (!capabilities.editableStatuses.includes(originalDoc.status)) {
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
- req.payload.logger.error("Failed to update broadcast in provider:", 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
+ }
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.status)) {
1633
+ if (capabilities.editableStatuses.includes(doc.sendStatus)) {
1572
1634
  await provider.delete(doc.providerId);
1573
1635
  }
1574
1636
  } catch (error) {
1575
- req.payload.logger.error("Failed to delete broadcast from provider:", error);
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
  }