@antelopejs/dms-mailing 0.7.1 → 0.9.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 (255) hide show
  1. package/README.md +46 -31
  2. package/dist/automation/email-event-trigger.d.ts +2 -2
  3. package/dist/automation/send-template-action.d.ts +11 -3
  4. package/dist/automation/send-template-action.js +1 -1
  5. package/dist/automation/send-template-action.js.map +1 -1
  6. package/dist/crons/jobs.d.ts +1 -1
  7. package/dist/data/body.d.ts +2 -1
  8. package/dist/data/body.js +4 -2
  9. package/dist/data/body.js.map +1 -1
  10. package/dist/data/read-only-routes.d.ts +4 -4
  11. package/dist/data/sends-table.d.ts +4 -4
  12. package/dist/data/suppressions-table.d.ts +4 -4
  13. package/dist/data/templates-table.d.ts +35 -12
  14. package/dist/data/templates-table.js +30 -24
  15. package/dist/data/templates-table.js.map +1 -1
  16. package/dist/data-types/categories-type.d.ts +1 -2
  17. package/dist/data-types/categories-type.js +3 -5
  18. package/dist/data-types/categories-type.js.map +1 -1
  19. package/dist/data-types/category-type.d.ts +1 -2
  20. package/dist/data-types/category-type.js +3 -5
  21. package/dist/data-types/category-type.js.map +1 -1
  22. package/dist/db/models/send-events.model.d.ts +7 -5
  23. package/dist/db/models/sends.model.d.ts +7 -5
  24. package/dist/db/models/sends.model.js.map +1 -1
  25. package/dist/db/models/settings.model.d.ts +7 -5
  26. package/dist/db/models/suppressions.model.d.ts +7 -5
  27. package/dist/db/models/template-versions.model.d.ts +7 -5
  28. package/dist/db/models/template-versions.model.js +4 -3
  29. package/dist/db/models/template-versions.model.js.map +1 -1
  30. package/dist/db/models/templates.model.d.ts +7 -5
  31. package/dist/db/models/templates.model.js +2 -1
  32. package/dist/db/models/templates.model.js.map +1 -1
  33. package/dist/db/models/webhook-status.model.d.ts +7 -5
  34. package/dist/db/tables/templates.table.js.map +1 -1
  35. package/dist/engine/changes.js +8 -18
  36. package/dist/engine/changes.js.map +1 -1
  37. package/dist/engine/conditions.d.ts +2 -1
  38. package/dist/engine/conditions.js +5 -5
  39. package/dist/engine/conditions.js.map +1 -1
  40. package/dist/engine/coverage.d.ts +2 -1
  41. package/dist/engine/coverage.js.map +1 -1
  42. package/dist/engine/fallback-html.js +8 -8
  43. package/dist/engine/fallback-html.js.map +1 -1
  44. package/dist/engine/formats.d.ts +4 -6
  45. package/dist/engine/formats.js +22 -8
  46. package/dist/engine/formats.js.map +1 -1
  47. package/dist/engine/paths.d.ts +4 -2
  48. package/dist/engine/paths.js +16 -5
  49. package/dist/engine/paths.js.map +1 -1
  50. package/dist/engine/plain-text.js +2 -4
  51. package/dist/engine/plain-text.js.map +1 -1
  52. package/dist/engine/resolve.d.ts +2 -1
  53. package/dist/engine/resolve.js +1 -1
  54. package/dist/engine/resolve.js.map +1 -1
  55. package/dist/engine/system-variables.d.ts +2 -1
  56. package/dist/engine/system-variables.js +1 -1
  57. package/dist/engine/system-variables.js.map +1 -1
  58. package/dist/engine/tokens.d.ts +2 -1
  59. package/dist/engine/tokens.js.map +1 -1
  60. package/dist/hooks/tenant-export.js +10 -9
  61. package/dist/hooks/tenant-export.js.map +1 -1
  62. package/dist/pages/sends.js.map +1 -1
  63. package/dist/pages/templates.js +3 -1
  64. package/dist/pages/templates.js.map +1 -1
  65. package/dist/pages/toolbar.js.map +1 -1
  66. package/dist/routes/events.js +6 -4
  67. package/dist/routes/events.js.map +1 -1
  68. package/dist/routes/index.d.ts +1 -0
  69. package/dist/routes/index.js +1 -0
  70. package/dist/routes/index.js.map +1 -1
  71. package/dist/routes/metrics.js +31 -31
  72. package/dist/routes/metrics.js.map +1 -1
  73. package/dist/routes/period.js +5 -5
  74. package/dist/routes/period.js.map +1 -1
  75. package/dist/routes/sends.js +4 -5
  76. package/dist/routes/sends.js.map +1 -1
  77. package/dist/routes/settings.d.ts +1 -1
  78. package/dist/routes/settings.js +16 -14
  79. package/dist/routes/settings.js.map +1 -1
  80. package/dist/routes/suppressions.js +9 -6
  81. package/dist/routes/suppressions.js.map +1 -1
  82. package/dist/routes/template-content.d.ts +27 -0
  83. package/dist/routes/template-content.js +3 -0
  84. package/dist/routes/template-content.js.map +1 -0
  85. package/dist/routes/templates.d.ts +7 -30
  86. package/dist/routes/templates.js +7 -6
  87. package/dist/routes/templates.js.map +1 -1
  88. package/dist/routes/zod-json.d.ts +3 -0
  89. package/dist/routes/zod-json.js +21 -0
  90. package/dist/routes/zod-json.js.map +1 -0
  91. package/dist/services/attention.js +16 -8
  92. package/dist/services/attention.js.map +1 -1
  93. package/dist/services/catalog.js +4 -2
  94. package/dist/services/catalog.js.map +1 -1
  95. package/dist/services/categories.js +6 -6
  96. package/dist/services/categories.js.map +1 -1
  97. package/dist/services/checked-send.js +16 -9
  98. package/dist/services/checked-send.js.map +1 -1
  99. package/dist/services/declaration-error.d.ts +1 -1
  100. package/dist/services/declaration-error.js.map +1 -1
  101. package/dist/services/declarations.js +4 -1
  102. package/dist/services/declarations.js.map +1 -1
  103. package/dist/services/events.js +13 -7
  104. package/dist/services/events.js.map +1 -1
  105. package/dist/services/localized-text.d.ts +2 -1
  106. package/dist/services/localized-text.js +2 -1
  107. package/dist/services/localized-text.js.map +1 -1
  108. package/dist/services/metrics.js +2 -5
  109. package/dist/services/metrics.js.map +1 -1
  110. package/dist/services/render.js.map +1 -1
  111. package/dist/services/send-import.js +7 -3
  112. package/dist/services/send-import.js.map +1 -1
  113. package/dist/services/send.js +7 -7
  114. package/dist/services/send.js.map +1 -1
  115. package/dist/services/settings.d.ts +4 -2
  116. package/dist/services/settings.js +5 -4
  117. package/dist/services/settings.js.map +1 -1
  118. package/dist/services/starters.d.ts +3 -2
  119. package/dist/services/starters.js +5 -5
  120. package/dist/services/starters.js.map +1 -1
  121. package/dist/services/template-stats.js +5 -2
  122. package/dist/services/template-stats.js.map +1 -1
  123. package/dist/services/templates.d.ts +2 -1
  124. package/dist/services/templates.js +8 -4
  125. package/dist/services/templates.js.map +1 -1
  126. package/dist/services/webhook-status.d.ts +2 -2
  127. package/dist/services/webhook-status.js +6 -4
  128. package/dist/services/webhook-status.js.map +1 -1
  129. package/dist/types/json.d.ts +1 -0
  130. package/dist/types/json.js +3 -0
  131. package/dist/types/json.js.map +1 -0
  132. package/dist/types/settings.d.ts +2 -2
  133. package/dist/validation/declarations.schema.d.ts +44 -44
  134. package/dist/validation/declarations.schema.js +6 -5
  135. package/dist/validation/declarations.schema.js.map +1 -1
  136. package/dist/validation/guards.d.ts +3 -0
  137. package/dist/validation/guards.js +15 -0
  138. package/dist/validation/guards.js.map +1 -0
  139. package/dist/validation/json.d.ts +5 -0
  140. package/dist/validation/json.js +54 -0
  141. package/dist/validation/json.js.map +1 -0
  142. package/dist/validation/json.schema.d.ts +2 -0
  143. package/dist/validation/json.schema.js +9 -0
  144. package/dist/validation/json.schema.js.map +1 -0
  145. package/dist/validation/send-event-type.schema.d.ts +2 -0
  146. package/dist/validation/send-event-type.schema.js +11 -0
  147. package/dist/validation/send-event-type.schema.js.map +1 -0
  148. package/dist/validation/settings.schema.js +1 -1
  149. package/dist/validation/settings.schema.js.map +1 -1
  150. package/dist/validation/templates.schema.d.ts +44 -42
  151. package/dist/validation/templates.schema.js +4 -3
  152. package/dist/validation/templates.schema.js.map +1 -1
  153. package/frontend-vue/app/build/components/TemplateDrawerFooter.vue +45 -0
  154. package/frontend-vue/app/build/components/TemplateDrawerPerformance.vue +102 -0
  155. package/frontend-vue/app/build/components/TemplateDrawerVariables.vue +110 -0
  156. package/frontend-vue/app/build/composables/useDrawerStats.ts +78 -0
  157. package/frontend-vue/app/components/DuplicateModal.vue +3 -11
  158. package/frontend-vue/app/components/FixAddressModal.vue +2 -1
  159. package/frontend-vue/app/components/NewTemplateModal.vue +3 -11
  160. package/frontend-vue/app/components/RealSendModal.vue +6 -17
  161. package/frontend-vue/app/components/RenderingModal.vue +2 -1
  162. package/frontend-vue/app/components/SendDrawer.vue +5 -4
  163. package/frontend-vue/app/components/StarterGallery.vue +3 -11
  164. package/frontend-vue/app/components/TemplateDrawer.vue +40 -269
  165. package/frontend-vue/app/components/TestSendModal.vue +7 -18
  166. package/frontend-vue/app/components/VersionHistory.vue +2 -7
  167. package/frontend-vue/app/components/WebhookStatus.vue +2 -1
  168. package/frontend-vue/app/components/editor/EditorWorkspace.vue +2 -7
  169. package/frontend-vue/build/backend-boundary.ts +122 -0
  170. package/frontend-vue/dms.frontend.build.ts +7 -0
  171. package/frontend-vue/eslint.config.mjs +13 -0
  172. package/frontend-vue/package.json +4 -2
  173. package/frontend-vue/pnpm-lock.yaml +250 -61
  174. package/frontend-vue/tests/backend-boundary.test.ts +203 -0
  175. package/frontend-vue/tests/dnd.test.ts +1 -2
  176. package/frontend-vue/tests/editor-block-list.test.ts +136 -53
  177. package/frontend-vue/tests/helpers/auth-fetch.ts +123 -0
  178. package/frontend-vue/tests/helpers/nuxt-globals.ts +17 -1
  179. package/frontend-vue/tests/new-template-modal.test.ts +41 -6
  180. package/frontend-vue/tests/page-reads.test.ts +26 -6
  181. package/frontend-vue/tests/send-actions.test.ts +0 -1
  182. package/frontend-vue/tests/send-drawer.test.ts +2 -6
  183. package/frontend-vue/tests/send-modals.test.ts +8 -5
  184. package/frontend-vue/tests/sortable-list.test.ts +54 -62
  185. package/frontend-vue/tests/suppression.test.ts +1 -2
  186. package/frontend-vue/tests/template-drawer.test.ts +27 -18
  187. package/frontend-vue/tests/template-gallery.test.ts +7 -5
  188. package/frontend-vue/tests/template-preview-frame.test.ts +2 -5
  189. package/package.json +16 -20
  190. package/skills/dms-mailing/SKILL.md +1 -1
  191. package/skills/dms-mailing/cases.json +49 -0
  192. package/src/automation/email-event-trigger.ts +2 -2
  193. package/src/automation/send-template-action.ts +33 -14
  194. package/src/crons/jobs.ts +1 -1
  195. package/src/data/body.ts +7 -3
  196. package/src/data/templates-table.ts +55 -56
  197. package/src/data-types/categories-type.ts +2 -2
  198. package/src/data-types/category-type.ts +2 -2
  199. package/src/db/models/sends.model.ts +2 -2
  200. package/src/db/models/template-versions.model.ts +10 -4
  201. package/src/db/models/templates.model.ts +6 -3
  202. package/src/db/tables/templates.table.ts +1 -1
  203. package/src/engine/changes.ts +11 -28
  204. package/src/engine/conditions.ts +14 -8
  205. package/src/engine/coverage.ts +2 -1
  206. package/src/engine/fallback-html.ts +13 -13
  207. package/src/engine/formats.ts +35 -21
  208. package/src/engine/paths.ts +41 -7
  209. package/src/engine/plain-text.ts +2 -4
  210. package/src/engine/resolve.ts +12 -4
  211. package/src/engine/system-variables.ts +6 -4
  212. package/src/engine/tokens.ts +3 -3
  213. package/src/hooks/tenant-export.ts +11 -13
  214. package/src/pages/sends.ts +5 -3
  215. package/src/pages/templates.ts +4 -2
  216. package/src/pages/toolbar.ts +4 -2
  217. package/src/routes/events.ts +10 -10
  218. package/src/routes/index.ts +1 -0
  219. package/src/routes/metrics.ts +35 -33
  220. package/src/routes/period.ts +5 -5
  221. package/src/routes/sends.ts +23 -26
  222. package/src/routes/settings.ts +21 -17
  223. package/src/routes/suppressions.ts +12 -8
  224. package/src/routes/template-content.ts +45 -0
  225. package/src/routes/templates.ts +16 -49
  226. package/src/routes/zod-json.ts +28 -0
  227. package/src/services/attention.ts +20 -18
  228. package/src/services/catalog.ts +4 -8
  229. package/src/services/categories.ts +6 -6
  230. package/src/services/checked-send.ts +17 -19
  231. package/src/services/declaration-error.ts +1 -1
  232. package/src/services/declarations.ts +7 -6
  233. package/src/services/events.ts +18 -13
  234. package/src/services/localized-text.ts +4 -2
  235. package/src/services/metrics.ts +5 -7
  236. package/src/services/notify.ts +1 -1
  237. package/src/services/render.ts +5 -1
  238. package/src/services/send-import.ts +16 -11
  239. package/src/services/send.ts +10 -13
  240. package/src/services/settings.ts +17 -16
  241. package/src/services/starters.ts +12 -8
  242. package/src/services/template-stats.ts +11 -4
  243. package/src/services/templates.ts +24 -15
  244. package/src/services/webhook-status.ts +13 -8
  245. package/src/types/json.ts +1 -0
  246. package/src/types/settings.ts +2 -2
  247. package/src/validation/declarations.schema.ts +6 -5
  248. package/src/validation/guards.ts +17 -0
  249. package/src/validation/json.schema.ts +7 -0
  250. package/src/validation/json.ts +66 -0
  251. package/src/validation/send-event-type.schema.ts +10 -0
  252. package/src/validation/settings.schema.ts +7 -2
  253. package/src/validation/templates.schema.ts +9 -4
  254. package/frontend-vue/tests/i18n-editor.test.ts +0 -50
  255. package/frontend-vue/tests/i18n.test.ts +0 -138
