@antelopejs/interface-dms 0.10.3 → 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.
- package/CHANGELOG.md +47 -0
- package/README.md +21 -10
- package/dist/attachments.d.ts +15 -2
- package/dist/attachments.js +14 -1
- package/dist/attachments.js.map +1 -1
- package/dist/auth/db/models/sessions.model.d.ts +7 -5
- package/dist/auth/db/models/sessions.model.js +2 -9
- package/dist/auth/db/models/sessions.model.js.map +1 -1
- package/dist/auth/db/models/userExternalIdentities.model.d.ts +7 -5
- package/dist/auth/db/models/users.model.d.ts +7 -5
- package/dist/auth/db/tables/users.table.d.ts +4 -2
- package/dist/auth/db/tables/users.table.js.map +1 -1
- package/dist/auth/index.d.ts +4 -4
- package/dist/auth/internal/jwt-auth-decorator.d.ts +1 -1
- package/dist/auth/internal/jwt-auth-decorator.js +6 -3
- package/dist/auth/internal/jwt-auth-decorator.js.map +1 -1
- package/dist/auth/internal/request-authenticators.d.ts +12 -0
- package/dist/auth/internal/request-authenticators.js +53 -10
- package/dist/auth/internal/request-authenticators.js.map +1 -1
- package/dist/base/activity-feed.d.ts +34 -32
- package/dist/base/activity-feed.js +2 -0
- package/dist/base/activity-feed.js.map +1 -1
- package/dist/base/banner.d.ts +12 -10
- package/dist/base/banner.js +2 -0
- package/dist/base/banner.js.map +1 -1
- package/dist/base/block-registry/internal/describe.js +3 -9
- package/dist/base/block-registry/internal/describe.js.map +1 -1
- package/dist/base/block-registry/internal/ui.js +1 -1
- package/dist/base/block-registry/internal/ui.js.map +1 -1
- package/dist/base/card.d.ts +11 -9
- package/dist/base/card.js +2 -0
- package/dist/base/card.js.map +1 -1
- package/dist/base/chart-card.d.ts +4 -2
- package/dist/base/chart-card.js +2 -0
- package/dist/base/chart-card.js.map +1 -1
- package/dist/base/chart-schemas.d.ts +148 -148
- package/dist/base/chart.d.ts +65 -0
- package/dist/base/chart.js +65 -0
- package/dist/base/chart.js.map +1 -1
- package/dist/base/code-block.d.ts +5 -3
- package/dist/base/code-block.js +2 -0
- package/dist/base/code-block.js.map +1 -1
- package/dist/base/custom.d.ts +5 -0
- package/dist/base/custom.js +5 -0
- package/dist/base/custom.js.map +1 -1
- package/dist/base/data-types/default/address.d.ts +5 -0
- package/dist/base/data-types/default/address.js +5 -0
- package/dist/base/data-types/default/address.js.map +1 -1
- package/dist/base/data-types/default/choices.d.ts +20 -0
- package/dist/base/data-types/default/choices.js +20 -0
- package/dist/base/data-types/default/choices.js.map +1 -1
- package/dist/base/data-types/default/files.d.ts +22 -5
- package/dist/base/data-types/default/files.js +20 -0
- package/dist/base/data-types/default/files.js.map +1 -1
- package/dist/base/data-types/default/numbers.d.ts +15 -0
- package/dist/base/data-types/default/numbers.js +25 -9
- package/dist/base/data-types/default/numbers.js.map +1 -1
- package/dist/base/data-types/default/relations.d.ts +10 -2
- package/dist/base/data-types/default/relations.js +9 -6
- package/dist/base/data-types/default/relations.js.map +1 -1
- package/dist/base/data-types/default/strings.d.ts +25 -0
- package/dist/base/data-types/default/strings.js +25 -0
- package/dist/base/data-types/default/strings.js.map +1 -1
- package/dist/base/data-types/default-types.d.ts +1 -1
- package/dist/base/data-types/field-types.d.ts +10 -0
- package/dist/base/data-types/field-types.js +10 -0
- package/dist/base/data-types/field-types.js.map +1 -1
- package/dist/base/data-types/internal/relation-endpoints.js +1 -0
- package/dist/base/data-types/internal/relation-endpoints.js.map +1 -1
- package/dist/base/data-types/status-type.d.ts +5 -0
- package/dist/base/data-types/status-type.js +5 -0
- package/dist/base/data-types/status-type.js.map +1 -1
- package/dist/base/display.d.ts +6 -6
- package/dist/base/empty-state.d.ts +15 -13
- package/dist/base/empty-state.js +2 -0
- package/dist/base/empty-state.js.map +1 -1
- package/dist/base/form-block-schema.d.ts +50 -50
- package/dist/base/form-schema.d.ts +2 -0
- package/dist/base/form-schema.js +2 -0
- package/dist/base/form-schema.js.map +1 -1
- package/dist/base/grid.d.ts +4 -0
- package/dist/base/grid.js +4 -0
- package/dist/base/grid.js.map +1 -1
- package/dist/base/guard.d.ts +30 -28
- package/dist/base/guard.js +2 -0
- package/dist/base/guard.js.map +1 -1
- package/dist/base/helpers/archive-mode-helpers.d.ts +1 -1
- package/dist/base/helpers/file-refs.js +15 -15
- package/dist/base/helpers/file-refs.js.map +1 -1
- package/dist/base/internal/form-schema-validation.js +7 -1
- package/dist/base/internal/form-schema-validation.js.map +1 -1
- package/dist/base/internal/realtime-topics.js.map +1 -1
- package/dist/base/internal/searchable.d.ts +2 -1
- package/dist/base/internal/searchable.js +1 -3
- package/dist/base/internal/searchable.js.map +1 -1
- package/dist/base/key-value-list.d.ts +21 -19
- package/dist/base/key-value-list.js +2 -0
- package/dist/base/key-value-list.js.map +1 -1
- package/dist/base/kpi-card.d.ts +4 -2
- package/dist/base/kpi-card.js +2 -0
- package/dist/base/kpi-card.js.map +1 -1
- package/dist/base/live-toggle.d.ts +2 -0
- package/dist/base/live-toggle.js +2 -0
- package/dist/base/live-toggle.js.map +1 -1
- package/dist/base/meter.d.ts +23 -21
- package/dist/base/meter.js +2 -0
- package/dist/base/meter.js.map +1 -1
- package/dist/base/nav-card-grid.d.ts +24 -19
- package/dist/base/nav-card-grid.js +5 -0
- package/dist/base/nav-card-grid.js.map +1 -1
- package/dist/base/page-toolbar.d.ts +2 -0
- package/dist/base/page-toolbar.js +2 -0
- package/dist/base/page-toolbar.js.map +1 -1
- package/dist/base/period-selector.d.ts +3 -1
- package/dist/base/period-selector.js +2 -0
- package/dist/base/period-selector.js.map +1 -1
- package/dist/base/placeholder.d.ts +2 -0
- package/dist/base/placeholder.js +2 -0
- package/dist/base/placeholder.js.map +1 -1
- package/dist/base/resource-form-schema.d.ts +2 -2
- package/dist/base/searchable.d.ts +3 -2
- package/dist/base/searchable.js +2 -2
- package/dist/base/searchable.js.map +1 -1
- package/dist/base/section.d.ts +6 -2
- package/dist/base/section.js +4 -0
- package/dist/base/section.js.map +1 -1
- package/dist/base/stack.d.ts +6 -0
- package/dist/base/stack.js +6 -0
- package/dist/base/stack.js.map +1 -1
- package/dist/base/stat-group.d.ts +16 -14
- package/dist/base/stat-group.js +2 -0
- package/dist/base/stat-group.js.map +1 -1
- package/dist/base/tab.d.ts +10 -8
- package/dist/base/tab.js +2 -0
- package/dist/base/tab.js.map +1 -1
- package/dist/base/table-view/column-display.d.ts +1 -1
- package/dist/base/table-view/column-display.js.map +1 -1
- package/dist/base/table-view/data-functions.d.ts +10 -4
- package/dist/base/table-view/data-functions.js.map +1 -1
- package/dist/base/table-view/factory.d.ts +5 -0
- package/dist/base/table-view/factory.js +8 -4
- package/dist/base/table-view/factory.js.map +1 -1
- package/dist/base/table-view/internal/auth.d.ts +17 -3
- package/dist/base/table-view/internal/auth.js +26 -5
- package/dist/base/table-view/internal/auth.js.map +1 -1
- package/dist/base/table-view/internal/column-conditions.d.ts +8 -1
- package/dist/base/table-view/internal/column-conditions.js +25 -13
- package/dist/base/table-view/internal/column-conditions.js.map +1 -1
- package/dist/base/table-view/internal/column-display.d.ts +1 -2
- package/dist/base/table-view/internal/column-display.js +3 -9
- package/dist/base/table-view/internal/column-display.js.map +1 -1
- package/dist/base/table-view/internal/factory-helpers.d.ts +1 -1
- package/dist/base/table-view/internal/factory-helpers.js +17 -14
- package/dist/base/table-view/internal/factory-helpers.js.map +1 -1
- package/dist/base/table-view/internal/factory-lifecycle-created.d.ts +2 -3
- package/dist/base/table-view/internal/factory-lifecycle-created.js +5 -5
- package/dist/base/table-view/internal/factory-lifecycle-created.js.map +1 -1
- package/dist/base/table-view/internal/factory-lifecycle-filter.d.ts +2 -2
- package/dist/base/table-view/internal/factory-private-actions.d.ts +2 -2
- package/dist/base/table-view/internal/files.d.ts +2 -1
- package/dist/base/table-view/internal/files.js +14 -5
- package/dist/base/table-view/internal/files.js.map +1 -1
- package/dist/base/table-view/internal/footer.d.ts +3 -3
- package/dist/base/table-view/internal/footer.js +16 -15
- package/dist/base/table-view/internal/footer.js.map +1 -1
- package/dist/base/table-view/internal/guards.d.ts +27 -5
- package/dist/base/table-view/internal/guards.js +127 -49
- package/dist/base/table-view/internal/guards.js.map +1 -1
- package/dist/base/table-view/internal/meta-helpers.d.ts +1 -1
- package/dist/base/table-view/internal/meta-helpers.js +3 -4
- package/dist/base/table-view/internal/meta-helpers.js.map +1 -1
- package/dist/base/table-view/internal/meta.js +2 -3
- package/dist/base/table-view/internal/meta.js.map +1 -1
- package/dist/base/table-view/internal/option-contract.d.ts +1 -1
- package/dist/base/table-view/internal/option-footer.d.ts +2 -2
- package/dist/base/table-view/internal/option-row-actions.d.ts +1 -1
- package/dist/base/table-view/internal/realtime.d.ts +52 -11
- package/dist/base/table-view/internal/realtime.js +23 -16
- package/dist/base/table-view/internal/realtime.js.map +1 -1
- package/dist/base/table-view/internal/resource-form.d.ts +1 -1
- package/dist/base/table-view/internal/resource-form.js +1 -1
- package/dist/base/table-view/internal/resource-form.js.map +1 -1
- package/dist/base/table-view/internal/row-actions-schema.d.ts +12 -12
- package/dist/base/table-view/internal/row-rule-shape.d.ts +11 -5
- package/dist/base/table-view/internal/row-rule-shape.js +60 -30
- package/dist/base/table-view/internal/row-rule-shape.js.map +1 -1
- package/dist/base/table-view/internal/row-rules.d.ts +28 -1
- package/dist/base/table-view/internal/row-rules.js +8 -19
- package/dist/base/table-view/internal/row-rules.js.map +1 -1
- package/dist/base/table-view/internal/tabs.d.ts +1 -1
- package/dist/base/table-view/internal/tabs.js +1 -4
- package/dist/base/table-view/internal/tabs.js.map +1 -1
- package/dist/base/table-view/internal/validation.d.ts +2 -2
- package/dist/base/table-view/internal/validation.js +7 -7
- package/dist/base/table-view/internal/validation.js.map +1 -1
- package/dist/base/table-view/meta.d.ts +7 -6
- package/dist/base/table-view/meta.js +12 -7
- package/dist/base/table-view/meta.js.map +1 -1
- package/dist/base/table-view/resource-form.d.ts +3 -1
- package/dist/base/table-view/resource-form.js +2 -0
- package/dist/base/table-view/resource-form.js.map +1 -1
- package/dist/base/table-view/routes.d.ts +73 -21
- package/dist/base/table-view/routes.js +13 -10
- package/dist/base/table-view/routes.js.map +1 -1
- package/dist/base/table-view/schema.d.ts +152 -152
- package/dist/base/table-view/source.js +4 -1
- package/dist/base/table-view/source.js.map +1 -1
- package/dist/base/table-view.d.ts +1 -1
- package/dist/base/table-view.js.map +1 -1
- package/dist/base/top-list-card.d.ts +35 -33
- package/dist/base/top-list-card.js +2 -0
- package/dist/base/top-list-card.js.map +1 -1
- package/dist/base/types/error.js +5 -2
- package/dist/base/types/error.js.map +1 -1
- package/dist/base/types/guards.d.ts +4 -4
- package/dist/base/types/json.d.ts +1 -10
- package/dist/base/types/row-action.d.ts +7 -7
- package/dist/component-options/confirm-dialog.d.ts +2 -0
- package/dist/component-options/confirm-dialog.js.map +1 -1
- package/dist/component-options/data-type.d.ts +2 -2
- package/dist/component-options/data-type.js +7 -7
- package/dist/component-options/data-type.js.map +1 -1
- package/dist/component-options/row-action.d.ts +14 -9
- package/dist/component-slots.d.ts +11 -2
- package/dist/component-slots.js +1 -0
- package/dist/component-slots.js.map +1 -1
- package/dist/component.d.ts +7 -8
- package/dist/component.js +17 -25
- package/dist/component.js.map +1 -1
- package/dist/data-controllers/members.d.ts +11 -7
- package/dist/data-controllers/members.js +1 -1
- package/dist/data-controllers/members.js.map +1 -1
- package/dist/data-controllers/roles.d.ts +32 -10
- package/dist/db/internal/exportJobs.model.d.ts +7 -5
- package/dist/db/internal/inviteResolutions.model.d.ts +7 -5
- package/dist/db/internal/tenantLifecycle.model.d.ts +7 -5
- package/dist/db/models/roles.model.d.ts +7 -5
- package/dist/db/models/tenantMembers.model.d.ts +7 -5
- package/dist/db/models/tenants.model.d.ts +7 -5
- package/dist/db/models/user_invites.model.d.ts +7 -5
- package/dist/db/tables/tenants.table.d.ts +9 -0
- package/dist/db/tables/tenants.table.js +4 -0
- package/dist/db/tables/tenants.table.js.map +1 -1
- package/dist/guards.d.ts +1 -1
- package/dist/hooks.d.ts +10 -9
- package/dist/hooks.js +42 -33
- package/dist/hooks.js.map +1 -1
- package/dist/internal/attachments.d.ts +2 -2
- package/dist/internal/component-builder.js +2 -1
- package/dist/internal/component-builder.js.map +1 -1
- package/dist/internal/component-serialization.d.ts +2 -4
- package/dist/internal/component-serialization.js +4 -3
- package/dist/internal/component-serialization.js.map +1 -1
- package/dist/internal/component-slots.d.ts +19 -2
- package/dist/internal/component-slots.js +35 -12
- package/dist/internal/component-slots.js.map +1 -1
- package/dist/internal/component-watch-filter.d.ts +11 -0
- package/dist/internal/component-watch-filter.js +22 -0
- package/dist/internal/component-watch-filter.js.map +1 -0
- package/dist/internal/component-watches.js +0 -1
- package/dist/internal/component-watches.js.map +1 -1
- package/dist/internal/own-entry.d.ts +7 -0
- package/dist/internal/own-entry.js +13 -0
- package/dist/internal/own-entry.js.map +1 -0
- package/dist/invite-extension-payloads.d.ts +14 -2
- package/dist/invite-extensions/internal/delivery.js +1 -2
- package/dist/invite-extensions/internal/delivery.js.map +1 -1
- package/dist/invite-extensions/internal/edit.d.ts +2 -1
- package/dist/invite-extensions/internal/edit.js +8 -9
- package/dist/invite-extensions/internal/edit.js.map +1 -1
- package/dist/invite-extensions/internal/form-slot.js +8 -15
- package/dist/invite-extensions/internal/form-slot.js.map +1 -1
- package/dist/invite-extensions/internal/payload-slices.d.ts +33 -3
- package/dist/invite-extensions/internal/payload-slices.js +84 -19
- package/dist/invite-extensions/internal/payload-slices.js.map +1 -1
- package/dist/invite-extensions/internal/types.d.ts +10 -1
- package/dist/invite-resolution.js +6 -4
- package/dist/invite-resolution.js.map +1 -1
- package/dist/json.d.ts +10 -0
- package/dist/json.js +3 -0
- package/dist/json.js.map +1 -0
- package/dist/layout-banners.d.ts +5 -2
- package/dist/layout-banners.js.map +1 -1
- package/dist/page/controllers.d.ts +2 -2
- package/dist/page/internal/extension-validation.js +3 -2
- package/dist/page/internal/extension-validation.js.map +1 -1
- package/dist/page/internal/metadata-layout.js +1 -1
- package/dist/page/internal/metadata-layout.js.map +1 -1
- package/dist/page/internal/metadata-publish.js +3 -2
- package/dist/page/internal/metadata-publish.js.map +1 -1
- package/dist/page/internal/metadata-registration.js +5 -4
- package/dist/page/internal/metadata-registration.js.map +1 -1
- package/dist/page/internal/metadata-upload-registration.d.ts +3 -1
- package/dist/page/internal/metadata-upload-registration.js +9 -0
- package/dist/page/internal/metadata-upload-registration.js.map +1 -1
- package/dist/page/internal/metadata-uploads.d.ts +18 -5
- package/dist/page/internal/metadata-uploads.js +7 -8
- package/dist/page/internal/metadata-uploads.js.map +1 -1
- package/dist/page/internal/page-record.d.ts +2 -3
- package/dist/page/internal/page-record.js +19 -14
- package/dist/page/internal/page-record.js.map +1 -1
- package/dist/page/layout-filter.js +17 -6
- package/dist/page/layout-filter.js.map +1 -1
- package/dist/page/metadata.js +5 -7
- package/dist/page/metadata.js.map +1 -1
- package/dist/request-tenant.d.ts +10 -4
- package/dist/request-tenant.js +16 -34
- package/dist/request-tenant.js.map +1 -1
- package/dist/tenant-export.d.ts +2 -1
- package/dist/tenant-lifecycle.d.ts +13 -4
- package/dist/tenant-lifecycle.js +51 -18
- package/dist/tenant-lifecycle.js.map +1 -1
- package/dist/types.d.ts +20 -0
- package/dist/uploads.js +42 -59
- package/dist/uploads.js.map +1 -1
- package/dist/utils/fire-and-forget.js.map +1 -1
- package/dist/utils/internal/decorated-class.d.ts +14 -0
- package/dist/utils/internal/decorated-class.js +37 -0
- package/dist/utils/internal/decorated-class.js.map +1 -0
- package/dist/utils/internal/type-check.d.ts +9 -0
- package/dist/utils/internal/type-check.js +13 -5
- package/dist/utils/internal/type-check.js.map +1 -1
- package/dist/utils/internal/value-parser.d.ts +1 -1
- package/dist/utils/internal/value-parser.js +22 -9
- package/dist/utils/internal/value-parser.js.map +1 -1
- package/dist/watch-actions.d.ts +1 -1
- package/docs/1.core/1.introduction.md +82 -77
- package/docs/1.core/10.layout-banners.md +57 -45
- package/docs/1.core/11.modules-and-dynamic-menus.md +178 -0
- package/docs/1.core/12.frontend-modules-and-request-helpers.md +155 -0
- package/docs/1.core/13.tenant-data-export.md +120 -0
- package/docs/1.core/2.pages-and-modules.md +140 -367
- package/docs/1.core/3.components.md +21 -22
- package/docs/1.core/4.permissions.md +92 -71
- package/docs/1.core/5.tenancy.md +112 -86
- package/docs/1.core/6.invites.md +90 -65
- package/docs/1.core/7.hooks.md +96 -166
- package/docs/1.core/8.realtime.md +44 -45
- package/docs/1.core/9.quick-actions.md +37 -30
- package/docs/2.auth/1.introduction.md +33 -35
- package/docs/2.auth/2.authentication.md +60 -38
- package/docs/2.auth/3.tokens-and-sessions.md +56 -55
- package/docs/2.auth/4.users.md +82 -52
- package/docs/2.auth/5.external-identities.md +25 -26
- package/docs/3.base/1.introduction.md +64 -60
- package/docs/3.base/10.display-blocks.md +15 -16
- package/docs/3.base/11.page-layouts.md +273 -0
- package/docs/3.base/12.table-row-actions.md +122 -0
- package/docs/3.base/13.table-filters-and-forms.md +104 -0
- package/docs/3.base/14.table-events-and-helpers.md +114 -0
- package/docs/3.base/15.table-sources-and-gates.md +24 -0
- package/docs/3.base/2.types.md +44 -41
- package/docs/3.base/3.forms.md +27 -28
- package/docs/3.base/4.tables.md +34 -367
- package/docs/3.base/5.charts-and-widgets.md +28 -29
- package/docs/3.base/6.layout.md +21 -270
- package/docs/3.base/8.data-types.md +39 -40
- package/docs/3.base/9.helpers.md +36 -38
- package/docs/4.notifications/1.introduction.md +22 -26
- package/docs/4.notifications/2.categories-and-subjects.md +58 -55
- package/docs/4.notifications/3.sending.md +72 -68
- package/docs/5.html-render/1.introduction.md +119 -81
- package/docs/5.html-render/2.built-in-emails.md +71 -51
- package/docs/index.md +18 -29
- package/docs/tsconfig.json +4 -0
- package/package.json +12 -12
- package/docs/images/emails-admin-invite.png +0 -0
- package/docs/images/emails-change-notice.png +0 -0
- package/docs/images/emails-change-verification.png +0 -0
- package/docs/images/emails-digest-logo-dark.png +0 -0
- package/docs/images/emails-digest-logo-light.png +0 -0
- package/docs/images/emails-digest.png +0 -0
- package/docs/images/emails-export-partial.png +0 -0
- package/docs/images/emails-export-ready.png +0 -0
- package/docs/images/emails-reset-password.png +0 -0
- package/docs/images/emails-two-factor.png +0 -0
- 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
|
|
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
|
|
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
|
-
|
|
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.
|
|
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
|
|
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
|
-
##
|
|
31
|
+
## What the domain provides
|
|
33
32
|
|
|
34
|
-
- **
|
|
35
|
-
- **Documented props
|
|
36
|
-
- **
|
|
37
|
-
- **Registry
|
|
38
|
-
- **
|
|
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
|
|
41
|
+
This interface relies on one other AntelopeJS package:
|
|
43
42
|
|
|
44
|
-
- [
|
|
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
|
|
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
|
|
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.
|
|
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.
|
|
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
|
|
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
|
|
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
|
|
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,
|
|
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 {
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
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
|
|
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
|
-
|
|
155
|
+
The promise rejects in three cases:
|
|
141
156
|
|
|
142
|
-
On a non-2xx response
|
|
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 {
|
|
147
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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` |
|
|
264
|
+
| `_propsType` | `TProps \| undefined` | Optional field that only carries the props type. `RegisterHtmlTemplate` never sets it. Do not read it. |
|
|
235
265
|
|
|
236
|
-
|
|
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` |
|
|
258
|
-
| `serviceSecret` | `string` |
|
|
259
|
-
| `serviceTokenLifetime` | `number` |
|
|
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
|
|
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: "
|
|
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
|
-
|
|
318
|
+
[Configuration](/docs/building/configuration#template-rendering-in-htmlrender) lists this block with the other DMS options.
|
|
290
319
|
|
|
291
|
-
## Email
|
|
320
|
+
## Email layout
|
|
292
321
|
|
|
293
|
-
The DMS ships the frame of its own emails as `EmailLayout
|
|
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) |
|
|
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) |
|
|
327
|
-
| `publicLogoOnly` | `boolean` (optional) | Leaves the logo out unless a mail client can load it
|
|
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
|
|
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`
|
|
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
|
-
|
|
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
|
-
|
|
368
|
+
Set a logo that is not an SVG, and a public `clientBaseUrl`, for the logo to show.
|
|
336
369
|
|
|
337
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
##
|
|
385
|
+
## Send rendered HTML as email
|
|
348
386
|
|
|
349
|
-
Rendering and sending are separate steps. Render with `GenerateHtml`, then
|
|
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
|
-
|
|
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
|
|
3
|
-
description: "The DMS sends
|
|
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)
|
|
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` |
|
|
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 | `
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
43
|
+
## What each email contains
|
|
51
44
|
|
|
52
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
###
|
|
51
|
+
### `EmailUserValidation`
|
|
59
52
|
|
|
60
|
-
|
|
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
|
-
|
|
59
|
+
### `EmailResetPassword`
|
|
63
60
|
|
|
64
|
-
|
|
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
|
-
|
|
67
|
+
### `EmailTwoFactor`
|
|
67
68
|
|
|
68
|
-
|
|
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
|
-
###
|
|
74
|
+
### `EmailAdminInvite`
|
|
71
75
|
|
|
72
|
-
The
|
|
76
|
+
The invitation names the inviter, the workspace and the platform when the backend knows them.
|
|
73
77
|
|
|
74
|
-
|
|
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
|
-
###
|
|
85
|
+
### `EmailChangeVerification`
|
|
77
86
|
|
|
78
|
-
The
|
|
87
|
+
The new address receives the code that confirms the change.
|
|
79
88
|
|
|
80
|
-
|
|
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
|
-
###
|
|
95
|
+
### `EmailChangeNotice`
|
|
83
96
|
|
|
84
|
-
|
|
97
|
+
The current address receives a notice, not the confirmation code.
|
|
85
98
|
|
|
86
|
-
|
|
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
|
-
|
|
103
|
+
### `EmailExportReady`
|
|
89
104
|
|
|
90
|
-
|
|
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
|
|
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`)
|
|
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
|
|
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
|
|
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
|
|
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 [`
|
|
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
|
|
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.
|