@antelopejs/interface-dms 0.10.4 → 0.12.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 (374) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/README.md +21 -10
  3. package/dist/attachments.d.ts +15 -2
  4. package/dist/attachments.js +14 -1
  5. package/dist/attachments.js.map +1 -1
  6. package/dist/auth/db/models/sessions.model.d.ts +7 -5
  7. package/dist/auth/db/models/sessions.model.js +2 -9
  8. package/dist/auth/db/models/sessions.model.js.map +1 -1
  9. package/dist/auth/db/models/userExternalIdentities.model.d.ts +7 -5
  10. package/dist/auth/db/models/users.model.d.ts +7 -5
  11. package/dist/auth/db/tables/users.table.d.ts +2 -2
  12. package/dist/auth/db/tables/users.table.js.map +1 -1
  13. package/dist/auth/index.d.ts +4 -4
  14. package/dist/auth/internal/jwt-auth-decorator.d.ts +1 -1
  15. package/dist/auth/internal/jwt-auth-decorator.js +6 -3
  16. package/dist/auth/internal/jwt-auth-decorator.js.map +1 -1
  17. package/dist/auth/internal/request-authenticators.d.ts +12 -0
  18. package/dist/auth/internal/request-authenticators.js +53 -10
  19. package/dist/auth/internal/request-authenticators.js.map +1 -1
  20. package/dist/base/activity-feed.d.ts +34 -32
  21. package/dist/base/activity-feed.js +2 -0
  22. package/dist/base/activity-feed.js.map +1 -1
  23. package/dist/base/banner.d.ts +12 -10
  24. package/dist/base/banner.js +2 -0
  25. package/dist/base/banner.js.map +1 -1
  26. package/dist/base/block-registry/internal/describe.js +3 -9
  27. package/dist/base/block-registry/internal/describe.js.map +1 -1
  28. package/dist/base/block-registry/internal/ui.js +1 -1
  29. package/dist/base/block-registry/internal/ui.js.map +1 -1
  30. package/dist/base/card.d.ts +11 -9
  31. package/dist/base/card.js +2 -0
  32. package/dist/base/card.js.map +1 -1
  33. package/dist/base/chart-card.d.ts +4 -2
  34. package/dist/base/chart-card.js +2 -0
  35. package/dist/base/chart-card.js.map +1 -1
  36. package/dist/base/chart-schemas.d.ts +148 -148
  37. package/dist/base/chart.d.ts +65 -0
  38. package/dist/base/chart.js +65 -0
  39. package/dist/base/chart.js.map +1 -1
  40. package/dist/base/code-block.d.ts +5 -3
  41. package/dist/base/code-block.js +2 -0
  42. package/dist/base/code-block.js.map +1 -1
  43. package/dist/base/custom.d.ts +5 -0
  44. package/dist/base/custom.js +5 -0
  45. package/dist/base/custom.js.map +1 -1
  46. package/dist/base/data-types/default/address.d.ts +5 -0
  47. package/dist/base/data-types/default/address.js +5 -0
  48. package/dist/base/data-types/default/address.js.map +1 -1
  49. package/dist/base/data-types/default/choices.d.ts +20 -0
  50. package/dist/base/data-types/default/choices.js +20 -0
  51. package/dist/base/data-types/default/choices.js.map +1 -1
  52. package/dist/base/data-types/default/files.d.ts +22 -5
  53. package/dist/base/data-types/default/files.js +20 -0
  54. package/dist/base/data-types/default/files.js.map +1 -1
  55. package/dist/base/data-types/default/numbers.d.ts +15 -0
  56. package/dist/base/data-types/default/numbers.js +25 -9
  57. package/dist/base/data-types/default/numbers.js.map +1 -1
  58. package/dist/base/data-types/default/relations.d.ts +10 -2
  59. package/dist/base/data-types/default/relations.js +9 -6
  60. package/dist/base/data-types/default/relations.js.map +1 -1
  61. package/dist/base/data-types/default/strings.d.ts +25 -0
  62. package/dist/base/data-types/default/strings.js +25 -0
  63. package/dist/base/data-types/default/strings.js.map +1 -1
  64. package/dist/base/data-types/default-types.d.ts +1 -1
  65. package/dist/base/data-types/field-types.d.ts +10 -0
  66. package/dist/base/data-types/field-types.js +10 -0
  67. package/dist/base/data-types/field-types.js.map +1 -1
  68. package/dist/base/data-types/internal/relation-endpoints.js +1 -0
  69. package/dist/base/data-types/internal/relation-endpoints.js.map +1 -1
  70. package/dist/base/data-types/status-type.d.ts +5 -0
  71. package/dist/base/data-types/status-type.js +5 -0
  72. package/dist/base/data-types/status-type.js.map +1 -1
  73. package/dist/base/display.d.ts +6 -6
  74. package/dist/base/empty-state.d.ts +15 -13
  75. package/dist/base/empty-state.js +2 -0
  76. package/dist/base/empty-state.js.map +1 -1
  77. package/dist/base/form-block-schema.d.ts +50 -50
  78. package/dist/base/form-schema.d.ts +2 -0
  79. package/dist/base/form-schema.js +2 -0
  80. package/dist/base/form-schema.js.map +1 -1
  81. package/dist/base/grid.d.ts +4 -0
  82. package/dist/base/grid.js +4 -0
  83. package/dist/base/grid.js.map +1 -1
  84. package/dist/base/guard.d.ts +30 -28
  85. package/dist/base/guard.js +2 -0
  86. package/dist/base/guard.js.map +1 -1
  87. package/dist/base/helpers/archive-mode-helpers.d.ts +1 -1
  88. package/dist/base/helpers/file-refs.js +15 -15
  89. package/dist/base/helpers/file-refs.js.map +1 -1
  90. package/dist/base/internal/form-schema-validation.js +7 -1
  91. package/dist/base/internal/form-schema-validation.js.map +1 -1
  92. package/dist/base/internal/realtime-topics.js.map +1 -1
  93. package/dist/base/internal/searchable.d.ts +2 -1
  94. package/dist/base/internal/searchable.js +1 -3
  95. package/dist/base/internal/searchable.js.map +1 -1
  96. package/dist/base/key-value-list.d.ts +21 -19
  97. package/dist/base/key-value-list.js +2 -0
  98. package/dist/base/key-value-list.js.map +1 -1
  99. package/dist/base/kpi-card.d.ts +4 -2
  100. package/dist/base/kpi-card.js +2 -0
  101. package/dist/base/kpi-card.js.map +1 -1
  102. package/dist/base/live-toggle.d.ts +2 -0
  103. package/dist/base/live-toggle.js +2 -0
  104. package/dist/base/live-toggle.js.map +1 -1
  105. package/dist/base/meter.d.ts +23 -21
  106. package/dist/base/meter.js +2 -0
  107. package/dist/base/meter.js.map +1 -1
  108. package/dist/base/nav-card-grid.d.ts +24 -19
  109. package/dist/base/nav-card-grid.js +5 -0
  110. package/dist/base/nav-card-grid.js.map +1 -1
  111. package/dist/base/page-toolbar.d.ts +2 -0
  112. package/dist/base/page-toolbar.js +2 -0
  113. package/dist/base/page-toolbar.js.map +1 -1
  114. package/dist/base/period-selector.d.ts +3 -1
  115. package/dist/base/period-selector.js +2 -0
  116. package/dist/base/period-selector.js.map +1 -1
  117. package/dist/base/placeholder.d.ts +2 -0
  118. package/dist/base/placeholder.js +2 -0
  119. package/dist/base/placeholder.js.map +1 -1
  120. package/dist/base/resource-form-schema.d.ts +2 -2
  121. package/dist/base/searchable.d.ts +3 -2
  122. package/dist/base/searchable.js +2 -2
  123. package/dist/base/searchable.js.map +1 -1
  124. package/dist/base/section.d.ts +6 -2
  125. package/dist/base/section.js +4 -0
  126. package/dist/base/section.js.map +1 -1
  127. package/dist/base/stack.d.ts +6 -0
  128. package/dist/base/stack.js +6 -0
  129. package/dist/base/stack.js.map +1 -1
  130. package/dist/base/stat-group.d.ts +16 -14
  131. package/dist/base/stat-group.js +2 -0
  132. package/dist/base/stat-group.js.map +1 -1
  133. package/dist/base/tab.d.ts +10 -8
  134. package/dist/base/tab.js +2 -0
  135. package/dist/base/tab.js.map +1 -1
  136. package/dist/base/table-view/column-display.d.ts +1 -1
  137. package/dist/base/table-view/column-display.js.map +1 -1
  138. package/dist/base/table-view/data-functions.d.ts +10 -4
  139. package/dist/base/table-view/data-functions.js.map +1 -1
  140. package/dist/base/table-view/factory.d.ts +5 -0
  141. package/dist/base/table-view/factory.js +8 -4
  142. package/dist/base/table-view/factory.js.map +1 -1
  143. package/dist/base/table-view/internal/auth.d.ts +17 -3
  144. package/dist/base/table-view/internal/auth.js +26 -5
  145. package/dist/base/table-view/internal/auth.js.map +1 -1
  146. package/dist/base/table-view/internal/column-conditions.d.ts +8 -1
  147. package/dist/base/table-view/internal/column-conditions.js +25 -13
  148. package/dist/base/table-view/internal/column-conditions.js.map +1 -1
  149. package/dist/base/table-view/internal/column-display.d.ts +1 -2
  150. package/dist/base/table-view/internal/column-display.js +3 -9
  151. package/dist/base/table-view/internal/column-display.js.map +1 -1
  152. package/dist/base/table-view/internal/factory-helpers.d.ts +1 -1
  153. package/dist/base/table-view/internal/factory-helpers.js +17 -14
  154. package/dist/base/table-view/internal/factory-helpers.js.map +1 -1
  155. package/dist/base/table-view/internal/factory-lifecycle-created.d.ts +2 -3
  156. package/dist/base/table-view/internal/factory-lifecycle-created.js +5 -5
  157. package/dist/base/table-view/internal/factory-lifecycle-created.js.map +1 -1
  158. package/dist/base/table-view/internal/factory-lifecycle-filter.d.ts +2 -2
  159. package/dist/base/table-view/internal/factory-private-actions.d.ts +2 -2
  160. package/dist/base/table-view/internal/files.d.ts +2 -1
  161. package/dist/base/table-view/internal/files.js +14 -5
  162. package/dist/base/table-view/internal/files.js.map +1 -1
  163. package/dist/base/table-view/internal/footer.d.ts +3 -3
  164. package/dist/base/table-view/internal/footer.js +16 -15
  165. package/dist/base/table-view/internal/footer.js.map +1 -1
  166. package/dist/base/table-view/internal/guards.d.ts +27 -5
  167. package/dist/base/table-view/internal/guards.js +127 -49
  168. package/dist/base/table-view/internal/guards.js.map +1 -1
  169. package/dist/base/table-view/internal/meta-helpers.d.ts +1 -1
  170. package/dist/base/table-view/internal/meta-helpers.js +3 -4
  171. package/dist/base/table-view/internal/meta-helpers.js.map +1 -1
  172. package/dist/base/table-view/internal/meta.js +2 -3
  173. package/dist/base/table-view/internal/meta.js.map +1 -1
  174. package/dist/base/table-view/internal/option-contract.d.ts +1 -1
  175. package/dist/base/table-view/internal/option-footer.d.ts +2 -2
  176. package/dist/base/table-view/internal/option-row-actions.d.ts +1 -1
  177. package/dist/base/table-view/internal/realtime.d.ts +52 -11
  178. package/dist/base/table-view/internal/realtime.js +23 -16
  179. package/dist/base/table-view/internal/realtime.js.map +1 -1
  180. package/dist/base/table-view/internal/resource-form.d.ts +1 -1
  181. package/dist/base/table-view/internal/resource-form.js +1 -1
  182. package/dist/base/table-view/internal/resource-form.js.map +1 -1
  183. package/dist/base/table-view/internal/row-actions-schema.d.ts +12 -12
  184. package/dist/base/table-view/internal/row-rule-shape.d.ts +11 -5
  185. package/dist/base/table-view/internal/row-rule-shape.js +60 -30
  186. package/dist/base/table-view/internal/row-rule-shape.js.map +1 -1
  187. package/dist/base/table-view/internal/row-rules.d.ts +28 -1
  188. package/dist/base/table-view/internal/row-rules.js +8 -19
  189. package/dist/base/table-view/internal/row-rules.js.map +1 -1
  190. package/dist/base/table-view/internal/tabs.d.ts +1 -1
  191. package/dist/base/table-view/internal/tabs.js +1 -4
  192. package/dist/base/table-view/internal/tabs.js.map +1 -1
  193. package/dist/base/table-view/internal/validation.d.ts +2 -2
  194. package/dist/base/table-view/internal/validation.js +7 -7
  195. package/dist/base/table-view/internal/validation.js.map +1 -1
  196. package/dist/base/table-view/meta.d.ts +7 -6
  197. package/dist/base/table-view/meta.js +12 -7
  198. package/dist/base/table-view/meta.js.map +1 -1
  199. package/dist/base/table-view/resource-form.d.ts +3 -1
  200. package/dist/base/table-view/resource-form.js +2 -0
  201. package/dist/base/table-view/resource-form.js.map +1 -1
  202. package/dist/base/table-view/routes.d.ts +73 -21
  203. package/dist/base/table-view/routes.js +13 -10
  204. package/dist/base/table-view/routes.js.map +1 -1
  205. package/dist/base/table-view/schema.d.ts +152 -152
  206. package/dist/base/table-view/source.js +4 -1
  207. package/dist/base/table-view/source.js.map +1 -1
  208. package/dist/base/table-view.d.ts +1 -1
  209. package/dist/base/table-view.js.map +1 -1
  210. package/dist/base/top-list-card.d.ts +35 -33
  211. package/dist/base/top-list-card.js +2 -0
  212. package/dist/base/top-list-card.js.map +1 -1
  213. package/dist/base/types/error.js +5 -2
  214. package/dist/base/types/error.js.map +1 -1
  215. package/dist/base/types/guards.d.ts +4 -4
  216. package/dist/base/types/json.d.ts +1 -10
  217. package/dist/base/types/row-action.d.ts +7 -7
  218. package/dist/component-options/confirm-dialog.d.ts +2 -0
  219. package/dist/component-options/confirm-dialog.js.map +1 -1
  220. package/dist/component-options/data-type.d.ts +2 -2
  221. package/dist/component-options/data-type.js +7 -7
  222. package/dist/component-options/data-type.js.map +1 -1
  223. package/dist/component-options/row-action.d.ts +14 -9
  224. package/dist/component-slots.d.ts +11 -2
  225. package/dist/component-slots.js +1 -0
  226. package/dist/component-slots.js.map +1 -1
  227. package/dist/component.d.ts +7 -8
  228. package/dist/component.js +17 -25
  229. package/dist/component.js.map +1 -1
  230. package/dist/data-controllers/members.d.ts +11 -7
  231. package/dist/data-controllers/members.js +1 -1
  232. package/dist/data-controllers/members.js.map +1 -1
  233. package/dist/data-controllers/roles.d.ts +32 -10
  234. package/dist/db/internal/exportJobs.model.d.ts +7 -5
  235. package/dist/db/internal/inviteResolutions.model.d.ts +7 -5
  236. package/dist/db/internal/tenantLifecycle.model.d.ts +7 -5
  237. package/dist/db/models/roles.model.d.ts +7 -5
  238. package/dist/db/models/tenantMembers.model.d.ts +7 -5
  239. package/dist/db/models/tenants.model.d.ts +7 -5
  240. package/dist/db/models/user_invites.model.d.ts +7 -5
  241. package/dist/guards.d.ts +1 -1
  242. package/dist/hooks.d.ts +1 -2
  243. package/dist/hooks.js +33 -26
  244. package/dist/hooks.js.map +1 -1
  245. package/dist/internal/attachments.d.ts +2 -2
  246. package/dist/internal/component-builder.js +2 -1
  247. package/dist/internal/component-builder.js.map +1 -1
  248. package/dist/internal/component-serialization.d.ts +2 -4
  249. package/dist/internal/component-serialization.js +4 -3
  250. package/dist/internal/component-serialization.js.map +1 -1
  251. package/dist/internal/component-slots.d.ts +19 -2
  252. package/dist/internal/component-slots.js +35 -12
  253. package/dist/internal/component-slots.js.map +1 -1
  254. package/dist/internal/component-watch-filter.d.ts +11 -0
  255. package/dist/internal/component-watch-filter.js +22 -0
  256. package/dist/internal/component-watch-filter.js.map +1 -0
  257. package/dist/internal/component-watches.js +0 -1
  258. package/dist/internal/component-watches.js.map +1 -1
  259. package/dist/internal/own-entry.d.ts +7 -0
  260. package/dist/internal/own-entry.js +13 -0
  261. package/dist/internal/own-entry.js.map +1 -0
  262. package/dist/invite-extension-payloads.d.ts +14 -2
  263. package/dist/invite-extensions/internal/delivery.js +1 -2
  264. package/dist/invite-extensions/internal/delivery.js.map +1 -1
  265. package/dist/invite-extensions/internal/edit.d.ts +2 -1
  266. package/dist/invite-extensions/internal/edit.js +8 -9
  267. package/dist/invite-extensions/internal/edit.js.map +1 -1
  268. package/dist/invite-extensions/internal/form-slot.js +8 -15
  269. package/dist/invite-extensions/internal/form-slot.js.map +1 -1
  270. package/dist/invite-extensions/internal/payload-slices.d.ts +33 -3
  271. package/dist/invite-extensions/internal/payload-slices.js +84 -19
  272. package/dist/invite-extensions/internal/payload-slices.js.map +1 -1
  273. package/dist/invite-extensions/internal/types.d.ts +10 -1
  274. package/dist/invite-resolution.js +6 -4
  275. package/dist/invite-resolution.js.map +1 -1
  276. package/dist/json.d.ts +10 -0
  277. package/dist/json.js +3 -0
  278. package/dist/json.js.map +1 -0
  279. package/dist/layout-banners.d.ts +5 -2
  280. package/dist/layout-banners.js.map +1 -1
  281. package/dist/page/controllers.d.ts +2 -2
  282. package/dist/page/internal/extension-validation.js +3 -2
  283. package/dist/page/internal/extension-validation.js.map +1 -1
  284. package/dist/page/internal/metadata-layout.js +1 -1
  285. package/dist/page/internal/metadata-layout.js.map +1 -1
  286. package/dist/page/internal/metadata-publish.js +3 -2
  287. package/dist/page/internal/metadata-publish.js.map +1 -1
  288. package/dist/page/internal/metadata-registration.js +5 -4
  289. package/dist/page/internal/metadata-registration.js.map +1 -1
  290. package/dist/page/internal/metadata-upload-registration.d.ts +3 -1
  291. package/dist/page/internal/metadata-upload-registration.js +9 -0
  292. package/dist/page/internal/metadata-upload-registration.js.map +1 -1
  293. package/dist/page/internal/metadata-uploads.d.ts +18 -5
  294. package/dist/page/internal/metadata-uploads.js +7 -8
  295. package/dist/page/internal/metadata-uploads.js.map +1 -1
  296. package/dist/page/internal/page-record.d.ts +2 -3
  297. package/dist/page/internal/page-record.js +19 -14
  298. package/dist/page/internal/page-record.js.map +1 -1
  299. package/dist/page/layout-filter.js +17 -6
  300. package/dist/page/layout-filter.js.map +1 -1
  301. package/dist/page/metadata.js +5 -7
  302. package/dist/page/metadata.js.map +1 -1
  303. package/dist/request-tenant.d.ts +10 -4
  304. package/dist/request-tenant.js +16 -34
  305. package/dist/request-tenant.js.map +1 -1
  306. package/dist/tenant-export.d.ts +2 -1
  307. package/dist/tenant-lifecycle.d.ts +13 -4
  308. package/dist/tenant-lifecycle.js +51 -18
  309. package/dist/tenant-lifecycle.js.map +1 -1
  310. package/dist/types.d.ts +20 -0
  311. package/dist/uploads.js +42 -59
  312. package/dist/uploads.js.map +1 -1
  313. package/dist/utils/fire-and-forget.js.map +1 -1
  314. package/dist/utils/internal/decorated-class.d.ts +14 -0
  315. package/dist/utils/internal/decorated-class.js +37 -0
  316. package/dist/utils/internal/decorated-class.js.map +1 -0
  317. package/dist/utils/internal/type-check.d.ts +9 -0
  318. package/dist/utils/internal/type-check.js +13 -5
  319. package/dist/utils/internal/type-check.js.map +1 -1
  320. package/dist/utils/internal/value-parser.d.ts +1 -1
  321. package/dist/utils/internal/value-parser.js +22 -9
  322. package/dist/utils/internal/value-parser.js.map +1 -1
  323. package/dist/watch-actions.d.ts +1 -1
  324. package/docs/1.core/1.introduction.md +82 -77
  325. package/docs/1.core/10.layout-banners.md +57 -45
  326. package/docs/1.core/11.modules-and-dynamic-menus.md +178 -0
  327. package/docs/1.core/12.frontend-modules-and-request-helpers.md +155 -0
  328. package/docs/1.core/13.tenant-data-export.md +120 -0
  329. package/docs/1.core/2.pages-and-modules.md +140 -367
  330. package/docs/1.core/3.components.md +21 -22
  331. package/docs/1.core/4.permissions.md +92 -71
  332. package/docs/1.core/5.tenancy.md +112 -86
  333. package/docs/1.core/6.invites.md +90 -65
  334. package/docs/1.core/7.hooks.md +96 -166
  335. package/docs/1.core/8.realtime.md +44 -45
  336. package/docs/1.core/9.quick-actions.md +37 -30
  337. package/docs/2.auth/1.introduction.md +33 -35
  338. package/docs/2.auth/2.authentication.md +60 -38
  339. package/docs/2.auth/3.tokens-and-sessions.md +56 -55
  340. package/docs/2.auth/4.users.md +79 -51
  341. package/docs/2.auth/5.external-identities.md +25 -26
  342. package/docs/3.base/1.introduction.md +64 -60
  343. package/docs/3.base/10.display-blocks.md +15 -16
  344. package/docs/3.base/11.page-layouts.md +273 -0
  345. package/docs/3.base/12.table-row-actions.md +122 -0
  346. package/docs/3.base/13.table-filters-and-forms.md +104 -0
  347. package/docs/3.base/14.table-events-and-helpers.md +114 -0
  348. package/docs/3.base/15.table-sources-and-gates.md +24 -0
  349. package/docs/3.base/2.types.md +44 -41
  350. package/docs/3.base/3.forms.md +27 -28
  351. package/docs/3.base/4.tables.md +34 -367
  352. package/docs/3.base/5.charts-and-widgets.md +28 -29
  353. package/docs/3.base/6.layout.md +21 -270
  354. package/docs/3.base/8.data-types.md +39 -40
  355. package/docs/3.base/9.helpers.md +36 -38
  356. package/docs/4.notifications/1.introduction.md +22 -26
  357. package/docs/4.notifications/2.categories-and-subjects.md +58 -55
  358. package/docs/4.notifications/3.sending.md +72 -68
  359. package/docs/5.html-render/1.introduction.md +119 -81
  360. package/docs/5.html-render/2.built-in-emails.md +71 -51
  361. package/docs/index.md +18 -29
  362. package/docs/tsconfig.json +4 -0
  363. package/package.json +11 -11
  364. package/docs/images/emails-admin-invite.png +0 -0
  365. package/docs/images/emails-change-notice.png +0 -0
  366. package/docs/images/emails-change-verification.png +0 -0
  367. package/docs/images/emails-digest-logo-dark.png +0 -0
  368. package/docs/images/emails-digest-logo-light.png +0 -0
  369. package/docs/images/emails-digest.png +0 -0
  370. package/docs/images/emails-export-partial.png +0 -0
  371. package/docs/images/emails-export-ready.png +0 -0
  372. package/docs/images/emails-reset-password.png +0 -0
  373. package/docs/images/emails-two-factor.png +0 -0
  374. package/docs/images/emails-user-validation.png +0 -0