@@ -0,0 +1,78 @@
1
+ import { computed, type ComputedRef, type Ref } from 'vue'
2
+ import type { TemplatePerformance, TemplateRow } from '../../types/mailing'
3
+ import { SENDS_PATH } from '../../utils/pages'
4
+
5
+ /**
6
+ * The figures of a template's drawer and the send log they link to: the
7
+ * drawer's script run that builds them, called where it stood.
8
+ */
9
+ export function useDrawerStats(
10
+ template: ComputedRef<TemplateRow | null>,
11
+ performance: Ref<TemplatePerformance | null>,
12
+ t: ReturnType<typeof useI18n>['t'],
13
+ uiLocale: Ref<string>,
14
+ ) {
15
+ /** The send log reads these: `?template=` narrows it, `?view=` opens a tab. */
16
+ const SENDS_TEMPLATE_KEY = 'template'
17
+ const SENDS_VIEW_KEY = 'view'
18
+ const PROBLEMS_VIEW = 'problems'
19
+ const OPEN_RATE_DIGITS = 1
20
+ const PERCENT = 100
21
+
22
+ const formatNumber = (value: number) =>
23
+ new Intl.NumberFormat(uiLocale.value, {
24
+ maximumFractionDigits: OPEN_RATE_DIGITS,
25
+ }).format(value)
26
+
27
+ /** A rate in percent, written the reader's way ("20.8%", "20,8 %"). */
28
+ const formatPercent = (value: number) =>
29
+ new Intl.NumberFormat(uiLocale.value, {
30
+ style: 'percent',
31
+ maximumFractionDigits: OPEN_RATE_DIGITS,
32
+ }).format(value / PERCENT)
33
+
34
+ /** The send log narrowed to this template, on one of its tabs if given. */
35
+ function sendLogRoute(view?: string) {
36
+ return {
37
+ path: SENDS_PATH,
38
+ query: {
39
+ [SENDS_TEMPLATE_KEY]: template.value?.slug ?? '',
40
+ ...(view ? { [SENDS_VIEW_KEY]: view } : {}),
41
+ },
42
+ }
43
+ }
44
+
45
+ function sendLogUrl(view?: string): string {
46
+ const { path, query } = sendLogRoute(view)
47
+ return `${path}?${new URLSearchParams(query).toString()}`
48
+ }
49
+
50
+ const hasSends = computed(() => Boolean(performance.value?.sends))
51
+
52
+ const stats = computed(() => [
53
+ {
54
+ id: 'sends',
55
+ eyebrow: t('dms_mailing.templates.drawer.stats_sends'),
56
+ value: formatNumber(performance.value?.sends ?? 0),
57
+ },
58
+ {
59
+ id: 'open',
60
+ eyebrow: t('dms_mailing.templates.drawer.stats_open'),
61
+ value: performance.value?.sends
62
+ ? formatPercent(performance.value.openRate)
63
+ : '—',
64
+ },
65
+ {
66
+ id: 'problems',
67
+ eyebrow: t('dms_mailing.templates.drawer.stats_problems'),
68
+ value: formatNumber(performance.value?.problems ?? 0),
69
+ detail: performance.value?.problems
70
+ ? t('dms_mailing.templates.drawer.problems_detail')
71
+ : undefined,
72
+ detailTone: 'error' as const,
73
+ to: performance.value?.problems ? sendLogUrl(PROBLEMS_VIEW) : undefined,
74
+ },
75
+ ])
76
+
77
+ return { formatNumber, sendLogRoute, hasSends, stats }
78
+ }
@@ -6,11 +6,7 @@ import {
6
6
  useTemplateContentRead,
7
7
  useTemplateListRead,
8
8
  } from '../composables/useMailingReads'
