payload-plugin-newsletter 0.16.5 → 0.16.6

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,24 @@
1
+ ## [0.16.6] - 2025-07-29
2
+
3
+ ### Fixed
4
+ - **Critical: Update Sync Now Works** - Fixed the afterChange hook that was blocking update operations
5
+ - Removed the `operation !== 'create'` check that prevented the afterChange hook from running on updates
6
+ - Moved update sync logic from beforeChange to afterChange for proper architectural pattern
7
+ - Updates are now synced AFTER they're saved to Payload, ensuring consistency
8
+ - Provider sync failures no longer block Payload updates
9
+
10
+ ### Improved
11
+ - **Better Hook Architecture** - Sync operations now happen in the correct lifecycle stage
12
+ - beforeChange was architecturally wrong - if provider sync failed, data would be inconsistent
13
+ - afterChange ensures Payload data is saved first, then syncs to provider
14
+ - More resilient to network failures and API errors
15
+
16
+ ### Technical
17
+ - Consolidated create and update logic in a single afterChange hook
18
+ - Added comprehensive content change detection before syncing
19
+ - Enhanced logging for update sync operations
20
+ - Removed redundant beforeChange hook logic
21
+
1
22
  ## [0.16.5] - 2025-07-29
2
23
 
3
24
  ### Breaking Changes
@@ -1435,58 +1435,130 @@ var createBroadcastsCollection = (pluginConfig) => {
1435
1435
  }
1436
1436
  ],