@@ -1,19 +1,18 @@
1
1
  ---
2
2
  title: "Introduction"
3
- description: "Turn a named template and a props object into an HTML string, rendered by the frontend and delivered by your code — as an email, a file or a response."
3
+ description: "Turn a named template and a props object into an HTML string. The frontend renders it, and your code delivers it as an email, a file or a response."
4
+ mode: reference
4
5
  ---
5
6
 
6
- # Introduction
7
-
8
7
  ## Overview
9
8
 
10
- The `html-render` domain of `@antelopejs/interface-dms` turns a named template plus a props object into an HTML string. You declare a template once with `RegisterHtmlTemplate`, which records the name in a process-wide registry and hands back a typed reference; passing that reference to `GenerateHtml` produces the rendered HTML.
9
+ The `html-render` domain of `@antelopejs/interface-dms` turns a named template and a props object into an HTML string. You declare a template once with `RegisterHtmlTemplate`. It records the name in a process-wide registry and returns a typed reference. Pass that reference to `GenerateHtml` to get the rendered HTML.
11
10
 
12
- Rendering itself does not happen in the backend process. `GenerateHtml` performs an HTTP `POST` to the render endpoint configured on the DMS (`htmlRender.renderEndpoint`), authenticated with a signed service token, and returns the HTML the endpoint sends back. The backend therefore owns the template *names* and the props *types*; the markup lives with the renderer behind that endpoint.
11
+ The backend process does not render anything itself. `GenerateHtml` sends an HTTP `POST` to the render endpoint configured on the DMS (`htmlRender.renderEndpoint`), authenticated with a signed service token, and returns the HTML the endpoint sends back. The backend owns the template names and the props types. The markup lives with the renderer behind that endpoint.
13
12
 