9
- import {
10
- HTTP_CONFLICT,
11
- apiErrorMessage,
12
- apiErrorStatus,
13
- } from '../utils/api-error'
9
+ import { HTTP_CONFLICT, apiErrorStatus } from '../utils/api-error'
14
10
  import {
15
11
  fitName,
16
12
  freeSlug,
@@ -31,9 +27,8 @@ const props = defineProps<DuplicateModalProps>()
31
27
  const emit = defineEmits<{ success: [created?: boolean] }>()
32
28
 
33
29
  const api = useMailingApi()
34
- const toast = useToast()
30
+ const { presentError } = useApiErrorPresenter()
35
31
  const { t } = useI18n()
36
- const { processApiMessage } = useTranslation()
37
32
 
38
33
  const templateId = computed(() => props.rowData?._id ?? props.templateId ?? '')
39
34
  const readTemplates = useTemplateListRead()
@@ -105,10 +100,7 @@ function reportFailure(error: unknown): void {
105
100
  takenSlugs.value = new Set([...takenSlugs.value, slug.value])
106
101
  return
107
102
  }
108
- toast.add({
109
- color: 'error',
110
- title: processApiMessage(apiErrorMessage(error)),
111
- })
103
+ presentError(error, { fallbackKey: 'error.500.description' })
112
104
  }