1437
1437
  hooks: {
1438
- // Sync with provider on create
1438
+ // Sync with provider on create and update
1439
1439
  afterChange: [
1440
- async ({ doc, operation, req }) => {
1441
- if (!hasProviders || operation !== "create") return doc;
1442
- try {
1443
- const providerConfig = await getBroadcastConfig(req, pluginConfig);
1444
- if (!providerConfig || !providerConfig.token) {
1445
- req.payload.logger.error("Broadcast provider not configured in Newsletter Settings or environment variables");
1446
- return doc;
1447
- }
1448
- const { BroadcastApiProvider: BroadcastApiProvider2 } = await Promise.resolve().then(() => (init_broadcast2(), broadcast_exports));
1449
- const provider = new BroadcastApiProvider2(providerConfig);
1450
- const htmlContent = await convertToEmailSafeHtml(doc.contentSection?.content);
1451
- const providerBroadcast = await provider.create({
1452
- name: doc.subject,
1453
- // Use subject as name since we removed the name field
1454
- subject: doc.subject,
1455
- preheader: doc.contentSection?.preheader,
1456
- content: htmlContent,
1457
- trackOpens: doc.settings?.trackOpens,
1458
- trackClicks: doc.settings?.trackClicks,
1459
- replyTo: doc.settings?.replyTo || providerConfig.replyTo,
1460
- audienceIds: doc.audienceIds?.map((a) => a.audienceId)
1461
- });
1462
- await req.payload.update({
1463
- collection: "broadcasts",
1464
- id: doc.id,
1465
- data: {
1440
+ async ({ doc, operation, req, previousDoc }) => {
1441
+ if (!hasProviders) return doc;
1442
+ if (operation === "create") {
1443
+ try {
1444
+ const providerConfig = await getBroadcastConfig(req, pluginConfig);
1445
+ if (!providerConfig || !providerConfig.token) {
1446
+ req.payload.logger.error("Broadcast provider not configured in Newsletter Settings or environment variables");
1447
+ return doc;
1448
+ }
1449
+ const { BroadcastApiProvider: BroadcastApiProvider2 } = await Promise.resolve().then(() => (init_broadcast2(), broadcast_exports));
1450
+ const provider = new BroadcastApiProvider2(providerConfig);
1451
+ const htmlContent = await convertToEmailSafeHtml(doc.contentSection?.content);
1452
+ const providerBroadcast = await provider.create({
1453
+ name: doc.subject,
1454
+ // Use subject as name since we removed the name field
1455
+ subject: doc.subject,
1456
+ preheader: doc.contentSection?.preheader,
1457
+ content: htmlContent,
1458
+ trackOpens: doc.settings?.trackOpens,
1459
+ trackClicks: doc.settings?.trackClicks,
1460
+ replyTo: doc.settings?.replyTo || providerConfig.replyTo,
1461
+ audienceIds: doc.audienceIds?.map((a) => a.audienceId)
1462
+ });
1463
+ await req.payload.update({
1464
+ collection: "broadcasts",
1465
+ id: doc.id,
1466
+ data: {
1467
+ providerId: providerBroadcast.id,
1468
+ providerData: providerBroadcast.providerData
1469
+ },
1470
+ req
1471
+ });
1472
+ return {
1473
+ ...doc,
1466
1474
  providerId: providerBroadcast.id,
1467
1475
  providerData: providerBroadcast.providerData
1468
- },
1469
- req
1476
+ };
1477
+ } catch (error) {
1478
+ if (error instanceof Error) {
1479
+ req.payload.logger.error("Failed to create broadcast in provider:", {
1480
+ message: error.message,
1481
+ stack: error.stack,
1482
+ name: error.name,
1483
+ // If it's a BroadcastProviderError, it might have additional details
1484
+ ...error.details
1485
+ });
1486
+ } else {
1487
+ req.payload.logger.error("Failed to create broadcast in provider:", error);
1488
+ }
1489
+ return doc;
1490
+ }
1491
+ }
1492
+ if (operation === "update" && doc.providerId) {
1493
+ req.payload.logger.info("Broadcast afterChange update hook triggered", {
1494
+ operation,
1495
+ hasProviderId: !!doc.providerId,
1496
+ sendStatus: doc.sendStatus,
1497
+ publishStatus: doc._status
1470
1498
  });
1471
- return {
1472
- ...doc,
1473
- providerId: providerBroadcast.id,
1474
- providerData: providerBroadcast.providerData
1475
- };
1476
- } catch (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);
1499
+ try {
1500
+ const providerConfig = await getBroadcastConfig(req, pluginConfig);
1501
+ if (!providerConfig || !providerConfig.token) {
1502
+ req.payload.logger.error("Broadcast provider not configured in Newsletter Settings or environment variables");
1503
+ return doc;
1504
+ }
1505
+ const { BroadcastApiProvider: BroadcastApiProvider2 } = await Promise.resolve().then(() => (init_broadcast2(), broadcast_exports));
1506
+ const provider = new BroadcastApiProvider2(providerConfig);
1507
+ const capabilities = provider.getCapabilities();
1508
+ const sendStatus = doc.sendStatus || "draft" /* DRAFT */;
1509
+ if (!capabilities.editableStatuses.includes(sendStatus)) {
1510
+ req.payload.logger.info(`Skipping sync for broadcast in status: ${sendStatus}`);
1511
+ return doc;
1512
+ }
1513
+ 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);
1514
+ if (contentChanged) {
1515
+ const updates = {};
1516
+ if (doc.subject !== previousDoc?.subject) {
1517
+ updates.name = doc.subject;
1518
+ updates.subject = doc.subject;
1519
+ }
1520
+ if (doc.contentSection?.preheader !== previousDoc?.contentSection?.preheader) {
1521
+ updates.preheader = doc.contentSection?.preheader;
1522
+ }
1523
+ if (JSON.stringify(doc.contentSection?.content) !== JSON.stringify(previousDoc?.contentSection?.content)) {
1524
+ updates.content = await convertToEmailSafeHtml(doc.contentSection?.content);
1525
+ }
1526
+ if (doc.settings?.trackOpens !== previousDoc?.settings?.trackOpens) {
1527
+ updates.trackOpens = doc.settings.trackOpens;
1528
+ }
1529
+ if (doc.settings?.trackClicks !== previousDoc?.settings?.trackClicks) {
1530
+ updates.trackClicks = doc.settings.trackClicks;
1531
+ }
1532
+ if (doc.settings?.replyTo !== previousDoc?.settings?.replyTo) {
1533
+ updates.replyTo = doc.settings.replyTo || providerConfig.replyTo;
1534
+ }
1535
+ if (JSON.stringify(doc.audienceIds) !== JSON.stringify(previousDoc?.audienceIds)) {
1536
+ updates.audienceIds = doc.audienceIds?.map((a) => a.audienceId);
1537
+ }
1538
+ req.payload.logger.info("Syncing broadcast updates to provider", {
1539
+ providerId: doc.providerId,
1540
+ updates
1541
+ });
1542
+ await provider.update(doc.providerId, updates);
1543
+ req.payload.logger.info(`Broadcast ${doc.id} synced to provider successfully`);
1544
+ } else {
1545
+ req.payload.logger.info("No content changes to sync to provider");
1546
+ }
1547
+ } catch (error) {
1548
+ if (error instanceof Error) {
1549
+ req.payload.logger.error("Failed to sync broadcast update to provider:", {
1550
+ message: error.message,
1551
+ stack: error.stack,
1552
+ name: error.name,
1553
+ // If it's a BroadcastProviderError, it might have additional details
1554
+ ...error.details
1555
+ });
1556
+ } else {
1557
+ req.payload.logger.error("Failed to sync broadcast update to provider:", error);
1558
+ }
1487
1559
  }
1488
- return doc;
1489
1560
  }
1561
+ return doc;
1490
1562
  },
1491
1563
  // Hook to send when published
1492
1564
  async ({ doc, operation, previousDoc, req }) => {
@@ -1544,79 +1616,8 @@ var createBroadcastsCollection = (pluginConfig) => {
1544
1616
  return doc;
1545
1617
  }
1546
1618
  ],
1547
- // Sync updates with provider
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
- ],
1619
+ // beforeChange hooks can be added here if needed
1620
+ beforeChange: [],
1620
1621
  // Handle deletion
1621
1622
  afterDelete: [
1622
1623
  async ({ doc, req }) => {