@antelopejs/dms-mailing 0.3.4 → 0.4.1
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/README.md +232 -22
- package/dist/automation/email-event-trigger.js +2 -2
- package/dist/automation/email-event-trigger.js.map +1 -1
- package/dist/automation/send-template-action.js +2 -2
- package/dist/automation/send-template-action.js.map +1 -1
- package/dist/config.js +3 -1
- package/dist/config.js.map +1 -1
- package/dist/constants.d.ts +10 -1
- package/dist/constants.js +15 -3
- package/dist/constants.js.map +1 -1
- package/dist/crons/attention.d.ts +3 -0
- package/dist/crons/attention.js +19 -0
- package/dist/crons/attention.js.map +1 -0
- package/dist/crons/index.d.ts +2 -0
- package/dist/crons/index.js +2 -0
- package/dist/crons/index.js.map +1 -1
- package/dist/crons/jobs.d.ts +12 -0
- package/dist/crons/jobs.js +27 -0
- package/dist/crons/jobs.js.map +1 -0
- package/dist/crons/retention.d.ts +2 -4
- package/dist/crons/retention.js +7 -22
- package/dist/crons/retention.js.map +1 -1
- package/dist/data/displays.d.ts +5 -0
- package/dist/data/displays.js +23 -0
- package/dist/data/displays.js.map +1 -0
- package/dist/data/index.d.ts +1 -0
- package/dist/data/index.js +1 -0
- package/dist/data/index.js.map +1 -1
- package/dist/data/locale-fallback.d.ts +3 -0
- package/dist/data/locale-fallback.js +20 -0
- package/dist/data/locale-fallback.js.map +1 -0
- package/dist/data/read-only-routes.d.ts +7 -0
- package/dist/data/read-only-routes.js +13 -0
- package/dist/data/read-only-routes.js.map +1 -0
- package/dist/data/sends-table.d.ts +19 -5
- package/dist/data/sends-table.js +185 -17
- package/dist/data/sends-table.js.map +1 -1
- package/dist/data/suppressions-table.d.ts +24 -0
- package/dist/data/suppressions-table.js +120 -0
- package/dist/data/suppressions-table.js.map +1 -0
- package/dist/data/templates-table.d.ts +20 -2
- package/dist/data/templates-table.js +188 -31
- package/dist/data/templates-table.js.map +1 -1
- package/dist/data-types/categories-type.d.ts +11 -13
- package/dist/data-types/categories-type.js +3 -3
- package/dist/data-types/categories-type.js.map +1 -1
- package/dist/data-types/category-type.js +1 -1
- package/dist/data-types/category-type.js.map +1 -1
- package/dist/db/models/index.d.ts +3 -0
- package/dist/db/models/index.js +3 -0
- package/dist/db/models/index.js.map +1 -1
- package/dist/db/models/sends.model.d.ts +12 -1
- package/dist/db/models/sends.model.js +64 -0
- package/dist/db/models/sends.model.js.map +1 -1
- package/dist/db/models/settings.model.d.ts +0 -1
- package/dist/db/models/settings.model.js +0 -7
- package/dist/db/models/settings.model.js.map +1 -1
- package/dist/db/models/suppressions.model.d.ts +28 -0
- package/dist/db/models/suppressions.model.js +46 -0
- package/dist/db/models/suppressions.model.js.map +1 -0
- package/dist/db/models/template-versions.model.d.ts +24 -0
- package/dist/db/models/template-versions.model.js +17 -0
- package/dist/db/models/template-versions.model.js.map +1 -0
- package/dist/db/models/templates.model.d.ts +2 -0
- package/dist/db/models/templates.model.js +3 -0
- package/dist/db/models/templates.model.js.map +1 -1
- package/dist/db/models/webhook-status.model.d.ts +24 -0
- package/dist/db/models/webhook-status.model.js +21 -0
- package/dist/db/models/webhook-status.model.js.map +1 -0
- package/dist/db/tables/index.d.ts +3 -0
- package/dist/db/tables/index.js +3 -0
- package/dist/db/tables/index.js.map +1 -1
- package/dist/db/tables/send-events.table.d.ts +2 -2
- package/dist/db/tables/send-events.table.js.map +1 -1
- package/dist/db/tables/sends.table.d.ts +10 -2
- package/dist/db/tables/sends.table.js +34 -1
- package/dist/db/tables/sends.table.js.map +1 -1
- package/dist/db/tables/suppressions.table.d.ts +12 -0
- package/dist/db/tables/suppressions.table.js +53 -0
- package/dist/db/tables/suppressions.table.js.map +1 -0
- package/dist/db/tables/template-versions.table.d.ts +13 -0
- package/dist/db/tables/template-versions.table.js +59 -0
- package/dist/db/tables/template-versions.table.js.map +1 -0
- package/dist/db/tables/templates.table.d.ts +11 -0
- package/dist/db/tables/templates.table.js +45 -0
- package/dist/db/tables/templates.table.js.map +1 -1
- package/dist/db/tables/webhook-status.table.d.ts +14 -0
- package/dist/db/tables/webhook-status.table.js +58 -0
- package/dist/db/tables/webhook-status.table.js.map +1 -0
- package/dist/engine/changes.d.ts +12 -0
- package/dist/engine/changes.js +107 -0
- package/dist/engine/changes.js.map +1 -0
- package/dist/engine/conditions.js +0 -2
- package/dist/engine/conditions.js.map +1 -1
- package/dist/engine/coverage.d.ts +1 -0
- package/dist/engine/coverage.js +6 -2
- package/dist/engine/coverage.js.map +1 -1
- package/dist/engine/fallback-html.d.ts +1 -1
- package/dist/engine/fallback-html.js +51 -34
- package/dist/engine/fallback-html.js.map +1 -1
- package/dist/engine/index.d.ts +3 -0
- package/dist/engine/index.js +3 -0
- package/dist/engine/index.js.map +1 -1
- package/dist/engine/plain-text.d.ts +3 -0
- package/dist/engine/plain-text.js +60 -0
- package/dist/engine/plain-text.js.map +1 -0
- package/dist/engine/resolve.d.ts +2 -1
- package/dist/engine/resolve.js +17 -9
- package/dist/engine/resolve.js.map +1 -1
- package/dist/engine/system-variables.d.ts +10 -0
- package/dist/engine/system-variables.js +70 -0
- package/dist/engine/system-variables.js.map +1 -0
- package/dist/engine/tokens.d.ts +2 -1
- package/dist/engine/tokens.js +6 -2
- package/dist/engine/tokens.js.map +1 -1
- package/dist/hooks/tenant-export.js +13 -1
- package/dist/hooks/tenant-export.js.map +1 -1
- package/dist/implementations/dms-mailing/index.d.ts +1 -1
- package/dist/implementations/dms-mailing/index.js +2 -2
- package/dist/implementations/dms-mailing/index.js.map +1 -1
- package/dist/index.js +13 -11
- package/dist/index.js.map +1 -1
- package/dist/pages/editor.js +9 -2
- package/dist/pages/editor.js.map +1 -1
- package/dist/pages/index.d.ts +1 -0
- package/dist/pages/index.js +1 -0
- package/dist/pages/index.js.map +1 -1
- package/dist/pages/module.d.ts +2 -0
- package/dist/pages/module.js +26 -2
- package/dist/pages/module.js.map +1 -1
- package/dist/pages/overview.d.ts +2 -6
- package/dist/pages/overview.js +109 -52
- package/dist/pages/overview.js.map +1 -1
- package/dist/pages/sends.d.ts +3 -4
- package/dist/pages/sends.js +197 -60
- package/dist/pages/sends.js.map +1 -1
- package/dist/pages/settings/form.js +114 -53
- package/dist/pages/settings/form.js.map +1 -1
- package/dist/pages/settings/page.d.ts +0 -1
- package/dist/pages/settings/page.js +8 -11
- package/dist/pages/settings/page.js.map +1 -1
- package/dist/pages/suppressions.d.ts +9 -0
- package/dist/pages/suppressions.js +151 -0
- package/dist/pages/suppressions.js.map +1 -0
- package/dist/pages/templates.d.ts +0 -1
- package/dist/pages/templates.js +115 -41
- package/dist/pages/templates.js.map +1 -1
- package/dist/permissions.js +2 -2
- package/dist/permissions.js.map +1 -1
- package/dist/quick-actions.js +7 -4
- package/dist/quick-actions.js.map +1 -1
- package/dist/routes/events.d.ts +8 -5
- package/dist/routes/events.js +55 -24
- package/dist/routes/events.js.map +1 -1
- package/dist/routes/index.d.ts +2 -0
- package/dist/routes/index.js +2 -0
- package/dist/routes/index.js.map +1 -1
- package/dist/routes/metrics.d.ts +60 -23
- package/dist/routes/metrics.js +231 -83
- package/dist/routes/metrics.js.map +1 -1
- package/dist/routes/period.d.ts +2 -1
- package/dist/routes/period.js +9 -3
- package/dist/routes/period.js.map +1 -1
- package/dist/routes/sends.d.ts +49 -8
- package/dist/routes/sends.js +239 -24
- package/dist/routes/sends.js.map +1 -1
- package/dist/routes/settings.d.ts +44 -5
- package/dist/routes/settings.js +190 -15
- package/dist/routes/settings.js.map +1 -1
- package/dist/routes/suppressions.d.ts +16 -0
- package/dist/routes/suppressions.js +92 -0
- package/dist/routes/suppressions.js.map +1 -0
- package/dist/routes/template-versions.d.ts +28 -0
- package/dist/routes/template-versions.js +87 -0
- package/dist/routes/template-versions.js.map +1 -0
- package/dist/routes/templates.d.ts +60 -6
- package/dist/routes/templates.js +204 -35
- package/dist/routes/templates.js.map +1 -1
- package/dist/services/attention.d.ts +19 -6
- package/dist/services/attention.js +177 -12
- package/dist/services/attention.js.map +1 -1
- package/dist/services/catalog.d.ts +3 -0
- package/dist/services/catalog.js +25 -0
- package/dist/services/catalog.js.map +1 -0
- package/dist/services/categories.d.ts +8 -32
- package/dist/services/categories.js +14 -25
- package/dist/services/categories.js.map +1 -1
- package/dist/services/checked-send.d.ts +9 -0
- package/dist/services/checked-send.js +54 -0
- package/dist/services/checked-send.js.map +1 -0
- package/dist/services/events.d.ts +5 -0
- package/dist/services/events.js +102 -9
- package/dist/services/events.js.map +1 -1
- package/dist/services/metrics.d.ts +17 -6
- package/dist/services/metrics.js +57 -18
- package/dist/services/metrics.js.map +1 -1
- package/dist/services/notify.d.ts +18 -10
- package/dist/services/notify.js +82 -37
- package/dist/services/notify.js.map +1 -1
- package/dist/services/provider-banner.d.ts +14 -0
- package/dist/services/provider-banner.js +115 -0
- package/dist/services/provider-banner.js.map +1 -0
- package/dist/services/render.js.map +1 -1
- package/dist/services/send-stat-items.d.ts +7 -0
- package/dist/services/send-stat-items.js +127 -0
- package/dist/services/send-stat-items.js.map +1 -0
- package/dist/services/send-stats.d.ts +23 -0
- package/dist/services/send-stats.js +63 -0
- package/dist/services/send-stats.js.map +1 -0
- package/dist/services/send.d.ts +18 -3
- package/dist/services/send.js +81 -17
- package/dist/services/send.js.map +1 -1
- package/dist/services/settings.d.ts +5 -0
- package/dist/services/settings.js +28 -7
- package/dist/services/settings.js.map +1 -1
- package/dist/services/starters.d.ts +15 -0
- package/dist/services/starters.js +264 -0
- package/dist/services/starters.js.map +1 -0
- package/dist/services/suppressions.d.ts +20 -0
- package/dist/services/suppressions.js +53 -0
- package/dist/services/suppressions.js.map +1 -0
- package/dist/services/template-attention.d.ts +4 -0
- package/dist/services/template-attention.js +43 -0
- package/dist/services/template-attention.js.map +1 -0
- package/dist/services/template-stats.d.ts +27 -0
- package/dist/services/template-stats.js +59 -0
- package/dist/services/template-stats.js.map +1 -0
- package/dist/services/templates.d.ts +31 -4
- package/dist/services/templates.js +240 -21
- package/dist/services/templates.js.map +1 -1
- package/dist/services/webhook-status.d.ts +27 -0
- package/dist/services/webhook-status.js +134 -0
- package/dist/services/webhook-status.js.map +1 -0
- package/dist/types/blocks.d.ts +1 -0
- package/dist/types/index.d.ts +2 -0
- package/dist/types/index.js +2 -0
- package/dist/types/index.js.map +1 -1
- package/dist/types/sends.d.ts +10 -1
- package/dist/types/sends.js +35 -2
- package/dist/types/sends.js.map +1 -1
- package/dist/types/suppressions.d.ts +9 -0
- package/dist/types/suppressions.js +10 -0
- package/dist/types/suppressions.js.map +1 -0
- package/dist/types/webhook.d.ts +17 -0
- package/dist/types/webhook.js +3 -0
- package/dist/types/webhook.js.map +1 -0
- package/dist/validation/settings.schema.d.ts +144 -9
- package/dist/validation/settings.schema.js +113 -16
- package/dist/validation/settings.schema.js.map +1 -1
- package/dist/validation/templates.schema.d.ts +137 -10
- package/dist/validation/templates.schema.js +28 -2
- package/dist/validation/templates.schema.js.map +1 -1
- package/frontend-vue/app/build/components/FormFieldRow.vue +90 -0
- package/frontend-vue/app/build/components/FormRows.vue +26 -0
- package/frontend-vue/app/build/components/RequiredLegend.vue +11 -0
- package/frontend-vue/app/components/CategoriesInput.vue +323 -77
- package/frontend-vue/app/components/CategoryInput.vue +13 -14
- package/frontend-vue/app/components/CategoryLabel.vue +34 -0
- package/frontend-vue/app/components/DuplicateModal.vue +201 -0
- package/frontend-vue/app/components/Editor.vue +55 -20
- package/frontend-vue/app/components/FixAddressModal.vue +133 -0
- package/frontend-vue/app/components/FunnelCard.vue +146 -43
- package/frontend-vue/app/components/LocaleSelect.vue +98 -0
- package/frontend-vue/app/components/NewTemplateModal.vue +388 -102
- package/frontend-vue/app/components/NewTemplateSourceTile.vue +70 -0
- package/frontend-vue/app/components/OverviewGate.vue +220 -0
- package/frontend-vue/app/components/PageHeader.vue +98 -0
- package/frontend-vue/app/components/RealSendModal.vue +555 -0
- package/frontend-vue/app/components/RecipientsField.vue +41 -0
- package/frontend-vue/app/components/RenderingModal.vue +189 -0
- package/frontend-vue/app/components/RetentionInput.vue +212 -0
- package/frontend-vue/app/components/SendDrawer.vue +542 -209
- package/frontend-vue/app/components/StarterGallery.vue +153 -0
- package/frontend-vue/app/components/SystemVariables.vue +63 -0
- package/frontend-vue/app/components/TemplateCard.vue +218 -57
- package/frontend-vue/app/components/TemplateDrawer.vue +785 -274
- package/frontend-vue/app/components/TemplateGallery.vue +312 -56
- package/frontend-vue/app/components/TemplatePreviewFrame.vue +112 -22
- package/frontend-vue/app/components/TestSendModal.vue +266 -95
- package/frontend-vue/app/components/TokenText.vue +61 -10
- package/frontend-vue/app/components/VariablePath.vue +20 -0
- package/frontend-vue/app/components/VersionHistory.vue +196 -0
- package/frontend-vue/app/components/VersionPreview.vue +164 -0
- package/frontend-vue/app/components/WebhookStatus.vue +130 -0
- package/frontend-vue/app/components/blocks/Button.vue +1 -1
- package/frontend-vue/app/components/blocks/Code.vue +1 -1
- package/frontend-vue/app/components/blocks/Footer.vue +56 -7
- package/frontend-vue/app/components/blocks/Heading.vue +1 -1
- package/frontend-vue/app/components/blocks/Hero.vue +13 -7
- package/frontend-vue/app/components/blocks/List.vue +50 -6
- package/frontend-vue/app/components/blocks/Paragraph.vue +1 -1
- package/frontend-vue/app/components/blocks/Total.vue +3 -3
- package/frontend-vue/app/components/editor/EditorAutocompleteMenu.vue +50 -0
- package/frontend-vue/app/components/editor/EditorBlock.vue +37 -90
- package/frontend-vue/app/components/editor/EditorBlockFields.vue +140 -0
- package/frontend-vue/app/components/editor/EditorBlockInspector.vue +92 -0
- package/frontend-vue/app/components/editor/EditorBlockList.vue +32 -25
- package/frontend-vue/app/components/editor/EditorBlockPicker.vue +58 -0
- package/frontend-vue/app/components/editor/EditorBlockTools.vue +71 -0
- package/frontend-vue/app/components/editor/EditorCanvas.vue +105 -70
- package/frontend-vue/app/components/editor/EditorCompare.vue +211 -0
- package/frontend-vue/app/components/editor/EditorConditionFields.vue +56 -46
- package/frontend-vue/app/components/editor/EditorConditionInspector.vue +54 -0
- package/frontend-vue/app/components/editor/EditorEnvelope.vue +77 -0
- package/frontend-vue/app/components/editor/EditorIfBlock.vue +36 -86
- package/frontend-vue/app/components/editor/EditorInsertPoint.vue +64 -0
- package/frontend-vue/app/components/editor/EditorInspector.vue +80 -0
- package/frontend-vue/app/components/editor/EditorLeftPane.vue +103 -0
- package/frontend-vue/app/components/editor/EditorLifecycle.vue +67 -0
- package/frontend-vue/app/components/editor/EditorLoading.vue +23 -0
- package/frontend-vue/app/components/editor/EditorLocaleSwitch.vue +61 -0
- package/frontend-vue/app/components/editor/EditorMissingLocale.vue +72 -48
- package/frontend-vue/app/components/editor/EditorMissingPolicy.vue +42 -0
- package/frontend-vue/app/components/editor/EditorNotice.vue +127 -0
- package/frontend-vue/app/components/editor/EditorOutline.vue +65 -0
- package/frontend-vue/app/components/editor/EditorPalette.vue +95 -42
- package/frontend-vue/app/components/editor/EditorPaletteGroup.vue +66 -0
- package/frontend-vue/app/components/editor/EditorPublishReview.vue +102 -0
- package/frontend-vue/app/components/editor/EditorSaveState.vue +88 -0
- package/frontend-vue/app/components/editor/EditorShortcutHint.vue +17 -0
- package/frontend-vue/app/components/editor/EditorSimChip.vue +51 -0
- package/frontend-vue/app/components/editor/EditorTemplatePane.vue +131 -0
- package/frontend-vue/app/components/editor/EditorTestDataPane.vue +98 -0
- package/frontend-vue/app/components/editor/EditorTitle.vue +82 -0
- package/frontend-vue/app/components/editor/EditorTokenField.vue +224 -0
- package/frontend-vue/app/components/editor/EditorToolbar.vue +34 -134
- package/frontend-vue/app/components/editor/EditorVariableForm.vue +100 -0
- package/frontend-vue/app/components/editor/EditorVariableRow.vue +87 -0
- package/frontend-vue/app/components/editor/EditorVariablesPane.vue +161 -0
- package/frontend-vue/app/components/editor/EditorViewSwitches.vue +76 -0
- package/frontend-vue/app/components/editor/EditorVisibility.vue +101 -0
- package/frontend-vue/app/components/editor/EditorWorkspace.vue +62 -149
- package/frontend-vue/app/composables/useBlockContext.ts +16 -0
- package/frontend-vue/app/composables/useEditorActions.ts +381 -0
- package/frontend-vue/app/composables/useEditorAnnouncements.ts +51 -0
- package/frontend-vue/app/composables/useEditorContext.ts +30 -23
- package/frontend-vue/app/composables/useEditorSession.ts +299 -0
- package/frontend-vue/app/composables/useEditorShortcuts.ts +81 -0
- package/frontend-vue/app/composables/useLocalizedDefaults.ts +17 -0
- package/frontend-vue/app/composables/useMailingApi.ts +142 -27
- package/frontend-vue/app/composables/useOverviewSetup.ts +13 -0
- package/frontend-vue/app/composables/useReplayToast.ts +72 -0
- package/frontend-vue/app/composables/useTemplateCategories.ts +35 -0
- package/frontend-vue/app/composables/useTemplateDrawer.ts +196 -62
- package/frontend-vue/app/composables/useTemplateEditor.ts +318 -86
- package/frontend-vue/app/emails/EmailMailingTemplate.vue +16 -4
- package/frontend-vue/app/emails/components/MailingBlocks.vue +96 -38
- package/frontend-vue/app/emails/components/MailingTextLines.vue +20 -0
- package/frontend-vue/app/plugins/column-displays.ts +65 -0
- package/frontend-vue/app/plugins/{template-gallery-display.client.ts → template-gallery-display.ts} +1 -1
- package/frontend-vue/app/types/mailing.ts +193 -18
- package/frontend-vue/app/utils/api-error.ts +33 -0
- package/frontend-vue/app/utils/autocomplete.ts +120 -0
- package/frontend-vue/app/utils/blocks/components.ts +10 -10
- package/frontend-vue/app/utils/blocks/dnd.ts +7 -3
- package/frontend-vue/app/utils/blocks/factory.ts +199 -38
- package/frontend-vue/app/utils/blocks/fields.ts +39 -19
- package/frontend-vue/app/utils/blocks/index.ts +1 -0
- package/frontend-vue/app/utils/blocks/outline.ts +91 -0
- package/frontend-vue/app/utils/blocks/tree.ts +114 -2
- package/frontend-vue/app/utils/categories.ts +201 -15
- package/frontend-vue/app/utils/changes.ts +152 -0
- package/frontend-vue/app/utils/conditions.ts +25 -19
- package/frontend-vue/app/utils/editor-checks.ts +266 -0
- package/frontend-vue/app/utils/editor-route.ts +43 -4
- package/frontend-vue/app/utils/funnel.ts +74 -0
- package/frontend-vue/app/utils/gallery.ts +179 -1
- package/frontend-vue/app/utils/history.ts +66 -0
- package/frontend-vue/app/utils/lifecycle.ts +185 -0
- package/frontend-vue/app/utils/locales.ts +107 -2
- package/frontend-vue/app/utils/overview-setup.ts +83 -0
- package/frontend-vue/app/utils/pages.ts +5 -0
- package/frontend-vue/app/utils/paths.ts +51 -15
- package/frontend-vue/app/utils/period.ts +62 -22
- package/frontend-vue/app/utils/problems.ts +181 -0
- package/frontend-vue/app/utils/recipients.ts +49 -0
- package/frontend-vue/app/utils/rendering-title.ts +23 -0
- package/frontend-vue/app/utils/send-actions.ts +207 -0
- package/frontend-vue/app/utils/send-link.ts +28 -0
- package/frontend-vue/app/utils/send-snippet.ts +97 -0
- package/frontend-vue/app/utils/send-stats.ts +24 -0
- package/frontend-vue/app/utils/send-values.ts +156 -0
- package/frontend-vue/app/utils/shortcuts.ts +113 -0
- package/frontend-vue/app/utils/slug.ts +82 -0
- package/frontend-vue/app/utils/starters.ts +23 -0
- package/frontend-vue/app/utils/suppression.ts +112 -0
- package/frontend-vue/app/utils/system-variables.ts +165 -0
- package/frontend-vue/app/utils/template-attention.ts +104 -0
- package/frontend-vue/app/utils/timeline.ts +221 -19
- package/frontend-vue/app/utils/tokens.ts +115 -13
- package/frontend-vue/app/utils/variable-types.ts +23 -0
- package/frontend-vue/app/utils/variables.ts +208 -1
- package/frontend-vue/app/utils/version-history.ts +53 -0
- package/frontend-vue/app/utils/webhook-status.ts +170 -0
- package/frontend-vue/dms.frontend.ts +19 -11
- package/frontend-vue/i18n/locales/mailing-en-GB.json +1173 -161
- package/frontend-vue/i18n/locales/mailing-fr-FR.json +1220 -208
- package/frontend-vue/package.json +2 -2
- package/frontend-vue/pnpm-lock.yaml +46 -161
- package/frontend-vue/tests/api-error.test.ts +27 -0
- package/frontend-vue/tests/autocomplete.test.ts +84 -0
- package/frontend-vue/tests/block-components.test.ts +0 -3
- package/frontend-vue/tests/block-fields.test.ts +12 -2
- package/frontend-vue/tests/blocks-factory.test.ts +46 -0
- package/frontend-vue/tests/blocks-outline.test.ts +102 -0
- package/frontend-vue/tests/blocks-tree.test.ts +28 -1
- package/frontend-vue/tests/categories-input.test.ts +140 -0
- package/frontend-vue/tests/categories.test.ts +148 -15
- package/frontend-vue/tests/changes.test.ts +136 -0
- package/frontend-vue/tests/column-displays.test.ts +73 -0
- package/frontend-vue/tests/conditions.test.ts +49 -21
- package/frontend-vue/tests/dnd.test.ts +10 -0
- package/frontend-vue/tests/editor-block-list.test.ts +25 -2
- package/frontend-vue/tests/editor-checks.test.ts +178 -0
- package/frontend-vue/tests/editor-route.test.ts +37 -9
- package/frontend-vue/tests/editor-session.test.ts +207 -0
- package/frontend-vue/tests/footer-block.test.ts +89 -0
- package/frontend-vue/tests/funnel-card.test.ts +117 -0
- package/frontend-vue/tests/funnel.test.ts +81 -0
- package/frontend-vue/tests/gallery.test.ts +124 -2
- package/frontend-vue/tests/helpers/nuxt-globals.ts +232 -0
- package/frontend-vue/tests/history.test.ts +48 -0
- package/frontend-vue/tests/i18n-editor.test.ts +37 -8
- package/frontend-vue/tests/i18n.test.ts +55 -0
- package/frontend-vue/tests/lifecycle.test.ts +145 -0
- package/frontend-vue/tests/locales.test.ts +60 -0
- package/frontend-vue/tests/localized-defaults.test.ts +54 -0
- package/frontend-vue/tests/new-template-modal.test.ts +211 -0
- package/frontend-vue/tests/overview-gate.test.ts +147 -0
- package/frontend-vue/tests/overview-setup.test.ts +78 -0
- package/frontend-vue/tests/paths.test.ts +23 -0
- package/frontend-vue/tests/period.test.ts +59 -10
- package/frontend-vue/tests/problems.test.ts +153 -0
- package/frontend-vue/tests/recipients.test.ts +47 -0
- package/frontend-vue/tests/rendering-title.test.ts +24 -0
- package/frontend-vue/tests/replay-toast.test.ts +67 -0
- package/frontend-vue/tests/retention-input.test.ts +130 -0
- package/frontend-vue/tests/send-actions.test.ts +128 -0
- package/frontend-vue/tests/send-modals.test.ts +416 -0
- package/frontend-vue/tests/send-snippet.test.ts +51 -0
- package/frontend-vue/tests/send-stats.test.ts +12 -0
- package/frontend-vue/tests/send-values.test.ts +119 -0
- package/frontend-vue/tests/shortcuts.test.ts +76 -0
- package/frontend-vue/tests/slug.test.ts +88 -0
- package/frontend-vue/tests/suppression.test.ts +159 -0
- package/frontend-vue/tests/system-variables.test.ts +133 -0
- package/frontend-vue/tests/template-attention.test.ts +123 -0
- package/frontend-vue/tests/template-drawer.test.ts +450 -0
- package/frontend-vue/tests/template-editor.test.ts +218 -9
- package/frontend-vue/tests/template-gallery-display.test.ts +32 -0
- package/frontend-vue/tests/template-gallery.test.ts +371 -0
- package/frontend-vue/tests/template-overlays.test.ts +125 -0
- package/frontend-vue/tests/timeline.test.ts +175 -20
- package/frontend-vue/tests/tokens.test.ts +79 -1
- package/frontend-vue/tests/variable-path.test.ts +23 -0
- package/frontend-vue/tests/variable-types.test.ts +20 -0
- package/frontend-vue/tests/variables.test.ts +90 -2
- package/frontend-vue/tests/version-history.test.ts +41 -0
- package/frontend-vue/tests/webhook-status.test.ts +108 -0
- package/frontend-vue/vitest.config.ts +4 -1
- package/package.json +14 -12
- package/src/automation/email-event-trigger.ts +3 -3
- package/src/automation/send-template-action.ts +7 -5
- package/src/config.ts +3 -1
- package/src/constants.ts +18 -2
- package/src/crons/attention.ts +21 -0
- package/src/crons/index.ts +2 -0
- package/src/crons/jobs.ts +35 -0
- package/src/crons/retention.ts +7 -22
- package/src/data/displays.ts +18 -0
- package/src/data/index.ts +1 -0
- package/src/data/locale-fallback.ts +26 -0
- package/src/data/read-only-routes.ts +14 -0
- package/src/data/sends-table.ts +204 -19
- package/src/data/suppressions-table.ts +127 -0
- package/src/data/templates-table.ts +295 -63
- package/src/data-types/categories-type.ts +4 -5
- package/src/data-types/category-type.ts +1 -1
- package/src/db/models/index.ts +3 -0
- package/src/db/models/sends.model.ts +102 -1
- package/src/db/models/settings.model.ts +2 -12
- package/src/db/models/suppressions.model.ts +53 -0
- package/src/db/models/template-versions.model.ts +23 -0
- package/src/db/models/templates.model.ts +17 -0
- package/src/db/models/webhook-status.model.ts +25 -0
- package/src/db/tables/index.ts +3 -0
- package/src/db/tables/send-events.table.ts +6 -2
- package/src/db/tables/sends.table.ts +37 -2
- package/src/db/tables/suppressions.table.ts +46 -0
- package/src/db/tables/template-versions.table.ts +44 -0
- package/src/db/tables/templates.table.ts +67 -5
- package/src/db/tables/webhook-status.table.ts +44 -0
- package/src/engine/changes.ts +198 -0
- package/src/engine/conditions.ts +1 -7
- package/src/engine/coverage.ts +7 -2
- package/src/engine/fallback-html.ts +80 -42
- package/src/engine/index.ts +3 -0
- package/src/engine/plain-text.ts +86 -0
- package/src/engine/resolve.ts +49 -15
- package/src/engine/system-variables.ts +124 -0
- package/src/engine/tokens.ts +13 -1
- package/src/hooks/tenant-export.ts +21 -2
- package/src/implementations/dms-mailing/index.ts +1 -1
- package/src/index.ts +29 -13
- package/src/pages/editor.ts +13 -2
- package/src/pages/index.ts +6 -3
- package/src/pages/module.ts +31 -2
- package/src/pages/overview.ts +187 -128
- package/src/pages/sends.ts +271 -112
- package/src/pages/settings/form.ts +144 -54
- package/src/pages/settings/page.ts +9 -17
- package/src/pages/suppressions.ts +157 -0
- package/src/pages/templates.ts +140 -43
- package/src/permissions.ts +6 -4
- package/src/quick-actions.ts +12 -5
- package/src/routes/events.ts +110 -33
- package/src/routes/index.ts +2 -0
- package/src/routes/metrics.ts +348 -98
- package/src/routes/period.ts +23 -6
- package/src/routes/sends.ts +413 -41
- package/src/routes/settings.ts +291 -26
- package/src/routes/suppressions.ts +87 -0
- package/src/routes/template-versions.ts +116 -0
- package/src/routes/templates.ts +334 -48
- package/src/services/attention.ts +283 -30
- package/src/services/catalog.ts +28 -0
- package/src/services/categories.ts +28 -27
- package/src/services/checked-send.ts +123 -0
- package/src/services/events.ts +174 -20
- package/src/services/metrics.ts +137 -28
- package/src/services/notify.ts +126 -44
- package/src/services/provider-banner.ts +165 -0
- package/src/services/render.ts +1 -0
- package/src/services/send-stat-items.ts +160 -0
- package/src/services/send-stats.ts +122 -0
- package/src/services/send.ts +193 -31
- package/src/services/settings.ts +52 -8
- package/src/services/starters.ts +383 -0
- package/src/services/suppressions.ts +116 -0
- package/src/services/template-attention.ts +82 -0
- package/src/services/template-stats.ts +97 -0
- package/src/services/templates.ts +400 -37
- package/src/services/webhook-status.ts +202 -0
- package/src/types/blocks.ts +6 -0
- package/src/types/index.ts +2 -0
- package/src/types/sends.ts +64 -2
- package/src/types/suppressions.ts +27 -0
- package/src/types/webhook.ts +29 -0
- package/src/validation/settings.schema.ts +173 -17
- package/src/validation/templates.schema.ts +53 -1
- package/dist/antelope-modules.d.ts +0 -32
- package/dist/antelope-modules.js +0 -36
- package/dist/antelope-modules.js.map +0 -1
- package/dist/test/antelope.test.d.ts +0 -2
- package/dist/test/antelope.test.js +0 -180
- package/dist/test/antelope.test.js.map +0 -1
- package/dist/test/helpers/http.d.ts +0 -4
- package/dist/test/helpers/http.js +0 -28
- package/dist/test/helpers/http.js.map +0 -1
- package/dist/test/helpers/owner.d.ts +0 -5
- package/dist/test/helpers/owner.js +0 -30
- package/dist/test/helpers/owner.js.map +0 -1
- package/dist/test/integration/events.test.d.ts +0 -1
- package/dist/test/integration/events.test.js +0 -143
- package/dist/test/integration/events.test.js.map +0 -1
- package/dist/test/integration/retention.test.d.ts +0 -1
- package/dist/test/integration/retention.test.js +0 -291
- package/dist/test/integration/retention.test.js.map +0 -1
- package/dist/test/integration/sends.test.d.ts +0 -1
- package/dist/test/integration/sends.test.js +0 -210
- package/dist/test/integration/sends.test.js.map +0 -1
- package/dist/test/integration/settings.test.d.ts +0 -1
- package/dist/test/integration/settings.test.js +0 -137
- package/dist/test/integration/settings.test.js.map +0 -1
- package/dist/test/integration/setup.test.d.ts +0 -1
- package/dist/test/integration/setup.test.js +0 -32
- package/dist/test/integration/setup.test.js.map +0 -1
- package/dist/test/integration/templates.test.d.ts +0 -1
- package/dist/test/integration/templates.test.js +0 -233
- package/dist/test/integration/templates.test.js.map +0 -1
- package/dist/test/integration/tenant-export.test.d.ts +0 -1
- package/dist/test/integration/tenant-export.test.js +0 -85
- package/dist/test/integration/tenant-export.test.js.map +0 -1
- package/dist/test/unit/attention.test.d.ts +0 -1
- package/dist/test/unit/attention.test.js +0 -109
- package/dist/test/unit/attention.test.js.map +0 -1
- package/dist/test/unit/automation.test.d.ts +0 -1
- package/dist/test/unit/automation.test.js +0 -104
- package/dist/test/unit/automation.test.js.map +0 -1
- package/dist/test/unit/categories.test.d.ts +0 -1
- package/dist/test/unit/categories.test.js +0 -38
- package/dist/test/unit/categories.test.js.map +0 -1
- package/dist/test/unit/engine/conditions.test.d.ts +0 -1
- package/dist/test/unit/engine/conditions.test.js +0 -23
- package/dist/test/unit/engine/conditions.test.js.map +0 -1
- package/dist/test/unit/engine/coverage.test.d.ts +0 -1
- package/dist/test/unit/engine/coverage.test.js +0 -106
- package/dist/test/unit/engine/coverage.test.js.map +0 -1
- package/dist/test/unit/engine/fallback-html.test.d.ts +0 -1
- package/dist/test/unit/engine/fallback-html.test.js +0 -78
- package/dist/test/unit/engine/fallback-html.test.js.map +0 -1
- package/dist/test/unit/engine/paths.test.d.ts +0 -1
- package/dist/test/unit/engine/paths.test.js +0 -33
- package/dist/test/unit/engine/paths.test.js.map +0 -1
- package/dist/test/unit/engine/resolve.test.d.ts +0 -1
- package/dist/test/unit/engine/resolve.test.js +0 -157
- package/dist/test/unit/engine/resolve.test.js.map +0 -1
- package/dist/test/unit/engine/tokens.test.d.ts +0 -1
- package/dist/test/unit/engine/tokens.test.js +0 -26
- package/dist/test/unit/engine/tokens.test.js.map +0 -1
- package/dist/test/unit/events.test.d.ts +0 -1
- package/dist/test/unit/events.test.js +0 -13
- package/dist/test/unit/events.test.js.map +0 -1
- package/dist/test/unit/metrics.test.d.ts +0 -1
- package/dist/test/unit/metrics.test.js +0 -108
- package/dist/test/unit/metrics.test.js.map +0 -1
- package/dist/test/unit/notify.test.d.ts +0 -1
- package/dist/test/unit/notify.test.js +0 -49
- package/dist/test/unit/notify.test.js.map +0 -1
- package/dist/test/unit/period.test.d.ts +0 -1
- package/dist/test/unit/period.test.js +0 -61
- package/dist/test/unit/period.test.js.map +0 -1
- package/dist/test/unit/permissions.test.d.ts +0 -1
- package/dist/test/unit/permissions.test.js +0 -27
- package/dist/test/unit/permissions.test.js.map +0 -1
- package/dist/test/unit/provider.test.d.ts +0 -1
- package/dist/test/unit/provider.test.js +0 -47
- package/dist/test/unit/provider.test.js.map +0 -1
- package/dist/test/unit/realtime.test.d.ts +0 -1
- package/dist/test/unit/realtime.test.js +0 -13
- package/dist/test/unit/realtime.test.js.map +0 -1
- package/dist/test/unit/render.test.d.ts +0 -1
- package/dist/test/unit/render.test.js +0 -23
- package/dist/test/unit/render.test.js.map +0 -1
- package/dist/test/unit/retention.test.d.ts +0 -1
- package/dist/test/unit/retention.test.js +0 -66
- package/dist/test/unit/retention.test.js.map +0 -1
- package/dist/test/unit/send-batch.test.d.ts +0 -1
- package/dist/test/unit/send-batch.test.js +0 -46
- package/dist/test/unit/send-batch.test.js.map +0 -1
- package/dist/test/unit/send.test.d.ts +0 -1
- package/dist/test/unit/send.test.js +0 -120
- package/dist/test/unit/send.test.js.map +0 -1
- package/dist/test/unit/smoke.test.d.ts +0 -1
- package/dist/test/unit/smoke.test.js +0 -10
- package/dist/test/unit/smoke.test.js.map +0 -1
- package/dist/test/unit/status.test.d.ts +0 -1
- package/dist/test/unit/status.test.js +0 -16
- package/dist/test/unit/status.test.js.map +0 -1
- package/dist/test/unit/templates.test.d.ts +0 -1
- package/dist/test/unit/templates.test.js +0 -48
- package/dist/test/unit/templates.test.js.map +0 -1
- package/dist/test/unit/tenant-export.test.d.ts +0 -1
- package/dist/test/unit/tenant-export.test.js +0 -59
- package/dist/test/unit/tenant-export.test.js.map +0 -1
- package/dist/test/unit/webhook-secret.test.d.ts +0 -1
- package/dist/test/unit/webhook-secret.test.js +0 -43
- package/dist/test/unit/webhook-secret.test.js.map +0 -1
- package/frontend-vue/app/components/AttentionCard.vue +0 -78
- package/frontend-vue/app/components/HtmlPreviewModal.vue +0 -20
- package/frontend-vue/app/components/ProviderChip.vue +0 -39
- package/frontend-vue/app/components/editor/EditorBlockSettings.vue +0 -186
- package/frontend-vue/app/components/editor/EditorDataPanel.vue +0 -130
- package/frontend-vue/app/components/editor/EditorDraftBar.vue +0 -35
- package/frontend-vue/app/components/editor/EditorRail.vue +0 -58
- package/frontend-vue/app/components/editor/EditorSubjectSettings.vue +0 -37
- package/frontend-vue/app/components/editor/EditorTemplateSettings.vue +0 -87
- package/frontend-vue/app/components/editor/EditorTestSendModal.vue +0 -66
- package/frontend-vue/app/components/editor/EditorVariables.vue +0 -163
- package/frontend-vue/app/composables/useMailingPeriodData.ts +0 -39
- package/frontend-vue/tests/smoke.test.ts +0 -8
- package/src/antelope-modules.ts +0 -48
- package/src/test/antelope.test.ts +0 -190
- package/src/test/helpers/http.ts +0 -23
- package/src/test/helpers/owner.ts +0 -37
- package/src/test/integration/events.test.ts +0 -159
- package/src/test/integration/retention.test.ts +0 -363
- package/src/test/integration/sends.test.ts +0 -233
- package/src/test/integration/settings.test.ts +0 -128
- package/src/test/integration/setup.test.ts +0 -35
- package/src/test/integration/templates.test.ts +0 -272
- package/src/test/integration/tenant-export.test.ts +0 -125
- package/src/test/unit/attention.test.ts +0 -129
- package/src/test/unit/automation.test.ts +0 -130
- package/src/test/unit/categories.test.ts +0 -51
- package/src/test/unit/engine/conditions.test.ts +0 -35
- package/src/test/unit/engine/coverage.test.ts +0 -118
- package/src/test/unit/engine/fallback-html.test.ts +0 -86
- package/src/test/unit/engine/paths.test.ts +0 -39
- package/src/test/unit/engine/resolve.test.ts +0 -176
- package/src/test/unit/engine/tokens.test.ts +0 -26
- package/src/test/unit/events.test.ts +0 -19
- package/src/test/unit/metrics.test.ts +0 -170
- package/src/test/unit/notify.test.ts +0 -71
- package/src/test/unit/period.test.ts +0 -91
- package/src/test/unit/permissions.test.ts +0 -30
- package/src/test/unit/provider.test.ts +0 -57
- package/src/test/unit/realtime.test.ts +0 -11
- package/src/test/unit/render.test.ts +0 -23
- package/src/test/unit/retention.test.ts +0 -91
- package/src/test/unit/send-batch.test.ts +0 -72
- package/src/test/unit/send.test.ts +0 -161
- package/src/test/unit/smoke.test.ts +0 -8
- package/src/test/unit/status.test.ts +0 -14
- package/src/test/unit/templates.test.ts +0 -52
- package/src/test/unit/tenant-export.test.ts +0 -88
- package/src/test/unit/webhook-secret.test.ts +0 -51
|
@@ -1,5 +1,16 @@
|
|
|
1
|
+
import type { ActivityFeedItem } from "@antelopejs/interface-dms/base";
|
|
2
|
+
import type {
|
|
3
|
+
ComposedText,
|
|
4
|
+
ComposedTextParam,
|
|
5
|
+
} from "@antelopejs/interface-dms/base/types";
|
|
1
6
|
import type { SendSummary, MailingTemplate } from "../db";
|
|
2
|
-
import {
|
|
7
|
+
import { LOCALE_LIST_SEPARATOR } from "../constants";
|
|
8
|
+
import {
|
|
9
|
+
isResolvedStage,
|
|
10
|
+
PROBLEM_STATUSES,
|
|
11
|
+
type TemplateAttentionReason,
|
|
12
|
+
} from "../types";
|
|
13
|
+
import { isDeliveryUntracked, type ProviderTracking } from "./metrics";
|
|
3
14
|
|
|
4
15
|
export type AttentionTone = "error" | "warning" | "neutral";
|
|
5
16
|
|
|
@@ -9,13 +20,24 @@ export interface AttentionItem {
|
|
|
9
20
|
icon: string;
|
|
10
21
|
title: string;
|
|
11
22
|
description: string;
|
|
23
|
+
/** The verb of the next step, an i18n key ("Review problems"). */
|
|
24
|
+
action: string;
|
|
12
25
|
to: string;
|
|
13
|
-
|
|
26
|
+
/** The figure the title leads with, shown bold. */
|
|
27
|
+
count?: number;
|
|
28
|
+
params?: Record<string, ComposedTextParam>;
|
|
29
|
+
/** Details shown in place of `description` where a feed lists them. */
|
|
30
|
+
details?: ComposedText[];
|
|
14
31
|
}
|
|
15
32
|
|
|
16
33
|
const TEMPLATES_PAGE = "/modules/mailing/templates";
|
|
34
|
+
const TEMPLATES_ATTENTION_PAGE = `${TEMPLATES_PAGE}?tab=attention`;
|
|
17
35
|
const SENDS_PAGE = "/modules/mailing/sends";
|
|
18
|
-
const SENDS_PROBLEMS_PAGE = `${SENDS_PAGE}?
|
|
36
|
+
const SENDS_PROBLEMS_PAGE = `${SENDS_PAGE}?view=problems`;
|
|
37
|
+
/** A settings form section, by the id the DMS form gives its anchor. */
|
|
38
|
+
const SETTINGS_SECTION = "/modules/mailing/settings#dms-form-content-section-";
|
|
39
|
+
const SENDER_SETTINGS = `${SETTINGS_SECTION}sender`;
|
|
40
|
+
const PROVIDER_SETTINGS = `${SETTINGS_SECTION}provider`;
|
|
19
41
|
const STALE_DRAFT_DAYS = 7;
|
|
20
42
|
const DAY_MS = 86_400_000;
|
|
21
43
|
|
|
@@ -25,8 +47,9 @@ const isStaleDraft = (template: MailingTemplate, now: number): boolean =>
|
|
|
25
47
|
now - new Date(template.updatedAt).getTime() > STALE_DRAFT_DAYS * DAY_MS;
|
|
26
48
|
|
|
27
49
|
function problemsItem(sends: SendSummary[]): AttentionItem[] {
|
|
28
|
-
const problems = sends.filter(
|
|
29
|
-
|
|
50
|
+
const problems = sends.filter(
|
|
51
|
+
(send) =>
|
|
52
|
+
PROBLEM_STATUSES.includes(send.status) && !isResolvedStage(send.stage),
|
|
30
53
|
);
|
|
31
54
|
if (!problems.length) return [];
|
|
32
55
|
return [
|
|
@@ -36,8 +59,11 @@ function problemsItem(sends: SendSummary[]): AttentionItem[] {
|
|
|
36
59
|
icon: "i-ph-warning-octagon",
|
|
37
60
|
title: "$dms_mailing.attention.problem_sends.title",
|
|
38
61
|
description: "$dms_mailing.attention.problem_sends.description",
|
|
62
|
+
action: "$dms_mailing.attention.problem_sends.action",
|
|
39
63
|
to: SENDS_PROBLEMS_PAGE,
|
|
40
|
-
|
|
64
|
+
count: problems.length,
|
|
65
|
+
params: problemBreakdown(problems),
|
|
66
|
+
details: problemDetails(problems),
|
|
41
67
|
},
|
|
42
68
|
];
|
|
43
69
|
}
|
|
@@ -54,19 +80,30 @@ function staleDraftsItem(templates: MailingTemplate[]): AttentionItem[] {
|
|
|
54
80
|
icon: "i-ph-pencil-simple",
|
|
55
81
|
title: "$dms_mailing.attention.stale_drafts.title",
|
|
56
82
|
description: "$dms_mailing.attention.stale_drafts.description",
|
|
57
|
-
|
|
58
|
-
|
|
83
|
+
action: "$dms_mailing.attention.stale_drafts.action",
|
|
84
|
+
to: TEMPLATES_ATTENTION_PAGE,
|
|
85
|
+
count: stale.length,
|
|
86
|
+
params: { count: stale.length, days: STALE_DRAFT_DAYS },
|
|
59
87
|
},
|
|
60
88
|
];
|
|
61
89
|
}
|
|
62
90
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
91
|
+
const publishedBy = (template: MailingTemplate, end: Date): boolean =>
|
|
92
|
+
!template.publishedAt ||
|
|
93
|
+
new Date(template.publishedAt).getTime() <= end.getTime();
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Live templates no send of the window used. Only the window is read, so the
|
|
97
|
+
* item says "not sent in this period" rather than "never sent", and leaves
|
|
98
|
+
* out a template published after the window closed.
|
|
99
|
+
*/
|
|
100
|
+
function unusedLiveItem(window: AttentionWindow): AttentionItem[] {
|
|
101
|
+
const used = new Set(window.sends.map((send) => send.templateSlug));
|
|
102
|
+
const unused = window.templates.filter(
|
|
103
|
+
(template) =>
|
|
104
|
+
template.status === "live" &&
|
|
105
|
+
!used.has(template.slug) &&
|
|
106
|
+
publishedBy(template, window.windowEnd),
|
|
70
107
|
);
|
|
71
108
|
if (!unused.length) return [];
|
|
72
109
|
return [
|
|
@@ -76,8 +113,10 @@ function neverSentItem(
|
|
|
76
113
|
icon: "i-ph-eye-slash",
|
|
77
114
|
title: "$dms_mailing.attention.unused_live.title",
|
|
78
115
|
description: "$dms_mailing.attention.unused_live.description",
|
|
79
|
-
|
|
80
|
-
|
|
116
|
+
action: "$dms_mailing.attention.unused_live.action",
|
|
117
|
+
to: TEMPLATES_ATTENTION_PAGE,
|
|
118
|
+
count: unused.length,
|
|
119
|
+
params: { count: unused.length, names: namesOf(unused) },
|
|
81
120
|
},
|
|
82
121
|
];
|
|
83
122
|
}
|
|
@@ -91,15 +130,12 @@ export interface AttentionWindow {
|
|
|
91
130
|
templates: MailingTemplate[];
|
|
92
131
|
sends: SendSummary[];
|
|
93
132
|
excluded: SendSummary[];
|
|
133
|
+
/** When the window closes. */
|
|
134
|
+
windowEnd: Date;
|
|
94
135
|
/** What the configured provider can report; `null` when it is unreachable. */
|
|
95
136
|
tracking: ProviderTracking | null;
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
/** The provider features the Overview's rates depend on. */
|
|
99
|
-
export interface ProviderTracking {
|
|
100
|
-
name: string;
|
|
101
|
-
openTracking: boolean;
|
|
102
|
-
clickTracking: boolean;
|
|
137
|
+
/** Whether the settings name the address e-mails go out from. */
|
|
138
|
+
isSenderSet: boolean;
|
|
103
139
|
}
|
|
104
140
|
|
|
105
141
|
function testOnlyWindowItem(window: AttentionWindow): AttentionItem[] {
|
|
@@ -111,22 +147,65 @@ function testOnlyWindowItem(window: AttentionWindow): AttentionItem[] {
|
|
|
111
147
|
icon: "i-ph-flask",
|
|
112
148
|
title: "$dms_mailing.attention.test_only_window.title",
|
|
113
149
|
description: "$dms_mailing.attention.test_only_window.description",
|
|
150
|
+
action: "$dms_mailing.attention.test_only_window.action",
|
|
114
151
|
to: SENDS_PAGE,
|
|
152
|
+
count: window.excluded.length,
|
|
115
153
|
params: { count: window.excluded.length },
|
|
116
154
|
},
|
|
117
155
|
];
|
|
118
156
|
}
|
|
119
157
|
|
|
158
|
+
function senderMissingItem(window: AttentionWindow): AttentionItem[] {
|
|
159
|
+
if (window.isSenderSet) return [];
|
|
160
|
+
return [
|
|
161
|
+
{
|
|
162
|
+
id: "sender-missing",
|
|
163
|
+
tone: "warning",
|
|
164
|
+
icon: "i-ph-at",
|
|
165
|
+
title: "$dms_mailing.attention.sender_missing.title",
|
|
166
|
+
description: "$dms_mailing.attention.sender_missing.description",
|
|
167
|
+
action: "$dms_mailing.attention.sender_missing.action",
|
|
168
|
+
to: SENDER_SETTINGS,
|
|
169
|
+
},
|
|
170
|
+
];
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* With nothing reported back, every send stays "sent": the delivery figures
|
|
175
|
+
* show "—" and this item says why, and where the webhook that would report
|
|
176
|
+
* them is set.
|
|
177
|
+
*/
|
|
178
|
+
function untrackedDeliveryItem(window: AttentionWindow): AttentionItem[] {
|
|
179
|
+
if (!isDeliveryUntracked(window.sends, window.tracking)) return [];
|
|
180
|
+
return [
|
|
181
|
+
{
|
|
182
|
+
id: "untracked-delivery",
|
|
183
|
+
tone: "neutral",
|
|
184
|
+
icon: "i-ph-plugs",
|
|
185
|
+
title: "$dms_mailing.attention.untracked_delivery.title",
|
|
186
|
+
description: "$dms_mailing.attention.untracked_delivery.description",
|
|
187
|
+
action: "$dms_mailing.attention.untracked_delivery.action",
|
|
188
|
+
to: PROVIDER_SETTINGS,
|
|
189
|
+
params: { provider: window.tracking?.name ?? "" },
|
|
190
|
+
},
|
|
191
|
+
];
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const showsEngagement = (send: SendSummary): boolean =>
|
|
195
|
+
send.opens > 0 || send.clicks > 0;
|
|
196
|
+
|
|
120
197
|
/**
|
|
121
|
-
* Open and click rates can only move if
|
|
122
|
-
*
|
|
123
|
-
*
|
|
124
|
-
*
|
|
198
|
+
* Open and click rates can only move if something reports those events: the
|
|
199
|
+
* provider itself, or its webhook. Said only while the window shows none at
|
|
200
|
+
* all, so the item never contradicts rates the webhook does fill; when the
|
|
201
|
+
* delivery itself is unmeasured, that item says it already.
|
|
125
202
|
*/
|
|
126
203
|
function untrackedRatesItem(window: AttentionWindow): AttentionItem[] {
|
|
127
204
|
const { tracking } = window;
|
|
128
205
|
if (!tracking || !window.sends.length) return [];
|
|
129
206
|
if (tracking.openTracking && tracking.clickTracking) return [];
|
|
207
|
+
if (isDeliveryUntracked(window.sends, tracking)) return [];
|
|
208
|
+
if (window.sends.some(showsEngagement)) return [];
|
|
130
209
|
return [
|
|
131
210
|
{
|
|
132
211
|
id: "untracked-rates",
|
|
@@ -134,18 +213,192 @@ function untrackedRatesItem(window: AttentionWindow): AttentionItem[] {
|
|
|
134
213
|
icon: "i-ph-eye-slash",
|
|
135
214
|
title: "$dms_mailing.attention.untracked_rates.title",
|
|
136
215
|
description: "$dms_mailing.attention.untracked_rates.description",
|
|
137
|
-
|
|
216
|
+
action: "$dms_mailing.attention.untracked_rates.action",
|
|
217
|
+
to: PROVIDER_SETTINGS,
|
|
138
218
|
params: { provider: tracking.name },
|
|
139
219
|
},
|
|
140
220
|
];
|
|
141
221
|
}
|
|
142
222
|
|
|
223
|
+
const MAX_LISTED_NAMES = 2;
|
|
224
|
+
const LIST_SEPARATOR = ", ";
|
|
225
|
+
const MORE_NAMES_KEY = "$dms_mailing.attention.more_names";
|
|
226
|
+
|
|
227
|
+
/** The first names, and how many others there are ("A, B and 2 more"). */
|
|
228
|
+
function namesOf(templates: MailingTemplate[]): ComposedTextParam {
|
|
229
|
+
const names = templates
|
|
230
|
+
.slice(0, MAX_LISTED_NAMES)
|
|
231
|
+
.map((template) => template.name)
|
|
232
|
+
.join(LIST_SEPARATOR);
|
|
233
|
+
const more = templates.length - MAX_LISTED_NAMES;
|
|
234
|
+
if (more <= 0) return names;
|
|
235
|
+
return {
|
|
236
|
+
key: MORE_NAMES_KEY,
|
|
237
|
+
params: { names, count: { type: "count", value: more } },
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
function problemBreakdown(problems: SendSummary[]): Record<string, number> {
|
|
242
|
+
const countOf = (status: string) =>
|
|
243
|
+
problems.filter((send) => send.status === status).length;
|
|
244
|
+
return {
|
|
245
|
+
count: problems.length,
|
|
246
|
+
bounced: countOf("bounced"),
|
|
247
|
+
failed: countOf("failed"),
|
|
248
|
+
spam: countOf("spam"),
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
const PROBLEM_PARTS = ["bounced", "failed", "spam"] as const;
|
|
253
|
+
const PROBLEM_PART_KEY = "$dms_mailing.attention.problem_sends.parts.";
|
|
254
|
+
|
|
255
|
+
/** One counted detail per problem kind present ("2 provider failures"). */
|
|
256
|
+
function problemDetails(problems: SendSummary[]): ComposedText[] {
|
|
257
|
+
return PROBLEM_PARTS.map((status) => ({
|
|
258
|
+
status,
|
|
259
|
+
count: problems.filter((send) => send.status === status).length,
|
|
260
|
+
}))
|
|
261
|
+
.filter((part) => part.count > 0)
|
|
262
|
+
.map((part) => ({
|
|
263
|
+
key: `${PROBLEM_PART_KEY}${part.status}`,
|
|
264
|
+
params: { count: { type: "count", value: part.count } },
|
|
265
|
+
}));
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
const localeCodes = (template: MailingTemplate): string[] =>
|
|
269
|
+
template.locales.split(LOCALE_LIST_SEPARATOR).filter(Boolean);
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* The locales any live template carries: they stand for the workspace's
|
|
273
|
+
* languages, since the server holds no list of its own.
|
|
274
|
+
*/
|
|
275
|
+
export function knownLocalesOf(templates: MailingTemplate[]): Set<string> {
|
|
276
|
+
return new Set(
|
|
277
|
+
templates
|
|
278
|
+
.filter((template) => template.status === "live")
|
|
279
|
+
.flatMap(localeCodes),
|
|
280
|
+
);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/** A live template missing one of the `known` locales. */
|
|
284
|
+
function isMissingLocale(
|
|
285
|
+
template: MailingTemplate,
|
|
286
|
+
known: Set<string>,
|
|
287
|
+
): boolean {
|
|
288
|
+
if (template.status !== "live") return false;
|
|
289
|
+
const own = new Set(localeCodes(template));
|
|
290
|
+
return [...known].some((code) => !own.has(code));
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/** Live templates missing a locale the workspace writes in elsewhere. */
|
|
294
|
+
export function missingLocaleTemplates(
|
|
295
|
+
templates: MailingTemplate[],
|
|
296
|
+
): MailingTemplate[] {
|
|
297
|
+
const known = knownLocalesOf(templates);
|
|
298
|
+
return templates.filter((template) => isMissingLocale(template, known));
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
function missingLocalesItem(templates: MailingTemplate[]): AttentionItem[] {
|
|
302
|
+
const missing = missingLocaleTemplates(templates);
|
|
303
|
+
if (!missing.length) return [];
|
|
304
|
+
return [
|
|
305
|
+
{
|
|
306
|
+
id: "missing-locales",
|
|
307
|
+
tone: "warning",
|
|
308
|
+
icon: "i-ph-translate",
|
|
309
|
+
title: "$dms_mailing.attention.missing_locales.title",
|
|
310
|
+
description: "$dms_mailing.attention.missing_locales.description",
|
|
311
|
+
action: "$dms_mailing.attention.missing_locales.action",
|
|
312
|
+
to: TEMPLATES_ATTENTION_PAGE,
|
|
313
|
+
count: missing.length,
|
|
314
|
+
params: { count: missing.length, names: namesOf(missing) },
|
|
315
|
+
},
|
|
316
|
+
];
|
|
317
|
+
}
|
|
318
|
+
|
|
143
319
|
export function buildAttentionItems(window: AttentionWindow): AttentionItem[] {
|
|
144
320
|
return [
|
|
145
321
|
...problemsItem(window.sends),
|
|
322
|
+
...missingLocalesItem(window.templates),
|
|
323
|
+
...senderMissingItem(window),
|
|
324
|
+
...untrackedDeliveryItem(window),
|
|
146
325
|
...testOnlyWindowItem(window),
|
|
147
326
|
...untrackedRatesItem(window),
|
|
148
|
-
...
|
|
327
|
+
...unusedLiveItem(window),
|
|
149
328
|
...staleDraftsItem(window.templates),
|
|
150
329
|
];
|
|
151
330
|
}
|
|
331
|
+
|
|
332
|
+
const COUNT_PARAM = "count";
|
|
333
|
+
|
|
334
|
+
function feedParams(
|
|
335
|
+
item: AttentionItem,
|
|
336
|
+
): Record<string, ComposedTextParam> | undefined {
|
|
337
|
+
const params: Record<string, ComposedTextParam> = { ...item.params };
|
|
338
|
+
const count = item.count ?? item.params?.[COUNT_PARAM];
|
|
339
|
+
if (typeof count === "number") {
|
|
340
|
+
params[COUNT_PARAM] = { type: "count", value: count };
|
|
341
|
+
}
|
|
342
|
+
return Object.keys(params).length ? params : undefined;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* An attention item as an `ActivityFeed` entry: the explanation (or its
|
|
347
|
+
* counted details) under the title, the verb of the next step as its trailing
|
|
348
|
+
* text, the whole entry linking to that step.
|
|
349
|
+
*/
|
|
350
|
+
export function attentionFeedItem(item: AttentionItem): ActivityFeedItem {
|
|
351
|
+
return {
|
|
352
|
+
id: item.id,
|
|
353
|
+
icon: item.icon,
|
|
354
|
+
tone: item.tone,
|
|
355
|
+
title: item.title,
|
|
356
|
+
meta: item.details?.length ? item.details : [item.description],
|
|
357
|
+
params: feedParams(item),
|
|
358
|
+
time: item.action,
|
|
359
|
+
to: item.to,
|
|
360
|
+
};
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
/** What the template rules read besides the template itself. */
|
|
364
|
+
export interface TemplateAttentionContext {
|
|
365
|
+
now: number;
|
|
366
|
+
/** The slugs a real send used in the stats window. */
|
|
367
|
+
sentSlugs: Set<string>;
|
|
368
|
+
/** The locales any live template carries. */
|
|
369
|
+
knownLocales: Set<string>;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
type TemplateAttentionRule = (
|
|
373
|
+
template: MailingTemplate,
|
|
374
|
+
context: TemplateAttentionContext,
|
|
375
|
+
) => boolean;
|
|
376
|
+
|
|
377
|
+
const TEMPLATE_ATTENTION_RULES: Record<
|
|
378
|
+
TemplateAttentionReason,
|
|
379
|
+
TemplateAttentionRule
|
|
380
|
+
> = {
|
|
381
|
+
stale_draft: (template, context) => isStaleDraft(template, context.now),
|
|
382
|
+
never_sent: (template, context) =>
|
|
383
|
+
template.status === "live" && !context.sentSlugs.has(template.slug),
|
|
384
|
+
missing_locale: (template, context) =>
|
|
385
|
+
isMissingLocale(template, context.knownLocales),
|
|
386
|
+
};
|
|
387
|
+
|
|
388
|
+
const TEMPLATE_ATTENTION_REASONS = Object.keys(
|
|
389
|
+
TEMPLATE_ATTENTION_RULES,
|
|
390
|
+
) as TemplateAttentionReason[];
|
|
391
|
+
|
|
392
|
+
/**
|
|
393
|
+
* Why a template needs a look, in a stable order: a draft never published and
|
|
394
|
+
* untouched for a week, a live template no real send used in the stats
|
|
395
|
+
* window, a live template missing a locale the others carry.
|
|
396
|
+
*/
|
|
397
|
+
export function templateAttentionReasons(
|
|
398
|
+
template: MailingTemplate,
|
|
399
|
+
context: TemplateAttentionContext,
|
|
400
|
+
): TemplateAttentionReason[] {
|
|
401
|
+
return TEMPLATE_ATTENTION_REASONS.filter((reason) =>
|
|
402
|
+
TEMPLATE_ATTENTION_RULES[reason](template, context),
|
|
403
|
+
);
|
|
404
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import type { ModuleStatus } from "@antelopejs/interface-dms/page";
|
|
4
|
+
import { readCapabilities } from "./provider";
|
|
5
|
+
|
|
6
|
+
const PACKAGE_JSON_PATH = path.join(__dirname, "../../package.json");
|
|
7
|
+
|
|
8
|
+
interface PackageManifest {
|
|
9
|
+
version?: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/** The module's published version, as the catalog tile and the sidebar show it. */
|
|
13
|
+
export function moduleVersion(): string | undefined {
|
|
14
|
+
try {
|
|
15
|
+
const manifest = JSON.parse(
|
|
16
|
+
readFileSync(PACKAGE_JSON_PATH, "utf8"),
|
|
17
|
+
) as PackageManifest;
|
|
18
|
+
return manifest.version;
|
|
19
|
+
} catch {
|
|
20
|
+
return undefined;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** No provider answering means no e-mail can leave: the owner must look. */
|
|
25
|
+
export async function catalogStatus(): Promise<ModuleStatus> {
|
|
26
|
+
const capabilities = await readCapabilities();
|
|
27
|
+
return capabilities ? "live" : "attention";
|
|
28
|
+
}
|
|
@@ -1,43 +1,44 @@
|
|
|
1
|
-
import { z } from "zod";
|
|
2
1
|
import type { MailingTemplate } from "../db";
|
|
3
2
|
import type { TemplateCategory } from "../types";
|
|
4
|
-
import {
|
|
3
|
+
import type { CategoryEntry } from "../validation/settings.schema";
|
|
5
4
|
|
|
6
|
-
/**
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
try {
|
|
13
|
-
return JSON.parse(value) as unknown;
|
|
14
|
-
} catch {
|
|
15
|
-
return [];
|
|
16
|
-
}
|
|
17
|
-
})
|
|
18
|
-
.pipe(z.array(categorySchema));
|
|
5
|
+
/** The categories a list keeps, without the removed ones it moves away. */
|
|
6
|
+
export function keptCategories(entries: CategoryEntry[]): TemplateCategory[] {
|
|
7
|
+
return entries
|
|
8
|
+
.filter((entry) => entry.moveTo === undefined)
|
|
9
|
+
.map(({ id, label, icon }) => ({ id, label, icon }));
|
|
10
|
+
}
|
|
19
11
|
|
|
20
|
-
|
|
12
|
+
/** Where the templates of each removed category go, by its id. */
|
|
13
|
+
export function categoryMoves(entries: CategoryEntry[]): Map<string, string> {
|
|
14
|
+
return new Map(
|
|
15
|
+
entries.flatMap((entry) =>
|
|
16
|
+
entry.moveTo === undefined ? [] : [[entry.id, entry.moveTo] as const],
|
|
17
|
+
),
|
|
18
|
+
);
|
|
19
|
+
}
|
|
21
20
|
|
|
22
|
-
/**
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
export function parseCategories(raw: unknown): TemplateCategory[] {
|
|
27
|
-
const parsed = categoriesSchema.safeParse(raw ?? []);
|
|
28
|
-
return parsed.success ? parsed.data : [];
|
|
21
|
+
/** A template whose category has to change, and the one it gets. */
|
|
22
|
+
export interface CategoryReassignment {
|
|
23
|
+
templateId: string;
|
|
24
|
+
category: string;
|
|
29
25
|
}
|
|
30
26
|
|
|
31
27
|
/**
|
|
32
|
-
*
|
|
33
|
-
*
|
|
28
|
+
* The templates whose category is not in `categories` any more, each with the
|
|
29
|
+
* category it moves to: the one `moves` names, else none. Deleting a category
|
|
30
|
+
* must never leave a template pointing at nothing.
|
|
34
31
|
*/
|
|
35
32
|
export function reassignRemovedCategories(
|
|
36
33
|
templates: MailingTemplate[],
|
|
37
34
|
categories: TemplateCategory[],
|
|
38
|
-
|
|
35
|
+
moves: ReadonlyMap<string, string> = new Map(),
|
|
36
|
+
): CategoryReassignment[] {
|
|
39
37
|
const kept = new Set(categories.map((category) => category.id));
|
|
40
38
|
return templates
|
|
41
39
|
.filter((template) => template.category && !kept.has(template.category))
|
|
42
|
-
.map((template) =>
|
|
40
|
+
.map((template) => ({
|
|
41
|
+
templateId: template._id,
|
|
42
|
+
category: moves.get(template.category as string) ?? "",
|
|
43
|
+
}));
|
|
43
44
|
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import type { EmailAddress } from "@antelopejs/interface-email";
|
|
2
|
+
import type { SendTemplateResult } from "@antelopejs/interface-dms-mailing";
|
|
3
|
+
import { type MailingSuppression, normalizeAddress } from "../db";
|
|
4
|
+
import {
|
|
5
|
+
aggregateResults,
|
|
6
|
+
type DeliveredMessage,
|
|
7
|
+
type ModuleSendParams,
|
|
8
|
+
prepare,
|
|
9
|
+
recordAll,
|
|
10
|
+
sendTemplate,
|
|
11
|
+
} from "./send";
|
|
12
|
+
import { suppressionsOf } from "./suppressions";
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* A send the module checks against the suppression list. `allowSuppressed`
|
|
16
|
+
* names the addresses that still get it: the editor's own address on a test.
|
|
17
|
+
*/
|
|
18
|
+
export interface CheckedSendParams extends ModuleSendParams {
|
|
19
|
+
allowSuppressed?: string[];
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** Why nothing was sent to a recipient, as the log and the dialogs say it. */
|
|
23
|
+
export const SUPPRESSED_ERROR_PREFIX = "$dms_mailing.errors.suppressed.";
|
|
24
|
+
|
|
25
|
+
const NO_LATENCY = null;
|
|
26
|
+
|
|
27
|
+
const addressOf = (to: EmailAddress): string =>
|
|
28
|
+
typeof to === "string" ? to : to.email;
|
|
29
|
+
|
|
30
|
+
const toRecipients = (to: EmailAddress | EmailAddress[]): EmailAddress[] =>
|
|
31
|
+
Array.isArray(to) ? to : [to];
|
|
32
|
+
|
|
33
|
+
/** The error a suppressed recipient's send keeps: an i18n key per reason. */
|
|
34
|
+
export function suppressedError(row: MailingSuppression): string {
|
|
35
|
+
return `${SUPPRESSED_ERROR_PREFIX}${row.reason}`;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** The entry of each recipient nothing may be sent to, by its address. */
|
|
39
|
+
async function blockedRecipients(
|
|
40
|
+
params: CheckedSendParams,
|
|
41
|
+
recipients: EmailAddress[],
|
|
42
|
+
): Promise<Map<string, MailingSuppression>> {
|
|
43
|
+
const suppressed = await suppressionsOf(
|
|
44
|
+
params.tenantId,
|
|
45
|
+
recipients.map(addressOf),
|
|
46
|
+
);
|
|
47
|
+
for (const allowed of params.allowSuppressed ?? []) {
|
|
48
|
+
suppressed.delete(normalizeAddress(allowed));
|
|
49
|
+
}
|
|
50
|
+
return suppressed;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function suppressedMessage(
|
|
54
|
+
to: EmailAddress,
|
|
55
|
+
row: MailingSuppression,
|
|
56
|
+
): DeliveredMessage {
|
|
57
|
+
return {
|
|
58
|
+
to,
|
|
59
|
+
status: "suppressed",
|
|
60
|
+
outcome: { latencyMs: NO_LATENCY, error: suppressedError(row) },
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Logs a `suppressed` send for each blocked recipient, after the same checks
|
|
66
|
+
* as a real send: an unknown or paused template is still refused.
|
|
67
|
+
*/
|
|
68
|
+
async function recordSuppressed(
|
|
69
|
+
slug: string,
|
|
70
|
+
params: CheckedSendParams,
|
|
71
|
+
blocked: EmailAddress[],
|
|
72
|
+
rows: Map<string, MailingSuppression>,
|
|
73
|
+
): Promise<SendTemplateResult[]> {
|
|
74
|
+
const prepared = await prepare(params.tenantId, slug, params);
|
|
75
|
+
const messages = blocked.map((to) =>
|
|
76
|
+
suppressedMessage(
|
|
77
|
+
to,
|
|
78
|
+
rows.get(normalizeAddress(addressOf(to))) as MailingSuppression,
|
|
79
|
+
),
|
|
80
|
+
);
|
|
81
|
+
return recordAll(prepared, params, messages);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
async function sendAllowed(
|
|
85
|
+
slug: string,
|
|
86
|
+
params: CheckedSendParams,
|
|
87
|
+
allowed: EmailAddress[],
|
|
88
|
+
): Promise<SendTemplateResult[]> {
|
|
89
|
+
if (!allowed.length) return [];
|
|
90
|
+
const result = await sendTemplate(slug, { ...params, to: allowed });
|
|
91
|
+
return result.results ?? [result];
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Sends a template to every recipient but those on the suppression list,
|
|
96
|
+
* which are logged as `suppressed` instead. The results keep the order the
|
|
97
|
+
* recipients were given in.
|
|
98
|
+
*/
|
|
99
|
+
export async function sendUnlessSuppressed(
|
|
100
|
+
slug: string,
|
|
101
|
+
params: CheckedSendParams,
|
|
102
|
+
): Promise<SendTemplateResult> {
|
|
103
|
+
const recipients = toRecipients(params.to);
|
|
104
|
+
const rows = await blockedRecipients(params, recipients);
|
|
105
|
+
if (!rows.size) return sendTemplate(slug, params);
|
|
106
|
+
const isBlocked = (to: EmailAddress): boolean =>
|
|
107
|
+
rows.has(normalizeAddress(addressOf(to)));
|
|
108
|
+
const blocked = recipients.filter(isBlocked);
|
|
109
|
+
const suppressedResults = await recordSuppressed(slug, params, blocked, rows);
|
|
110
|
+
const sentResults = await sendAllowed(
|
|
111
|
+
slug,
|
|
112
|
+
params,
|
|
113
|
+
recipients.filter((to) => !isBlocked(to)),
|
|
114
|
+
);
|
|
115
|
+
return aggregateResults(
|
|
116
|
+
recipients.map(
|
|
117
|
+
(to) =>
|
|
118
|
+
(isBlocked(to)
|
|
119
|
+
? suppressedResults.shift()
|
|
120
|
+
: sentResults.shift()) as SendTemplateResult,
|
|
121
|
+
),
|
|
122
|
+
);
|
|
123
|
+
}
|