@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.
Files changed (707) 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 +115 -41
  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 +2 -2
  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 +0 -3
  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 +140 -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 -21
  412. package/frontend-vue/tests/dnd.test.ts +10 -0
  413. package/frontend-vue/tests/editor-block-list.test.ts +25 -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 +207 -0
  417. package/frontend-vue/tests/footer-block.test.ts +89 -0
  418. package/frontend-vue/tests/funnel-card.test.ts +117 -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 +37 -8
  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 +211 -0
  429. package/frontend-vue/tests/overview-gate.test.ts +147 -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 +130 -0
  438. package/frontend-vue/tests/send-actions.test.ts +128 -0
  439. package/frontend-vue/tests/send-modals.test.ts +416 -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 +76 -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 +450 -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 +371 -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 +23 -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/frontend-vue/vitest.config.ts +4 -1
  461. package/package.json +14 -12
  462. package/src/automation/email-event-trigger.ts +3 -3
  463. package/src/automation/send-template-action.ts +7 -5
  464. package/src/config.ts +3 -1
  465. package/src/constants.ts +18 -2
  466. package/src/crons/attention.ts +21 -0
  467. package/src/crons/index.ts +2 -0
  468. package/src/crons/jobs.ts +35 -0
  469. package/src/crons/retention.ts +7 -22
  470. package/src/data/displays.ts +18 -0
  471. package/src/data/index.ts +1 -0
  472. package/src/data/locale-fallback.ts +26 -0
  473. package/src/data/read-only-routes.ts +14 -0
  474. package/src/data/sends-table.ts +204 -19
  475. package/src/data/suppressions-table.ts +127 -0
  476. package/src/data/templates-table.ts +295 -63
  477. package/src/data-types/categories-type.ts +4 -5
  478. package/src/data-types/category-type.ts +1 -1
  479. package/src/db/models/index.ts +3 -0
  480. package/src/db/models/sends.model.ts +102 -1
  481. package/src/db/models/settings.model.ts +2 -12
  482. package/src/db/models/suppressions.model.ts +53 -0
  483. package/src/db/models/template-versions.model.ts +23 -0
  484. package/src/db/models/templates.model.ts +17 -0
  485. package/src/db/models/webhook-status.model.ts +25 -0
  486. package/src/db/tables/index.ts +3 -0
  487. package/src/db/tables/send-events.table.ts +6 -2
  488. package/src/db/tables/sends.table.ts +37 -2
  489. package/src/db/tables/suppressions.table.ts +46 -0
  490. package/src/db/tables/template-versions.table.ts +44 -0
  491. package/src/db/tables/templates.table.ts +67 -5
  492. package/src/db/tables/webhook-status.table.ts +44 -0
  493. package/src/engine/changes.ts +198 -0
  494. package/src/engine/conditions.ts +1 -7
  495. package/src/engine/coverage.ts +7 -2
  496. package/src/engine/fallback-html.ts +80 -42
  497. package/src/engine/index.ts +3 -0
  498. package/src/engine/plain-text.ts +86 -0
  499. package/src/engine/resolve.ts +49 -15
  500. package/src/engine/system-variables.ts +124 -0
  501. package/src/engine/tokens.ts +13 -1
  502. package/src/hooks/tenant-export.ts +21 -2
  503. package/src/implementations/dms-mailing/index.ts +1 -1
  504. package/src/index.ts +29 -13
  505. package/src/pages/editor.ts +13 -2
  506. package/src/pages/index.ts +6 -3
  507. package/src/pages/module.ts +31 -2
  508. package/src/pages/overview.ts +187 -128
  509. package/src/pages/sends.ts +271 -112
  510. package/src/pages/settings/form.ts +144 -54
  511. package/src/pages/settings/page.ts +9 -17
  512. package/src/pages/suppressions.ts +157 -0
  513. package/src/pages/templates.ts +140 -43
  514. package/src/permissions.ts +6 -4
  515. package/src/quick-actions.ts +12 -5
  516. package/src/routes/events.ts +110 -33
  517. package/src/routes/index.ts +2 -0
  518. package/src/routes/metrics.ts +348 -98
  519. package/src/routes/period.ts +23 -6
  520. package/src/routes/sends.ts +413 -41
  521. package/src/routes/settings.ts +291 -26
  522. package/src/routes/suppressions.ts +87 -0
  523. package/src/routes/template-versions.ts +116 -0
  524. package/src/routes/templates.ts +334 -48
  525. package/src/services/attention.ts +283 -30
  526. package/src/services/catalog.ts +28 -0
  527. package/src/services/categories.ts +28 -27
  528. package/src/services/checked-send.ts +123 -0
  529. package/src/services/events.ts +174 -20
  530. package/src/services/metrics.ts +137 -28
  531. package/src/services/notify.ts +126 -44
  532. package/src/services/provider-banner.ts +165 -0
  533. package/src/services/render.ts +1 -0
  534. package/src/services/send-stat-items.ts +160 -0
  535. package/src/services/send-stats.ts +122 -0
  536. package/src/services/send.ts +193 -31
  537. package/src/services/settings.ts +52 -8
  538. package/src/services/starters.ts +383 -0
  539. package/src/services/suppressions.ts +116 -0
  540. package/src/services/template-attention.ts +82 -0
  541. package/src/services/template-stats.ts +97 -0
  542. package/src/services/templates.ts +400 -37
  543. package/src/services/webhook-status.ts +202 -0
  544. package/src/types/blocks.ts +6 -0
  545. package/src/types/index.ts +2 -0
  546. package/src/types/sends.ts +64 -2
  547. package/src/types/suppressions.ts +27 -0
  548. package/src/types/webhook.ts +29 -0
  549. package/src/validation/settings.schema.ts +173 -17
  550. package/src/validation/templates.schema.ts +53 -1
  551. package/dist/antelope-modules.d.ts +0 -32
  552. package/dist/antelope-modules.js +0 -36
  553. package/dist/antelope-modules.js.map +0 -1
  554. package/dist/test/antelope.test.d.ts +0 -2
  555. package/dist/test/antelope.test.js +0 -180
  556. package/dist/test/antelope.test.js.map +0 -1
  557. package/dist/test/helpers/http.d.ts +0 -4
  558. package/dist/test/helpers/http.js +0 -28
  559. package/dist/test/helpers/http.js.map +0 -1
  560. package/dist/test/helpers/owner.d.ts +0 -5
  561. package/dist/test/helpers/owner.js +0 -30
  562. package/dist/test/helpers/owner.js.map +0 -1
  563. package/dist/test/integration/events.test.d.ts +0 -1
  564. package/dist/test/integration/events.test.js +0 -143
  565. package/dist/test/integration/events.test.js.map +0 -1
  566. package/dist/test/integration/retention.test.d.ts +0 -1
  567. package/dist/test/integration/retention.test.js +0 -291
  568. package/dist/test/integration/retention.test.js.map +0 -1
  569. package/dist/test/integration/sends.test.d.ts +0 -1
  570. package/dist/test/integration/sends.test.js +0 -210
  571. package/dist/test/integration/sends.test.js.map +0 -1
  572. package/dist/test/integration/settings.test.d.ts +0 -1
  573. package/dist/test/integration/settings.test.js +0 -137
  574. package/dist/test/integration/settings.test.js.map +0 -1
  575. package/dist/test/integration/setup.test.d.ts +0 -1
  576. package/dist/test/integration/setup.test.js +0 -32
  577. package/dist/test/integration/setup.test.js.map +0 -1
  578. package/dist/test/integration/templates.test.d.ts +0 -1
  579. package/dist/test/integration/templates.test.js +0 -233
  580. package/dist/test/integration/templates.test.js.map +0 -1
  581. package/dist/test/integration/tenant-export.test.d.ts +0 -1
  582. package/dist/test/integration/tenant-export.test.js +0 -85
  583. package/dist/test/integration/tenant-export.test.js.map +0 -1
  584. package/dist/test/unit/attention.test.d.ts +0 -1
  585. package/dist/test/unit/attention.test.js +0 -109
  586. package/dist/test/unit/attention.test.js.map +0 -1
  587. package/dist/test/unit/automation.test.d.ts +0 -1
  588. package/dist/test/unit/automation.test.js +0 -104
  589. package/dist/test/unit/automation.test.js.map +0 -1
  590. package/dist/test/unit/categories.test.d.ts +0 -1
  591. package/dist/test/unit/categories.test.js +0 -38
  592. package/dist/test/unit/categories.test.js.map +0 -1
  593. package/dist/test/unit/engine/conditions.test.d.ts +0 -1
  594. package/dist/test/unit/engine/conditions.test.js +0 -23
  595. package/dist/test/unit/engine/conditions.test.js.map +0 -1
  596. package/dist/test/unit/engine/coverage.test.d.ts +0 -1
  597. package/dist/test/unit/engine/coverage.test.js +0 -106
  598. package/dist/test/unit/engine/coverage.test.js.map +0 -1
  599. package/dist/test/unit/engine/fallback-html.test.d.ts +0 -1
  600. package/dist/test/unit/engine/fallback-html.test.js +0 -78
  601. package/dist/test/unit/engine/fallback-html.test.js.map +0 -1
  602. package/dist/test/unit/engine/paths.test.d.ts +0 -1
  603. package/dist/test/unit/engine/paths.test.js +0 -33
  604. package/dist/test/unit/engine/paths.test.js.map +0 -1
  605. package/dist/test/unit/engine/resolve.test.d.ts +0 -1
  606. package/dist/test/unit/engine/resolve.test.js +0 -157
  607. package/dist/test/unit/engine/resolve.test.js.map +0 -1
  608. package/dist/test/unit/engine/tokens.test.d.ts +0 -1
  609. package/dist/test/unit/engine/tokens.test.js +0 -26
  610. package/dist/test/unit/engine/tokens.test.js.map +0 -1
  611. package/dist/test/unit/events.test.d.ts +0 -1
  612. package/dist/test/unit/events.test.js +0 -13
  613. package/dist/test/unit/events.test.js.map +0 -1
  614. package/dist/test/unit/metrics.test.d.ts +0 -1
  615. package/dist/test/unit/metrics.test.js +0 -108
  616. package/dist/test/unit/metrics.test.js.map +0 -1
  617. package/dist/test/unit/notify.test.d.ts +0 -1
  618. package/dist/test/unit/notify.test.js +0 -49
  619. package/dist/test/unit/notify.test.js.map +0 -1
  620. package/dist/test/unit/period.test.d.ts +0 -1
  621. package/dist/test/unit/period.test.js +0 -61
  622. package/dist/test/unit/period.test.js.map +0 -1
  623. package/dist/test/unit/permissions.test.d.ts +0 -1
  624. package/dist/test/unit/permissions.test.js +0 -27
  625. package/dist/test/unit/permissions.test.js.map +0 -1
  626. package/dist/test/unit/provider.test.d.ts +0 -1
  627. package/dist/test/unit/provider.test.js +0 -47
  628. package/dist/test/unit/provider.test.js.map +0 -1
  629. package/dist/test/unit/realtime.test.d.ts +0 -1
  630. package/dist/test/unit/realtime.test.js +0 -13
  631. package/dist/test/unit/realtime.test.js.map +0 -1
  632. package/dist/test/unit/render.test.d.ts +0 -1
  633. package/dist/test/unit/render.test.js +0 -23
  634. package/dist/test/unit/render.test.js.map +0 -1
  635. package/dist/test/unit/retention.test.d.ts +0 -1
  636. package/dist/test/unit/retention.test.js +0 -66
  637. package/dist/test/unit/retention.test.js.map +0 -1
  638. package/dist/test/unit/send-batch.test.d.ts +0 -1
  639. package/dist/test/unit/send-batch.test.js +0 -46
  640. package/dist/test/unit/send-batch.test.js.map +0 -1
  641. package/dist/test/unit/send.test.d.ts +0 -1
  642. package/dist/test/unit/send.test.js +0 -120
  643. package/dist/test/unit/send.test.js.map +0 -1
  644. package/dist/test/unit/smoke.test.d.ts +0 -1
  645. package/dist/test/unit/smoke.test.js +0 -10
  646. package/dist/test/unit/smoke.test.js.map +0 -1
  647. package/dist/test/unit/status.test.d.ts +0 -1
  648. package/dist/test/unit/status.test.js +0 -16
  649. package/dist/test/unit/status.test.js.map +0 -1
  650. package/dist/test/unit/templates.test.d.ts +0 -1
  651. package/dist/test/unit/templates.test.js +0 -48
  652. package/dist/test/unit/templates.test.js.map +0 -1
  653. package/dist/test/unit/tenant-export.test.d.ts +0 -1
  654. package/dist/test/unit/tenant-export.test.js +0 -59
  655. package/dist/test/unit/tenant-export.test.js.map +0 -1
  656. package/dist/test/unit/webhook-secret.test.d.ts +0 -1
  657. package/dist/test/unit/webhook-secret.test.js +0 -43
  658. package/dist/test/unit/webhook-secret.test.js.map +0 -1
  659. package/frontend-vue/app/components/AttentionCard.vue +0 -78
  660. package/frontend-vue/app/components/HtmlPreviewModal.vue +0 -20
  661. package/frontend-vue/app/components/ProviderChip.vue +0 -39
  662. package/frontend-vue/app/components/editor/EditorBlockSettings.vue +0 -186
  663. package/frontend-vue/app/components/editor/EditorDataPanel.vue +0 -130
  664. package/frontend-vue/app/components/editor/EditorDraftBar.vue +0 -35
  665. package/frontend-vue/app/components/editor/EditorRail.vue +0 -58
  666. package/frontend-vue/app/components/editor/EditorSubjectSettings.vue +0 -37
  667. package/frontend-vue/app/components/editor/EditorTemplateSettings.vue +0 -87
  668. package/frontend-vue/app/components/editor/EditorTestSendModal.vue +0 -66
  669. package/frontend-vue/app/components/editor/EditorVariables.vue +0 -163
  670. package/frontend-vue/app/composables/useMailingPeriodData.ts +0 -39
  671. package/frontend-vue/tests/smoke.test.ts +0 -8
  672. package/src/antelope-modules.ts +0 -48
  673. package/src/test/antelope.test.ts +0 -190
  674. package/src/test/helpers/http.ts +0 -23
  675. package/src/test/helpers/owner.ts +0 -37
  676. package/src/test/integration/events.test.ts +0 -159
  677. package/src/test/integration/retention.test.ts +0 -363
  678. package/src/test/integration/sends.test.ts +0 -233
  679. package/src/test/integration/settings.test.ts +0 -128
  680. package/src/test/integration/setup.test.ts +0 -35
  681. package/src/test/integration/templates.test.ts +0 -272
  682. package/src/test/integration/tenant-export.test.ts +0 -125
  683. package/src/test/unit/attention.test.ts +0 -129
  684. package/src/test/unit/automation.test.ts +0 -130
  685. package/src/test/unit/categories.test.ts +0 -51
  686. package/src/test/unit/engine/conditions.test.ts +0 -35
  687. package/src/test/unit/engine/coverage.test.ts +0 -118
  688. package/src/test/unit/engine/fallback-html.test.ts +0 -86
  689. package/src/test/unit/engine/paths.test.ts +0 -39
  690. package/src/test/unit/engine/resolve.test.ts +0 -176
  691. package/src/test/unit/engine/tokens.test.ts +0 -26
  692. package/src/test/unit/events.test.ts +0 -19
  693. package/src/test/unit/metrics.test.ts +0 -170
  694. package/src/test/unit/notify.test.ts +0 -71
  695. package/src/test/unit/period.test.ts +0 -91
  696. package/src/test/unit/permissions.test.ts +0 -30
  697. package/src/test/unit/provider.test.ts +0 -57
  698. package/src/test/unit/realtime.test.ts +0 -11
  699. package/src/test/unit/render.test.ts +0 -23
  700. package/src/test/unit/retention.test.ts +0 -91
  701. package/src/test/unit/send-batch.test.ts +0 -72
  702. package/src/test/unit/send.test.ts +0 -161
  703. package/src/test/unit/smoke.test.ts +0 -8
  704. package/src/test/unit/status.test.ts +0 -14
  705. package/src/test/unit/templates.test.ts +0 -52
  706. package/src/test/unit/tenant-export.test.ts +0 -88
  707. package/src/test/unit/webhook-secret.test.ts +0 -51
