payload-plugin-newsletter 0.25.1 → 0.25.3

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.
@@ -1501,7 +1501,7 @@ var createSendBroadcastEndpoint = (config, collectionSlug) => {
1501
1501
  return {
1502
1502
  path: "/:id/send",
1503
1503
  method: "post",
1504
- handler: async (req) => {
1504
+ handler: (async (req) => {
1505
1505
  try {
1506
1506
  const auth = await requireAdmin(req, config);
1507
1507
  if (!auth.authorized) {
@@ -1575,7 +1575,7 @@ var createSendBroadcastEndpoint = (config, collectionSlug) => {
1575
1575
  error: "Failed to send broadcast"
1576
1576
  }, { status: 500 });
1577
1577
  }
1578
- }
1578
+ })
1579
1579
  };
1580
1580
  };
1581
1581
 
@@ -1585,7 +1585,7 @@ var createScheduleBroadcastEndpoint = (config, collectionSlug) => {
1585
1585
  return {
1586
1586
  path: "/:id/schedule",
1587
1587
  method: "post",
1588
- handler: async (req) => {
1588
+ handler: (async (req) => {
1589
1589
  try {
1590
1590
  const auth = await requireAdmin(req, config);
1591
1591
  if (!auth.authorized) {
@@ -1679,7 +1679,7 @@ var createScheduleBroadcastEndpoint = (config, collectionSlug) => {
1679
1679
  error: "Failed to schedule broadcast"
1680
1680
  }, { status: 500 });
1681
1681
  }
1682
- }
1682
+ })
1683
1683
  };
1684
1684
  };
1685
1685
 
@@ -1688,7 +1688,7 @@ var createTestBroadcastEndpoint = (config, collectionSlug) => {
1688
1688
  return {
1689
1689
  path: "/:id/test",
1690
1690
  method: "post",
1691
- handler: async (req) => {
1691
+ handler: (async (req) => {
1692
1692
  try {
1693
1693
  const auth = await requireAdmin(req, config);
1694
1694
  if (!auth.authorized) {
@@ -1762,7 +1762,7 @@ var createTestBroadcastEndpoint = (config, collectionSlug) => {
1762
1762
  error: "Failed to send test email"
1763
1763
  }, { status: 500 });
1764
1764
  }
1765
- }
1765
+ })
1766
1766
  };
1767
1767
  };
1768
1768
 
@@ -1838,6 +1838,10 @@ async function populateBlockMediaFields(node, payload, config) {
1838
1838
  }
1839
1839
  }
1840
1840
  }
1841
+ if (field.type === "richText" && node.fields[field.name]) {
1842
+ await populateRichTextUploads(node.fields[field.name], payload);
1843
+ payload.logger?.info(`Processed rich text field ${field.name} for upload nodes`);
1844
+ }
1841
1845
  }
1842
1846
  }
1843
1847
  }
@@ -1847,11 +1851,51 @@ async function populateBlockMediaFields(node, payload, config) {
1847
1851
  }
1848
1852
  }
1849
1853
  }