14
- The interface produces HTML and nothing else. Delivering that HTML — as a transactional email, as a file, as a response body — is your code's job.
13
+ The interface produces HTML and nothing else. Your code delivers that HTML, as a transactional email, a file or a response body.
15
14
 
16
- Everything below is exported from a single subpath:
15
+ Everything below is exported from one subpath:
17
16
 
18
17
  ```typescript
19
18
  import {
@@ -29,23 +28,23 @@ import type {
29
28
  } from "@antelopejs/interface-dms/html-render";
30
29
  ```
31
30
 
32
- ## Key Features
31
+ ## What the domain provides
33
32
 
34
- - **Named template registry** — `RegisterHtmlTemplate` records a template name at import time, so the whole process shares one catalogue of renderable templates.
35
- - **Documented props** — the type parameter on `RegisterHtmlTemplate` travels with the returned `HtmlTemplateRef`, recording on the reference itself which props the template expects.
36
- - **Remote rendering over HTTP** — `GenerateHtml` calls the configured render endpoint with a signed service token and resolves to the returned HTML string.
37
- - **Registry inspection** — `GetHtmlTemplate` and `GetAllHtmlTemplates` read back what has been registered, one name at a time or in bulk.
38
- - **Configurable endpoint and token** — `HtmlRenderConfig` types the `htmlRender` block of the DMS module configuration: endpoint URL, signing secret, token lifetime.
33
+ - **A named template registry.** `RegisterHtmlTemplate` records a template name at import time, so the whole process shares one list of renderable templates.
34
+ - **Documented props.** The type parameter of `RegisterHtmlTemplate` travels with the returned `HtmlTemplateRef`, so the reference records which props the template expects.
35
+ - **Rendering over HTTP.** `GenerateHtml` calls the configured render endpoint with a signed service token and resolves to the returned HTML string.
36
+ - **Registry lookups.** `GetHtmlTemplate` and `GetAllHtmlTemplates` read back what is registered, one name at a time or all at once.
37
+ - **Endpoint and token settings.** `HtmlRenderConfig` types the `htmlRender` block of the DMS module configuration: the endpoint URL, the signing secret and the token lifetime.
39
38
 