@@ -1,25 +1,161 @@
1
+ import { randomBytes } from "node:crypto";
1
2
  import {
2
3
  Context,
3
4
  Controller,
4
5
  Get,
5
- JSONBody,
6
6
  Post,
7
+ Put,
8
+ RawBody,
7
9
  type RequestContext,
8
10
  } from "@antelopejs/interface-api";
9
- import { assertValidation } from "@antelopejs/interface-api-util";
11
+ import { assert, assertValidation } from "@antelopejs/interface-api-util";
12
+ import { GetApiBaseUrl } from "@antelopejs/interface-dms/api-base-url";
13
+ import { GetClientBaseUrl } from "@antelopejs/interface-dms/client-base-url";
10
14
  import { AuthUserWithPermission } from "@antelopejs/interface-dms/guards";
11
15
  import { getRequestTenantId } from "@antelopejs/interface-dms/request-tenant";
12
16
  import type { User } from "@antelopejs/interface-dms/auth/db";
13
17
  import { TenantScopedModel } from "@antelopejs/interface-dms/tenant-scoped-model";
14
- import { API_BASE_PATH } from "../constants";
15
- import { TemplateModel } from "../db";
18
+ import { z } from "zod";
19
+ import { API_BASE_PATH, ARCHIVED_STATUS, HTTP_BAD_REQUEST } from "../constants";
20
+ import { parseBody } from "../data/body";
21
+ import { SendModel, TemplateModel } from "../db";
16
22
  import { MailingSettingsPage } from "../pages/settings/page";
