@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
@@ -0,0 +1,53 @@
1
+ import { createHash } from "node:crypto";
2
+ import { BasicDataModel } from "@antelopejs/interface-database-decorators";
3
+ import { MAILING_SUPPRESSIONS_TABLE_NAME, MailingSuppression } from "../tables";
4
+
5
+ type NewSuppression = Omit<MailingSuppression, "_id" | "createdAt">;
6
+
7
+ const ID_LENGTH = 32;
8
+
9
+ /** The address as the list keeps it: trimmed and lower-cased. */
10
+ export function normalizeAddress(email: string): string {
11
+ return email.trim().toLowerCase();
12
+ }
13
+
14
+ /** The id of an address's entry: the same whoever adds it. */
15
+ export function suppressionIdOf(email: string): string {
16
+ return createHash("sha256")
17
+ .update(normalizeAddress(email))
18
+ .digest("hex")
19
+ .slice(0, ID_LENGTH);
20
+ }
21
+
22
+ export class SuppressionModel extends BasicDataModel(
23
+ MailingSuppression,
24
+ MAILING_SUPPRESSIONS_TABLE_NAME,
25
+ ) {
26
+ async findByEmail(email: string): Promise<MailingSuppression | undefined> {
27
+ return this.get(suppressionIdOf(email));
28
+ }
29
+
30
+ /** The entries of the addresses given, whatever their case. */
31
+ async listByEmails(emails: string[]): Promise<MailingSuppression[]> {
32
+ const unique = [...new Set(emails.map(normalizeAddress))];
33
+ if (!unique.length) return [];
34
+ return this.getBy("email", ...unique);
35
+ }
36
+
37
+ /**
38
+ * Puts an address on the list unless it already is.
39
+ * @returns Whether this call added it
40
+ */
41
+ async add(entry: NewSuppression): Promise<boolean> {
42
+ const email = normalizeAddress(entry.email);
43
+ const id = suppressionIdOf(email);
44
+ if (await this.get(id)) return false;
45
+ try {
46
+ await this.insert({ ...entry, email, _id: id });
47
+ return true;
48
+ } catch (error) {
49
+ if (await this.get(id)) return false;
50
+ throw error;
51
+ }
52
+ }
53
+ }
@@ -0,0 +1,23 @@
1
+ import { BasicDataModel } from "@antelopejs/interface-database-decorators";
2
+ import {
3
+ MAILING_TEMPLATE_VERSIONS_TABLE_NAME,
4
+ MailingTemplateVersion,
5
+ } from "../tables";
6
+
7
+ export class TemplateVersionModel extends BasicDataModel(
8
+ MailingTemplateVersion,
9
+ MAILING_TEMPLATE_VERSIONS_TABLE_NAME,
10
+ ) {
11
+ async listForTemplate(templateId: string): Promise<MailingTemplateVersion[]> {
12
+ const rows = await this.getBy("templateId", templateId);
13
+ return rows.sort((left, right) => right.version - left.version);
14
+ }
15
+
16
+ async getVersion(
17
+ templateId: string,
18
+ version: number,
19
+ ): Promise<MailingTemplateVersion | undefined> {
20
+ const rows = await this.getBy("templateId", templateId);
21
+ return rows.find((row) => row.version === version);
22
+ }
23
+ }
@@ -1,6 +1,12 @@
1
1
  import { BasicDataModel } from "@antelopejs/interface-database-decorators";
2
2
  import { MAILING_TEMPLATES_TABLE_NAME, MailingTemplate } from "../tables";
3
3
 
4
+ /** The attention fields of a template, as the refresh writes them. */
5
+ export type TemplateAttentionFields = Pick<
6
+ MailingTemplate,
7
+ "attention" | "needsAttention"
8
+ >;
9
+
4
10
  export class TemplateModel extends BasicDataModel(
5
11
  MailingTemplate,
6
12
  MAILING_TEMPLATES_TABLE_NAME,
@@ -9,4 +15,15 @@ export class TemplateModel extends BasicDataModel(
9
15
  const rows = await this.getBy("slug", slug);
10
16
  return rows[0];
11
17
  }
18
+
19
+ /**
20
+ * Writes the attention fields straight to the table: they describe the
21
+ * template, they are no edit of it, so `updatedAt` stays where it was.
22
+ */
23
+ async writeAttention(
24
+ id: string,
25
+ fields: TemplateAttentionFields,
26
+ ): Promise<void> {
27
+ await this.table.get(id).update(fields).run();
28
+ }
12
29
  }