40
39
  ## Dependencies
41
40
 
42
- This interface relies on the following AntelopeJS packages:
41
+ This interface relies on one other AntelopeJS package:
43
42
 
44
- - [@antelopejs/interface-core](https://github.com/AntelopeJS/interface-core) - `InterfaceFunction` and `RegisteringProxy`, the primitives the interface is built from.
43
+ - [`@antelopejs/interface-core`](https://github.com/AntelopeJS/interface-core) provides `InterfaceFunction` and `RegisteringProxy`, the two helpers the interface is built from.
45
44
 
46
- ## Quick Start
45
+ ## Quick start
47
46
 
48
- Register a template next to the code that uses it, then render it with props.
47
+ Register a template next to the code that uses it, then render it with props. Registration runs when the module is imported, so keep the reference in module scope.
49
48
 
50
49
  ```typescript
51
50
  import {
@@ -58,7 +57,6 @@ interface WelcomeProps {
58
57
  activationLink: string;
59
58
  }
60
59
 
61
- // Registration happens at import time; keep the ref in module scope.
62
60
  const WelcomeTemplate = RegisterHtmlTemplate<WelcomeProps>("EmailWelcome");
63
61
 
64
62
  export async function buildWelcomeHtml(
@@ -69,13 +67,13 @@ export async function buildWelcomeHtml(
69
67
  }
70
68
  ```
71
69
 
72
- `GenerateHtml` needs the render endpoint to be reachable, so it belongs in request or job code — do not call it at import time.
70
+ `GenerateHtml` needs a reachable render endpoint, so call it from request or job code, never at import time.
73
71
 
74
- ## RegisterHtmlTemplate
72
+ ## `RegisterHtmlTemplate`
75
73
 
76
74
  Registers a template name and returns the reference you render with.
77
75
 
78
- ```typescript
76
+ ```typescript fragment
79
77
  function RegisterHtmlTemplate<TProps = unknown>(
80
78
  name: string,
81
79
  ): HtmlTemplateRef<TProps>;
@@ -83,9 +81,9 @@ function RegisterHtmlTemplate<TProps = unknown>(
83
81
 
84
82
  | Parameter | Description |
85
83
  | --------- | ----------- |
86
- | `name` | The template identifier. It is the key the registry stores the template under, and the `templateName` sent to the render endpoint. |
84
+ | `name` | The template identifier. The registry stores the template under this key, and `GenerateHtml` sends it to the render endpoint as `templateName`. |
87
85
 
88
- The type parameter `TProps` declares the props the template expects. It is carried by the returned [`HtmlTemplateRef`](#htmltemplateref) as a record of the template's shape; [`GenerateHtml`](#generatehtml) takes its `props` argument as `unknown`, so the compiler does not check the two against each other at the call site. Omitting `TProps` leaves the reference's props type as `unknown`.
86
+ The type parameter `TProps` declares the props the template expects. The returned [`HtmlTemplateRef`](#htmltemplateref) carries it as a record of the template's shape. [`GenerateHtml`](#generatehtml) takes its `props` argument as `unknown`, so the compiler does not check the props against `TProps` at the call site. Without `TProps`, the reference's props type is `unknown`.
89
87
 
90
88
  ```typescript
91
89
  import { RegisterHtmlTemplate } from "@antelopejs/interface-dms/html-render";
@@ -100,51 +98,82 @@ export const InvoiceTemplate =
100
98
  RegisterHtmlTemplate<InvoiceProps>("EmailInvoice");
101
99
  ```
102
100
 
103
- The registry is keyed by name, so registering an existing name replaces the earlier entry. The DMS registers seven names for its own emails, listed in [Built-in Emails](/docs/interfaces/html-render/built-in-emails) — pick different names for your templates.
101
+ The registry is keyed by name, so registering an existing name replaces the earlier entry. The DMS registers seven names for its own emails, listed in [Built-in emails](/docs/interfaces/html-render/built-in-emails). Give your templates other names.
104
102
 
105
- Call `RegisterHtmlTemplate` at module scope, in a file your module imports during startup. The returned reference is a plain object, so exporting it from that module is the usual way to share it.
103
+ Call `RegisterHtmlTemplate` at module scope, in a file your module imports during startup. The returned reference is a plain object, so export it from that file to share it.
106
104
 
107
- The subpath also exports an `internal` namespace holding the registering proxy this function writes through. It is implementation detail — register through `RegisterHtmlTemplate`.
105
+ The subpath also exports an `internal` namespace that holds the registering proxy this function writes through. It is marked `@internal`. Register through `RegisterHtmlTemplate` instead.
108
106
 
109
- ## GenerateHtml
107
+ ## `GenerateHtml`
110
108
 
111
109
  Renders a registered template and resolves to the resulting HTML.
112
110
 
113
- ```typescript
111
+ ```typescript fragment
114
112
  const GenerateHtml: (
115
113
  template: HtmlTemplateRef<unknown>,
116
114
  props: unknown,
115
+ language?: string,
117
116
  ) => Promise<string>;
118
117
  ```
119
118
 
120
119
  | Parameter | Description |
121
120
  | --------- | ----------- |
122
121
  | `template` | The reference returned by `RegisterHtmlTemplate`. Pass the reference, not the name string. |
123
- | `props` | The data the template renders with, serialised into the request body. The render endpoint expects a JSON object and rejects anything else with a `400`, so keep the props type an object shape. Typed `unknown` at this boundary, so the props type recorded on the reference is not enforced here — match it yourself. |
122
+ | `props` | The data the template renders with, serialized into the request body. The DMS render endpoint expects a JSON object and answers `400` to anything else, so keep the props type an object shape. The parameter is typed `unknown`, so the compiler does not enforce the props type recorded on the reference. Match it yourself. |
123
+ | `language` | Optional. The language to render in, sent in the `x-content-language` header. The DMS render endpoint renders in the language its first two letters name when a locale file exists for it, and in English otherwise. Without it, the template renders in English. |
124
124
 
125
125
  ```typescript
126
- import { GenerateHtml } from "@antelopejs/interface-dms/html-render";
127
- import { InvoiceTemplate } from "./templates";
128
-
129
- export async function renderInvoice(invoiceNumber: string): Promise<string> {
130
- return GenerateHtml(InvoiceTemplate, {
131
- invoiceNumber,
132
- amount: "240.00 EUR",
133
- dueDate: "2026-08-15",
134
- });
126
+ import {
127
+ GenerateHtml,
128
+ RegisterHtmlTemplate,
129
+ } from "@antelopejs/interface-dms/html-render";
130
+
131
+ interface InvoiceProps {
132
+ invoiceNumber: string;
133
+ amount: string;
134
+ dueDate: string;
135
+ }
136
+
137
+ const InvoiceTemplate = RegisterHtmlTemplate<InvoiceProps>("EmailInvoice");
138
+
139
+ export async function renderInvoice(
140
+ invoiceNumber: string,
141
+ language: string,
142
+ ): Promise<string> {
143
+ return GenerateHtml(
144
+ InvoiceTemplate,
145
+ { invoiceNumber, amount: "240.00 EUR", dueDate: "2026-08-15" },
146
+ language,
147
+ );
135
148
  }
136
149
  ```
137
150
 
138
- The call `POST`s `{ templateName, props }` as JSON to `htmlRender.renderEndpoint`, with a service token signed from `htmlRender.serviceSecret` in the `x-dms-service-token` header, and reads the `html` field out of the JSON response.
151
+ The call sends `{ templateName, props }` as JSON to `htmlRender.renderEndpoint`. It carries a service token signed with `htmlRender.serviceSecret` in the `x-dms-service-token` header, and it reads the `html` field of the JSON response.
152
+
153
+ `GenerateHtml` does not look the name up in the registry before the call. It sends whatever name the reference carries. When the render endpoint does not know the name, the promise rejects. The backend runs no missing-template check of its own.
139
154
 
140
- `GenerateHtml` does not consult the registry before calling out: it sends whatever name the reference carries. A name the render endpoint does not know surfaces as a rejected promise, not as a missing-template check on the backend.
155
+ The promise rejects in three cases:
141
156
 
142
- On a non-2xx response the promise rejects with an `Error` whose message starts with `Failed to render HTML template:`, followed by the response body's `error` string, its `statusMessage`, or `HTTP <status>`. When the endpoint is unreachable the underlying `fetch` failure propagates instead, without that prefix. Wrap the call when a failed render must not abort the surrounding operation:
157
+ - On a non-2xx response, with an `Error` whose message starts with `Failed to render HTML template:`. The rest is the `message` string of the response body, else its `error` string, else its `statusMessage`, else `HTTP <status>`. The DMS render endpoint answers `401` with `Invalid service token`, `400` with `Invalid request body`, and `404` with `Template "<name>" not found or render failed`.
158
+ - On a 2xx response without an `html` string, with the message `Failed to render HTML template: the render service answered no html`.
159
+ - When the endpoint is unreachable, with the `fetch` error itself, without that prefix.
160
+
161
+ To keep a failed render from stopping the surrounding operation, catch the rejection:
143
162
 
144
163
  ```typescript
145
164
  import { Logging } from "@antelopejs/interface-core/logging";
146
- import { GenerateHtml } from "@antelopejs/interface-dms/html-render";
147
- import { InvoiceTemplate } from "./templates";
165
+ import {
166
+ GenerateHtml,
167
+ RegisterHtmlTemplate,
168
+ } from "@antelopejs/interface-dms/html-render";
169
+
170
+ interface InvoiceProps {
171
+ invoiceNumber: string;
172
+ amount: string;
173
+ dueDate: string;
174
+ }
175
+
176
+ const InvoiceTemplate = RegisterHtmlTemplate<InvoiceProps>("EmailInvoice");
148
177
 
149
178
  export async function renderInvoiceOrNull(
150
179
  invoiceNumber: string,
@@ -156,17 +185,18 @@ export async function renderInvoiceOrNull(
156
185
  dueDate: "2026-08-15",
157
186
  });
158
187
  } catch (error) {
159
- Logging.Error(`Invoice render failed: ${(error as Error).message}`);
188
+ const reason = error instanceof Error ? error.message : String(error);
189
+ Logging.Error(`Invoice render failed: ${reason}`);
160
190
  return null;
161
191
  }
162
192
  }
163
193
  ```
164
194
 
165
- ## GetHtmlTemplate
195
+ ## `GetHtmlTemplate`
166
196
 
167
- Looks a single template up in the registry.
197
+ Looks one template up in the registry.
168
198
 
169
- ```typescript
199
+ ```typescript fragment
170
200
  const GetHtmlTemplate: (key: string) => Promise<HtmlTemplateInfo | undefined>;
171
201
  ```
172
202
 
@@ -184,11 +214,11 @@ export async function isTemplateRegistered(name: string): Promise<boolean> {
184
214
  }
185
215
  ```
186
216
 
187
- ## GetAllHtmlTemplates
217
+ ## `GetAllHtmlTemplates`
188
218
 
189
219
  Resolves to every registered template, keyed by name.
190
220
 
191
- ```typescript
221
+ ```typescript fragment
192
222
  const GetAllHtmlTemplates: () => Promise<Record<string, HtmlTemplateInfo>>;
193
223
  ```
194
224
 
@@ -200,9 +230,9 @@ export async function listTemplateNames(): Promise<string[]> {
200
230
  }
201
231
  ```
202
232
 
203
- The result is a fresh object built from the registry, so mutating it does not change what is registered. Because registration happens as modules are imported, call this after startup rather than at import time if you want the complete set.
233
+ The DMS builds a new object from the registry on each call, so changing the result does not change what is registered. Registration happens as modules are imported, so call this function after startup to get the complete set.
204
234
 
205
- ## HtmlTemplateInfo
235
+ ## `HtmlTemplateInfo`
206
236
 
207
237
  What the registry stores for each template, and what `GetHtmlTemplate` and `GetAllHtmlTemplates` resolve to.
208
238
 
@@ -224,16 +254,16 @@ export async function describeTemplates(): Promise<string> {
224
254
  }
225
255
  ```
226
256
 
227
- ## HtmlTemplateRef
257
+ ## `HtmlTemplateRef`
228
258
 
229
- The handle `RegisterHtmlTemplate` returns and `GenerateHtml` accepts.
259
+ The reference `RegisterHtmlTemplate` returns and `GenerateHtml` accepts.
230
260
 
231
261
  | Field | Type | Description |
232
262
  | ----- | ---- | ----------- |
233
263
  | `name` | `string` | The template name sent to the render endpoint. |
234
- | `_propsType` | `TProps \| undefined` | Type-only carrier for the props type. It is never assigned a value; do not read it. |
264
+ | `_propsType` | `TProps \| undefined` | Optional field that only carries the props type. `RegisterHtmlTemplate` never sets it. Do not read it. |
235
265
 
236
- Annotate the reference explicitly when you collect handles in a shared module and want the props type visible in that module's declarations:
266
+ To keep the props type visible in the declarations of a module that collects references, annotate the reference:
237
267
 
238
268
  ```typescript
239
269
  import type { HtmlTemplateRef } from "@antelopejs/interface-dms/html-render";
@@ -248,24 +278,23 @@ export const AlertTemplate: HtmlTemplateRef<AlertProps> =
248
278
  RegisterHtmlTemplate<AlertProps>("EmailAlert");
249
279
  ```
250
280
 
251
- ## HtmlRenderConfig
281
+ ## `HtmlRenderConfig`
252
282
 
253
283
  Types the `htmlRender` block of the DMS module configuration in `antelope.config.ts`.
254
284
 
255
285
  | Option | Type | Description |
256
286
  | ------ | ---- | ----------- |
257
- | `renderEndpoint` | `string` | Absolute URL `GenerateHtml` posts to. Default `http://localhost:3001/api/html/render`. |
258
- | `serviceSecret` | `string` | Secret used to sign the service token that authenticates the render call. Default `"dev"`, which the DMS refuses in production. |
259
- | `serviceTokenLifetime` | `number` | Lifetime of that service token, in milliseconds. The frontend render server rejects a token valid for more than five minutes. Default `300000` (5 minutes). |
287
+ | `renderEndpoint` | `string` | The absolute URL `GenerateHtml` posts to. Default `http://localhost:3001/api/html/render`. |
288
+ | `serviceSecret` | `string` | The secret that signs the service token of each render call. Default `"dev"`. The DMS refuses a production start when the value is empty or `"dev"`. |
289
+ | `serviceTokenLifetime` | `number` | The lifetime of that service token, in milliseconds. The frontend render server rejects a token valid for more than five minutes. Default `300000` (five minutes). |
260
290
 
261
- The DMS merges the block you supply over those defaults, so a configuration only needs the keys it changes:
291
+ The DMS merges the block you supply over those defaults, so a configuration needs only the keys it changes. In this example, `envOverrides` reads the secret from `DMS_HTML_RENDER_SECRET` at start:
262
292
 
263
293
  ```typescript
264
294
  import { defineConfig } from "@antelopejs/interface-core/config";
265
295
 
266
296
  export default defineConfig({
267
297
  name: "acme-admin",
268
- // Read at start; a production start without it is refused by the DMS.
269
298
  envOverrides: {
270
299
  DMS_HTML_RENDER_SECRET: "modules.dms.config.htmlRender.serviceSecret",
271
300
  },
@@ -274,7 +303,7 @@ export default defineConfig({
274
303
  source: {
275
304
  type: "package",
276
305
  package: "@antelopejs/dms",
277
- version: ">=0.4.3 <1.0.0",
306
+ version: "latest",
278
307
  },
279
308
  config: {
280
309
  htmlRender: {
@@ -286,11 +315,11 @@ export default defineConfig({
286
315
  });
287
316
  ```
288
317
 
289
- The Configuration chapter of the DMS guide covers where this block sits alongside the other DMS options.
318
+ [Configuration](/docs/building/configuration#template-rendering-in-htmlrender) lists this block with the other DMS options.
290
319
 
291
- ## Email Layout
320
+ ## Email layout
292
321
 
293
- The DMS ships the frame of its own emails as `EmailLayout`, which a module's email template (registered through `dms.email.ts`) may reuse: the branding logo, a card, an optional heading and a `footer` slot below a separator.
322
+ The DMS ships the frame of its own emails as `EmailLayout`. A module's email template, registered through its `dms.email.ts`, can reuse it. The frame draws the branding logo and a white card that holds an optional heading, the content, and a `footer` slot below a separator.
294
323
 
295
324
  ```vue
296
325
  <script setup lang="ts">
@@ -319,34 +348,43 @@ defineProps<{ subject: string; preheader: string; locale: string }>();
319
348
 
320
349
  | Prop | Type | Description |
321
350
  | ---- | ---- | ----------- |
322
- | `title` | `string` (optional) | Drawn as the `h1` heading. Without it the email has no heading. |
351
+ | `title` | `string` (optional) | Drawn as the `h1` heading. Without it, the email has no heading. |
323
352
  | `lang` | `string` (optional) | The `lang` of the document. Default `"en"`. |
324
- | `preheader` | `string` (optional) | Inbox preview text, hidden in the body. |
353
+ | `preheader` | `string` (optional) | The inbox preview text, hidden in the body. |
325
354
  | `divider` | `boolean` (optional) | Draws the separator above the `footer` slot. Default `true`. |
326
- | `logoAlt` | `string` (optional) | Alternative text of the logo. Default `"Logo"`; pass `""` for a decorative logo. |
327
- | `publicLogoOnly` | `boolean` (optional) | Leaves the logo out unless a mail client can load it (see below). Default `false`. |
355
+ | `logoAlt` | `string` (optional) | The alternative text of the logo. Default `"Logo"`. Pass `""` for a decorative logo. |
356
+ | `publicLogoOnly` | `boolean` (optional) | Leaves the logo out unless a mail client can load it. The rules follow the table. Default `false`. |
328
357
 
329
- The `head` slot adds elements to the document head (a `<title>`, a viewport `<meta>`).
358
+ The `head` slot adds elements to the document head, such as a `<title>` or a viewport `<meta>`.
330
359
 
331
- The logo is `branding.logo.email.light` from the app config, or `branding.logo.default.light` without an email logo, made absolute against `public.dms.clientBaseUrl` when it starts with `/`. A recipient's mail client fetches it from the internet, and Gmail and Outlook refuse SVG: with `publicLogoOnly`, the layout leaves the logo out when it is an SVG, is not an `http(s)` URL, or is served from a host a mail client cannot reach (`localhost`, `*.local`, `*.internal`, a loopback, private or link-local address), rather than drawing a broken image. Set a PNG logo and a public `clientBaseUrl` for it to show. A `branding.logo.email.dark` goes through the same rules and, when it differs from the light one, is offered to the mail clients in dark mode: the logo is then drawn as `<picture><source media="(prefers-color-scheme: dark)" srcset="…"><img …></picture>`, and a client that ignores the source draws the light image. The same check is exported as `emailLogoUrl(logoPath, clientBaseUrl)` from `#dms-layout/app/emails/components/index`, for a template that draws its own header.
360
+ The logo is `branding.logo.email.light` from the app config, or `branding.logo.default.light` when no email logo is set. A logo path that starts with `/` is made absolute against `public.dms.clientBaseUrl`, or against the `DMS_CLIENT_BASE_URL` environment variable of the frontend server when it is set.
332
361
 
333
- ### A rendered custom digest
362
+ A recipient's mail client fetches the logo from the internet, and Gmail and Outlook refuse SVG. With `publicLogoOnly`, the layout leaves the logo out instead of drawing a broken image when the logo:
363
+
364
+ - is an SVG,
365
+ - does not resolve to an `http` or `https` URL, or
366
+ - is served from a host a mail client cannot reach: `localhost`, a `*.localhost`, `*.local` or `*.internal` name, or a loopback, private or link-local address.
334
367
 
335
- The playground's `EmailPlaygroundDigest` reuses this layout with a preheader, `divider="false"` and `public-logo-only`. Here is its actual rendered HTML in English, with fictitious activity for Alex Morgan. With the default SVG logo, the header image is intentionally absent.
368
+ Set a logo that is not an SVG, and a public `clientBaseUrl`, for the logo to show.
336
369
 
337
- ![Rendered custom playground digest without a logo or footer divider](../images/emails-digest.png)
370
+ A `branding.logo.email.dark` goes through the same rules. When it differs from the light logo, the layout offers it to mail clients in dark mode. The logo is then drawn as `<picture><source media="(prefers-color-scheme: dark)" srcset="…"><img …></picture>`, and a client that ignores the source draws the light image. The layout has no dark styles of its own. The card stays white on its pale grey background (`#f6f9fc`) in both modes, and only the logo changes.
338
371
 
339
- The same template below uses two demonstration PNG logos supplied through the app config. The light-mode capture selects the teal logo; the dark-mode capture selects the navy logo. The reserved demo hostname is mapped to a local image server for these captures, so they do not establish that a recipient can fetch the logo from the internet.
372
+ `#dms-layout/app/emails/components/index` exports the same check as `emailLogoUrl(logoPath, clientBaseUrl)`, for a template that draws its own header. It returns the absolute URL, or `""` when the logo fails the rules.
373
+
374
+ ### A rendered custom digest
340
375
 
341
- ![Custom digest with the light demonstration logo in browser light mode](../images/emails-digest-logo-light.png)
376
+ The playground's `EmailPlaygroundDigest` template (`packages/dms/playground/frontend-vue/app/emails/EmailPlaygroundDigest.vue`) reuses this layout with a preheader, `:divider="false"` and `public-logo-only`. Rendered in English for a user named Alex Morgan, it contains:
342
377
 
343
- ![Custom digest with the dark demonstration logo in browser dark mode](../images/emails-digest-logo-dark.png)
378
+ - the heading **Your playground digest**,
379
+ - the greeting `Hello Alex Morgan,`,
380
+ - the line `<count> things happened in the playground since your last visit.`, with the same count in the hidden preheader,
381
+ - the footer `Sent by the DMS playground's emails demo.`, with no separator above it.
344
382
 
345
- Dark mode changes the selected logo, not the card: `EmailLayout` keeps its white card and pale background in both browser modes. Without a dark logo source, the unbranded examples look identical in both modes. These captures use the real frontend render endpoint and show the returned HTML directly, not the playground's code viewer. No email is sent. Mail clients may strip `<picture>`, block remote images or transform colors; test delivery and appearance in your target clients separately.
383
+ With the default SVG logo, the email has no logo at all. With a PNG light logo and a different PNG dark logo set in the app config, a mail client in light mode draws the light logo above the heading, and a client that follows dark mode draws the dark one. The card and the text keep the same colors in both modes. A local render does not show whether a recipient's mail client can fetch the logo from the internet.
346
384
 
347
- ## Sending Rendered HTML as Email
385
+ ## Send rendered HTML as email
348
386
 
349
- Rendering and sending are separate steps. Render with `GenerateHtml`, then hand the string to the standard AntelopeJS email interface, which needs an implementing module (such as `@antelopejs/nodemailer`) loaded in your project configuration.
387
+ Rendering and sending are separate steps. Render with `GenerateHtml`, then pass the string to the AntelopeJS email interface, `@antelopejs/interface-email`. That interface needs an implementing module, such as `@antelopejs/nodemailer`, loaded in your project configuration.
350
388
 
351
389
  ```typescript
352
390
  import { Send } from "@antelopejs/interface-email";
@@ -381,4 +419,4 @@ export async function sendReceipt(
381
419
  }
382
420
  ```
383
421
 
384
- This is the shape the DMS uses for its own account emails, and it keeps the two failure modes apart: a rejected `GenerateHtml` means the HTML was never produced, while a `success: false` response from `Send` means it was produced but not delivered.
422
+ The DMS sends its own account emails the same way, and the two steps fail apart. A rejected `GenerateHtml` means the DMS never produced the HTML. A `success: false` response from `Send` means the HTML exists but the email was not delivered.
@@ -1,99 +1,119 @@
1
1
  ---
2
- title: "Built-in Emails"
3
- description: "The DMS sends seven emails of its own."
2
+ title: "Built-in emails"
3
+ description: "The seven emails the DMS sends: when each one goes out, its props, what it says, its language, its logo, and how to replace it."
4
+ mode: reference
4
5
  ---
5
6
 
6
- # Built-in Emails
7
-
8
7
  ## Overview
9
8
 
10
- The DMS sends seven emails of its own. Each one is a template registered through [`RegisterHtmlTemplate`](/docs/interfaces/html-render/introduction#registerhtmltemplate), rendered by the frontend server from a Vue component in `frontend-vue/layers/dms-layout/app/emails/`, and delivered through `@antelopejs/interface-email`. They share one frame, `EmailLayout`: the branding logo, a white card, a heading, the body, a separator and a footer.
9
+ The DMS sends seven emails of its own. Each one is a template registered through [`RegisterHtmlTemplate`](/docs/interfaces/html-render/introduction#registerhtmltemplate). The frontend server renders it from a Vue component in `frontend-vue/layers/dms-layout/app/emails/`, and the DMS delivers it through `Send` from `@antelopejs/interface-email`. The DMS sets no sender address, so every email goes out from the default sender of the email module your project loads.
11
10
 
12
11
  | Template | Sent when | Sent to | English subject |
13
12
  | -------- | --------- | ------- | ------- |
14
- | `EmailUserValidation` | A user signs up while `auth.mustValidateEmail` is on, or asks for a new code | The new account's address | `Verify Your Email` |
13
+ | `EmailUserValidation` | With `auth.mustValidateEmail` on, a user who has not validated their email asks for a code, or an admin resends it from the Members page | The account's address | `Verify Your Email` |
15
14
  | `EmailResetPassword` | A user asks for a password reset | The account's address | `Reset Your Password` |
16
- | `EmailTwoFactor` | A user signs in with email as their second factor | The account's address | `Your verification code` |
17
- | `EmailAdminInvite` | A member is invited to the platform or to a workspace | The invited address | `You're invited to join <workspace> on <platform>`, shorter when a name is unknown |
15
+ | `EmailTwoFactor` | A user signs in with email as their second factor, adds email as a second-factor method, or asks for a new code | The account's address | `Your verification code` |
16
+ | `EmailAdminInvite` | A member is invited to the platform or to a workspace | The invited address | See [`EmailAdminInvite`](#emailadmininvite) |
18
17
  | `EmailChangeVerification` | A user asks to change their sign-in email | The new address | `Confirm your new sign-in email` |
19
18
  | `EmailChangeNotice` | The same request | The current address | `Your sign-in email is being changed` |
20
19
  | `EmailExportReady` | A data export delivered by email has finished | The user who asked for it | `Your data export is ready` |
21
20
 
22
- Each email, subject included, is written in the recipient's language, English or French (see [Language](#language)).
21
+ An account created through an invitation link starts validated, so it receives no `EmailUserValidation`.
22
+
23
+ Each email, subject included, is written in the recipient's language, English or French. See [Language](#language).
23
24
 
24
- These names are taken: pick other names for your own emails. To change what a built-in email looks like, see [Replacing a built-in email](#replacing-a-built-in-email).
25
+ These names are taken. Give your own emails other names. To change what a built-in email looks like, see [Replacing a built-in email](#replacing-a-built-in-email).
25
26
 
26
27
  ## Props
27
28
 
28
- What the backend passes to each template. A template that replaces a built-in one receives the same props.
29
+ The backend passes these props to each template. A template that replaces a built-in one receives the same props.
29
30
 
30
31
  | Template | Props |
31
32
  | -------- | ----- |
32
33
  | `EmailUserValidation` | `userName`, `validationCode`, `expiresInHours` (from `auth.emailValidationTokenLifetime`), `expiresIn` |
33
34
  | `EmailResetPassword` | `userName`, `resetCode`, `expiresInHours` (from `auth.passwordRecoverTokenLifetime`), `expiresIn` |
34
35
  | `EmailTwoFactor` | `userName`, `verificationCode` |
35
- | `EmailAdminInvite` | `email`, `signupLink`, `expiresInDays`, and when known `workspaceName`, `inviterName`, `platformName` (`meta.title`) |
36
+ | `EmailAdminInvite` | `email`, `signupLink`, `expiresInDays`, and when known `workspaceName`, `inviterName` and `platformName` (`meta.title`) |
36
37
  | `EmailChangeVerification` | `userName`, `newEmail`, `code`, `expiresInHours` (from `auth.emailValidationTokenLifetime`), `expiresIn` |
37
38
  | `EmailChangeNotice` | `userName`, `newEmail` |
38
39
  | `EmailExportReady` | `userName`, `downloadLink`, `expiresInHours`, `expiresIn`, `partial`, `failedSources` (the export sources that could not be included) |
39
40
 
40
- `userName` is the user's name, or their email address when they have none. `expiresInHours` is the lifetime as a number, for the template to word in the recipient's language; `expiresIn` is the same lifetime already worded in English (`24 hours`), kept for templates written before `expiresInHours`.
41
-
42
- ## Rendered examples
43
-
44
- These captures show HTML returned by the DMS frontend's real render endpoint, opened directly in Chromium, rather than the playground's HTML code viewer. All names, codes, addresses and links are fictitious; no email is sent. The default SVG logo is intentionally omitted by `publicLogoOnly`.
45
-
46
- ### EmailUserValidation
47
-
48
- The signup email asks Alex to verify their address with a six-digit code, valid for 24 hours.
41
+ `userName` is the user's name, or their email address when they have none. `expiresInHours` is the lifetime as a number, for the template to word in the recipient's language. `expiresIn` is the same lifetime already worded in English (`24 hours`). It stays for templates written before `expiresInHours` existed.
49
42
 
50
- ![Rendered signup verification email with a six-digit code and a 24-hour expiry](../images/emails-user-validation.png)
43
+ ## What each email contains
51
44
 
52
- ### EmailResetPassword
45
+ Every built-in email uses the same frame, `EmailLayout`. A white card with rounded corners sits on a pale grey background. The card holds the logo when a mail client can load it (see [Logo](#logo)), a centered heading, the body text, a separator, and a footer in small, centered grey text. With the default SVG logo, the emails carry no logo.
53
46
 
54
- The password-reset email uses a separate code and the configured recovery lifetime: two hours in this example.
47
+ A code sits in a grey box under the label `Your verification code is:`, in large monospaced characters. A link is a dark button with white text. An expiry line in small grey text follows the code or the button.
55
48
 
56
- ![Rendered password-reset email with a two-hour code expiry](../images/emails-reset-password.png)
49
+ The English text of each email follows, with the props in angle brackets. A lifetime in hours reads `1 hour` for one hour and `N hours` otherwise.
57
50
 
58
- ### EmailTwoFactor
51
+ ### `EmailUserValidation`
59
52
 
60
- The second-factor email provides a sign-in code. Its footer tells the recipient to ignore the message if they did not attempt to sign in.
53
+ - Heading: `Verify Your Email`.
54
+ - Body: `Hello <userName>,`, then `Thank you for signing up! Enter the verification code below to verify your email address:`.
55
+ - Code: `validationCode`, six uppercase letters and digits.
56
+ - Expiry: `This code expires in <expiresInHours>.` The default `auth.emailValidationTokenLifetime` gives 24 hours.
57
+ - Footer: `If you didn't create an account, you can safely ignore this email.`
61
58
 
62
- ![Rendered email second-factor verification code](../images/emails-two-factor.png)
59
+ ### `EmailResetPassword`
63
60
 
64
- ### EmailAdminInvite
61
+ - Heading: `Reset Your Password`.
62
+ - Body: `Hello <userName>,`, then `We received a request to reset your password. Enter the verification code below to reset your password:`.
63
+ - Code: `resetCode`, six uppercase letters and digits, separate from the validation code.
64
+ - Expiry: `This code expires in <expiresInHours>.` The default `auth.passwordRecoverTokenLifetime` gives 24 hours.
65
+ - Footer: `If you didn't request a password reset, you can safely ignore this email. Your password will remain unchanged.`
65
66
 
66
- The invitation names the inviter, workspace and platform when they are known. Its button uses a fictitious destination here.
67
+ ### `EmailTwoFactor`
67
68
 
68
- ![Rendered invitation from Jordan Lee to Northwind Studio on Acme Dashboard](../images/emails-admin-invite.png)
69
+ - Heading: `Your verification code`.
70
+ - Body: `Hello <userName>,`, then `Use this verification code to finish signing in:`.
71
+ - Code: `verificationCode`, six digits. The email shows no expiry line.
72
+ - Footer: `If you did not attempt to sign in, you can safely ignore this email.`
69
73
 
70
- ### EmailChangeVerification
74
+ ### `EmailAdminInvite`
71
75
 
72
- The new address receives the code that confirms the sign-in email change.
76
+ The invitation names the inviter, the workspace and the platform when the backend knows them.
73
77
 
74
- ![Rendered confirmation email naming the new sign-in address and verification code](../images/emails-change-verification.png)
78
+ - Subject: `<inviterName> invited you to join <target>` when the inviter is known, `You're invited to join <target>` otherwise, and `You're Invited to Join` when there is no target. The target is `<workspaceName> on <platformName>` when both are known, else whichever one is known.
79
+ - Heading: `You're invited to <workspaceName>`, or `You're Invited!` without a workspace.
80
+ - Body: `Hello,`, then the invitation line and `Click the button below to create your account:`. The invitation line is `<inviterName> invited you to join <target>.` when the inviter is known, `You've been invited to join <target>.` otherwise, and `You've been invited to join our platform.` when there is no target.
81
+ - Button: `Create Account`, which opens `signupLink`.
82
+ - Expiry: `This invitation expires in <expiresInDays> days.` The DMS sends 7 days.
83
+ - Footer: `If you didn't expect this invitation, you can safely ignore this email.`
75
84
 
76
- ### EmailChangeNotice
85
+ ### `EmailChangeVerification`
77
86
 
78
- The current address receives a notice, not the confirmation code. It tells the recipient what to do if they did not request the change.
87
+ The new address receives the code that confirms the change.
79
88
 
80
- ![Rendered security notice sent to the current sign-in address](../images/emails-change-notice.png)
89
+ - Heading: `Confirm your new sign-in email`.
90
+ - Body: `Hello <userName>,`, then `Enter the code below to make <newEmail> the sign-in email of your account:`.
91
+ - Code: `code`, six digits.
92
+ - Expiry: `This code expires in <expiresInHours>.`, from `auth.emailValidationTokenLifetime`.
93
+ - Footer: `If you didn't ask for this change, you can safely ignore this email: your account keeps its current address.`
81
94
 
82
- ### EmailExportReady
95
+ ### `EmailChangeNotice`
83
96
 
84
- A complete export provides a download button and an expiry. A partial export also names the sources that could not be included.
97
+ The current address receives a notice, not the confirmation code.
85
98
 
86
- ![Rendered complete data-export email with a download button and expiry](../images/emails-export-ready.png)
99
+ - Heading: `Your sign-in email is being changed`.
100
+ - Body: `Hello <userName>,`, then `Someone signed in to your account asked to change its sign-in email to <newEmail>. The change takes effect once that address is confirmed.`
101
+ - Footer: `If this wasn't you, change your password now and sign out the other sessions in Security: your account keeps this address until the new one is confirmed.`
87
102
 
88
- ![Rendered partial data-export email warning that Activity history is missing](../images/emails-export-partial.png)
103
+ ### `EmailExportReady`
89
104
 
90
- These are browser previews, not Gmail, Outlook or Apple Mail compatibility tests. See [Email Layout](/docs/interfaces/html-render/introduction#email-layout) for the custom digest, logo variants and the limits of dark-mode support.
105
+ - Heading: `Your data export is ready`.
106
+ - Body: `Hello <userName>,`, then `Your data export has been prepared. Use the button below to download the archive:`.
107
+ - Button: `Download export`, which opens `downloadLink`.
108
+ - Expiry: `This download link expires in <expiresInHours>.` An export link lasts 24 hours.
109
+ - Partial export: when `partial` is true, a further paragraph says the export is **incomplete**, lists `failedSources` separated by commas, and asks the recipient to request a new export if they need those parts.
110
+ - Footer: `If you didn't request this export, you can safely ignore this email.`
91
111
 
92
112
  ## Language
93
113
 
94
- Each email is written in the recipient's language: the account's `language`, or for an invitation the language it was sent in. English and French are supported; any other language falls back to English.
114
+ Each email is written in the recipient's language. That is the account's `language`, or for an invitation the language the invitation was sent in. English and French are supported, and any other language falls back to English.
95
115
 
96
- The body text lives in the `dms-layout` locale files (`i18n/locales/layout-*.json`), under one key per email (`emails.user_validation`, `emails.reset_password`, `emails.two_factor`, `emails.admin_invite`, `emails.email_change_verification`, `emails.email_change_notice`, `emails.export_ready`) plus the shared `emails.common`. Locale files merge in module priority order, so a module registered with a priority above the DMS's (`0`) can reword any of these keys in its own `i18n/locales/*.json`:
116
+ The body text lives in the `dms-layout` locale files (`i18n/locales/layout-*.json`). Each email has one key: `emails.user_validation`, `emails.reset_password`, `emails.two_factor`, `emails.admin_invite`, `emails.email_change_verification`, `emails.email_change_notice` and `emails.export_ready`. The shared texts sit under `emails.common`. Locale files merge in module priority order. A module registered with a priority above the DMS's (`0`) can reword any of these keys in its own `i18n/locales/*.json`:
97
117
 
98
118
  ```json [frontend-vue/i18n/locales/acme-en-GB.json]
99
119
  {
@@ -105,13 +125,13 @@ The body text lives in the `dms-layout` locale files (`i18n/locales/layout-*.jso
105
125
  }
106
126
  ```
107
127
 
108
- The subject is set by the backend, which sends it in the same language. It cannot be reworded from a module.
128
+ The backend sets the subject, in the same language. A module cannot reword it.
109
129
 
110
130
  ## Logo
111
131
 
112
- Every built-in email draws `branding.logo.email.light` from the app config, or `branding.logo.default.light` when no email logo is set (see [Email Layout](/docs/interfaces/html-render/introduction#email-layout)), with `publicLogoOnly` on. The logo shows only when a mail client can load it: a PNG or JPEG served from a public `http(s)` address. The default DMS logo is an SVG, which Gmail and Outlook refuse, so out of the box the emails carry no logo. To show yours, set an `email` logo in your layer's `app/app.config.ts`, and serve the dashboard from a public `clientBaseUrl`:
132
+ Every built-in email draws `branding.logo.email.light` from the app config, or `branding.logo.default.light` when no email logo is set, with `publicLogoOnly` on. See [Email layout](/docs/interfaces/html-render/introduction#email-layout) for the rules. The logo shows only when a mail client can load it, which means an image other than SVG served from a public `http` or `https` address. The default DMS logo is an SVG, which Gmail and Outlook refuse, so by default the emails carry no logo. To show yours, set an `email` logo in your layer's `app/app.config.ts`, and serve the dashboard from a public `clientBaseUrl`:
113
133
 
114
- ```ts [frontend-vue/app/app.config.ts]
134
+ ```ts fragment [frontend-vue/app/app.config.ts]
115
135
  export default defineDmsAppConfig({
116
136
  branding: {
117
137
  logo: {
@@ -124,13 +144,13 @@ export default defineDmsAppConfig({
124
144
  });
125
145
  ```
126
146
 
127
- The loader hands every layer's `app/app.config.ts` to the email renderer too (`@antelopejs/dms-frontend` 0.6.0 or later), merged in module priority order, so a module registered with a priority above the DMS's (`0`) wins. `dark` is optional: when it passes the same check and differs from `light`, the header becomes a `<picture>` whose dark source the mail clients that follow the reader's dark mode pick, and the others draw `light`.
147
+ The email renderer reads every layer's `app/app.config.ts`, merged in module priority order, so the config of a module registered with a priority above the DMS's (`0`) wins. `dark` is optional. When it passes the same rules and differs from `light`, the header becomes a `<picture>`. Mail clients that follow the reader's dark mode pick its dark source, and the others draw `light`.
128
148
 
129
149
  ## Replacing a built-in email
130
150
 
131
- The render server looks a template up by its file name, in module priority order, and renders the first match. A module registered with a `priority` above the DMS's (`0`, see [`AddFrontendModule`](/docs/interfaces/core/pages-and-modules#addfrontendmoduleoptions)) therefore replaces a built-in email by shipping a component of the same name in its `dms.email.ts`:
151
+ The render server looks a template up by its file name, in module priority order, and renders the first match. A module registered with a `priority` above the DMS's (`0`, see [`AddFrontendModuleOptions`](/docs/interfaces/core/pages-and-modules#addfrontendmoduleoptions)) replaces a built-in email when its `dms.email.ts` ships a component of the same name:
132
152
 
133
- ```ts [frontend-vue/dms.email.ts]
153
+ ```ts fragment [frontend-vue/dms.email.ts]
134
154
  export const serverEmailTemplates = import.meta.glob("./app/emails/**/*.vue");
135
155
  ```
136
156
 
@@ -154,4 +174,4 @@ const { t, locale } = useI18n();
154
174
  </template>
155
175
  ```
156
176
 
157
- The replacement receives the props listed in [Props](#props) and keeps the backend's subject. Do not register the name again with `RegisterHtmlTemplate`: the DMS already sends it. At equal priority the first module by id wins, so set the priority explicitly rather than relying on it.
177
+ The replacement receives the props listed in [Props](#props) and keeps the backend's subject. Do not register the name again with `RegisterHtmlTemplate`, because the DMS already registers and sends it. At equal priority, the module whose id sorts first wins, so set the priority explicitly.