17
- import { reassignRemovedCategories } from "../services/categories";
18
- import { getSettings, saveSettings } from "../services/settings";
19
- import type { MailingSettingsValues } from "../types";
20
- import { settingsSchema } from "../validation/settings.schema";
23
+ import {
24
+ categoryMoves,
25
+ keptCategories,
26
+ reassignRemovedCategories,
27
+ } from "../services/categories";
28
+ import { readCapabilities } from "../services/provider";
29
+ import {
30
+ clearedToEmpty,
31
+ getSettings,
32
+ type MissingVariablesPolicy,
33
+ mergeSettingsPatch,
34
+ missingVariablesPolicy,
35
+ saveSettings,
36
+ WEBHOOK_SECRET_BYTES,
37
+ } from "../services/settings";
38
+ import {
39
+ readWebhookStatus,
40
+ sendWebhookTest,
41
+ type WebhookTestOutcome,
42
+ } from "../services/webhook-status";
43
+ import type { MailingSettingsValues, WebhookStatus } from "../types";
44
+ import {
45
+ MAX_RETENTION_DAYS,
46
+ MIN_RETENTION_DAYS,
47
+ type SettingsPatch,
48
+ settingsPatchSchema,
49
+ } from "../validation/settings.schema";
50
+ import { retentionLimit } from "../crons/retention";
21
51
 