@@ -0,0 +1,25 @@
1
+ import { BasicDataModel } from "@antelopejs/interface-database-decorators";
2
+ import {
3
+ MAILING_WEBHOOK_STATUS_TABLE_NAME,
4
+ MailingWebhookStatus,
5
+ } from "../tables";
6
+
7
+ type WebhookStatusPatch = Partial<Omit<MailingWebhookStatus, "_id">>;
8
+
9
+ export class WebhookStatusModel extends BasicDataModel(
10
+ MailingWebhookStatus,
11
+ MAILING_WEBHOOK_STATUS_TABLE_NAME,
12
+ ) {
13
+ /** Writes the tenant's row, creating it on the first call. */
14
+ async record(tenantId: string, patch: WebhookStatusPatch): Promise<void> {
15
+ if (await this.get(tenantId)) {
16
+ await this.update(tenantId, patch);
17
+ return;
18
+ }
19
+ try {
20
+ await this.insert({ ...patch, _id: tenantId });
21
+ } catch {
22
+ await this.update(tenantId, patch);
23
+ }
24
+ }
25
+ }
@@ -1,4 +1,7 @@
1
1
  export * from "./send-events.table";
2
2
  export * from "./sends.table";
3
3
  export * from "./settings.table";
4
+ export * from "./suppressions.table";
5
+ export * from "./template-versions.table";
4
6
  export * from "./templates.table";
7
+ export * from "./webhook-status.table";
@@ -5,7 +5,7 @@ import {
5
5
  Table,
6
6
  } from "@antelopejs/interface-database-decorators";
7
7
  import { TENANT_SCHEMA_NAME } from "@antelopejs/interface-dms/constants";
8
- import type { SendEventType } from "../../types";
8
+ import type { SendStatus } from "../../types";
9
9
 
10
10
  export const MAILING_SEND_EVENTS_TABLE_NAME = "mailing_send_events";
11
11
 
