payload-plugin-newsletter 0.25.0 → 0.25.2

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,22 @@
1
+ ## [0.25.2] - 2025-08-15
2
+
3
+ ### Fixed
4
+ - Fixed issue where editing draft broadcasts created duplicate entries in the provider instead of updating the existing broadcast
5
+ - Broadcasts now immediately sync to provider on creation with minimal data to establish the association
6
+
7
+ ## [0.25.1] - 2025-08-09
8
+
9
+ ### Fixed
10
+ - **Removed webhook status updates**: Eliminated permission errors when processing Broadcast webhooks
11
+ - Webhook handler no longer attempts to update global settings
12
+ - Fixes "Only administrators can modify newsletter settings" errors
13
+
14
+ ### Changed
15
+ - **Webhook subscribers now start as active**: `subscriber.created` webhook events now set status as 'active' instead of 'pending'
16
+ - Better aligns with single opt-in workflows
17
+ - Matches Broadcast's confirmed subscriber status
18
+ - Welcome emails still prevented by `importedFromProvider` flag
19
+
1
20
  ## [0.25.0] - 2025-08-08
2
21
 
3
22
  ### Changed
@@ -1514,7 +1514,7 @@ var createSendBroadcastEndpoint = (config, collectionSlug) => {
1514
1514
  return {
1515
1515
  path: "/:id/send",
1516
1516
  method: "post",
1517
- handler: async (req) => {
1517
+ handler: (async (req) => {
1518
1518
  try {
1519
1519
  const auth = await requireAdmin(req, config);
1520
1520
  if (!auth.authorized) {
@@ -1588,7 +1588,7 @@ var createSendBroadcastEndpoint = (config, collectionSlug) => {
1588
1588
  error: "Failed to send broadcast"
1589
1589
  }, { status: 500 });
1590
1590
  }
1591
- }
1591
+ })
1592
1592
  };
1593
1593
  };
1594
1594
 
@@ -1598,7 +1598,7 @@ var createScheduleBroadcastEndpoint = (config, collectionSlug) => {
1598
1598
  return {
1599
1599
  path: "/:id/schedule",
1600
1600
  method: "post",
1601
- handler: async (req) => {
1601
+ handler: (async (req) => {
1602
1602
  try {
1603
1603
  const auth = await requireAdmin(req, config);
1604
1604
  if (!auth.authorized) {
@@ -1692,7 +1692,7 @@ var createScheduleBroadcastEndpoint = (config, collectionSlug) => {
1692
1692
  error: "Failed to schedule broadcast"
1693
1693
  }, { status: 500 });
1694
1694
  }
1695
- }
1695
+ })
1696
1696
  };
1697
1697
  };
1698
1698
 
@@ -1701,7 +1701,7 @@ var createTestBroadcastEndpoint = (config, collectionSlug) => {
1701
1701
  return {
1702
1702
  path: "/:id/test",
1703
1703
  method: "post",
1704
- handler: async (req) => {
1704
+ handler: (async (req) => {
1705
1705
  try {
1706
1706
  const auth = await requireAdmin(req, config);
1707
1707
  if (!auth.authorized) {
@@ -1775,7 +1775,7 @@ var createTestBroadcastEndpoint = (config, collectionSlug) => {
1775
1775
  error: "Failed to send test email"
1776
1776
  }, { status: 500 });
1777
1777
  }
1778
- }
1778
+ })
1779
1779
  };
1780
1780
  };
1781
1781
 
@@ -1864,7 +1864,7 @@ var createBroadcastPreviewEndpoint = (config, _collectionSlug) => {
1864
1864
  return {
1865
1865
  path: "/preview",
1866
1866
  method: "post",
1867
- handler: async (req) => {
1867
+ handler: (async (req) => {
1868
1868
  try {
1869
1869
  const data = await (req.json?.() || Promise.resolve({}));
1870
1870
  const { content, preheader, subject, documentData } = data;
@@ -1904,7 +1904,7 @@ var createBroadcastPreviewEndpoint = (config, _collectionSlug) => {
1904
1904
  error: "Failed to generate email preview"
1905
1905
  }, { status: 500 });
1906
1906
  }
1907
- }
1907
+ })
1908
1908
  };
1909
1909
  };
1910
1910
 