22
52
  const INVALID_SETTINGS = "$dms_mailing.errors.invalid_settings";
53
+ const CATEGORIES_REASSIGNED = "$dms_mailing.settings.categories.reassigned";
54
+ const SENDER_NAME_IGNORED = "$dms_mailing.settings.sender_name_ignored";
55
+ const WARNING_COLOR = "warning";
56
+ const EVENTS_PATH = `${API_BASE_PATH}/events`;
57
+ const DEFAULT_PROVIDER_SLUG = "provider";
58
+ const TRAILING_SLASHES = /\/+$/;
59
+ const NEXT_RUN_HOUR = 3;
60
+ const NEXT_RUN_MINUTE = 15;
61
+ const RETENTION_DAYS_PARAM = "days";
62
+
63
+ /** A settings field the form shows under another name. */
64
+ const FORM_FIELD_OF: Record<string, string> = {
65
+ blockOnMissingVariables: "missingVariables",
66
+ };
67
+
68
+ const retentionDaysSchema = z.coerce
69
+ .number()
70
+ .int()
71
+ .min(MIN_RETENTION_DAYS)
72
+ .max(MAX_RETENTION_DAYS);
73
+
74
+ /** The settings as the form reads them, with the values derived for display. */
75
+ export interface MailingSettingsResponse extends MailingSettingsValues {
76
+ /** Where the provider posts its events; read-only. */
77
+ webhookUrl: string;
78
+ /** The configured provider, as the read-only provider row shows it. */
79
+ provider: string;
80
+ /** `blockOnMissingVariables` as the two choices the form offers. */
81
+ missingVariables: MissingVariablesPolicy;
82
+ }
83
+
84
+ /** A toast the settings form shows next to its success one. */
85
+ export interface SettingsNotice {
86
+ title: string;
87
+ color?: string;
88
+ params?: Record<string, unknown>;
89
+ }
90
+
91
+ export interface SettingsWriteResponse extends MailingSettingsValues {
92
+ notice?: SettingsNotice;
93
+ }
94
+
95
+ /** What "Send a test event" did, and the webhook status after it. */
96
+ export interface WebhookTestResponse extends WebhookTestOutcome {
97
+ status: WebhookStatus;
98
+ }
99
+
100
+ export interface RetentionPreview {
101
+ days: number;
102
+ /** Sends the next nightly run would delete with this retention. */
103
+ count: number;
104
+ nextRunAt: string;
105
+ }
106
+
107
+ function providerSlug(name: string | undefined): string {
108
+ const slug = (name ?? "").toLowerCase().replace(/[^a-z0-9]+/g, "-");
109
+ return slug || DEFAULT_PROVIDER_SLUG;
110
+ }
111
+
112
+ /** The next 03:15 the retention cron fires at, server time. */
113
+ export function nextRetentionRun(now: Date = new Date()): Date {
114
+ const next = new Date(now);
115
+ next.setHours(NEXT_RUN_HOUR, NEXT_RUN_MINUTE, 0, 0);
116
+ if (next <= now) next.setDate(next.getDate() + 1);
117
+ return next;
118
+ }
119
+
120
+ /**
121
+ * Where a provider posts the events of a tenant: the API origin, the provider
122
+ * and the tenant in the path.
123
+ */
124
+ function webhookUrl(
125
+ origin: string,
126
+ provider: string | undefined,
127
+ tenantId: string,
128
+ ): string {
129
+ return [
130
+ origin.replace(TRAILING_SLASHES, ""),
131
+ EVENTS_PATH,
132
+ `/${providerSlug(provider)}`,
133
+ `/${encodeURIComponent(tenantId)}`,
134
+ ].join("");
135
+ }
136
+
137
+ /** The issue of a refused write, under the form field it belongs to. */
138
+ function formIssue(issue: z.ZodIssue): z.ZodIssue {
139
+ const [field, ...rest] = issue.path;
140
+ const formField = FORM_FIELD_OF[String(field)];
141
+ return formField ? { ...issue, path: [formField, ...rest] } : issue;
142
+ }
143
+
144
+ /** A refused write: the generic message, and each issue for its field. */
145
+ function invalidSettings(error: unknown): unknown {
146
+ if (!(error instanceof z.ZodError)) return INVALID_SETTINGS;
147
+ return { message: INVALID_SETTINGS, issues: error.issues.map(formIssue) };
148
+ }
149
+
150
+ function senderNotice(values: MailingSettingsValues): SettingsNotice[] {
151
+ return values.senderName && !values.senderEmail
152
+ ? [{ title: SENDER_NAME_IGNORED, color: WARNING_COLOR }]
153
+ : [];
154
+ }
155
+
156
+ function reassignedNotice(count: number): SettingsNotice[] {
157
+ return count ? [{ title: CATEGORIES_REASSIGNED, params: { count } }] : [];
158
+ }
23
159
 
