payload-plugin-newsletter 0.15.0 → 0.16.0
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 +72 -0
- package/README.md +48 -2
- package/dist/collections.cjs +232 -24
- package/dist/collections.cjs.map +1 -1
- package/dist/collections.js +232 -24
- package/dist/collections.js.map +1 -1
- package/dist/components.cjs +0 -438
- package/dist/components.cjs.map +1 -1
- package/dist/components.d.cts +1 -3
- package/dist/components.d.ts +1 -3
- package/dist/components.js +0 -437
- package/dist/components.js.map +1 -1
- package/dist/fields.cjs +182 -6
- package/dist/fields.cjs.map +1 -1
- package/dist/fields.d.cts +16 -1
- package/dist/fields.d.ts +16 -1
- package/dist/fields.js +178 -5
- package/dist/fields.js.map +1 -1
- package/dist/index.cjs +232 -24
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +232 -24
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,75 @@
|
|
|
1
|
+
## [0.16.0] - 2025-07-27
|
|
2
|
+
|
|
3
|
+
### Changed
|
|
4
|
+
- **Send = Publish Workflow** - Simplified broadcast sending to use Payload's native draft/publish system
|
|
5
|
+
- Publishing a broadcast now automatically sends it via the configured email provider
|
|
6
|
+
- Removed custom Send/Schedule modal in favor of Payload's built-in UI
|
|
7
|
+
- Scheduled publishing supported via Payload's Jobs Queue system
|
|
8
|
+
- Breaking: Removed `SendBroadcastModal` and `ActionsCell` components
|
|
9
|
+
- **Streamlined UI** - Removed custom action buttons from broadcasts list view
|
|
10
|
+
- Users now use standard Payload publish/schedule functionality
|
|
11
|
+
- Cleaner interface that follows Payload's patterns
|
|
12
|
+
- Less code to maintain while providing better integration
|
|
13
|
+
|
|
14
|
+
### Added
|
|
15
|
+
- **Automatic Send on Publish** - New `afterChange` hook that sends broadcasts when published
|
|
16
|
+
- Checks if broadcast is transitioning to published status
|
|
17
|
+
- Automatically calls provider's send method
|
|
18
|
+
- Updates broadcast status to "sending" after successful send
|
|
19
|
+
- Handles failures gracefully with status update to "failed"
|
|
20
|
+
- **Jobs Queue Documentation** - Added comprehensive setup instructions for scheduled publishing
|
|
21
|
+
- Vercel Cron configuration example
|
|
22
|
+
- Security setup with CRON_SECRET
|
|
23
|
+
- Step-by-step guide for enabling scheduled broadcasts
|
|
24
|
+
|
|
25
|
+
### Removed
|
|
26
|
+
- **Custom UI Components** (Breaking Change)
|
|
27
|
+
- `SendBroadcastModal` - Custom send/schedule modal
|
|
28
|
+
- `ActionsCell` - Custom action buttons in list view
|
|
29
|
+
- `actions` field from Broadcasts collection
|
|
30
|
+
- These are replaced by Payload's native publish/schedule functionality
|
|
31
|
+
|
|
32
|
+
### Technical
|
|
33
|
+
- Enabled `versions` configuration on Broadcasts collection with drafts and scheduled publishing
|
|
34
|
+
- Updated default columns to show both `_status` (Draft/Published) and `status` (send status)
|
|
35
|
+
- Improved TypeScript exports by removing deleted component references
|
|
36
|
+
- All tests passing with minor version upgrade
|
|
37
|
+
|
|
38
|
+
### Migration Guide
|
|
39
|
+
If you were using the custom Send/Schedule modal:
|
|
40
|
+
1. The functionality is now built into Payload's publish system
|
|
41
|
+
2. To send immediately: Click "Publish"
|
|
42
|
+
3. To schedule: Click "Schedule" (requires Jobs Queue setup)
|
|
43
|
+
4. Remove any imports of `SendBroadcastModal` or `ActionsCell` from your code
|
|
44
|
+
|
|
45
|
+
## [0.15.1] - 2025-07-27
|
|
46
|
+
|
|
47
|
+
### Fixed
|
|
48
|
+
- **Email-Compatible Block Editor** - Resolved Next.js serialization errors with custom blocks
|
|
49
|
+
- Custom blocks are now processed server-side using Lexical's proven BlocksFeature pattern
|
|
50
|
+
- Prevents "Functions cannot be passed directly to Client Components" errors
|
|
51
|
+
- Maintains full email compatibility while enabling custom block functionality
|
|
52
|
+
- **Block Validation System** - Added validation utilities for email compatibility
|
|
53
|
+
- `validateEmailBlocks()` warns about potentially incompatible block types
|
|
54
|
+
- `createEmailSafeBlocks()` processes blocks for email-safe configurations
|
|
55
|
+
- Automatic detection of complex field types that may not render in email clients
|
|
56
|
+
|
|
57
|
+
### Improved
|
|
58
|
+
- **Server-Side Block Processing** - Enhanced `createEmailLexicalEditor()` function
|
|
59
|
+
- Processes custom blocks into Lexical editor configuration before client serialization
|
|
60
|
+
- Clean separation between email-compatible and web-only content blocks
|
|
61
|
+
- Better performance through pre-configured editor instances
|
|
62
|
+
- **Enhanced Documentation** - Updated extension points guide with new approach
|
|
63
|
+
- Examples showing both legacy and new server-side processing methods
|
|
64
|
+
- Block validation utilities documentation
|
|
65
|
+
- Email compatibility best practices
|
|
66
|
+
|
|
67
|
+
### Technical
|
|
68
|
+
- Added `createEmailLexicalEditor()` for server-side editor configuration
|
|
69
|
+
- Enhanced `createEmailContentField()` to accept pre-configured editors
|
|
70
|
+
- New utility exports: `validateEmailBlocks`, `createEmailSafeBlocks`
|
|
71
|
+
- Improved TypeScript support for custom block configurations
|
|
72
|
+
|
|
1
73
|
## [0.15.0] - 2025-07-27
|
|
2
74
|
|
|
3
75
|
### Added
|
package/README.md
CHANGED
|
@@ -241,6 +241,50 @@ This adds a `broadcasts` collection with:
|
|
|
241
241
|
- Custom email blocks (buttons, dividers)
|
|
242
242
|
- Inline email preview with React Email
|
|
243
243
|
- Automatic sync with your email provider
|
|
244
|
+
- Draft/publish system with scheduled publishing support
|
|
245
|
+
|
|
246
|
+
### Send = Publish Workflow
|
|
247
|
+
|
|
248
|
+
The plugin integrates seamlessly with Payload's draft/publish system:
|
|
249
|
+
|
|
250
|
+
- **Draft**: Create and edit broadcasts without sending
|
|
251
|
+
- **Publish**: Publishing a broadcast automatically sends it via your configured email provider
|
|
252
|
+
- **Schedule**: Use Payload's scheduled publishing to send broadcasts at a future time
|
|
253
|
+
|
|
254
|
+
**How it works:**
|
|
255
|
+
1. Create a broadcast and save as draft
|
|
256
|
+
2. When ready, click "Publish" to send immediately
|
|
257
|
+
3. Or use "Schedule" to publish (and send) at a specific date/time
|
|
258
|
+
|
|
259
|
+
**Important**: Scheduled publishing requires configuring Payload's Jobs Queue. For Vercel deployments, add this to your `vercel.json`:
|
|
260
|
+
|
|
261
|
+
```json
|
|
262
|
+
{
|
|
263
|
+
"crons": [
|
|
264
|
+
{
|
|
265
|
+
"path": "/api/payload-jobs/run",
|
|
266
|
+
"schedule": "*/5 * * * *"
|
|
267
|
+
}
|
|
268
|
+
]
|
|
269
|
+
}
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
And secure the endpoint in your `payload.config.ts`:
|
|
273
|
+
|
|
274
|
+
```typescript
|
|
275
|
+
export default buildConfig({
|
|
276
|
+
// ... other config
|
|
277
|
+
jobs: {
|
|
278
|
+
access: {
|
|
279
|
+
run: ({ req }) => {
|
|
280
|
+
if (req.user) return true
|
|
281
|
+
const authHeader = req.headers.get('authorization')
|
|
282
|
+
return authHeader === `Bearer ${process.env.CRON_SECRET}`
|
|
283
|
+
},
|
|
284
|
+
},
|
|
285
|
+
},
|
|
286
|
+
})
|
|
287
|
+
```
|
|
244
288
|
|
|
245
289
|
### Custom Email Templates (v0.12.0+)
|
|
246
290
|
|
|
@@ -727,7 +771,7 @@ newsletterPlugin({
|
|
|
727
771
|
|
|
728
772
|
### Extending the Broadcasts Collection (v0.15.0+)
|
|
729
773
|
|
|
730
|
-
You can extend the Broadcasts collection with additional fields and custom blocks:
|
|
774
|
+
You can extend the Broadcasts collection with additional fields and custom email-compatible blocks:
|
|
731
775
|
|
|
732
776
|
```typescript
|
|
733
777
|
import type { Block } from 'payload'
|
|
@@ -753,7 +797,7 @@ newsletterPlugin({
|
|
|
753
797
|
admin: { position: 'sidebar' }
|
|
754
798
|
}
|
|
755
799
|
],
|
|
756
|
-
customBlocks: [customBlock],
|
|
800
|
+
customBlocks: [customBlock], // Processed server-side for email compatibility
|
|
757
801
|
fieldOverrides: {
|
|
758
802
|
content: (defaultField) => ({
|
|
759
803
|
...defaultField,
|
|
@@ -768,6 +812,8 @@ newsletterPlugin({
|
|
|
768
812
|
})
|
|
769
813
|
```
|
|
770
814
|
|
|
815
|
+
**Note**: Custom blocks are processed server-side to ensure email compatibility and prevent Next.js serialization errors.
|
|
816
|
+
|
|
771
817
|
For complete extensibility documentation, see the [Extension Points Guide](./docs/architecture/extension-points.md).
|
|
772
818
|
|
|
773
819
|
## Troubleshooting
|
package/dist/collections.cjs
CHANGED
|
@@ -542,6 +542,103 @@ init_types();
|
|
|
542
542
|
|
|
543
543
|
// src/fields/emailContent.ts
|
|
544
544
|
var import_richtext_lexical = require("@payloadcms/richtext-lexical");
|
|
545
|
+
|
|
546
|
+
// src/utils/blockValidation.ts
|
|
547
|
+
var EMAIL_INCOMPATIBLE_TYPES = [
|
|
548
|
+
"chart",
|
|
549
|
+
"dataTable",
|
|
550
|
+
"interactive",
|
|
551
|
+
"streamable",
|
|
552
|
+
"video",
|
|
553
|
+
"iframe",
|
|
554
|
+
"form",
|
|
555
|
+
"carousel",
|
|
556
|
+
"tabs",
|
|
557
|
+
"accordion",
|
|
558
|
+
"map"
|
|
559
|
+
];
|
|
560
|
+
var validateEmailBlocks = (blocks) => {
|
|
561
|
+
blocks.forEach((block) => {
|
|
562
|
+
if (EMAIL_INCOMPATIBLE_TYPES.includes(block.slug)) {
|
|
563
|
+
console.warn(`\u26A0\uFE0F Block "${block.slug}" may not be email-compatible. Consider creating an email-specific version.`);
|
|
564
|
+
}
|
|
565
|
+
const hasComplexFields = block.fields?.some((field) => {
|
|
566
|
+
const complexTypes = ["code", "json", "richText", "blocks", "array"];
|
|
567
|
+
return complexTypes.includes(field.type);
|
|
568
|
+
});
|
|
569
|
+
if (hasComplexFields) {
|
|
570
|
+
console.warn(`\u26A0\uFE0F Block "${block.slug}" contains complex field types that may not render consistently in email clients.`);
|
|
571
|
+
}
|
|
572
|
+
});
|
|
573
|
+
};
|
|
574
|
+
var createEmailSafeBlocks = (customBlocks = []) => {
|
|
575
|
+
validateEmailBlocks(customBlocks);
|
|
576
|
+
const baseBlocks = [
|
|
577
|
+
{
|
|
578
|
+
slug: "button",
|
|
579
|
+
fields: [
|
|
580
|
+
{
|
|
581
|
+
name: "text",
|
|
582
|
+
type: "text",
|
|
583
|
+
label: "Button Text",
|
|
584
|
+
required: true
|
|
585
|
+
},
|
|
586
|
+
{
|
|
587
|
+
name: "url",
|
|
588
|
+
type: "text",
|
|
589
|
+
label: "Button URL",
|
|
590
|
+
required: true,
|
|
591
|
+
admin: {
|
|
592
|
+
description: "Enter the full URL (including https://)"
|
|
593
|
+
}
|
|
594
|
+
},
|
|
595
|
+
{
|
|
596
|
+
name: "style",
|
|
597
|
+
type: "select",
|
|
598
|
+
label: "Button Style",
|
|
599
|
+
defaultValue: "primary",
|
|
600
|
+
options: [
|
|
601
|
+
{ label: "Primary", value: "primary" },
|
|
602
|
+
{ label: "Secondary", value: "secondary" },
|
|
603
|
+
{ label: "Outline", value: "outline" }
|
|
604
|
+
]
|
|
605
|
+
}
|
|
606
|
+
],
|
|
607
|
+
interfaceName: "EmailButton",
|
|
608
|
+
labels: {
|
|
609
|
+
singular: "Button",
|
|
610
|
+
plural: "Buttons"
|
|
611
|
+
}
|
|
612
|
+
},
|
|
613
|
+
{
|
|
614
|
+
slug: "divider",
|
|
615
|
+
fields: [
|
|
616
|
+
{
|
|
617
|
+
name: "style",
|
|
618
|
+
type: "select",
|
|
619
|
+
label: "Divider Style",
|
|
620
|
+
defaultValue: "solid",
|
|
621
|
+
options: [
|
|
622
|
+
{ label: "Solid", value: "solid" },
|
|
623
|
+
{ label: "Dashed", value: "dashed" },
|
|
624
|
+
{ label: "Dotted", value: "dotted" }
|
|
625
|
+
]
|
|
626
|
+
}
|
|
627
|
+
],
|
|
628
|
+
interfaceName: "EmailDivider",
|
|
629
|
+
labels: {
|
|
630
|
+
singular: "Divider",
|
|
631
|
+
plural: "Dividers"
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
];
|
|
635
|
+
return [
|
|
636
|
+
...baseBlocks,
|
|
637
|
+
...customBlocks
|
|
638
|
+
];
|
|
639
|
+
};
|
|
640
|
+
|
|
641
|
+
// src/fields/emailContent.ts
|
|
545
642
|
var createEmailSafeFeatures = (additionalBlocks) => {
|
|
546
643
|
const baseBlocks = [
|
|
547
644
|
{
|
|
@@ -679,16 +776,89 @@ var createEmailSafeFeatures = (additionalBlocks) => {
|
|
|
679
776
|
})
|
|
680
777
|
];
|
|
681
778
|
};
|
|
779
|
+
var createEmailLexicalEditor = (customBlocks = []) => {
|
|
780
|
+
const emailSafeBlocks = createEmailSafeBlocks(customBlocks);
|
|
781
|
+
return (0, import_richtext_lexical.lexicalEditor)({
|
|
782
|
+
features: [
|
|
783
|
+
// Toolbars
|
|
784
|
+
(0, import_richtext_lexical.FixedToolbarFeature)(),
|
|
785
|
+
(0, import_richtext_lexical.InlineToolbarFeature)(),
|
|
786
|
+
// Basic text formatting
|
|
787
|
+
(0, import_richtext_lexical.BoldFeature)(),
|
|
788
|
+
(0, import_richtext_lexical.ItalicFeature)(),
|
|
789
|
+
(0, import_richtext_lexical.UnderlineFeature)(),
|
|
790
|
+
(0, import_richtext_lexical.StrikethroughFeature)(),
|
|
791
|
+
// Links with enhanced configuration
|
|
792
|
+
(0, import_richtext_lexical.LinkFeature)({
|
|
793
|
+
fields: [
|
|
794
|
+
{
|
|
795
|
+
name: "url",
|
|
796
|
+
type: "text",
|
|
797
|
+
required: true,
|
|
798
|
+
admin: {
|
|
799
|
+
description: "Enter the full URL (including https://)"
|
|
800
|
+
}
|
|
801
|
+
},
|
|
802
|
+
{
|
|
803
|
+
name: "newTab",
|
|
804
|
+
type: "checkbox",
|
|
805
|
+
label: "Open in new tab",
|
|
806
|
+
defaultValue: false
|
|
807
|
+
}
|
|
808
|
+
]
|
|
809
|
+
}),
|
|
810
|
+
// Lists
|
|
811
|
+
(0, import_richtext_lexical.OrderedListFeature)(),
|
|
812
|
+
(0, import_richtext_lexical.UnorderedListFeature)(),
|
|
813
|
+
// Headings - limited to h1, h2, h3 for email compatibility
|
|
814
|
+
(0, import_richtext_lexical.HeadingFeature)({
|
|
815
|
+
enabledHeadingSizes: ["h1", "h2", "h3"]
|
|
816
|
+
}),
|
|
817
|
+
// Basic paragraph and alignment
|
|
818
|
+
(0, import_richtext_lexical.ParagraphFeature)(),
|
|
819
|
+
(0, import_richtext_lexical.AlignFeature)(),
|
|
820
|
+
// Blockquotes
|
|
821
|
+
(0, import_richtext_lexical.BlockquoteFeature)(),
|
|
822
|
+
// Upload feature for images
|
|
823
|
+
(0, import_richtext_lexical.UploadFeature)({
|
|
824
|
+
collections: {
|
|
825
|
+
media: {
|
|
826
|
+
fields: [
|
|
827
|
+
{
|
|
828
|
+
name: "caption",
|
|
829
|
+
type: "text",
|
|
830
|
+
admin: {
|
|
831
|
+
description: "Optional caption for the image"
|
|
832
|
+
}
|
|
833
|
+
},
|
|
834
|
+
{
|
|
835
|
+
name: "altText",
|
|
836
|
+
type: "text",
|
|
837
|
+
label: "Alt Text",
|
|
838
|
+
required: true,
|
|
839
|
+
admin: {
|
|
840
|
+
description: "Alternative text for accessibility and when image cannot be displayed"
|
|
841
|
+
}
|
|
842
|
+
}
|
|
843
|
+
]
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
}),
|
|
847
|
+
// Email-safe blocks (processed server-side)
|
|
848
|
+
(0, import_richtext_lexical.BlocksFeature)({
|
|
849
|
+
blocks: emailSafeBlocks
|
|
850
|
+
})
|
|
851
|
+
]
|
|
852
|
+
});
|
|
853
|
+
};
|
|
682
854
|
var emailSafeFeatures = createEmailSafeFeatures();
|
|
683
855
|
var createEmailContentField = (overrides) => {
|
|
684
|
-
const
|
|
856
|
+
const editor = overrides?.editor || createEmailLexicalEditor(overrides?.additionalBlocks);
|
|
685
857
|
return {
|
|
686
858
|
name: "content",
|
|
687
859
|
type: "richText",
|
|
688
860
|
required: true,
|
|
689
|
-
editor
|
|
690
|
-
features
|
|
691
|
-
}),
|
|
861
|
+
editor,
|
|
692
862
|
admin: {
|
|
693
863
|
description: "Email content with limited formatting for compatibility",
|
|
694
864
|
...overrides?.admin
|
|
@@ -998,6 +1168,12 @@ var createBroadcastsCollection = (pluginConfig) => {
|
|
|
998
1168
|
const customizations = pluginConfig.customizations?.broadcasts;
|
|
999
1169
|
return {
|
|
1000
1170
|
slug: "broadcasts",
|
|
1171
|
+
versions: {
|
|
1172
|
+
drafts: {
|
|
1173
|
+
autosave: true,
|
|
1174
|
+
schedulePublish: true
|
|
1175
|
+
}
|
|
1176
|
+
},
|
|
1001
1177
|
labels: {
|
|
1002
1178
|
singular: "Broadcast",
|
|
1003
1179
|
plural: "Broadcasts"
|
|
@@ -1005,7 +1181,7 @@ var createBroadcastsCollection = (pluginConfig) => {
|
|
|
1005
1181
|
admin: {
|
|
1006
1182
|
useAsTitle: "subject",
|
|
1007
1183
|
description: "Individual email campaigns sent to subscribers",
|
|
1008
|
-
defaultColumns: ["subject", "
|
|
1184
|
+
defaultColumns: ["subject", "_status", "status", "sentAt", "recipientCount"]
|
|
1009
1185
|
},
|
|
1010
1186
|
fields: [
|
|
1011
1187
|
{
|
|
@@ -1040,13 +1216,15 @@ var createBroadcastsCollection = (pluginConfig) => {
|
|
|
1040
1216
|
}
|
|
1041
1217
|
},
|
|
1042
1218
|
// Apply content field customization if provided
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1219
|
+
// Process blocks server-side to avoid client serialization issues
|
|
1220
|
+
(() => {
|
|
1221
|
+
const emailEditor = createEmailLexicalEditor(customizations?.customBlocks);
|
|
1222
|
+
const baseField = createEmailContentField({
|
|
1223
|
+
admin: { description: "Email content" },
|
|
1224
|
+
editor: emailEditor
|
|
1225
|
+
});
|
|
1226
|
+
return customizations?.fieldOverrides?.content ? customizations.fieldOverrides.content(baseField) : baseField;
|
|
1227
|
+
})()
|
|
1050
1228
|
]
|
|
1051
1229
|
},
|
|
1052
1230
|
{
|
|
@@ -1216,18 +1394,6 @@ var createBroadcastsCollection = (pluginConfig) => {
|
|
|
1216
1394
|
condition: () => false
|
|
1217
1395
|
// Hidden by default
|
|
1218
1396
|
}
|
|
1219
|
-
},
|
|
1220
|
-
// UI Field for custom actions in list view
|
|
1221
|
-
{
|
|
1222
|
-
name: "actions",
|
|
1223
|
-
type: "ui",
|
|
1224
|
-
admin: {
|
|
1225
|
-
components: {
|
|
1226
|
-
Cell: "payload-plugin-newsletter/components#ActionsCell",
|
|
1227
|
-
Field: "payload-plugin-newsletter/components#EmptyField"
|
|
1228
|
-
},
|
|
1229
|
-
disableListColumn: false
|
|
1230
|
-
}
|
|
1231
1397
|
}
|
|
1232
1398
|
],
|
|
1233
1399
|
hooks: {
|
|
@@ -1341,6 +1507,48 @@ var createBroadcastsCollection = (pluginConfig) => {
|
|
|
1341
1507
|
req.payload.logger.error("Failed to delete broadcast from provider:", error);
|
|
1342
1508
|
}
|
|
1343
1509
|
return doc;
|
|
1510
|
+
},
|
|
1511
|
+
// Hook to send when published
|
|
1512
|
+
async ({ doc, req }) => {
|
|
1513
|
+
if (doc._status === "published" && doc.providerId) {
|
|
1514
|
+
if (doc.status === "sent" || doc.status === "sending") {
|
|
1515
|
+
return doc;
|
|
1516
|
+
}
|
|
1517
|
+
try {
|
|
1518
|
+
const broadcastConfig = pluginConfig.providers?.broadcast;
|
|
1519
|
+
const resendConfig = pluginConfig.providers?.resend;
|
|
1520
|
+
if (!broadcastConfig && !resendConfig) {
|
|
1521
|
+
req.payload.logger.error("No provider configured for sending");
|
|
1522
|
+
return doc;
|
|
1523
|
+
}
|
|
1524
|
+
if (broadcastConfig) {
|
|
1525
|
+
const { BroadcastApiProvider: BroadcastApiProvider2 } = await Promise.resolve().then(() => (init_broadcast2(), broadcast_exports));
|
|
1526
|
+
const provider = new BroadcastApiProvider2(broadcastConfig);
|
|
1527
|
+
await provider.send(doc.providerId);
|
|
1528
|
+
}
|
|
1529
|
+
await req.payload.update({
|
|
1530
|
+
collection: "broadcasts",
|
|
1531
|
+
id: doc.id,
|
|
1532
|
+
data: {
|
|
1533
|
+
status: "sending" /* SENDING */,
|
|
1534
|
+
sentAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1535
|
+
},
|
|
1536
|
+
req
|
|
1537
|
+
});
|
|
1538
|
+
req.payload.logger.info(`Broadcast ${doc.id} sent successfully`);
|
|
1539
|
+
} catch (error) {
|
|
1540
|
+
req.payload.logger.error(`Failed to send broadcast ${doc.id}:`, error);
|
|
1541
|
+
await req.payload.update({
|
|
1542
|
+
collection: "broadcasts",
|
|
1543
|
+
id: doc.id,
|
|
1544
|
+
data: {
|
|
1545
|
+
status: "failed" /* FAILED */
|
|
1546
|
+
},
|
|
1547
|
+
req
|
|
1548
|
+
});
|
|
1549
|
+
}
|
|
1550
|
+
}
|
|
1551
|
+
return doc;
|
|
1344
1552
|
}
|
|
1345
1553
|
]
|
|
1346
1554
|
}
|