1854
+ async function populateRichTextUploads(content, payload) {
1855
+ if (!content || typeof content !== "object") return;
1856
+ if (content.root?.children) {
1857
+ await processNodeArray(content.root.children);
1858
+ }
1859
+ if (Array.isArray(content)) {
1860
+ await processNodeArray(content);
1861
+ }
1862
+ async function processNodeArray(nodes) {
1863
+ await Promise.all(nodes.map(processNode));
1864
+ }
1865
+ async function processNode(node) {
1866
+ if (!node || typeof node !== "object") return;
1867
+ if (node.type === "upload" && node.relationTo === "media" && typeof node.value === "string" && node.value.match(/^[a-f0-9]{24}$/i)) {
1868
+ try {
1869
+ const media = await payload.findByID({
1870
+ collection: "media",
1871
+ id: node.value,
1872
+ depth: 0
1873
+ });
1874
+ if (media) {
1875
+ node.value = media;
1876
+ payload.logger?.info(`Populated rich text upload node:`, {
1877
+ mediaId: node.value,
1878
+ mediaUrl: media.url,
1879
+ filename: media.filename
1880
+ });
1881
+ }
1882
+ } catch (error) {
1883
+ payload.logger?.error(`Failed to populate rich text upload ${node.value}:`, error);
1884
+ }
1885
+ }
1886
+ if (node.children && Array.isArray(node.children)) {
1887
+ await processNodeArray(node.children);
1888
+ }
1889
+ if (node.root?.children && Array.isArray(node.root.children)) {
1890
+ await processNodeArray(node.root.children);
1891
+ }
1892
+ }
1893
+ }
1850
1894
  var createBroadcastPreviewEndpoint = (config, _collectionSlug) => {
1851
1895
  return {
1852
1896
  path: "/preview",
1853
1897
  method: "post",
1854
- handler: async (req) => {
1898
+ handler: (async (req) => {
1855
1899
  try {
1856
1900
  const data = await (req.json?.() || Promise.resolve({}));
1857
1901
  const { content, preheader, subject, documentData } = data;
@@ -1891,7 +1935,7 @@ var createBroadcastPreviewEndpoint = (config, _collectionSlug) => {
1891
1935
  error: "Failed to generate email preview"
1892
1936
  }, { status: 500 });
1893
1937
  }
1894
- }
1938
+ })
1895
1939
  };
1896
1940
  };
1897
1941
 