24
160
  export class MailingSettingsController extends Controller(
25
161
  `${API_BASE_PATH}/settings`,
@@ -33,34 +169,163 @@ export class MailingSettingsController extends Controller(
33
169
  @TenantScopedModel(TemplateModel)
34
170
  declare templates: TemplateModel;
35
171
 
172
+ @TenantScopedModel(SendModel)
173
+ declare sends: SendModel;
174
+
175
+ private get tenantId(): string {
176
+ return getRequestTenantId(this.ctx);
177
+ }
178
+
179
+ /**
180
+ * The origin a provider reaches the event route on: the dashboard's public
181
+ * URL (`clientBaseUrl`), whose proxy serves the route to anonymous callers
182
+ * as a declared public endpoint; else the API origin (`apiBaseUrl`), else
183
+ * this server's own. Always from the DMS configuration, never from
184
+ * forwarding headers: the webhook test posts to it from the server.
185
+ */
186
+ private async publicOrigin(): Promise<string> {
187
+ return (
188
+ (await GetClientBaseUrl()) ??
189
+ (await GetApiBaseUrl()) ??
190
+ this.ctx.url.origin
191
+ );
192
+ }
193
+
194
+ /** The address the provider posts to, as the form shows it. */
195
+ private async providerWebhookUrl(provider?: string): Promise<string> {
196
+ return webhookUrl(await this.publicOrigin(), provider, this.tenantId);
197
+ }
198
+
36
199
  @Get("")
37
- read(): Promise<MailingSettingsValues> {
38
- return getSettings(getRequestTenantId(this.ctx));
200
+ async read(): Promise<MailingSettingsResponse> {
201
+ const [values, capabilities] = await Promise.all([
202
+ getSettings(this.tenantId),
203
+ readCapabilities(),
204
+ ]);
205
+ return {
206
+ ...values,
207
+ webhookUrl: await this.providerWebhookUrl(capabilities?.name),
208
+ provider: capabilities?.name ?? "",
209
+ missingVariables: missingVariablesPolicy(values),
210
+ };
39
211
  }
40
212
 
41
- @Post("")
42
- async write(@JSONBody() body: unknown): Promise<MailingSettingsValues> {
43
- const values = assertValidation(
44
- body,
45
- (value) => settingsSchema.parse(value),
46
- () => INVALID_SETTINGS,
213
+ /**
214
+ * Applies what the form changed. The DMS form puts only the changed fields,
215
+ * `null` for a cleared one, so the body is a patch over the stored settings:
216
+ * each field sent is checked, the others are left as stored.
217
+ */
218
+ @Put("")
219
+ async update(@RawBody() raw: Buffer): Promise<SettingsWriteResponse> {
220
+ const patch = this.readPatch(raw);
221
+ const current = await getSettings(this.tenantId);
222
+ const merged = mergeSettingsPatch(current, patch);
223
+ const entries = patch.categories ?? current.categories;
224
+ const values = { ...merged, categories: keptCategories(entries) };
225
+ const saved = await saveSettings(this.tenantId, values);
226
+ const reassigned = patch.categories
227
+ ? await this.reassignCategories(values.categories, categoryMoves(entries))
228
+ : 0;
229
+ const [notice] = [...reassignedNotice(reassigned), ...senderNotice(saved)];
230
+ return { ...saved, ...(notice && { notice }) };
231
+ }
232
+
233
+ /** The body as a checked patch; 400 for anything else. */
234
+ private readPatch(raw: Buffer): SettingsPatch {
235
+ const body: unknown = parseBody(raw);
236
+ const isPatch =
237
+ typeof body === "object" && body !== null && !Array.isArray(body);
238
+ assert(isPatch, HTTP_BAD_REQUEST, INVALID_SETTINGS);
239
+ return assertValidation(
240
+ clearedToEmpty(body as Record<string, unknown>),
241
+ (value) => settingsPatchSchema.parse(value),
242
+ invalidSettings,
47
243
  );
48
- const saved = await saveSettings(getRequestTenantId(this.ctx), values);
49
- await this.dropRemovedCategories(values.categories);
50
- return saved;
244
+ }
245
+
246
+ /** Issues a new webhook secret; the old one stops working at once. */
247
+ @Post("/webhook-secret/rotate")
248
+ async rotateSecret(): Promise<{ value: string }> {
249
+ const current = await getSettings(this.tenantId);
250
+ const value = randomBytes(WEBHOOK_SECRET_BYTES).toString("hex");
251
+ await saveSettings(this.tenantId, { ...current, webhookSecret: value });
252
+ return { value };
253
+ }
254
+
255
+ /** The last event the webhook accepted, and the last call it refused. */
256
+ @Get("/webhook-status")
257
+ async webhookStatus(): Promise<WebhookStatus> {
258
+ return readWebhookStatus(this.tenantId);
259
+ }
260
+
261
+ /**
262
+ * Posts a test event to the webhook URL the form shows, with the stored
263
+ * secret, as a provider would. It reaches no send.
264
+ */
265
+ @Post("/webhook-test")
266
+ async webhookTest(): Promise<WebhookTestResponse> {
267
+ const [values, capabilities] = await Promise.all([
268
+ getSettings(this.tenantId),
269
+ readCapabilities(),
270
+ ]);
271
+ const url = await this.providerWebhookUrl(capabilities?.name);
272
+ const outcome = await sendWebhookTest(url, values.webhookSecret);
273
+ return { ...outcome, status: await readWebhookStatus(this.tenantId) };
274
+ }
275
+
276
+ @Get("/retention-preview")
277
+ async retentionPreview(): Promise<RetentionPreview> {
278
+ const days = await this.previewDays();
279
+ const nextRunAt = nextRetentionRun();
280
+ const limit = retentionLimit(days, nextRunAt);
281
+ return {
282
+ days,
283
+ count: await this.sends.countOlderThan(limit),
284
+ nextRunAt: nextRunAt.toISOString(),
285
+ };
286
+ }
287
+
288
+ /** The `days` asked for, within the retention bounds, else the stored. */
289
+ private async previewDays(): Promise<number> {
290
+ const raw = this.ctx.url.searchParams.get(RETENTION_DAYS_PARAM);
291
+ if (raw === null)
292
+ return (await getSettings(this.tenantId)).logRetentionDays;
293
+ return assertValidation(
294
+ raw,
295
+ (value) => retentionDaysSchema.parse(value),
296
+ invalidSettings,
297
+ );
298
+ }
299
+
300
+ /**
301
+ * How many templates sit in each category, for the categories editor. An
302
+ * archived template is left out: it no longer shows in the gallery.
303
+ */
304
+ @Get("/category-usage")
305
+ async categoryUsage(): Promise<{ counts: Record<string, number> }> {
306
+ const templates = await this.templates.getAll();
307
+ const counts: Record<string, number> = {};
308
+ for (const template of templates) {
309
+ if (!template.category || template.status === ARCHIVED_STATUS) continue;
310
+ counts[template.category] = (counts[template.category] ?? 0) + 1;
311
+ }
312
+ return { counts };
51
313
  }
52
314
 
53
315
  /**
54
- * A deleted category must not leave templates pointing at nothing: they fall
55
- * back to no category.
316
+ * A deleted category must not leave templates pointing at nothing: they go
317
+ * to the category the form moved them to, else to none.
318
+ * @returns How many templates changed category
56
319
  */
57
- private async dropRemovedCategories(
320
+ private async reassignCategories(
58
321
  categories: MailingSettingsValues["categories"],
59
- ): Promise<void> {
322
+ moves: ReadonlyMap<string, string>,
323
+ ): Promise<number> {
60
324
  const templates = await this.templates.getAll();
61
- const orphaned = reassignRemovedCategories(templates, categories);
62
- for (const id of orphaned) {
63
- await this.templates.update(id, { category: "" });
325
+ const changes = reassignRemovedCategories(templates, categories, moves);
326
+ for (const { templateId, category } of changes) {
327
+ await this.templates.update(templateId, { category });
64
328
  }
329
+ return changes.length;
65
330
  }
66
331
  }
@@ -0,0 +1,87 @@
1
+ import {
2
+ Context,
3
+ Controller,
4
+ Delete,
5
+ HTTPResult,
6
+ JSONBody,
7
+ Parameter,
8
+ Post,
9
+ type RequestContext,
10
+ } from "@antelopejs/interface-api";
11
+ import { assert, assertValidation } from "@antelopejs/interface-api-util";
12
+ import type { User } from "@antelopejs/interface-dms/auth/db";
13
+ import { AuthUserWithPermission } from "@antelopejs/interface-dms/guards";
14
+ import { getRequestTenantId } from "@antelopejs/interface-dms/request-tenant";
15
+ import { TenantScopedModel } from "@antelopejs/interface-dms/tenant-scoped-model";
16
+ import { z } from "zod";
17
+ import { API_BASE_PATH, HTTP_CONFLICT, HTTP_NOT_FOUND } from "../constants";
18
+ import { SuppressionModel } from "../db";
19
+ import { SuppressionsPageController } from "../pages/suppressions";
20
+ import { suppressAddress } from "../services/suppressions";
21
+
22
+ const INVALID_SUPPRESSION = "$dms_mailing.suppressions.errors.invalid";
23
+ const ALREADY_SUPPRESSED = "$dms_mailing.suppressions.errors.already";
24
+ const NOT_SUPPRESSED = "$dms_mailing.suppressions.errors.not_found";
25
+ const EMAIL_FIELD = "email";
26
+ const MAX_NOTE_LENGTH = 500;
27
+ /** A manual entry comes from no send. */
28
+ const NO_SEND = "";
29
+
30
+ const manualSuppressionSchema = z.object({
31
+ email: z.string().trim().email(),
32
+ note: z.string().trim().max(MAX_NOTE_LENGTH).optional(),
33
+ });
34
+
35
+ /** A refused entry: the generic message, and each issue under its field. */
36
+ function invalidSuppression(error: unknown): unknown {
37
+ if (!(error instanceof z.ZodError)) return INVALID_SUPPRESSION;
38
+ return { message: INVALID_SUPPRESSION, issues: error.issues };
39
+ }
40
+
41
+ /** An address already on the list, told under the address field. */
42
+ const alreadySuppressed = () => ({
43
+ message: ALREADY_SUPPRESSED,
44
+ issues: [{ path: [EMAIL_FIELD], message: ALREADY_SUPPRESSED }],
45
+ });
46
+
47
+ export class SuppressionsController extends Controller(
48
+ `${API_BASE_PATH}/suppressions`,
49
+ ) {
50
+ @Context()
51
+ declare ctx: RequestContext;
52
+
53
+ @AuthUserWithPermission(SuppressionsPageController)
54
+ declare user: User;
55
+
56
+ @TenantScopedModel(SuppressionModel)
57
+ declare suppressions: SuppressionModel;
58
+
59
+ /** Puts an address on the list by hand: nothing is sent to it any more. */
60
+ @Post("")
61
+ async add(@JSONBody() body: unknown): Promise<{ added: true }> {
62
+ const input = assertValidation(
63
+ body ?? {},
64
+ (value) => manualSuppressionSchema.parse(value),
65
+ invalidSuppression,
66
+ );
67
+ const isAdded = await suppressAddress(getRequestTenantId(this.ctx), {
68
+ email: input.email,
69
+ reason: "manual",
70
+ sendId: NO_SEND,
71
+ detail: input.note || undefined,
72
+ createdBy: this.user._id,
73
+ });
74
+ if (!isAdded) throw new HTTPResult(HTTP_CONFLICT, alreadySuppressed());
75
+ return { added: true };
76
+ }
77
+
78
+ /** Lifts an address off the list: the next send reaches it again. */
79
+ @Delete("/:id")
80
+ async remove(
81
+ @Parameter("id", "param") id: string,
82
+ ): Promise<{ removed: true }> {
83
+ const removed = await this.suppressions.delete(id);
84
+ assert(removed > 0, HTTP_NOT_FOUND, NOT_SUPPRESSED);
85
+ return { removed: true };
86
+ }
87
+ }
@@ -0,0 +1,116 @@
1
+ import {
2
+ Context,
3
+ Controller,
4
+ Get,
5
+ JSONBody,
6
+ Parameter,
7
+ Post,
8
+ type RequestContext,
9
+ } from "@antelopejs/interface-api";
10
+ import { assert, assertValidation } from "@antelopejs/interface-api-util";
11
+ import { AuthUserWithPermission } from "@antelopejs/interface-dms/guards";
12
+ import { getRequestTenantId } from "@antelopejs/interface-dms/request-tenant";
13
+ import { TenantScopedModel } from "@antelopejs/interface-dms/tenant-scoped-model";
14
+ import type { User } from "@antelopejs/interface-dms/auth/db";
15
+ import { API_BASE_PATH, HTTP_NOT_FOUND } from "../constants";
16
+ import type { TemplateChange } from "../engine";
17
+ import { TemplateModel, TemplateVersionModel } from "../db";
18
+ import { TemplatesPageController } from "../pages/templates";
19
+ import {
20
+ displayName,
21
+ parseVersionChanges,
22
+ parseVersionVariables,
23
+ restoreVersionAsDraft,
24
+ requireTemplate,
25
+ } from "../services/templates";
26
+ import type { VariableDefinition } from "../types";
27
+ import {
28
+ restoreVersionSchema,
29
+ versionNumberSchema,
30
+ } from "../validation/templates.schema";
31
+ import { assertCurrentDraft } from "./templates";
32
+
33
+ const VERSION_NOT_FOUND = "$dms_mailing.errors.version_not_found";
34
+
35
+ export interface TemplateVersionSummary {
36
+ version: number;
37
+ publishedAt: Date;
38
+ publishedBy: string;
39
+ locales: string;
40
+ changes: TemplateChange[];
41
+ /** The declarations the version shipped with. */
42
+ variables: VariableDefinition[];
43
+ }
44
+
45
+ export interface RestoredVersion {
46
+ /** The draft revision the editor carries on with. */
47
+ revision: number;
48
+ }
49
+
50
+ /** A template's published versions: its history, and restoring one. */
51
+ export class TemplateVersionsController extends Controller(
52
+ `${API_BASE_PATH}/templates`,
53
+ ) {
54
+ @Context()
55
+ declare ctx: RequestContext;
56
+
57
+ @AuthUserWithPermission(TemplatesPageController)
58
+ declare user: User;
59
+
60
+ @TenantScopedModel(TemplateModel)
61
+ declare templates: TemplateModel;
62
+
63
+ @TenantScopedModel(TemplateVersionModel)
64
+ declare versions: TemplateVersionModel;
65
+
66
+ @Get("/:id/versions")
67
+ async history(
68
+ @Parameter("id", "param") id: string,
69
+ ): Promise<{ versions: TemplateVersionSummary[] }> {
70
+ await requireTemplate(this.templates, id);
71
+ const rows = await this.versions.listForTemplate(id);
72
+ return {
73
+ versions: rows.map((row) => ({
74
+ version: row.version,
75
+ publishedAt: row.publishedAt,
76
+ publishedBy: row.publishedBy,
77
+ locales: row.locales,
78
+ changes: parseVersionChanges(row),
79
+ variables: parseVersionVariables(row),
80
+ })),
81
+ };
82
+ }
83
+
84
+ /**
85
+ * Copies a published version, content and declarations, into the draft:
86
+ * customers keep the live version until the normal review publishes it.
87
+ * Like a save, it is refused behind a draft saved elsewhere.
88
+ */
89
+ @Post("/:id/versions/:version/restore")
90
+ async restore(
91
+ @Parameter("id", "param") id: string,
92
+ @Parameter("version", "param") version: string,
93
+ @JSONBody() body: unknown,
94
+ ): Promise<RestoredVersion> {
95
+ const { revision } = assertValidation(body ?? {}, (value) =>
96
+ restoreVersionSchema.parse(value),
97
+ );
98
+ const number = assertValidation(
99
+ version,
100
+ (value) => versionNumberSchema.parse(value),
101
+ () => VERSION_NOT_FOUND,
102
+ HTTP_NOT_FOUND,
103
+ );
104
+ const template = await requireTemplate(this.templates, id);
105
+ assertCurrentDraft(template, revision);
106
+ const row = await this.versions.getVersion(id, number);
107
+ assert(row, HTTP_NOT_FOUND, VERSION_NOT_FOUND);
108
+ const next = await restoreVersionAsDraft(
109
+ getRequestTenantId(this.ctx),
110
+ template,
111
+ row,
112
+ displayName(this.user),
113
+ );
114
+ return { revision: next };
115
+ }
116
+ }