113
105
 
114
106
  async function submit(): Promise<void> {
@@ -26,6 +26,7 @@ const KEY_PREFIX = 'dms_mailing.sends.fix_address.'
26
26
  const api = useMailingApi()
27
27
  const reportReplay = useReplayToast()
28
28
  const { t } = useI18n()
29
+ const { presentError } = useApiErrorPresenter()
29
30
 
30
31
  const previous = computed(() => props.rowData?.recipientEmail ?? '')
31
32
  const address = ref(previous.value)
@@ -66,7 +67,7 @@ async function submit(): Promise<void> {
66
67
  reportReplay(result, email, key('sent', { email }))
67
68
  emit('success', true)
68
69
  } catch (error) {
69
- useApiError(error)
70
+ presentError(error, { fallbackKey: 'error.500.description' })
70
71
  } finally {
71
72
  sending.value = false
72
73
  }
@@ -9,11 +9,7 @@ import {
9
9
  useStartersRead,
10
10
  useTemplateListRead,
11
11
  } from '../composables/useMailingReads'
12
- import {
13
- HTTP_CONFLICT,
14
- apiErrorMessage,
15
- apiErrorStatus,
16
- } from '../utils/api-error'
12
+ import { HTTP_CONFLICT, apiErrorStatus } from '../utils/api-error'
17
13
  import { NO_CATEGORY, fromCategoryOption } from '../utils/categories'
18
14
  import { groupByCategory, matchesSearch } from '../utils/gallery'
19
15
  import { slugify, validateName, validateSlug } from '../utils/slug'
@@ -73,9 +69,8 @@ const props = defineProps<NewTemplateModalProps>()
73
69
  const emit = defineEmits<{ success: [created?: boolean] }>()
74
70
 
75
71
  const api = useMailingApi()
76
- const toast = useToast()
72
+ const { presentError } = useApiErrorPresenter()
77
73
  const { t } = useI18n()
78
- const { processApiMessage } = useTranslation()
79
74
 
80
75
  const name = ref(props.initialName ?? '')
81
76
  const slug = ref(slugify(name.value))
@@ -256,10 +251,7 @@ function reportFailure(error: unknown): void {
256
251
  refusedSlugs.value = new Set([...refusedSlugs.value, slug.value])
257
252
  return
258
253
  }
259
- toast.add({
260
- color: 'error',
261
- title: processApiMessage(apiErrorMessage(error)),
262
- })
254
+ presentError(error, { fallbackKey: 'error.500.description' })
263
255
  }