@@ -18,8 +18,12 @@ export class MailingSendEvent extends Table {
18
18
  @Field("string")
19
19
  declare sendId: string;
20
20
 
21
+ /**
22
+ * A provider event, or the status the send started with (`suppressed`
23
+ * when nothing was sent).
24
+ */
21
25
  @Field("string")
22
- declare type: SendEventType;
26
+ declare type: SendStatus;
23
27
 
24
28
  @Field("date")
25
29
  declare at: Date;
@@ -6,7 +6,7 @@ import {
6
6
  Table,
7
7
  } from "@antelopejs/interface-database-decorators";
8
8
  import { TENANT_SCHEMA_NAME } from "@antelopejs/interface-dms/constants";
9
- import type { SendStatus } from "../../types";
9
+ import type { SendStage, SendStatus } from "../../types";
10
10
 
11
11
  export const MAILING_SENDS_TABLE_NAME = "mailing_sends";
12
12
 
@@ -27,9 +27,18 @@ export class MailingSend extends Table {
27
27
  @Field("string")
28
28
  declare templateSlug: string;
29
29
 
30
+ /** Locale the e-mail was rendered in. */
30
31
  @Field("string")
31
32
  declare locale: string;
32
33
 
34
+ /** Locale the caller asked for; differs from `locale` on a fallback. */
35
+ @Field("string")
36
+ declare requestedLocale: string;
37
+
38
+ /** Template version the send used; 0 for a test send of a draft. */
39
+ @Field("number")
40
+ declare templateVersion: number;
41
+
33
42
  @Index()
34
43
  @Field("string")
35
44
  declare recipientEmail: string;
@@ -41,6 +50,11 @@ export class MailingSend extends Table {
41
50
  @Field("string")
42
51
  declare status: SendStatus;
43
52
 
53
+ /** The send log tab a status belongs to, derived from `status`. */
54
+ @Index()
55
+ @Field("string")
56
+ declare stage: SendStage;
57
+
44
58
  @Field("string")
45
59
  declare provider?: string;
46
60
 
@@ -48,8 +62,9 @@ export class MailingSend extends Table {
48
62
  @Field("string")
49
63
  declare providerMessageId?: string;
50
64
 
65
+ /** `null` for a send that never reached the provider (suppressed). */
51
66
  @Field("number")
52
- declare latencyMs: number;
67
+ declare latencyMs: number | null;
53
68
 
54
69
  @Field("string")
55
70
  declare error?: string;
@@ -81,4 +96,24 @@ export class MailingSend extends Table {
81
96
 
82
97
  @Field("boolean")
83
98
  declare isTest: boolean;
99
+
100
+ /** A bounce the receiving server called temporary (a full mailbox, 4.x.x). */
101
+ @Field("boolean")
102
+ declare isSoftBounce?: boolean;
103
+
104
+ /** The send this one repeats, sent again or to another address. */
105
+ @Field("string")
106
+ declare replayOf?: string;
107
+
108
+ /** The send that repeated this problem, once someone sent it again. */
109
+ @Field("string")
110
+ declare replayedBy?: string;
111
+
112
+ /** When someone dealt with this problem by sending it again. */
113
+ @Field("date")
114
+ declare resolvedAt?: Date;
115
+
116
+ /** Who sent it again: a user id. */
117
+ @Field("string")
118
+ declare resolvedBy?: string;
84
119
  }
@@ -0,0 +1,46 @@
1
+ import {
2
+ CreationTime,
3
+ Field,
4
+ Index,
5
+ RegisterTable,
6
+ Table,
7
+ } from "@antelopejs/interface-database-decorators";
8
+ import { TENANT_SCHEMA_NAME } from "@antelopejs/interface-dms/constants";
9
+ import type { SuppressionReason } from "../../types";
10
+
11
+ export const MAILING_SUPPRESSIONS_TABLE_NAME = "mailing_suppressions";
12
+
13
+ /**
14
+ * An address nothing is sent to any more. Its id derives from the address, so
15
+ * an address is on the list once whoever adds it first.
16
+ */
17
+ @RegisterTable(MAILING_SUPPRESSIONS_TABLE_NAME, TENANT_SCHEMA_NAME)
18
+ export class MailingSuppression extends Table {
19
+ @Field("string")
20
+ declare _id: string;
21
+
22
+ /** Trimmed and lower-cased. */
23
+ @Index()
24
+ @Field("string")
25
+ declare email: string;
26
+
27
+ @Field("string")
28
+ declare reason: SuppressionReason;
29
+
30
+ /** The send whose event put the address on the list. */
31
+ @Field("string")
32
+ declare sendId?: string;
33
+
34
+ /** The provider's words (`550 5.1.1 …`) or the note of a manual entry. */
35
+ @Field("string")
36
+ declare detail?: string;
37
+
38
+ /** The user who added it by hand. */
39
+ @Field("string")
40
+ declare createdBy?: string;
41
+
42
+ @Index()
43
+ @CreationTime()
44
+ @Field("date")
45
+ declare createdAt: Date;
46
+ }
@@ -0,0 +1,44 @@
1
+ import {
2
+ Field,
3
+ Index,
4
+ RegisterTable,
5
+ Table,
6
+ } from "@antelopejs/interface-database-decorators";
7
+ import { TENANT_SCHEMA_NAME } from "@antelopejs/interface-dms/constants";
8
+
9
+ export const MAILING_TEMPLATE_VERSIONS_TABLE_NAME = "mailing_template_versions";
10
+
11
+ /** One published version of a template, written once and never updated. */
12
+ @RegisterTable(MAILING_TEMPLATE_VERSIONS_TABLE_NAME, TENANT_SCHEMA_NAME)
13
+ export class MailingTemplateVersion extends Table {
14
+ @Field("string")
15
+ declare _id: string;
16
+
17
+ @Index()
18
+ @Field("string")
19
+ declare templateId: string;
20
+
21
+ @Field("number")
22
+ declare version: number;
23
+
24
+ /** JSON-serialized TemplateContent of this version. */
25
+ @Field("string")
26
+ declare json_content: string;
27
+
28
+ /** JSON-serialized VariableDefinition[] declared when it was published. */
29
+ @Field("string")
30
+ declare json_variables: string;
31
+
32
+ @Field("string")
33
+ declare locales: string;
34
+
35
+ @Field("date")
36
+ declare publishedAt: Date;
37
+
38
+ @Field("string")
39
+ declare publishedBy: string;
40
+
41
+ /** JSON-serialized TemplateChange[] this version brought. */
42
+ @Field("string")
43
+ declare json_changes: string;
44
+ }
@@ -40,23 +40,68 @@ export class MailingTemplate extends Table {
40
40
  @Field("string")
41
41
  declare status: TemplateStatus;
42
42
 
43
- /** JSON-serialized TemplateContent, replaced in place on every save. */
43
+ /**
44
+ * JSON-serialized TemplateContent customers receive: the last published
45
+ * version. Only a publish writes it.
46
+ */
44
47
  @Field("string")
45
48
  declare json_content: string;
46
49
 
47
- /** JSON-serialized VariableDefinition[] declared for this template. */
50
+ /**
51
+ * JSON-serialized TemplateContent the editor saves while drafting. Blank when
52
+ * nothing is waiting to be published.
53
+ */
54
+ @Field("string")
55
+ declare json_draft?: string;
56
+
57
+ /** Whether `json_draft` holds changes customers do not receive yet. */
58
+ @Field("boolean")
59
+ declare isDraftPending?: boolean;
60
+
61
+ @Field("date")
62
+ declare draftUpdatedAt?: Date;
63
+
64
+ @Field("string")
65
+ declare draftUpdatedBy?: string;
66
+
67
+ /**
68
+ * Moves on with every write of `json_draft`; a save or a publish carrying
69
+ * an older one is refused. 0 for a new template.
70
+ */
71
+ @Field("number")
72
+ declare draftRevision: number;
73
+
74
+ /** Version customers receive; 0 until a first publish. */
75
+ @Field("number")
76
+ declare publishedVersion: number;
77
+
78
+ @Field("string")
79
+ declare publishedBy?: string;
80
+
81
+ /**
82
+ * JSON-serialized VariableDefinition[] of the contract callers send
83
+ * against: the published declarations (the working ones until a first
84
+ * publish). Only a publish changes them on a published template.
85
+ */
48
86
  @Field("string")
49
87
  declare json_variables: string;
50
88
 
89
+ /**
90
+ * JSON-serialized VariableDefinition[] the draft declares, published with
91
+ * its content. Blank when the draft declares what `json_variables` does.
92
+ */
93
+ @Field("string")
94
+ declare json_variables_draft?: string;
95
+
51
96
  /** JSON-serialized data set used by the preview and the test send. */
52
97
  @Field("string")
53
98
  declare json_test_data: string;
54
99
 
55
100
  /**
56
- * Locale codes present in the current version's content, comma-separated.
101
+ * Locale codes present in the content customers receive (the draft's until
102
+ * a first publish), comma-separated.
57
103
  * Denormalised so a template row can show its coverage without loading the
58
- * version. Blank on rows written before this field existed: unknown, not
59
- * "none".
104
+ * version.
60
105
  */
61
106
  @Field("string")
62
107
  declare locales: string;
@@ -66,4 +111,21 @@ export class MailingTemplate extends Table {
66
111
 
67
112
  @Field("date")
68
113
  declare publishedAt?: Date;
114
+
115
+ /**
116
+ * Why the template needs a look (`stale_draft`, `never_sent`,
117
+ * `missing_locale`), comma-separated; blank when nothing does. Kept by the
118
+ * attention refresh, never by an edit.
119
+ */
120
+ @Field("string")
121
+ declare attention: string;
122
+
123
+ /** Whether `attention` names a reason: what the templates tab filters on. */
124
+ @Index()
125
+ @Field("boolean")
126
+ declare needsAttention: boolean;
127
+
128
+ /** When the template last left `live` by an unpublish. */
129
+ @Field("date")
130
+ declare unpublishedAt?: Date;
69
131
  }
@@ -0,0 +1,44 @@
1
+ import {
2
+ Field,
3
+ RegisterTable,
4
+ Table,
5
+ } from "@antelopejs/interface-database-decorators";
6
+ import { TENANT_SCHEMA_NAME } from "@antelopejs/interface-dms/constants";
7
+ import type { WebhookRefusal } from "../../types";
8
+
9
+ export const MAILING_WEBHOOK_STATUS_TABLE_NAME = "mailing_webhook_status";
10
+
11
+ /**
12
+ * The last request the tenant's webhook accepted and the last it refused, one
13
+ * row keyed by the tenant id. Kept apart from the settings so a webhook call
14
+ * never changes their revision.
15
+ */
16
+ @RegisterTable(MAILING_WEBHOOK_STATUS_TABLE_NAME, TENANT_SCHEMA_NAME)
17
+ export class MailingWebhookStatus extends Table {
18
+ @Field("string")
19
+ declare _id: string;
20
+
21
+ @Field("date")
22
+ declare receivedAt?: Date;
23
+
24
+ @Field("string")
25
+ declare receivedProvider?: string;
26
+
27
+ @Field("string")
28
+ declare receivedType?: string;
29
+
30
+ @Field("boolean")
31
+ declare isReceivedMatched?: boolean;
32
+
33
+ @Field("boolean")
34
+ declare isReceivedTest?: boolean;
35
+
36
+ @Field("date")
37
+ declare refusedAt?: Date;
38
+
39
+ @Field("string")
40
+ declare refusedProvider?: string;
41
+
42
+ @Field("string")
43
+ declare refusedReason?: WebhookRefusal;
44
+ }
@@ -0,0 +1,198 @@
1
+ import type {
2
+ Block,
3
+ BlockType,
4
+ LocaleContent,
5
+ TemplateContent,
6
+ VariableDefinition,
7
+ } from "../types";
8
+ import { walkBlocks } from "./walk";
9
+
10
+ export type TemplateChangeKind =
11
+ | "locale_added"
12
+ | "locale_removed"
13
+ | "subject"
14
+ | "preheader"
15
+ | "block_added"
16
+ | "block_removed"
17
+ | "block_changed"
18
+ | "blocks_reordered"
19
+ | "variable_added"
20
+ | "variable_removed"
21
+ | "variable_changed";
22
+
23
+ /**
24
+ * One difference between the published template and the draft: per locale
25
+ * for the content, per path for the variable declarations (the contract
26
+ * callers send against), which belong to no locale.
27
+ */
28
+ export interface TemplateChange {
29
+ /** Blank for a change of the variable declarations. */
30
+ locale: string;
31
+ kind: TemplateChangeKind;
32
+ blockId?: string;
33
+ blockType?: BlockType;
34
+ /** The declared path a variable change is about. */
35
+ variable?: string;
36
+ }
37
+
38
+ /** The locale a change of the variable declarations carries: none. */
39
+ export const CONTRACT_LOCALE = "";
40
+
41
+ interface BlockSnapshot {
42
+ type: BlockType;
43
+ signature: string;
44
+ }
45
+
46
+ const NESTED_KEYS = new Set(["children", "elseChildren"]);
47
+
48
+ /**
49
+ * A value with its object keys sorted at every depth, so two blocks equal but
50
+ * for the order their fields were written in read the same.
51
+ */
52
+ function canonical(value: unknown): unknown {
53
+ if (Array.isArray(value)) return value.map(canonical);
54
+ if (!value || typeof value !== "object") return value;
55
+ return Object.fromEntries(
56
+ Object.keys(value)
57
+ .sort()
58
+ .map((key) => [key, canonical((value as Record<string, unknown>)[key])]),
59
+ );
60
+ }
61
+
62
+ function ownFields(block: Block): string {
63
+ const entries = Object.entries(block).filter(
64
+ ([key]) => !NESTED_KEYS.has(key),
65
+ );
66
+ return JSON.stringify(canonical(Object.fromEntries(entries)));
67
+ }
68
+
69
+ function snapshotBlocks(blocks: Block[]): Map<string, BlockSnapshot> {
70
+ const snapshots = new Map<string, BlockSnapshot>();
71
+ walkBlocks(blocks, (block) => {
72
+ snapshots.set(block.id, { type: block.type, signature: ownFields(block) });
73
+ });
74
+ return snapshots;
75
+ }
76
+
77
+ function orderOf(blocks: Block[]): string[] {
78
+ const ids: string[] = [];
79
+ walkBlocks(blocks, (block) => ids.push(block.id));
80
+ return ids;
81
+ }
82
+
83
+ function blockChanges(
84
+ locale: string,
85
+ before: Map<string, BlockSnapshot>,
86
+ after: Map<string, BlockSnapshot>,
87
+ ): TemplateChange[] {
88
+ const removed = [...before]
89
+ .filter(([id]) => !after.has(id))
90
+ .map(([id, snapshot]) =>
91
+ blockChange(locale, "block_removed", id, snapshot),
92
+ );
93
+ const touched = [...after].flatMap(([id, snapshot]) => {
94
+ const previous = before.get(id);
95
+ if (!previous) return [blockChange(locale, "block_added", id, snapshot)];
96
+ if (previous.signature === snapshot.signature) return [];
97
+ return [blockChange(locale, "block_changed", id, snapshot)];
98
+ });
99
+ return [...touched, ...removed];
100
+ }
101
+
102
+ function blockChange(
103
+ locale: string,
104
+ kind: TemplateChangeKind,
105
+ blockId: string,
106
+ snapshot: BlockSnapshot,
107
+ ): TemplateChange {
108
+ return { locale, kind, blockId, blockType: snapshot.type };
109
+ }
110
+
111
+ function isReordered(before: LocaleContent, after: LocaleContent): boolean {
112
+ const kept = new Set(orderOf(after.blocks));
113
+ const previous = orderOf(before.blocks).filter((id) => kept.has(id));
114
+ const surviving = new Set(previous);
115
+ const next = orderOf(after.blocks).filter((id) => surviving.has(id));
116
+ return previous.join() !== next.join();
117
+ }
118
+
119
+ function localeChanges(
120
+ locale: string,
121
+ before: LocaleContent,
122
+ after: LocaleContent,
123
+ ): TemplateChange[] {
124
+ const changes: TemplateChange[] = [];
125
+ if (before.subject !== after.subject)
126
+ changes.push({ locale, kind: "subject" });
127
+ if (before.preheader !== after.preheader)
128
+ changes.push({ locale, kind: "preheader" });
129
+ changes.push(
130
+ ...blockChanges(
131
+ locale,
132
+ snapshotBlocks(before.blocks),
133
+ snapshotBlocks(after.blocks),
134
+ ),
135
+ );
136
+ if (isReordered(before, after))
137
+ changes.push({ locale, kind: "blocks_reordered" });
138
+ return changes;
139
+ }
140
+
141
+ /**
142
+ * What publishing `draft` would change for customers who receive `published`,
143
+ * locale by locale. An empty list means the draft carries nothing new.
144
+ */
145
+ export function diffContents(
146
+ published: TemplateContent,
147
+ draft: TemplateContent,
148
+ ): TemplateChange[] {
149
+ const locales = new Set([
150
+ ...Object.keys(published.locales),
151
+ ...Object.keys(draft.locales),
152
+ ]);
153
+ return [...locales].sort().flatMap((locale) => {
154
+ const before = published.locales[locale];
155
+ const after = draft.locales[locale];
156
+ if (!before) return [{ locale, kind: "locale_added" as const }];
157
+ if (!after) return [{ locale, kind: "locale_removed" as const }];
158
+ return localeChanges(locale, before, after);
159
+ });
160
+ }
161
+
162
+ function declarationsByPath(
163
+ variables: VariableDefinition[],
164
+ ): Map<string, string> {
165
+ return new Map(
166
+ variables.map((variable) => [
167
+ variable.path,
168
+ JSON.stringify(canonical(variable)),
169
+ ]),
170
+ );
171
+ }
172
+
173
+ function variableChange(
174
+ kind: TemplateChangeKind,
175
+ variable: string,
176
+ ): TemplateChange {
177
+ return { locale: CONTRACT_LOCALE, kind, variable };
178
+ }
179
+
180
+ /**
181
+ * What publishing the draft's variable declarations would change in the
182
+ * contract, path by path. Their order is not part of it.
183
+ */
184
+ export function diffVariables(
185
+ published: VariableDefinition[],
186
+ draft: VariableDefinition[],
187
+ ): TemplateChange[] {
188
+ const before = declarationsByPath(published);
189
+ const after = declarationsByPath(draft);
190
+ const paths = [...new Set([...before.keys(), ...after.keys()])].sort();
191
+ return paths.flatMap((path) => {
192
+ const previous = before.get(path);
193
+ const next = after.get(path);
194
+ if (previous === undefined) return [variableChange("variable_added", path)];
195
+ if (next === undefined) return [variableChange("variable_removed", path)];
196
+ return previous === next ? [] : [variableChange("variable_changed", path)];
197
+ });
198
+ }
@@ -14,17 +14,11 @@ const EVALUATORS: Record<ConditionOperator, Evaluator> = {
14
14
  lt: (actual, expected) => asNumber(actual) < asNumber(expected),
15
15
  };
16
16
 
17
- /**
18
- * An unknown operator hides the block rather than throwing. Every entry point
19
- * validates the operator against a `z.enum`, so this only guards content stored
20
- * before an operator existed: a missing block is inspectable in the preview,
21
- * a TypeError mid-send is a mail nobody receives.
22
- */
17
+ /** Whether `data` passes the condition a block is shown under. */
23
18
  export function evaluateCondition(
24
19
  condition: Condition,
25
20
  data: Record<string, unknown>,
26
21
  ): boolean {
27
22
  const evaluate = EVALUATORS[condition.operator];
28
- if (!evaluate) return false;
29
23
  return evaluate(getPath(data, condition.path), condition.value);
30
24
  }