@@ -2249,10 +2249,6 @@ var createBroadcastsCollection = (pluginConfig) => {
2249
2249
  async ({ doc, operation, req, previousDoc }) => {
2250
2250
  if (!hasProviders) return doc;
2251
2251
  if (operation === "create") {
2252
- if (!doc.subject || !doc.contentSection?.content) {
2253
- req.payload.logger.info("Skipping provider sync - broadcast has no subject or content yet");
2254
- return doc;
2255
- }
2256
2252
  try {
2257
2253
  const providerConfig = await getBroadcastConfig(req, pluginConfig);
2258
2254
  if (!providerConfig || !providerConfig.token) {
@@ -2261,45 +2257,32 @@ var createBroadcastsCollection = (pluginConfig) => {
2261
2257
  }
2262
2258
  const { BroadcastApiProvider: BroadcastApiProvider2 } = await Promise.resolve().then(() => (init_broadcast2(), broadcast_exports));
2263
2259
  const provider = new BroadcastApiProvider2(providerConfig);
2264
- req.payload.logger.info("Populating media fields and converting content to HTML...");
2265
- const populatedContent = await populateMediaFields(doc.contentSection?.content, req.payload, pluginConfig);
2266
- const emailPreviewConfig = pluginConfig.customizations?.broadcasts?.emailPreview;
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
2274
- customBlockConverter: pluginConfig.customizations?.broadcasts?.customBlockConverter
2275
- });
2276
- if (!htmlContent || htmlContent.trim() === "") {
2277
- req.payload.logger.info("Skipping provider sync - content is empty after conversion");
2278
- return doc;
2279
- }
2260
+ const subject = doc.subject || `Draft Broadcast ${(/* @__PURE__ */ new Date()).toISOString()}`;
2261
+ const htmlContent = doc.contentSection?.content ? await convertToEmailSafeHtml(
2262
+ await populateMediaFields(doc.contentSection.content, req.payload, pluginConfig),
2263
+ {
2264
+ wrapInTemplate: pluginConfig.customizations?.broadcasts?.emailPreview?.wrapInTemplate ?? true,
2265
+ customWrapper: pluginConfig.customizations?.broadcasts?.emailPreview?.customWrapper,
2266
+ preheader: doc.contentSection?.preheader,
2267
+ subject,
2268
+ documentData: doc,
2269
+ customBlockConverter: pluginConfig.customizations?.broadcasts?.customBlockConverter
2270
+ }
2271
+ ) : "<p>Draft content - to be updated</p>";
2280
2272
  const createData = {
2281
- name: doc.subject,
2282
- // Use subject as name since we removed the name field
2283
- subject: doc.subject,
2284
- preheader: doc.contentSection?.preheader,
2273
+ name: subject,
2274
+ // Use subject as name
2275
+ subject,
2276
+ preheader: doc.contentSection?.preheader || "",
2285
2277
  content: htmlContent,
2286
- trackOpens: doc.settings?.trackOpens,
2287
- trackClicks: doc.settings?.trackClicks,
2278
+ trackOpens: doc.settings?.trackOpens ?? true,
2279
+ trackClicks: doc.settings?.trackClicks ?? true,
2288
2280
  replyTo: doc.settings?.replyTo || providerConfig.replyTo,
2289
- audienceIds: doc.audienceIds?.map((a) => a.audienceId)
2281
+ audienceIds: doc.audienceIds?.map((a) => a.audienceId) || []
2290
2282
  };
2291
- req.payload.logger.info("Creating broadcast with data:", {
2292
- name: createData.name,
2283
+ req.payload.logger.info("Creating broadcast in provider with minimal data to establish association", {
2293
2284
  subject: createData.subject,
2294
- preheader: createData.preheader || "NONE",
2295
- contentLength: htmlContent ? htmlContent.length : 0,
2296
- contentPreview: htmlContent ? htmlContent.substring(0, 100) + "..." : "EMPTY",
2297
- trackOpens: createData.trackOpens,
2298
- trackClicks: createData.trackClicks,
2299
- replyTo: createData.replyTo,
2300
- audienceIds: createData.audienceIds || [],
2301
- apiUrl: providerConfig.apiUrl,
2302
- hasToken: !!providerConfig.token
2285
+ hasActualContent: !!doc.contentSection?.content
2303
2286
  });
2304
2287
  const providerBroadcast = await provider.create(createData);
2305
2288
  await req.payload.update({
@@ -2311,40 +2294,14 @@ var createBroadcastsCollection = (pluginConfig) => {
2311
2294
  },
2312
2295
  req
2313
2296
  });
2297
+ req.payload.logger.info(`Broadcast ${doc.id} created in provider with ID ${providerBroadcast.id}`);
2314
2298
  return {
2315
2299
  ...doc,
2316
2300
  providerId: providerBroadcast.id,
2317
2301
  providerData: providerBroadcast.providerData
2318
2302
  };
2319
2303
  } catch (error) {
2320
- req.payload.logger.error("Raw error from broadcast provider:");
2321
- req.payload.logger.error(error);
2322
- if (error instanceof Error) {
2323
- req.payload.logger.error("Error is instance of Error:", {
2324
- message: error.message,
2325
- stack: error.stack,
2326
- name: error.name,
2327
- // If it's a BroadcastProviderError, it might have additional details
2328
- ...error.details,
2329
- // Check if it's a fetch response error
2330
- ...error.response,
2331
- ...error.data,
2332
- ...error.status,
2333
- ...error.statusText
2334
- });
2335
- } else if (typeof error === "string") {
2336
- req.payload.logger.error("Error is a string:", error);
2337
- } else if (error && typeof error === "object") {
2338
- req.payload.logger.error("Error is an object:", JSON.stringify(error, null, 2));
2339
- } else {
2340
- req.payload.logger.error("Unknown error type:", typeof error);
2341
- }
2342
- req.payload.logger.error("Failed broadcast document:", {
2343
- id: doc.id,
2344
- subject: doc.subject,
2345
- hasContent: !!doc.contentSection?.content,
2346
- contentType: doc.contentSection?.content ? typeof doc.contentSection.content : "none"
2347
- });
2304
+ req.payload.logger.error("Failed to create broadcast in provider during initial creation:", error);
2348
2305
  return doc;
2349
2306
  }
2350
2307
  }
@@ -2364,61 +2321,8 @@ var createBroadcastsCollection = (pluginConfig) => {
2364
2321
  const { BroadcastApiProvider: BroadcastApiProvider2 } = await Promise.resolve().then(() => (init_broadcast2(), broadcast_exports));
2365
2322
  const provider = new BroadcastApiProvider2(providerConfig);
2366
2323
  if (!doc.providerId) {
2367
- if (!doc.subject || !doc.contentSection?.content) {
2368
- req.payload.logger.info("Still missing required fields for provider sync");
2369
- return doc;
2370
- }
2371
- req.payload.logger.info("Creating broadcast in provider (deferred from initial create)...");
2372
- const populatedContent = await populateMediaFields(doc.contentSection?.content, req.payload, pluginConfig);
2373
- const emailPreviewConfig = pluginConfig.customizations?.broadcasts?.emailPreview;
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
2381
- customBlockConverter: pluginConfig.customizations?.broadcasts?.customBlockConverter
2382
- });
2383
- if (!htmlContent || htmlContent.trim() === "") {
2384
- req.payload.logger.info("Skipping provider sync - content is empty after conversion");
2385
- return doc;
2386
- }
2387
- const createData = {
2388
- name: doc.subject,
2389
- subject: doc.subject,
2390
- preheader: doc.contentSection?.preheader,
2391
- content: htmlContent,
2392
- trackOpens: doc.settings?.trackOpens,
2393
- trackClicks: doc.settings?.trackClicks,
2394
- replyTo: doc.settings?.replyTo || providerConfig.replyTo,
2395
- audienceIds: doc.audienceIds?.map((a) => a.audienceId)
2396
- };
2397
- req.payload.logger.info("Creating broadcast with data:", {
2398
- name: createData.name,
2399
- subject: createData.subject,
2400
- preheader: createData.preheader || "NONE",
2401
- contentLength: htmlContent ? htmlContent.length : 0,
2402
- contentPreview: htmlContent ? htmlContent.substring(0, 100) + "..." : "EMPTY",
2403
- apiUrl: providerConfig.apiUrl,
2404
- hasToken: !!providerConfig.token
2405
- });
2406
- const providerBroadcast = await provider.create(createData);
2407
- await req.payload.update({
2408
- collection: "broadcasts",
2409
- id: doc.id,
2410
- data: {
2411
- providerId: providerBroadcast.id,
2412
- providerData: providerBroadcast.providerData
2413
- },
2414
- req
2415
- });
2416
- req.payload.logger.info(`Broadcast ${doc.id} created in provider successfully (deferred)`);
2417
- return {
2418
- ...doc,
2419
- providerId: providerBroadcast.id,
2420
- providerData: providerBroadcast.providerData
2421
- };
2324
+ req.payload.logger.warn(`Broadcast ${doc.id} has no providerId - provider sync skipped. This shouldn't happen with immediate creation.`);
2325
+ return doc;
2422
2326
  }
2423
2327
  if (doc.providerId) {
2424
2328
  const capabilities = provider.getCapabilities();