@antelopejs/dms-mailing 0.3.3 → 0.4.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.
Files changed (705) hide show
  1. package/README.md +232 -22
  2. package/dist/automation/email-event-trigger.js +2 -2
  3. package/dist/automation/email-event-trigger.js.map +1 -1
  4. package/dist/automation/send-template-action.js +2 -2
  5. package/dist/automation/send-template-action.js.map +1 -1
  6. package/dist/config.js +3 -1
  7. package/dist/config.js.map +1 -1
  8. package/dist/constants.d.ts +10 -1
  9. package/dist/constants.js +15 -3
  10. package/dist/constants.js.map +1 -1
  11. package/dist/crons/attention.d.ts +3 -0
  12. package/dist/crons/attention.js +19 -0
  13. package/dist/crons/attention.js.map +1 -0
  14. package/dist/crons/index.d.ts +2 -0
  15. package/dist/crons/index.js +2 -0
  16. package/dist/crons/index.js.map +1 -1
  17. package/dist/crons/jobs.d.ts +12 -0
  18. package/dist/crons/jobs.js +27 -0
  19. package/dist/crons/jobs.js.map +1 -0
  20. package/dist/crons/retention.d.ts +2 -4
  21. package/dist/crons/retention.js +7 -22
  22. package/dist/crons/retention.js.map +1 -1
  23. package/dist/data/displays.d.ts +5 -0
  24. package/dist/data/displays.js +23 -0
  25. package/dist/data/displays.js.map +1 -0
  26. package/dist/data/index.d.ts +1 -0
  27. package/dist/data/index.js +1 -0
  28. package/dist/data/index.js.map +1 -1
  29. package/dist/data/locale-fallback.d.ts +3 -0
  30. package/dist/data/locale-fallback.js +20 -0
  31. package/dist/data/locale-fallback.js.map +1 -0
  32. package/dist/data/read-only-routes.d.ts +7 -0
  33. package/dist/data/read-only-routes.js +13 -0
  34. package/dist/data/read-only-routes.js.map +1 -0
  35. package/dist/data/sends-table.d.ts +19 -5
  36. package/dist/data/sends-table.js +185 -17
  37. package/dist/data/sends-table.js.map +1 -1
  38. package/dist/data/suppressions-table.d.ts +24 -0
  39. package/dist/data/suppressions-table.js +120 -0
  40. package/dist/data/suppressions-table.js.map +1 -0
  41. package/dist/data/templates-table.d.ts +20 -2
  42. package/dist/data/templates-table.js +188 -31
  43. package/dist/data/templates-table.js.map +1 -1
  44. package/dist/data-types/categories-type.d.ts +11 -13
  45. package/dist/data-types/categories-type.js +3 -3
  46. package/dist/data-types/categories-type.js.map +1 -1
  47. package/dist/data-types/category-type.js +1 -1
  48. package/dist/data-types/category-type.js.map +1 -1
  49. package/dist/db/models/index.d.ts +3 -0
  50. package/dist/db/models/index.js +3 -0
  51. package/dist/db/models/index.js.map +1 -1
  52. package/dist/db/models/sends.model.d.ts +12 -1
  53. package/dist/db/models/sends.model.js +64 -0
  54. package/dist/db/models/sends.model.js.map +1 -1
  55. package/dist/db/models/settings.model.d.ts +0 -1
  56. package/dist/db/models/settings.model.js +0 -7
  57. package/dist/db/models/settings.model.js.map +1 -1
  58. package/dist/db/models/suppressions.model.d.ts +28 -0
  59. package/dist/db/models/suppressions.model.js +46 -0
  60. package/dist/db/models/suppressions.model.js.map +1 -0
  61. package/dist/db/models/template-versions.model.d.ts +24 -0
  62. package/dist/db/models/template-versions.model.js +17 -0
  63. package/dist/db/models/template-versions.model.js.map +1 -0
  64. package/dist/db/models/templates.model.d.ts +2 -0
  65. package/dist/db/models/templates.model.js +3 -0
  66. package/dist/db/models/templates.model.js.map +1 -1
  67. package/dist/db/models/webhook-status.model.d.ts +24 -0
  68. package/dist/db/models/webhook-status.model.js +21 -0
  69. package/dist/db/models/webhook-status.model.js.map +1 -0
  70. package/dist/db/tables/index.d.ts +3 -0
  71. package/dist/db/tables/index.js +3 -0
  72. package/dist/db/tables/index.js.map +1 -1
  73. package/dist/db/tables/send-events.table.d.ts +2 -2
  74. package/dist/db/tables/send-events.table.js.map +1 -1
  75. package/dist/db/tables/sends.table.d.ts +10 -2
  76. package/dist/db/tables/sends.table.js +34 -1
  77. package/dist/db/tables/sends.table.js.map +1 -1
  78. package/dist/db/tables/suppressions.table.d.ts +12 -0
  79. package/dist/db/tables/suppressions.table.js +53 -0
  80. package/dist/db/tables/suppressions.table.js.map +1 -0
  81. package/dist/db/tables/template-versions.table.d.ts +13 -0
  82. package/dist/db/tables/template-versions.table.js +59 -0
  83. package/dist/db/tables/template-versions.table.js.map +1 -0
  84. package/dist/db/tables/templates.table.d.ts +11 -0
  85. package/dist/db/tables/templates.table.js +45 -0
  86. package/dist/db/tables/templates.table.js.map +1 -1
  87. package/dist/db/tables/webhook-status.table.d.ts +14 -0
  88. package/dist/db/tables/webhook-status.table.js +58 -0
  89. package/dist/db/tables/webhook-status.table.js.map +1 -0
  90. package/dist/engine/changes.d.ts +12 -0
  91. package/dist/engine/changes.js +107 -0
  92. package/dist/engine/changes.js.map +1 -0
  93. package/dist/engine/conditions.js +0 -2
  94. package/dist/engine/conditions.js.map +1 -1
  95. package/dist/engine/coverage.d.ts +1 -0
  96. package/dist/engine/coverage.js +6 -2
  97. package/dist/engine/coverage.js.map +1 -1
  98. package/dist/engine/fallback-html.d.ts +1 -1
  99. package/dist/engine/fallback-html.js +51 -34
  100. package/dist/engine/fallback-html.js.map +1 -1
  101. package/dist/engine/index.d.ts +3 -0
  102. package/dist/engine/index.js +3 -0
  103. package/dist/engine/index.js.map +1 -1
  104. package/dist/engine/plain-text.d.ts +3 -0
  105. package/dist/engine/plain-text.js +60 -0
  106. package/dist/engine/plain-text.js.map +1 -0
  107. package/dist/engine/resolve.d.ts +2 -1
  108. package/dist/engine/resolve.js +17 -9
  109. package/dist/engine/resolve.js.map +1 -1
  110. package/dist/engine/system-variables.d.ts +10 -0
  111. package/dist/engine/system-variables.js +70 -0
  112. package/dist/engine/system-variables.js.map +1 -0
  113. package/dist/engine/tokens.d.ts +2 -1
  114. package/dist/engine/tokens.js +6 -2
  115. package/dist/engine/tokens.js.map +1 -1
  116. package/dist/hooks/tenant-export.js +13 -1
  117. package/dist/hooks/tenant-export.js.map +1 -1
  118. package/dist/implementations/dms-mailing/index.d.ts +1 -1
  119. package/dist/implementations/dms-mailing/index.js +2 -2
  120. package/dist/implementations/dms-mailing/index.js.map +1 -1
  121. package/dist/index.js +13 -11
  122. package/dist/index.js.map +1 -1
  123. package/dist/pages/editor.js +9 -2
  124. package/dist/pages/editor.js.map +1 -1
  125. package/dist/pages/index.d.ts +1 -0
  126. package/dist/pages/index.js +1 -0
  127. package/dist/pages/index.js.map +1 -1
  128. package/dist/pages/module.d.ts +2 -0
  129. package/dist/pages/module.js +26 -2
  130. package/dist/pages/module.js.map +1 -1
  131. package/dist/pages/overview.d.ts +2 -6
  132. package/dist/pages/overview.js +109 -52
  133. package/dist/pages/overview.js.map +1 -1
  134. package/dist/pages/sends.d.ts +3 -4
  135. package/dist/pages/sends.js +197 -60
  136. package/dist/pages/sends.js.map +1 -1
  137. package/dist/pages/settings/form.js +114 -53
  138. package/dist/pages/settings/form.js.map +1 -1
  139. package/dist/pages/settings/page.d.ts +0 -1
  140. package/dist/pages/settings/page.js +8 -11
  141. package/dist/pages/settings/page.js.map +1 -1
  142. package/dist/pages/suppressions.d.ts +9 -0
  143. package/dist/pages/suppressions.js +151 -0
  144. package/dist/pages/suppressions.js.map +1 -0
  145. package/dist/pages/templates.d.ts +0 -1
  146. package/dist/pages/templates.js +114 -40
  147. package/dist/pages/templates.js.map +1 -1
  148. package/dist/permissions.js +2 -2
  149. package/dist/permissions.js.map +1 -1
  150. package/dist/quick-actions.js +7 -4
  151. package/dist/quick-actions.js.map +1 -1
  152. package/dist/routes/events.d.ts +8 -5
  153. package/dist/routes/events.js +55 -24
  154. package/dist/routes/events.js.map +1 -1
  155. package/dist/routes/index.d.ts +2 -0
  156. package/dist/routes/index.js +2 -0
  157. package/dist/routes/index.js.map +1 -1
  158. package/dist/routes/metrics.d.ts +60 -23
  159. package/dist/routes/metrics.js +231 -83
  160. package/dist/routes/metrics.js.map +1 -1
  161. package/dist/routes/period.d.ts +2 -1
  162. package/dist/routes/period.js +9 -3
  163. package/dist/routes/period.js.map +1 -1
  164. package/dist/routes/sends.d.ts +49 -8
  165. package/dist/routes/sends.js +239 -24
  166. package/dist/routes/sends.js.map +1 -1
  167. package/dist/routes/settings.d.ts +44 -5
  168. package/dist/routes/settings.js +190 -15
  169. package/dist/routes/settings.js.map +1 -1
  170. package/dist/routes/suppressions.d.ts +16 -0
  171. package/dist/routes/suppressions.js +92 -0
  172. package/dist/routes/suppressions.js.map +1 -0
  173. package/dist/routes/template-versions.d.ts +28 -0
  174. package/dist/routes/template-versions.js +87 -0
  175. package/dist/routes/template-versions.js.map +1 -0
  176. package/dist/routes/templates.d.ts +60 -6
  177. package/dist/routes/templates.js +204 -35
  178. package/dist/routes/templates.js.map +1 -1
  179. package/dist/services/attention.d.ts +19 -6
  180. package/dist/services/attention.js +177 -12
  181. package/dist/services/attention.js.map +1 -1
  182. package/dist/services/catalog.d.ts +3 -0
  183. package/dist/services/catalog.js +25 -0
  184. package/dist/services/catalog.js.map +1 -0
  185. package/dist/services/categories.d.ts +8 -32
  186. package/dist/services/categories.js +14 -25
  187. package/dist/services/categories.js.map +1 -1
  188. package/dist/services/checked-send.d.ts +9 -0
  189. package/dist/services/checked-send.js +54 -0
  190. package/dist/services/checked-send.js.map +1 -0
  191. package/dist/services/events.d.ts +5 -0
  192. package/dist/services/events.js +102 -9
  193. package/dist/services/events.js.map +1 -1
  194. package/dist/services/metrics.d.ts +17 -6
  195. package/dist/services/metrics.js +57 -18
  196. package/dist/services/metrics.js.map +1 -1
  197. package/dist/services/notify.d.ts +18 -10
  198. package/dist/services/notify.js +82 -37
  199. package/dist/services/notify.js.map +1 -1
  200. package/dist/services/provider-banner.d.ts +14 -0
  201. package/dist/services/provider-banner.js +115 -0
  202. package/dist/services/provider-banner.js.map +1 -0
  203. package/dist/services/render.js.map +1 -1
  204. package/dist/services/send-stat-items.d.ts +7 -0
  205. package/dist/services/send-stat-items.js +127 -0
  206. package/dist/services/send-stat-items.js.map +1 -0
  207. package/dist/services/send-stats.d.ts +23 -0
  208. package/dist/services/send-stats.js +63 -0
  209. package/dist/services/send-stats.js.map +1 -0
  210. package/dist/services/send.d.ts +18 -3
  211. package/dist/services/send.js +81 -17
  212. package/dist/services/send.js.map +1 -1
  213. package/dist/services/settings.d.ts +5 -0
  214. package/dist/services/settings.js +28 -7
  215. package/dist/services/settings.js.map +1 -1
  216. package/dist/services/starters.d.ts +15 -0
  217. package/dist/services/starters.js +264 -0
  218. package/dist/services/starters.js.map +1 -0
  219. package/dist/services/suppressions.d.ts +20 -0
  220. package/dist/services/suppressions.js +53 -0
  221. package/dist/services/suppressions.js.map +1 -0
  222. package/dist/services/template-attention.d.ts +4 -0
  223. package/dist/services/template-attention.js +43 -0
  224. package/dist/services/template-attention.js.map +1 -0
  225. package/dist/services/template-stats.d.ts +27 -0
  226. package/dist/services/template-stats.js +59 -0
  227. package/dist/services/template-stats.js.map +1 -0
  228. package/dist/services/templates.d.ts +31 -4
  229. package/dist/services/templates.js +240 -21
  230. package/dist/services/templates.js.map +1 -1
  231. package/dist/services/webhook-status.d.ts +27 -0
  232. package/dist/services/webhook-status.js +134 -0
  233. package/dist/services/webhook-status.js.map +1 -0
  234. package/dist/types/blocks.d.ts +1 -0
  235. package/dist/types/index.d.ts +2 -0
  236. package/dist/types/index.js +2 -0
  237. package/dist/types/index.js.map +1 -1
  238. package/dist/types/sends.d.ts +10 -1
  239. package/dist/types/sends.js +35 -2
  240. package/dist/types/sends.js.map +1 -1
  241. package/dist/types/suppressions.d.ts +9 -0
  242. package/dist/types/suppressions.js +10 -0
  243. package/dist/types/suppressions.js.map +1 -0
  244. package/dist/types/webhook.d.ts +17 -0
  245. package/dist/types/webhook.js +3 -0
  246. package/dist/types/webhook.js.map +1 -0
  247. package/dist/validation/settings.schema.d.ts +144 -9
  248. package/dist/validation/settings.schema.js +113 -16
  249. package/dist/validation/settings.schema.js.map +1 -1
  250. package/dist/validation/templates.schema.d.ts +137 -10
  251. package/dist/validation/templates.schema.js +28 -2
  252. package/dist/validation/templates.schema.js.map +1 -1
  253. package/frontend-vue/app/build/components/FormFieldRow.vue +90 -0
  254. package/frontend-vue/app/build/components/FormRows.vue +26 -0
  255. package/frontend-vue/app/build/components/RequiredLegend.vue +11 -0
  256. package/frontend-vue/app/components/CategoriesInput.vue +323 -77
  257. package/frontend-vue/app/components/CategoryInput.vue +13 -14
  258. package/frontend-vue/app/components/CategoryLabel.vue +34 -0
  259. package/frontend-vue/app/components/DuplicateModal.vue +201 -0
  260. package/frontend-vue/app/components/Editor.vue +55 -20
  261. package/frontend-vue/app/components/FixAddressModal.vue +133 -0
  262. package/frontend-vue/app/components/FunnelCard.vue +146 -43
  263. package/frontend-vue/app/components/LocaleSelect.vue +98 -0
  264. package/frontend-vue/app/components/NewTemplateModal.vue +388 -102
  265. package/frontend-vue/app/components/NewTemplateSourceTile.vue +70 -0
  266. package/frontend-vue/app/components/OverviewGate.vue +220 -0
  267. package/frontend-vue/app/components/PageHeader.vue +98 -0
  268. package/frontend-vue/app/components/RealSendModal.vue +555 -0
  269. package/frontend-vue/app/components/RecipientsField.vue +41 -0
  270. package/frontend-vue/app/components/RenderingModal.vue +189 -0
  271. package/frontend-vue/app/components/RetentionInput.vue +212 -0
  272. package/frontend-vue/app/components/SendDrawer.vue +542 -209
  273. package/frontend-vue/app/components/StarterGallery.vue +153 -0
  274. package/frontend-vue/app/components/SystemVariables.vue +63 -0
  275. package/frontend-vue/app/components/TemplateCard.vue +218 -57
  276. package/frontend-vue/app/components/TemplateDrawer.vue +785 -274
  277. package/frontend-vue/app/components/TemplateGallery.vue +312 -56
  278. package/frontend-vue/app/components/TemplatePreviewFrame.vue +112 -22
  279. package/frontend-vue/app/components/TestSendModal.vue +266 -95
  280. package/frontend-vue/app/components/TokenText.vue +61 -10
  281. package/frontend-vue/app/components/VariablePath.vue +20 -0
  282. package/frontend-vue/app/components/VersionHistory.vue +196 -0
  283. package/frontend-vue/app/components/VersionPreview.vue +164 -0
  284. package/frontend-vue/app/components/WebhookStatus.vue +130 -0
  285. package/frontend-vue/app/components/blocks/Button.vue +1 -1
  286. package/frontend-vue/app/components/blocks/Code.vue +1 -1
  287. package/frontend-vue/app/components/blocks/Footer.vue +56 -7
  288. package/frontend-vue/app/components/blocks/Heading.vue +1 -1
  289. package/frontend-vue/app/components/blocks/Hero.vue +13 -7
  290. package/frontend-vue/app/components/blocks/List.vue +50 -6
  291. package/frontend-vue/app/components/blocks/Paragraph.vue +1 -1
  292. package/frontend-vue/app/components/blocks/Total.vue +3 -3
  293. package/frontend-vue/app/components/editor/EditorAutocompleteMenu.vue +50 -0
  294. package/frontend-vue/app/components/editor/EditorBlock.vue +37 -90
  295. package/frontend-vue/app/components/editor/EditorBlockFields.vue +140 -0
  296. package/frontend-vue/app/components/editor/EditorBlockInspector.vue +92 -0
  297. package/frontend-vue/app/components/editor/EditorBlockList.vue +32 -25
  298. package/frontend-vue/app/components/editor/EditorBlockPicker.vue +58 -0
  299. package/frontend-vue/app/components/editor/EditorBlockTools.vue +71 -0
  300. package/frontend-vue/app/components/editor/EditorCanvas.vue +105 -70
  301. package/frontend-vue/app/components/editor/EditorCompare.vue +211 -0
  302. package/frontend-vue/app/components/editor/EditorConditionFields.vue +56 -46
  303. package/frontend-vue/app/components/editor/EditorConditionInspector.vue +54 -0
  304. package/frontend-vue/app/components/editor/EditorEnvelope.vue +77 -0
  305. package/frontend-vue/app/components/editor/EditorIfBlock.vue +36 -86
  306. package/frontend-vue/app/components/editor/EditorInsertPoint.vue +64 -0
  307. package/frontend-vue/app/components/editor/EditorInspector.vue +80 -0
  308. package/frontend-vue/app/components/editor/EditorLeftPane.vue +103 -0
  309. package/frontend-vue/app/components/editor/EditorLifecycle.vue +67 -0
  310. package/frontend-vue/app/components/editor/EditorLoading.vue +23 -0
  311. package/frontend-vue/app/components/editor/EditorLocaleSwitch.vue +61 -0
  312. package/frontend-vue/app/components/editor/EditorMissingLocale.vue +72 -48
  313. package/frontend-vue/app/components/editor/EditorMissingPolicy.vue +42 -0
  314. package/frontend-vue/app/components/editor/EditorNotice.vue +127 -0
  315. package/frontend-vue/app/components/editor/EditorOutline.vue +65 -0
  316. package/frontend-vue/app/components/editor/EditorPalette.vue +95 -42
  317. package/frontend-vue/app/components/editor/EditorPaletteGroup.vue +66 -0
  318. package/frontend-vue/app/components/editor/EditorPublishReview.vue +102 -0
  319. package/frontend-vue/app/components/editor/EditorSaveState.vue +88 -0
  320. package/frontend-vue/app/components/editor/EditorShortcutHint.vue +17 -0
  321. package/frontend-vue/app/components/editor/EditorSimChip.vue +51 -0
  322. package/frontend-vue/app/components/editor/EditorTemplatePane.vue +131 -0
  323. package/frontend-vue/app/components/editor/EditorTestDataPane.vue +98 -0
  324. package/frontend-vue/app/components/editor/EditorTitle.vue +82 -0
  325. package/frontend-vue/app/components/editor/EditorTokenField.vue +224 -0
  326. package/frontend-vue/app/components/editor/EditorToolbar.vue +34 -134
  327. package/frontend-vue/app/components/editor/EditorVariableForm.vue +100 -0
  328. package/frontend-vue/app/components/editor/EditorVariableRow.vue +87 -0
  329. package/frontend-vue/app/components/editor/EditorVariablesPane.vue +161 -0
  330. package/frontend-vue/app/components/editor/EditorViewSwitches.vue +76 -0
  331. package/frontend-vue/app/components/editor/EditorVisibility.vue +101 -0
  332. package/frontend-vue/app/components/editor/EditorWorkspace.vue +62 -149
  333. package/frontend-vue/app/composables/useBlockContext.ts +16 -0
  334. package/frontend-vue/app/composables/useEditorActions.ts +381 -0
  335. package/frontend-vue/app/composables/useEditorAnnouncements.ts +51 -0
  336. package/frontend-vue/app/composables/useEditorContext.ts +30 -23
  337. package/frontend-vue/app/composables/useEditorSession.ts +299 -0
  338. package/frontend-vue/app/composables/useEditorShortcuts.ts +81 -0
  339. package/frontend-vue/app/composables/useLocalizedDefaults.ts +17 -0
  340. package/frontend-vue/app/composables/useMailingApi.ts +142 -27
  341. package/frontend-vue/app/composables/useOverviewSetup.ts +13 -0
  342. package/frontend-vue/app/composables/useReplayToast.ts +72 -0
  343. package/frontend-vue/app/composables/useTemplateCategories.ts +35 -0
  344. package/frontend-vue/app/composables/useTemplateDrawer.ts +196 -62
  345. package/frontend-vue/app/composables/useTemplateEditor.ts +318 -86
  346. package/frontend-vue/app/emails/EmailMailingTemplate.vue +16 -4
  347. package/frontend-vue/app/emails/components/MailingBlocks.vue +96 -38
  348. package/frontend-vue/app/emails/components/MailingTextLines.vue +20 -0
  349. package/frontend-vue/app/plugins/column-displays.ts +65 -0
  350. package/frontend-vue/app/plugins/{template-gallery-display.client.ts → template-gallery-display.ts} +1 -1
  351. package/frontend-vue/app/types/mailing.ts +193 -18
  352. package/frontend-vue/app/utils/api-error.ts +33 -0
  353. package/frontend-vue/app/utils/autocomplete.ts +120 -0
  354. package/frontend-vue/app/utils/blocks/components.ts +10 -10
  355. package/frontend-vue/app/utils/blocks/dnd.ts +7 -3
  356. package/frontend-vue/app/utils/blocks/factory.ts +199 -38
  357. package/frontend-vue/app/utils/blocks/fields.ts +39 -19
  358. package/frontend-vue/app/utils/blocks/index.ts +1 -0
  359. package/frontend-vue/app/utils/blocks/outline.ts +91 -0
  360. package/frontend-vue/app/utils/blocks/tree.ts +114 -2
  361. package/frontend-vue/app/utils/categories.ts +201 -15
  362. package/frontend-vue/app/utils/changes.ts +152 -0
  363. package/frontend-vue/app/utils/conditions.ts +25 -19
  364. package/frontend-vue/app/utils/editor-checks.ts +266 -0
  365. package/frontend-vue/app/utils/editor-route.ts +43 -4
  366. package/frontend-vue/app/utils/funnel.ts +74 -0
  367. package/frontend-vue/app/utils/gallery.ts +179 -1
  368. package/frontend-vue/app/utils/history.ts +66 -0
  369. package/frontend-vue/app/utils/lifecycle.ts +185 -0
  370. package/frontend-vue/app/utils/locales.ts +107 -2
  371. package/frontend-vue/app/utils/overview-setup.ts +83 -0
  372. package/frontend-vue/app/utils/pages.ts +5 -0
  373. package/frontend-vue/app/utils/paths.ts +51 -15
  374. package/frontend-vue/app/utils/period.ts +62 -22
  375. package/frontend-vue/app/utils/problems.ts +181 -0
  376. package/frontend-vue/app/utils/recipients.ts +49 -0
  377. package/frontend-vue/app/utils/rendering-title.ts +23 -0
  378. package/frontend-vue/app/utils/send-actions.ts +207 -0
  379. package/frontend-vue/app/utils/send-link.ts +28 -0
  380. package/frontend-vue/app/utils/send-snippet.ts +97 -0
  381. package/frontend-vue/app/utils/send-stats.ts +24 -0
  382. package/frontend-vue/app/utils/send-values.ts +156 -0
  383. package/frontend-vue/app/utils/shortcuts.ts +113 -0
  384. package/frontend-vue/app/utils/slug.ts +82 -0
  385. package/frontend-vue/app/utils/starters.ts +23 -0
  386. package/frontend-vue/app/utils/suppression.ts +112 -0
  387. package/frontend-vue/app/utils/system-variables.ts +165 -0
  388. package/frontend-vue/app/utils/template-attention.ts +104 -0
  389. package/frontend-vue/app/utils/timeline.ts +221 -19
  390. package/frontend-vue/app/utils/tokens.ts +115 -13
  391. package/frontend-vue/app/utils/variable-types.ts +23 -0
  392. package/frontend-vue/app/utils/variables.ts +208 -1
  393. package/frontend-vue/app/utils/version-history.ts +53 -0
  394. package/frontend-vue/app/utils/webhook-status.ts +170 -0
  395. package/frontend-vue/dms.frontend.ts +19 -11
  396. package/frontend-vue/i18n/locales/mailing-en-GB.json +1173 -161
  397. package/frontend-vue/i18n/locales/mailing-fr-FR.json +1220 -208
  398. package/frontend-vue/package.json +4 -1
  399. package/frontend-vue/pnpm-lock.yaml +46 -161
  400. package/frontend-vue/tests/api-error.test.ts +27 -0
  401. package/frontend-vue/tests/autocomplete.test.ts +84 -0
  402. package/frontend-vue/tests/block-components.test.ts +1 -1
  403. package/frontend-vue/tests/block-fields.test.ts +12 -2
  404. package/frontend-vue/tests/blocks-factory.test.ts +46 -0
  405. package/frontend-vue/tests/blocks-outline.test.ts +102 -0
  406. package/frontend-vue/tests/blocks-tree.test.ts +28 -1
  407. package/frontend-vue/tests/categories-input.test.ts +139 -0
  408. package/frontend-vue/tests/categories.test.ts +148 -15
  409. package/frontend-vue/tests/changes.test.ts +136 -0
  410. package/frontend-vue/tests/column-displays.test.ts +73 -0
  411. package/frontend-vue/tests/conditions.test.ts +49 -19
  412. package/frontend-vue/tests/dnd.test.ts +10 -0
  413. package/frontend-vue/tests/editor-block-list.test.ts +24 -2
  414. package/frontend-vue/tests/editor-checks.test.ts +178 -0
  415. package/frontend-vue/tests/editor-route.test.ts +37 -9
  416. package/frontend-vue/tests/editor-session.test.ts +206 -0
  417. package/frontend-vue/tests/footer-block.test.ts +88 -0
  418. package/frontend-vue/tests/funnel-card.test.ts +116 -0
  419. package/frontend-vue/tests/funnel.test.ts +81 -0
  420. package/frontend-vue/tests/gallery.test.ts +124 -2
  421. package/frontend-vue/tests/helpers/nuxt-globals.ts +232 -0
  422. package/frontend-vue/tests/history.test.ts +48 -0
  423. package/frontend-vue/tests/i18n-editor.test.ts +38 -0
  424. package/frontend-vue/tests/i18n.test.ts +55 -0
  425. package/frontend-vue/tests/lifecycle.test.ts +145 -0
  426. package/frontend-vue/tests/locales.test.ts +60 -0
  427. package/frontend-vue/tests/localized-defaults.test.ts +54 -0
  428. package/frontend-vue/tests/new-template-modal.test.ts +210 -0
  429. package/frontend-vue/tests/overview-gate.test.ts +146 -0
  430. package/frontend-vue/tests/overview-setup.test.ts +78 -0
  431. package/frontend-vue/tests/paths.test.ts +23 -0
  432. package/frontend-vue/tests/period.test.ts +59 -10
  433. package/frontend-vue/tests/problems.test.ts +153 -0
  434. package/frontend-vue/tests/recipients.test.ts +47 -0
  435. package/frontend-vue/tests/rendering-title.test.ts +24 -0
  436. package/frontend-vue/tests/replay-toast.test.ts +67 -0
  437. package/frontend-vue/tests/retention-input.test.ts +129 -0
  438. package/frontend-vue/tests/send-actions.test.ts +128 -0
  439. package/frontend-vue/tests/send-modals.test.ts +415 -0
  440. package/frontend-vue/tests/send-snippet.test.ts +51 -0
  441. package/frontend-vue/tests/send-stats.test.ts +12 -0
  442. package/frontend-vue/tests/send-values.test.ts +119 -0
  443. package/frontend-vue/tests/shortcuts.test.ts +75 -0
  444. package/frontend-vue/tests/slug.test.ts +88 -0
  445. package/frontend-vue/tests/suppression.test.ts +159 -0
  446. package/frontend-vue/tests/system-variables.test.ts +133 -0
  447. package/frontend-vue/tests/template-attention.test.ts +123 -0
  448. package/frontend-vue/tests/template-drawer.test.ts +449 -0
  449. package/frontend-vue/tests/template-editor.test.ts +218 -9
  450. package/frontend-vue/tests/template-gallery-display.test.ts +32 -0
  451. package/frontend-vue/tests/template-gallery.test.ts +370 -0
  452. package/frontend-vue/tests/template-overlays.test.ts +125 -0
  453. package/frontend-vue/tests/timeline.test.ts +175 -20
  454. package/frontend-vue/tests/tokens.test.ts +79 -1
  455. package/frontend-vue/tests/variable-path.test.ts +22 -0
  456. package/frontend-vue/tests/variable-types.test.ts +20 -0
  457. package/frontend-vue/tests/variables.test.ts +90 -2
  458. package/frontend-vue/tests/version-history.test.ts +41 -0
  459. package/frontend-vue/tests/webhook-status.test.ts +108 -0
  460. package/package.json +11 -9
  461. package/src/automation/email-event-trigger.ts +3 -3
  462. package/src/automation/send-template-action.ts +7 -5
  463. package/src/config.ts +3 -1
  464. package/src/constants.ts +18 -2
  465. package/src/crons/attention.ts +21 -0
  466. package/src/crons/index.ts +2 -0
  467. package/src/crons/jobs.ts +35 -0
  468. package/src/crons/retention.ts +7 -22
  469. package/src/data/displays.ts +18 -0
  470. package/src/data/index.ts +1 -0
  471. package/src/data/locale-fallback.ts +26 -0
  472. package/src/data/read-only-routes.ts +14 -0
  473. package/src/data/sends-table.ts +204 -19
  474. package/src/data/suppressions-table.ts +127 -0
  475. package/src/data/templates-table.ts +295 -63
  476. package/src/data-types/categories-type.ts +4 -5
  477. package/src/data-types/category-type.ts +1 -1
  478. package/src/db/models/index.ts +3 -0
  479. package/src/db/models/sends.model.ts +102 -1
  480. package/src/db/models/settings.model.ts +2 -12
  481. package/src/db/models/suppressions.model.ts +53 -0
  482. package/src/db/models/template-versions.model.ts +23 -0
  483. package/src/db/models/templates.model.ts +17 -0
  484. package/src/db/models/webhook-status.model.ts +25 -0
  485. package/src/db/tables/index.ts +3 -0
  486. package/src/db/tables/send-events.table.ts +6 -2
  487. package/src/db/tables/sends.table.ts +37 -2
  488. package/src/db/tables/suppressions.table.ts +46 -0
  489. package/src/db/tables/template-versions.table.ts +44 -0
  490. package/src/db/tables/templates.table.ts +67 -5
  491. package/src/db/tables/webhook-status.table.ts +44 -0
  492. package/src/engine/changes.ts +198 -0
  493. package/src/engine/conditions.ts +1 -7
  494. package/src/engine/coverage.ts +7 -2
  495. package/src/engine/fallback-html.ts +80 -42
  496. package/src/engine/index.ts +3 -0
  497. package/src/engine/plain-text.ts +86 -0
  498. package/src/engine/resolve.ts +49 -15
  499. package/src/engine/system-variables.ts +124 -0
  500. package/src/engine/tokens.ts +13 -1
  501. package/src/hooks/tenant-export.ts +21 -2
  502. package/src/implementations/dms-mailing/index.ts +1 -1
  503. package/src/index.ts +29 -13
  504. package/src/pages/editor.ts +13 -2
  505. package/src/pages/index.ts +6 -3
  506. package/src/pages/module.ts +31 -2
  507. package/src/pages/overview.ts +187 -128
  508. package/src/pages/sends.ts +271 -112
  509. package/src/pages/settings/form.ts +144 -54
  510. package/src/pages/settings/page.ts +9 -17
  511. package/src/pages/suppressions.ts +157 -0
  512. package/src/pages/templates.ts +139 -42
  513. package/src/permissions.ts +6 -4
  514. package/src/quick-actions.ts +12 -5
  515. package/src/routes/events.ts +110 -33
  516. package/src/routes/index.ts +2 -0
  517. package/src/routes/metrics.ts +348 -98
  518. package/src/routes/period.ts +23 -6
  519. package/src/routes/sends.ts +413 -41
  520. package/src/routes/settings.ts +291 -26
  521. package/src/routes/suppressions.ts +87 -0
  522. package/src/routes/template-versions.ts +116 -0
  523. package/src/routes/templates.ts +334 -48
  524. package/src/services/attention.ts +283 -30
  525. package/src/services/catalog.ts +28 -0
  526. package/src/services/categories.ts +28 -27
  527. package/src/services/checked-send.ts +123 -0
  528. package/src/services/events.ts +174 -20
  529. package/src/services/metrics.ts +137 -28
  530. package/src/services/notify.ts +126 -44
  531. package/src/services/provider-banner.ts +165 -0
  532. package/src/services/render.ts +1 -0
  533. package/src/services/send-stat-items.ts +160 -0
  534. package/src/services/send-stats.ts +122 -0
  535. package/src/services/send.ts +193 -31
  536. package/src/services/settings.ts +52 -8
  537. package/src/services/starters.ts +383 -0
  538. package/src/services/suppressions.ts +116 -0
  539. package/src/services/template-attention.ts +82 -0
  540. package/src/services/template-stats.ts +97 -0
  541. package/src/services/templates.ts +400 -37
  542. package/src/services/webhook-status.ts +202 -0
  543. package/src/types/blocks.ts +6 -0
  544. package/src/types/index.ts +2 -0
  545. package/src/types/sends.ts +64 -2
  546. package/src/types/suppressions.ts +27 -0
  547. package/src/types/webhook.ts +29 -0
  548. package/src/validation/settings.schema.ts +173 -17
  549. package/src/validation/templates.schema.ts +53 -1
  550. package/dist/antelope-modules.d.ts +0 -32
  551. package/dist/antelope-modules.js +0 -36
  552. package/dist/antelope-modules.js.map +0 -1
  553. package/dist/test/antelope.test.d.ts +0 -2
  554. package/dist/test/antelope.test.js +0 -180
  555. package/dist/test/antelope.test.js.map +0 -1
  556. package/dist/test/helpers/http.d.ts +0 -4
  557. package/dist/test/helpers/http.js +0 -28
  558. package/dist/test/helpers/http.js.map +0 -1
  559. package/dist/test/helpers/owner.d.ts +0 -5
  560. package/dist/test/helpers/owner.js +0 -30
  561. package/dist/test/helpers/owner.js.map +0 -1
  562. package/dist/test/integration/events.test.d.ts +0 -1
  563. package/dist/test/integration/events.test.js +0 -143
  564. package/dist/test/integration/events.test.js.map +0 -1
  565. package/dist/test/integration/retention.test.d.ts +0 -1
  566. package/dist/test/integration/retention.test.js +0 -291
  567. package/dist/test/integration/retention.test.js.map +0 -1
  568. package/dist/test/integration/sends.test.d.ts +0 -1
  569. package/dist/test/integration/sends.test.js +0 -210
  570. package/dist/test/integration/sends.test.js.map +0 -1
  571. package/dist/test/integration/settings.test.d.ts +0 -1
  572. package/dist/test/integration/settings.test.js +0 -137
  573. package/dist/test/integration/settings.test.js.map +0 -1
  574. package/dist/test/integration/setup.test.d.ts +0 -1
  575. package/dist/test/integration/setup.test.js +0 -32
  576. package/dist/test/integration/setup.test.js.map +0 -1
  577. package/dist/test/integration/templates.test.d.ts +0 -1
  578. package/dist/test/integration/templates.test.js +0 -233
  579. package/dist/test/integration/templates.test.js.map +0 -1
  580. package/dist/test/integration/tenant-export.test.d.ts +0 -1
  581. package/dist/test/integration/tenant-export.test.js +0 -85
  582. package/dist/test/integration/tenant-export.test.js.map +0 -1
  583. package/dist/test/unit/attention.test.d.ts +0 -1
  584. package/dist/test/unit/attention.test.js +0 -109
  585. package/dist/test/unit/attention.test.js.map +0 -1
  586. package/dist/test/unit/automation.test.d.ts +0 -1
  587. package/dist/test/unit/automation.test.js +0 -104
  588. package/dist/test/unit/automation.test.js.map +0 -1
  589. package/dist/test/unit/categories.test.d.ts +0 -1
  590. package/dist/test/unit/categories.test.js +0 -38
  591. package/dist/test/unit/categories.test.js.map +0 -1
  592. package/dist/test/unit/engine/conditions.test.d.ts +0 -1
  593. package/dist/test/unit/engine/conditions.test.js +0 -23
  594. package/dist/test/unit/engine/conditions.test.js.map +0 -1
  595. package/dist/test/unit/engine/coverage.test.d.ts +0 -1
  596. package/dist/test/unit/engine/coverage.test.js +0 -106
  597. package/dist/test/unit/engine/coverage.test.js.map +0 -1
  598. package/dist/test/unit/engine/fallback-html.test.d.ts +0 -1
  599. package/dist/test/unit/engine/fallback-html.test.js +0 -78
  600. package/dist/test/unit/engine/fallback-html.test.js.map +0 -1
  601. package/dist/test/unit/engine/paths.test.d.ts +0 -1
  602. package/dist/test/unit/engine/paths.test.js +0 -33
  603. package/dist/test/unit/engine/paths.test.js.map +0 -1
  604. package/dist/test/unit/engine/resolve.test.d.ts +0 -1
  605. package/dist/test/unit/engine/resolve.test.js +0 -157
  606. package/dist/test/unit/engine/resolve.test.js.map +0 -1
  607. package/dist/test/unit/engine/tokens.test.d.ts +0 -1
  608. package/dist/test/unit/engine/tokens.test.js +0 -26
  609. package/dist/test/unit/engine/tokens.test.js.map +0 -1
  610. package/dist/test/unit/events.test.d.ts +0 -1
  611. package/dist/test/unit/events.test.js +0 -13
  612. package/dist/test/unit/events.test.js.map +0 -1
  613. package/dist/test/unit/metrics.test.d.ts +0 -1
  614. package/dist/test/unit/metrics.test.js +0 -108
  615. package/dist/test/unit/metrics.test.js.map +0 -1
  616. package/dist/test/unit/notify.test.d.ts +0 -1
  617. package/dist/test/unit/notify.test.js +0 -49
  618. package/dist/test/unit/notify.test.js.map +0 -1
  619. package/dist/test/unit/period.test.d.ts +0 -1
  620. package/dist/test/unit/period.test.js +0 -61
  621. package/dist/test/unit/period.test.js.map +0 -1
  622. package/dist/test/unit/permissions.test.d.ts +0 -1
  623. package/dist/test/unit/permissions.test.js +0 -27
  624. package/dist/test/unit/permissions.test.js.map +0 -1
  625. package/dist/test/unit/provider.test.d.ts +0 -1
  626. package/dist/test/unit/provider.test.js +0 -47
  627. package/dist/test/unit/provider.test.js.map +0 -1
  628. package/dist/test/unit/realtime.test.d.ts +0 -1
  629. package/dist/test/unit/realtime.test.js +0 -13
  630. package/dist/test/unit/realtime.test.js.map +0 -1
  631. package/dist/test/unit/render.test.d.ts +0 -1
  632. package/dist/test/unit/render.test.js +0 -23
  633. package/dist/test/unit/render.test.js.map +0 -1
  634. package/dist/test/unit/retention.test.d.ts +0 -1
  635. package/dist/test/unit/retention.test.js +0 -66
  636. package/dist/test/unit/retention.test.js.map +0 -1
  637. package/dist/test/unit/send-batch.test.d.ts +0 -1
  638. package/dist/test/unit/send-batch.test.js +0 -46
  639. package/dist/test/unit/send-batch.test.js.map +0 -1
  640. package/dist/test/unit/send.test.d.ts +0 -1
  641. package/dist/test/unit/send.test.js +0 -120
  642. package/dist/test/unit/send.test.js.map +0 -1
  643. package/dist/test/unit/smoke.test.d.ts +0 -1
  644. package/dist/test/unit/smoke.test.js +0 -10
  645. package/dist/test/unit/smoke.test.js.map +0 -1
  646. package/dist/test/unit/status.test.d.ts +0 -1
  647. package/dist/test/unit/status.test.js +0 -16
  648. package/dist/test/unit/status.test.js.map +0 -1
  649. package/dist/test/unit/templates.test.d.ts +0 -1
  650. package/dist/test/unit/templates.test.js +0 -48
  651. package/dist/test/unit/templates.test.js.map +0 -1
  652. package/dist/test/unit/tenant-export.test.d.ts +0 -1
  653. package/dist/test/unit/tenant-export.test.js +0 -59
  654. package/dist/test/unit/tenant-export.test.js.map +0 -1
  655. package/dist/test/unit/webhook-secret.test.d.ts +0 -1
  656. package/dist/test/unit/webhook-secret.test.js +0 -43
  657. package/dist/test/unit/webhook-secret.test.js.map +0 -1
  658. package/frontend-vue/app/components/AttentionCard.vue +0 -78
  659. package/frontend-vue/app/components/HtmlPreviewModal.vue +0 -20
  660. package/frontend-vue/app/components/ProviderChip.vue +0 -39
  661. package/frontend-vue/app/components/editor/EditorBlockSettings.vue +0 -186
  662. package/frontend-vue/app/components/editor/EditorDataPanel.vue +0 -130
  663. package/frontend-vue/app/components/editor/EditorDraftBar.vue +0 -35
  664. package/frontend-vue/app/components/editor/EditorRail.vue +0 -58
  665. package/frontend-vue/app/components/editor/EditorSubjectSettings.vue +0 -37
  666. package/frontend-vue/app/components/editor/EditorTemplateSettings.vue +0 -87
  667. package/frontend-vue/app/components/editor/EditorTestSendModal.vue +0 -66
  668. package/frontend-vue/app/components/editor/EditorVariables.vue +0 -163
  669. package/frontend-vue/app/composables/useMailingPeriodData.ts +0 -39
  670. package/src/antelope-modules.ts +0 -48
  671. package/src/test/antelope.test.ts +0 -190
  672. package/src/test/helpers/http.ts +0 -23
  673. package/src/test/helpers/owner.ts +0 -37
  674. package/src/test/integration/events.test.ts +0 -159
  675. package/src/test/integration/retention.test.ts +0 -363
  676. package/src/test/integration/sends.test.ts +0 -233
  677. package/src/test/integration/settings.test.ts +0 -128
  678. package/src/test/integration/setup.test.ts +0 -35
  679. package/src/test/integration/templates.test.ts +0 -272
  680. package/src/test/integration/tenant-export.test.ts +0 -125
  681. package/src/test/unit/attention.test.ts +0 -129
  682. package/src/test/unit/automation.test.ts +0 -130
  683. package/src/test/unit/categories.test.ts +0 -51
  684. package/src/test/unit/engine/conditions.test.ts +0 -35
  685. package/src/test/unit/engine/coverage.test.ts +0 -118
  686. package/src/test/unit/engine/fallback-html.test.ts +0 -86
  687. package/src/test/unit/engine/paths.test.ts +0 -39
  688. package/src/test/unit/engine/resolve.test.ts +0 -176
  689. package/src/test/unit/engine/tokens.test.ts +0 -26
  690. package/src/test/unit/events.test.ts +0 -19
  691. package/src/test/unit/metrics.test.ts +0 -170
  692. package/src/test/unit/notify.test.ts +0 -71
  693. package/src/test/unit/period.test.ts +0 -91
  694. package/src/test/unit/permissions.test.ts +0 -30
  695. package/src/test/unit/provider.test.ts +0 -57
  696. package/src/test/unit/realtime.test.ts +0 -11
  697. package/src/test/unit/render.test.ts +0 -23
  698. package/src/test/unit/retention.test.ts +0 -91
  699. package/src/test/unit/send-batch.test.ts +0 -72
  700. package/src/test/unit/send.test.ts +0 -161
  701. package/src/test/unit/smoke.test.ts +0 -8
  702. package/src/test/unit/status.test.ts +0 -14
  703. package/src/test/unit/templates.test.ts +0 -52
  704. package/src/test/unit/tenant-export.test.ts +0 -88
  705. 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 { PROBLEM_STATUSES } from "../types";
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
- params?: Record<string, string | number>;
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}?tab=problems`;
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((send) =>
29
- PROBLEM_STATUSES.includes(send.status),
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
- params: { count: problems.length },
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
- to: TEMPLATES_PAGE,
58
- params: { count: stale.length },
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
- function neverSentItem(
64
- templates: MailingTemplate[],
65
- sends: SendSummary[],
66
- ): AttentionItem[] {
67
- const used = new Set(sends.map((send) => send.templateSlug));
68
- const unused = templates.filter(
69
- (template) => template.status === "live" && !used.has(template.slug),
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
- to: TEMPLATES_PAGE,
80
- params: { count: unused.length },
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 the provider reports those events. An
122
- * SMTP provider reports neither, so the rates sit at 0 % forever — which reads
123
- * as "nobody opened it" rather than "nobody can tell". Say so next to the
124
- * figures, and only while the window actually shows some.
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
- to: SENDS_PAGE,
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
- ...neverSentItem(window.templates, window.sends),
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 { categorySchema } from "../validation/settings.schema";
3
+ import type { CategoryEntry } from "../validation/settings.schema";
5
4
 
6
- /** Direct API callers may send a JSON string; accept both string and array. */
7
- const categoriesSchema = z
8
- .union([z.string(), z.array(categorySchema)])
9
- .transform((value) => {
10
- if (typeof value !== "string") return value;
11
- if (value === "") return [];
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
- export const CATEGORIES_SCHEMA = categoriesSchema;
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
- * Reads a category list off a form field or an API body. Never throws on shape:
24
- * this runs while rendering a settings field.
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
- * Ids of the templates whose category is not in `categories` any more. Deleting
33
- * a category must leave them uncategorised, never pointing at nothing.
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
- ): string[] {
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) => template._id);
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
+ }