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.
- package/CHANGELOG.md +15 -0
- package/dist/collections.cjs +77 -129
- package/dist/collections.cjs.map +1 -1
- package/dist/collections.js +77 -129
- package/dist/collections.js.map +1 -1
- package/dist/server.js +93 -145
- package/package.json +1 -1
- package/plugin-api-key-recommendations.md +0 -117
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,18 @@
|
|
|
1
|
+
## [0.25.3] - 2025-08-16
|
|
2
|
+
|
|
3
|
+
### Added
|
|
4
|
+
- Media population support for upload nodes within rich text fields in custom blocks
|
|
5
|
+
- Rich text fields in custom blocks now properly populate nested image uploads for email rendering
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
- Images inserted in rich text editors within custom blocks now render correctly in email previews
|
|
9
|
+
|
|
10
|
+
## [0.25.2] - 2025-08-15
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
- Fixed issue where editing draft broadcasts created duplicate entries in the provider instead of updating the existing broadcast
|
|
14
|
+
- Broadcasts now immediately sync to provider on creation with minimal data to establish the association
|
|
15
|
+
|
|
1
16
|
## [0.25.1] - 2025-08-09
|
|
2
17
|
|
|
3
18
|
### Fixed
|
package/dist/collections.cjs
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
|
|
|
@@ -1851,6 +1851,10 @@ async function populateBlockMediaFields(node, payload, config) {
|
|
|
1851
1851
|
}
|
|
1852
1852
|
}
|
|
1853
1853
|
}
|
|
1854
|
+
if (field.type === "richText" && node.fields[field.name]) {
|
|
1855
|
+
await populateRichTextUploads(node.fields[field.name], payload);
|
|
1856
|
+
payload.logger?.info(`Processed rich text field ${field.name} for upload nodes`);
|
|
1857
|
+
}
|
|
1854
1858
|
}
|
|
1855
1859
|
}
|
|
1856
1860
|
}
|
|
@@ -1860,11 +1864,51 @@ async function populateBlockMediaFields(node, payload, config) {
|
|
|
1860
1864
|
}
|
|
1861
1865
|
}
|
|
1862
1866
|
}
|
|
1867
|
+
async function populateRichTextUploads(content, payload) {
|
|
1868
|
+
if (!content || typeof content !== "object") return;
|
|
1869
|
+
if (content.root?.children) {
|
|
1870
|
+
await processNodeArray(content.root.children);
|
|
1871
|
+
}
|
|
1872
|
+
if (Array.isArray(content)) {
|
|
1873
|
+
await processNodeArray(content);
|
|
1874
|
+
}
|
|
1875
|
+
async function processNodeArray(nodes) {
|
|
1876
|
+
await Promise.all(nodes.map(processNode));
|
|
1877
|
+
}
|
|
1878
|
+
async function processNode(node) {
|
|
1879
|
+
if (!node || typeof node !== "object") return;
|
|
1880
|
+
if (node.type === "upload" && node.relationTo === "media" && typeof node.value === "string" && node.value.match(/^[a-f0-9]{24}$/i)) {
|
|
1881
|
+
try {
|
|
1882
|
+
const media = await payload.findByID({
|
|
1883
|
+
collection: "media",
|
|
1884
|
+
id: node.value,
|
|
1885
|
+
depth: 0
|
|
1886
|
+
});
|
|
1887
|
+
if (media) {
|
|
1888
|
+
node.value = media;
|
|
1889
|
+
payload.logger?.info(`Populated rich text upload node:`, {
|
|
1890
|
+
mediaId: node.value,
|
|
1891
|
+
mediaUrl: media.url,
|
|
1892
|
+
filename: media.filename
|
|
1893
|
+
});
|
|
1894
|
+
}
|
|
1895
|
+
} catch (error) {
|
|
1896
|
+
payload.logger?.error(`Failed to populate rich text upload ${node.value}:`, error);
|
|
1897
|
+
}
|
|
1898
|
+
}
|
|
1899
|
+
if (node.children && Array.isArray(node.children)) {
|
|
1900
|
+
await processNodeArray(node.children);
|
|
1901
|
+
}
|
|
1902
|
+
if (node.root?.children && Array.isArray(node.root.children)) {
|
|
1903
|
+
await processNodeArray(node.root.children);
|
|
1904
|
+
}
|
|
1905
|
+
}
|
|
1906
|
+
}
|
|
1863
1907
|
var createBroadcastPreviewEndpoint = (config, _collectionSlug) => {
|
|
1864
1908
|
return {
|
|
1865
1909
|
path: "/preview",
|
|
1866
1910
|
method: "post",
|
|
1867
|
-
handler: async (req) => {
|
|
1911
|
+
handler: (async (req) => {
|
|
1868
1912
|
try {
|
|
1869
1913
|
const data = await (req.json?.() || Promise.resolve({}));
|
|
1870
1914
|
const { content, preheader, subject, documentData } = data;
|
|
@@ -1904,7 +1948,7 @@ var createBroadcastPreviewEndpoint = (config, _collectionSlug) => {
|
|
|
1904
1948
|
error: "Failed to generate email preview"
|
|
1905
1949
|
}, { status: 500 });
|
|
1906
1950
|
}
|
|
1907
|
-
}
|
|
1951
|
+
})
|
|
1908
1952
|
};
|
|
1909
1953
|
};
|
|
1910
1954
|
|
|
@@ -2249,10 +2293,6 @@ var createBroadcastsCollection = (pluginConfig) => {
|
|
|
2249
2293
|
async ({ doc, operation, req, previousDoc }) => {
|
|
2250
2294
|
if (!hasProviders) return doc;
|
|
2251
2295
|
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
2296
|
try {
|
|
2257
2297
|
const providerConfig = await getBroadcastConfig(req, pluginConfig);
|
|
2258
2298
|
if (!providerConfig || !providerConfig.token) {
|
|
@@ -2261,45 +2301,32 @@ var createBroadcastsCollection = (pluginConfig) => {
|
|
|
2261
2301
|
}
|
|
2262
2302
|
const { BroadcastApiProvider: BroadcastApiProvider2 } = await Promise.resolve().then(() => (init_broadcast2(), broadcast_exports));
|
|
2263
2303
|
const provider = new BroadcastApiProvider2(providerConfig);
|
|
2264
|
-
|
|
2265
|
-
const
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
if (!htmlContent || htmlContent.trim() === "") {
|
|
2277
|
-
req.payload.logger.info("Skipping provider sync - content is empty after conversion");
|
|
2278
|
-
return doc;
|
|
2279
|
-
}
|
|
2304
|
+
const subject = doc.subject || `Draft Broadcast ${(/* @__PURE__ */ new Date()).toISOString()}`;
|
|
2305
|
+
const htmlContent = doc.contentSection?.content ? await convertToEmailSafeHtml(
|
|
2306
|
+
await populateMediaFields(doc.contentSection.content, req.payload, pluginConfig),
|
|
2307
|
+
{
|
|
2308
|
+
wrapInTemplate: pluginConfig.customizations?.broadcasts?.emailPreview?.wrapInTemplate ?? true,
|
|
2309
|
+
customWrapper: pluginConfig.customizations?.broadcasts?.emailPreview?.customWrapper,
|
|
2310
|
+
preheader: doc.contentSection?.preheader,
|
|
2311
|
+
subject,
|
|
2312
|
+
documentData: doc,
|
|
2313
|
+
customBlockConverter: pluginConfig.customizations?.broadcasts?.customBlockConverter
|
|
2314
|
+
}
|
|
2315
|
+
) : "<p>Draft content - to be updated</p>";
|
|
2280
2316
|
const createData = {
|
|
2281
|
-
name:
|
|
2282
|
-
// Use subject as name
|
|
2283
|
-
subject
|
|
2284
|
-
preheader: doc.contentSection?.preheader,
|
|
2317
|
+
name: subject,
|
|
2318
|
+
// Use subject as name
|
|
2319
|
+
subject,
|
|
2320
|
+
preheader: doc.contentSection?.preheader || "",
|
|
2285
2321
|
content: htmlContent,
|
|
2286
|
-
trackOpens: doc.settings?.trackOpens,
|
|
2287
|
-
trackClicks: doc.settings?.trackClicks,
|
|
2322
|
+
trackOpens: doc.settings?.trackOpens ?? true,
|
|
2323
|
+
trackClicks: doc.settings?.trackClicks ?? true,
|
|
2288
2324
|
replyTo: doc.settings?.replyTo || providerConfig.replyTo,
|
|
2289
|
-
audienceIds: doc.audienceIds?.map((a) => a.audienceId)
|
|
2325
|
+
audienceIds: doc.audienceIds?.map((a) => a.audienceId) || []
|
|
2290
2326
|
};
|
|
2291
|
-
req.payload.logger.info("Creating broadcast with data
|
|
2292
|
-
name: createData.name,
|
|
2327
|
+
req.payload.logger.info("Creating broadcast in provider with minimal data to establish association", {
|
|
2293
2328
|
subject: createData.subject,
|
|
2294
|
-
|
|
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
|
|
2329
|
+
hasActualContent: !!doc.contentSection?.content
|
|
2303
2330
|
});
|
|
2304
2331
|
const providerBroadcast = await provider.create(createData);
|
|
2305
2332
|
await req.payload.update({
|
|
@@ -2311,40 +2338,14 @@ var createBroadcastsCollection = (pluginConfig) => {
|
|
|
2311
2338
|
},
|
|
2312
2339
|
req
|
|
2313
2340
|
});
|
|
2341
|
+
req.payload.logger.info(`Broadcast ${doc.id} created in provider with ID ${providerBroadcast.id}`);
|
|
2314
2342
|
return {
|
|
2315
2343
|
...doc,
|
|
2316
2344
|
providerId: providerBroadcast.id,
|
|
2317
2345
|
providerData: providerBroadcast.providerData
|
|
2318
2346
|
};
|
|
2319
2347
|
} catch (error) {
|
|
2320
|
-
req.payload.logger.error("
|
|
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
|
-
});
|
|
2348
|
+
req.payload.logger.error("Failed to create broadcast in provider during initial creation:", error);
|
|
2348
2349
|
return doc;
|
|
2349
2350
|
}
|
|
2350
2351
|
}
|
|
@@ -2364,61 +2365,8 @@ var createBroadcastsCollection = (pluginConfig) => {
|
|
|
2364
2365
|
const { BroadcastApiProvider: BroadcastApiProvider2 } = await Promise.resolve().then(() => (init_broadcast2(), broadcast_exports));
|
|
2365
2366
|
const provider = new BroadcastApiProvider2(providerConfig);
|
|
2366
2367
|
if (!doc.providerId) {
|
|
2367
|
-
|
|
2368
|
-
|
|
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
|
-
};
|
|
2368
|
+
req.payload.logger.warn(`Broadcast ${doc.id} has no providerId - provider sync skipped. This shouldn't happen with immediate creation.`);
|
|
2369
|
+
return doc;
|
|
2422
2370
|
}
|
|
2423
2371
|
if (doc.providerId) {
|
|
2424
2372
|
const capabilities = provider.getCapabilities();
|