264
256
 
265
257
  async function submit(): Promise<void> {
@@ -7,7 +7,6 @@ import { useMailingApi } from '../composables/useMailingApi'
7
7
  import { useTemplateContentRead } from '../composables/useMailingReads'
8
8
  import type { SendResult } from '../composables/useMailingApi'
9
9
  import { useTemplateDrawer } from '../composables/useTemplateDrawer'
10
- import { apiErrorMessage } from '../utils/api-error'
11
10
  import { firstName, formatCardDate } from '../utils/gallery'
12
11
  import { localeChipClass, missingLocales } from '../utils/locales'
13
12
  import {
@@ -57,10 +56,10 @@ const emit = defineEmits<{ success: [sent?: boolean] }>()
57
56
 
58
57
  const api = useMailingApi()
59
58
  const toast = useToast()
59
+ const { presentError } = useApiErrorPresenter()
60
60
  const { t, locale: uiLocale } = useI18n()
61
61
  const onDate = (date: string): string =>
62
62
  t('dms_mailing.common.on_date', { date })
63
- const { processApiMessage } = useTranslation()
64
63
  const { uniqueLocales } = useUniqueLocales()
65
64
  const overlays = useTemplateDrawer()
66
65
 
@@ -110,10 +109,7 @@ onMounted(async () => {
110
109
  values.value = initialSendValues(fields.value, detail.value.testData)
111
110
  locale.value = detail.value.fallbackLocale
112
111
  } catch (error) {
113
- toast.add({
114
- color: 'error',
115
- title: processApiMessage(apiErrorMessage(error)),
116
- })
112
+ presentError(error, { fallbackKey: 'error.500.description' })
117
113
  } finally {
118
114
  loading.value = false
119
115
  }
@@ -296,15 +292,11 @@ function openSettings(): void {
296
292
  void navigateDms({ path: SETTINGS_PATH })
297
293
  }
298
294
 
299
- function report(results: SendResult[], to: string[]): void {
295
+ function report(results: SendResult[]): void {
300
296
  results
301
297
  .filter((result) => result.error)
302
298
  .forEach((result) =>
303
- toast.add({
304
- color: 'error',
305
- title: to[results.indexOf(result)] ?? result.sendId,
306
- description: processApiMessage(result.error),
307
- }),
299
+ presentError(result.error, { fallbackKey: 'error.500.description' }),
308
300
  )
309
301
  const sent = results.filter((result) => !result.error).length
310
302
  if (!sent) return
@@ -331,13 +323,10 @@ async function submit(): Promise<void> {
331
323
  locale: locale.value || undefined,
332
324
  data: sendData.value,
333
325
  })
334
- report(response.results, to)
326
+ report(response.results)
335
327
  close(true)
336
328
  } catch (error) {
337
- toast.add({
338
- color: 'error',
339
- title: processApiMessage(apiErrorMessage(error)),
340
- })
329
+ presentError(error, { fallbackKey: 'error.500.description' })
341
330
  } finally {
342
331
  sending.value = false
343
332
  }
@@ -45,6 +45,7 @@ const FRAME_WIDTHS: Record<RenderingView, string> = {
45
45
  }
46
46
 
47
47
  const { t, locale } = useI18n()
48
+ const { presentError } = useApiErrorPresenter()
48
49
 
49
50
  const view = ref<RenderingView>('desktop')
50
51
  const rendering = ref<SendRendering | null>(null)
@@ -99,7 +100,7 @@ async function load(): Promise<void> {
99
100
  rendering.value = html
100
101
  } catch (error) {
101
102
  failed.value = true
102
- useApiError(error)
103
+ presentError(error, { fallbackKey: `${KEY_PREFIX}error_description` })
103
104
  } finally {
104
105
  loading.value = false
105
106
  }
@@ -82,6 +82,7 @@ const props = defineProps<SendDrawerProps>()
82
82
 
83
83
  const api = useMailingApi()
84
84
  const toast = useToast()
85
+ const { presentError } = useApiErrorPresenter()
85
86
  const reportReplay = useReplayToast()
86
87
  const { confirm } = useConfirm()
87
88
  const { open: openModal } = useModal()
@@ -236,7 +237,7 @@ async function load(): Promise<void> {
236
237
  try {
237
238
  detail.value = await readSend()
238
239
  } catch (error) {
239
- useApiError(error)
240
+ presentError(error, { fallbackKey: `${KEY_PREFIX}send_unavailable` })
240
241
  } finally {
241
242
  loading.value = false
242
243
  }
@@ -264,8 +265,8 @@ async function loadTracking(): Promise<void> {
264
265
  async function showSend(id: string): Promise<void> {
265
266
  try {
266
267
  await props.showRow?.(id)
267
- } catch {
268
- toast.add({ color: 'error', title: key('send_unavailable') })
268
+ } catch (error) {
269
+ presentError(error, { fallbackKey: `${KEY_PREFIX}send_unavailable` })
269
270
  }
270
271
  }
271
272
 
@@ -363,7 +364,7 @@ async function replay(): Promise<void> {
363
364
  )
364
365
  settle()
365
366
  } catch (error) {
366
- useApiError(error)
367
+ presentError(error, { fallbackKey: 'error.500.description' })
367
368
  } finally {
368
369
  sending.value = false
369
370
  }
@@ -4,11 +4,7 @@ import { useLocalizedDefaults } from '../composables/useLocalizedDefaults'
4
4
  import { useMailingApi } from '../composables/useMailingApi'
5
5
  import { useStarters } from '../composables/useMailingReads'
6
6
  import { useTemplateDrawer } from '../composables/useTemplateDrawer'
7
- import {
8
- HTTP_CONFLICT,
9
- apiErrorMessage,
10
- apiErrorStatus,
11
- } from '../utils/api-error'
7
+ import { HTTP_CONFLICT, apiErrorStatus } from '../utils/api-error'
12
8
  import { editorRoute } from '../utils/editor-route'
13
9
  import type { StarterSummary } from '../types/mailing'
14
10
 
@@ -19,9 +15,8 @@ const SKELETON_STARTERS = 4
19
15
  const emit = defineEmits<{ created: [] }>()
20
16
 
21
17
  const api = useMailingApi()
22
- const toast = useToast()
18
+ const { presentError } = useApiErrorPresenter()
23
19
  const { t } = useI18n()
24
- const { processApiMessage } = useTranslation()
25
20
  const overlays = useTemplateDrawer()
26
21
 
27
22
  const localized = useLocalizedDefaults()
@@ -69,10 +64,7 @@ async function createFrom(starter: StarterSummary): Promise<void> {
69
64
  openNewTemplate(starter)
70
65
  return
71
66
  }
72
- toast.add({
73
- color: 'error',
74
- title: processApiMessage(apiErrorMessage(error)),
75
- })
67
+ presentError(error, { fallbackKey: 'error.500.description' })
76
68
  } finally {
77
69
  creatingId.value = null
78
70
  }