@@ -2236,10 +2280,6 @@ var createBroadcastsCollection = (pluginConfig) => {
2236
2280
  async ({ doc, operation, req, previousDoc }) => {
2237
2281
  if (!hasProviders) return doc;
2238
2282
  if (operation === "create") {
2239
- if (!doc.subject || !doc.contentSection?.content) {
2240
- req.payload.logger.info("Skipping provider sync - broadcast has no subject or content yet");
2241
- return doc;
2242
- }
2243
2283
  try {
2244
2284
  const providerConfig = await getBroadcastConfig(req, pluginConfig);
2245
2285
  if (!providerConfig || !providerConfig.token) {
@@ -2248,45 +2288,32 @@ var createBroadcastsCollection = (pluginConfig) => {
2248
2288
  }
2249
2289
  const { BroadcastApiProvider: BroadcastApiProvider2 } = await Promise.resolve().then(() => (init_broadcast2(), broadcast_exports));
2250
2290
  const provider = new BroadcastApiProvider2(providerConfig);
2251
- req.payload.logger.info("Populating media fields and converting content to HTML...");
2252
- const populatedContent = await populateMediaFields(doc.contentSection?.content, req.payload, pluginConfig);
2253
- const emailPreviewConfig = pluginConfig.customizations?.broadcasts?.emailPreview;
2254
- const htmlContent = await convertToEmailSafeHtml(populatedContent, {
2255
- wrapInTemplate: emailPreviewConfig?.wrapInTemplate ?? true,
2256
- customWrapper: emailPreviewConfig?.customWrapper,
2257
- preheader: doc.contentSection?.preheader,
2258
- subject: doc.subject,
2259
- documentData: doc,
2260
- // Pass entire document
2261
- customBlockConverter: pluginConfig.customizations?.broadcasts?.customBlockConverter
2262
- });
2263
- if (!htmlContent || htmlContent.trim() === "") {
2264
- req.payload.logger.info("Skipping provider sync - content is empty after conversion");
2265
- return doc;
2266
- }
2291
+ const subject = doc.subject || `Draft Broadcast ${(/* @__PURE__ */ new Date()).toISOString()}`;
2292
+ const htmlContent = doc.contentSection?.content ? await convertToEmailSafeHtml(
2293
+ await populateMediaFields(doc.contentSection.content, req.payload, pluginConfig),
2294
+ {
2295
+ wrapInTemplate: pluginConfig.customizations?.broadcasts?.emailPreview?.wrapInTemplate ?? true,
2296
+ customWrapper: pluginConfig.customizations?.broadcasts?.emailPreview?.customWrapper,
2297
+ preheader: doc.contentSection?.preheader,
2298
+ subject,
2299
+ documentData: doc,
2300
+ customBlockConverter: pluginConfig.customizations?.broadcasts?.customBlockConverter
2301
+ }
2302
+ ) : "<p>Draft content - to be updated</p>";
2267
2303
  const createData = {
2268
- name: doc.subject,
2269
- // Use subject as name since we removed the name field
2270
- subject: doc.subject,
2271
- preheader: doc.contentSection?.preheader,
2304
+ name: subject,
2305
+ // Use subject as name
2306
+ subject,
2307
+ preheader: doc.contentSection?.preheader || "",
2272
2308
  content: htmlContent,
2273
- trackOpens: doc.settings?.trackOpens,
2274
- trackClicks: doc.settings?.trackClicks,
2309
+ trackOpens: doc.settings?.trackOpens ?? true,
2310
+ trackClicks: doc.settings?.trackClicks ?? true,
2275
2311
  replyTo: doc.settings?.replyTo || providerConfig.replyTo,
2276
- audienceIds: doc.audienceIds?.map((a) => a.audienceId)
2312
+ audienceIds: doc.audienceIds?.map((a) => a.audienceId) || []
2277
2313
  };
2278
- req.payload.logger.info("Creating broadcast with data:", {
2279
- name: createData.name,
2314
+ req.payload.logger.info("Creating broadcast in provider with minimal data to establish association", {
2280
2315
  subject: createData.subject,
2281
- preheader: createData.preheader || "NONE",
2282
- contentLength: htmlContent ? htmlContent.length : 0,
2283
- contentPreview: htmlContent ? htmlContent.substring(0, 100) + "..." : "EMPTY",
2284
- trackOpens: createData.trackOpens,
2285
- trackClicks: createData.trackClicks,
2286
- replyTo: createData.replyTo,
2287
- audienceIds: createData.audienceIds || [],
2288
- apiUrl: providerConfig.apiUrl,
2289
- hasToken: !!providerConfig.token
2316
+ hasActualContent: !!doc.contentSection?.content
2290
2317
  });
2291
2318
  const providerBroadcast = await provider.create(createData);
2292
2319
  await req.payload.update({
@@ -2298,40 +2325,14 @@ var createBroadcastsCollection = (pluginConfig) => {
2298
2325
  },
2299
2326
  req
2300
2327
  });
2328
+ req.payload.logger.info(`Broadcast ${doc.id} created in provider with ID ${providerBroadcast.id}`);
2301
2329
  return {
2302
2330
  ...doc,
2303
2331
  providerId: providerBroadcast.id,
2304
2332
  providerData: providerBroadcast.providerData
2305
2333
  };
2306
2334
  } catch (error) {
2307
- req.payload.logger.error("Raw error from broadcast provider:");
2308
- req.payload.logger.error(error);
2309
- if (error instanceof Error) {
2310
- req.payload.logger.error("Error is instance of Error:", {
2311
- message: error.message,
2312
- stack: error.stack,
2313
- name: error.name,
2314
- // If it's a BroadcastProviderError, it might have additional details
2315
- ...error.details,
2316
- // Check if it's a fetch response error
2317
- ...error.response,
2318
- ...error.data,
2319
- ...error.status,
2320
- ...error.statusText
2321
- });
2322
- } else if (typeof error === "string") {
2323
- req.payload.logger.error("Error is a string:", error);
2324
- } else if (error && typeof error === "object") {
2325
- req.payload.logger.error("Error is an object:", JSON.stringify(error, null, 2));
2326
- } else {
2327
- req.payload.logger.error("Unknown error type:", typeof error);
2328
- }
2329
- req.payload.logger.error("Failed broadcast document:", {
2330
- id: doc.id,
2331
- subject: doc.subject,
2332
- hasContent: !!doc.contentSection?.content,
2333
- contentType: doc.contentSection?.content ? typeof doc.contentSection.content : "none"
2334
- });
2335
+ req.payload.logger.error("Failed to create broadcast in provider during initial creation:", error);
2335
2336
  return doc;
2336
2337
  }
2337
2338
  }
@@ -2351,61 +2352,8 @@ var createBroadcastsCollection = (pluginConfig) => {
2351
2352
  const { BroadcastApiProvider: BroadcastApiProvider2 } = await Promise.resolve().then(() => (init_broadcast2(), broadcast_exports));
2352
2353
  const provider = new BroadcastApiProvider2(providerConfig);
2353
2354
  if (!doc.providerId) {
2354
- if (!doc.subject || !doc.contentSection?.content) {
2355
- req.payload.logger.info("Still missing required fields for provider sync");
2356
- return doc;
2357
- }
2358
- req.payload.logger.info("Creating broadcast in provider (deferred from initial create)...");
2359
- const populatedContent = await populateMediaFields(doc.contentSection?.content, req.payload, pluginConfig);
2360
- const emailPreviewConfig = pluginConfig.customizations?.broadcasts?.emailPreview;
2361
- const htmlContent = await convertToEmailSafeHtml(populatedContent, {
2362
- wrapInTemplate: emailPreviewConfig?.wrapInTemplate ?? true,
2363
- customWrapper: emailPreviewConfig?.customWrapper,
2364
- preheader: doc.contentSection?.preheader,
2365
- subject: doc.subject,
2366
- documentData: doc,
2367
- // Pass entire document
2368
- customBlockConverter: pluginConfig.customizations?.broadcasts?.customBlockConverter
2369
- });
2370
- if (!htmlContent || htmlContent.trim() === "") {
2371
- req.payload.logger.info("Skipping provider sync - content is empty after conversion");
2372
- return doc;
2373
- }
2374
- const createData = {
2375
- name: doc.subject,
2376
- subject: doc.subject,
2377
- preheader: doc.contentSection?.preheader,
2378
- content: htmlContent,
2379
- trackOpens: doc.settings?.trackOpens,
2380
- trackClicks: doc.settings?.trackClicks,
2381
- replyTo: doc.settings?.replyTo || providerConfig.replyTo,
2382
- audienceIds: doc.audienceIds?.map((a) => a.audienceId)
2383
- };
2384
- req.payload.logger.info("Creating broadcast with data:", {
2385
- name: createData.name,
2386
- subject: createData.subject,
2387
- preheader: createData.preheader || "NONE",
2388
- contentLength: htmlContent ? htmlContent.length : 0,
2389
- contentPreview: htmlContent ? htmlContent.substring(0, 100) + "..." : "EMPTY",
2390
- apiUrl: providerConfig.apiUrl,
2391
- hasToken: !!providerConfig.token
2392
- });
2393
- const providerBroadcast = await provider.create(createData);
2394
- await req.payload.update({
2395
- collection: "broadcasts",
2396
- id: doc.id,
2397
- data: {
2398
- providerId: providerBroadcast.id,
2399
- providerData: providerBroadcast.providerData
2400
- },
2401
- req
2402
- });
2403
- req.payload.logger.info(`Broadcast ${doc.id} created in provider successfully (deferred)`);
2404
- return {
2405
- ...doc,
2406
- providerId: providerBroadcast.id,
2407
- providerData: providerBroadcast.providerData
2408
- };
2355
+ req.payload.logger.warn(`Broadcast ${doc.id} has no providerId - provider sync skipped. This shouldn't happen with immediate creation.`);
2356
+ return doc;
2409
2357
  }
2410
2358
  if (doc.providerId) {
2411
2359
  const capabilities = provider.getCapabilities();