@ultimat3/cli 29.3.2 → 30.0.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/CLAUDE.md +64 -40
- package/README.md +61 -11
- package/package.json +42 -28
- package/src/affected-tests.ts +97 -0
- package/src/affected-unowned.ts +43 -0
- package/src/affected.ts +25 -6
- package/src/app-boundaries.ts +29 -1
- package/src/app-evals.ts +12 -4
- package/src/app-load-unresolved.ts +62 -0
- package/src/app-load.ts +30 -37
- package/src/app-mcp.ts +14 -2
- package/src/app-module-graph.ts +232 -0
- package/src/app-reload-graph.ts +2 -2
- package/src/app-root.ts +37 -1
- package/src/bin.ts +1 -0
- package/src/boundary-findings.ts +6 -0
- package/src/budgets-freshness.ts +32 -0
- package/src/budgets.ts +20 -1
- package/src/build-capture-env.ts +9 -0
- package/src/cdp-shot-a11y.ts +2 -39
- package/src/ci-tested.ts +243 -0
- package/src/client-emit-customer-runtime.ts +139 -0
- package/src/client-emit-customer.ts +108 -0
- package/src/client-emit-drift.ts +72 -0
- package/src/client-emit-header.ts +67 -0
- package/src/client-emit-schemas.ts +175 -0
- package/src/client-emit-types.ts +121 -0
- package/src/client-emit-vendor.ts +146 -0
- package/src/client-errors-routes.ts +229 -0
- package/src/client-errors-script.ts +74 -0
- package/src/client-vitals-route.ts +197 -0
- package/src/client-vitals-script.ts +56 -0
- package/src/cmd-affected.ts +23 -0
- package/src/cmd-build-spec.ts +7 -1
- package/src/cmd-build.ts +35 -9
- package/src/cmd-ci-spec.ts +42 -6
- package/src/cmd-ci.ts +99 -2
- package/src/cmd-client-customer.ts +134 -0
- package/src/cmd-client-source.ts +81 -0
- package/src/cmd-client-spec.ts +48 -0
- package/src/cmd-client-write.ts +41 -0
- package/src/cmd-client.ts +111 -0
- package/src/cmd-db-recount.ts +120 -0
- package/src/cmd-db-spec.ts +36 -6
- package/src/cmd-db.ts +13 -3
- package/src/cmd-deploy-compose.ts +61 -3
- package/src/cmd-deploy-drain.ts +10 -2
- package/src/cmd-deploy-helm.ts +11 -0
- package/src/cmd-deploy-preflight.ts +118 -0
- package/src/cmd-deploy-spec.ts +58 -6
- package/src/cmd-deploy-verify.ts +380 -0
- package/src/cmd-deploy.ts +42 -9
- package/src/cmd-docs.ts +34 -4
- package/src/cmd-doctor.ts +66 -9
- package/src/cmd-drill-spec.ts +25 -0
- package/src/cmd-drill.ts +176 -0
- package/src/cmd-env-init.ts +102 -0
- package/src/cmd-env-spec.ts +10 -3
- package/src/cmd-env.ts +15 -10
- package/src/cmd-errors-recent.ts +60 -0
- package/src/cmd-errors-spec.ts +9 -1
- package/src/cmd-errors.ts +4 -0
- package/src/cmd-exercise-spec.ts +19 -0
- package/src/cmd-exercise.ts +129 -0
- package/src/cmd-generate-spec.ts +9 -1
- package/src/cmd-generate.ts +32 -27
- package/src/cmd-graph-load.ts +97 -0
- package/src/cmd-graph-spec.ts +16 -0
- package/src/cmd-graph.ts +73 -0
- package/src/cmd-i18n.ts +4 -1
- package/src/cmd-impact-spec.ts +13 -0
- package/src/cmd-impact.ts +64 -0
- package/src/cmd-jobs-resolve.ts +76 -0
- package/src/cmd-jobs-spec.ts +21 -2
- package/src/cmd-jobs.ts +10 -6
- package/src/cmd-mail-spec.ts +28 -0
- package/src/cmd-mail.ts +70 -0
- package/src/cmd-mcp-lock.ts +69 -0
- package/src/cmd-mcp-spec.ts +23 -4
- package/src/cmd-mcp.ts +17 -3
- package/src/cmd-new-spec.ts +8 -1
- package/src/cmd-new.ts +61 -9
- package/src/cmd-planned.ts +0 -6
- package/src/cmd-repro-spec.ts +31 -0
- package/src/cmd-repro.ts +174 -0
- package/src/cmd-stop-spec.ts +21 -0
- package/src/cmd-stop.ts +166 -0
- package/src/cmd-test-spec.ts +17 -4
- package/src/cmd-test.ts +70 -14
- package/src/cmd-upgrade-spec.ts +26 -0
- package/src/cmd-upgrade.ts +274 -0
- package/src/cmd-verify-spec.ts +3 -3
- package/src/db-backfill.ts +6 -0
- package/src/db-diagnostics.ts +106 -0
- package/src/db-generate.ts +28 -8
- package/src/db-reset-template.ts +1 -1
- package/src/db-rolling.ts +45 -0
- package/src/db-seed-generate.ts +188 -0
- package/src/deploy-verify-findings.ts +98 -0
- package/src/deploy-verify-flags.ts +109 -0
- package/src/deploy-verify-grade.ts +185 -0
- package/src/deploy-verify-queues.ts +53 -0
- package/src/dev-boot.ts +35 -15
- package/src/dev-child-watch.ts +16 -4
- package/src/dev-dashboard-guard.ts +8 -1
- package/src/dev-dashboard.ts +7 -0
- package/src/dev-lock-identity.ts +61 -0
- package/src/dev-lock-port-holder.ts +80 -0
- package/src/dev-lock-steal.ts +124 -0
- package/src/dev-lock.ts +92 -120
- package/src/dev-mail-templates.ts +51 -0
- package/src/dev-rebuild.ts +26 -2
- package/src/dev-route-table.ts +17 -0
- package/src/dev-supervisor.ts +5 -2
- package/src/dev-watch-tree.ts +11 -10
- package/src/dev-watch.ts +1 -1
- package/src/dispatch.ts +29 -6
- package/src/docs-section.ts +69 -0
- package/src/docs-sources.ts +100 -0
- package/src/doctor-auth.ts +11 -2
- package/src/doctor-chart-alerts.ts +55 -0
- package/src/doctor-pooler.ts +107 -0
- package/src/doctor-rolling.ts +35 -0
- package/src/doctor-row-security.ts +40 -0
- package/src/document-options.ts +13 -0
- package/src/drill-sync-lane.ts +25 -0
- package/src/drill-targets.ts +39 -0
- package/src/duplicate-packages.ts +1 -1
- package/src/env-production.ts +93 -0
- package/src/error-codes-shot.ts +59 -0
- package/src/error-codes-tooling.ts +29 -0
- package/src/error-codes.ts +100 -54
- package/src/error-contract.ts +2 -2
- package/src/errors.ts +22 -3
- package/src/exercise-database.ts +99 -0
- package/src/exercise-findings.ts +159 -0
- package/src/exercise-overrides.ts +42 -0
- package/src/exercise-registration.ts +58 -0
- package/src/exercise-static.ts +88 -0
- package/src/exercise-subject.ts +69 -0
- package/src/failure-capture-boot.ts +30 -0
- package/src/framework-env.ts +7 -1
- package/src/framework-schema.ts +72 -5
- package/src/generate-catalog-locales.ts +19 -0
- package/src/generate-chart-alerts.ts +156 -0
- package/src/generate-feature-required.ts +65 -0
- package/src/generate-feature.ts +83 -6
- package/src/generate-files.ts +24 -0
- package/src/generate-format-written.ts +32 -0
- package/src/generate-kinds.ts +6 -0
- package/src/generate-sign-in-config.ts +100 -0
- package/src/generate-sign-in-manifest.ts +28 -0
- package/src/generate-sign-in-mode.ts +101 -0
- package/src/generate-sign-in.ts +250 -0
- package/src/generate-write.ts +44 -7
- package/src/hand-rolled-socket.ts +72 -0
- package/src/i18n-app-key-shapes.ts +29 -0
- package/src/i18n-audit.ts +10 -3
- package/src/i18n-registration.ts +18 -3
- package/src/index.ts +2 -0
- package/src/island-bundle.ts +4 -3
- package/src/island-disposer.ts +116 -0
- package/src/island-realtime-state.ts +34 -6
- package/src/island-realtime.ts +6 -2
- package/src/island-routes.ts +3 -2
- package/src/isr-query-advice.ts +6 -11
- package/src/isr-query-guard.ts +4 -3
- package/src/job-budget.ts +95 -0
- package/src/jobs-json.ts +11 -0
- package/src/lint-findings.ts +104 -0
- package/src/local-cli.ts +34 -4
- package/src/loopback-host.ts +2 -2
- package/src/mail-preview-findings.ts +24 -0
- package/src/mcp-errors.ts +82 -2
- package/src/mcp-host.ts +11 -1
- package/src/mcp-lock-findings.ts +57 -0
- package/src/measure-paths.ts +12 -4
- package/src/measure-scope.ts +27 -13
- package/src/messages-client.ts +23 -0
- package/src/messages-db.ts +56 -0
- package/src/messages-gate.ts +107 -0
- package/src/messages-repro.ts +11 -0
- package/src/messages-shot.ts +51 -0
- package/src/messages-upgrade.ts +45 -0
- package/src/messages.ts +53 -178
- package/src/metrics-endpoint.ts +23 -5
- package/src/new-shapes-generate.ts +71 -0
- package/src/new-shapes.ts +63 -0
- package/src/openapi-compile-ir.ts +68 -0
- package/src/openapi-compile-schema.ts +284 -0
- package/src/openapi-compile.ts +273 -0
- package/src/openapi-refuse.ts +45 -0
- package/src/otlp-export.ts +2 -1
- package/src/output-findings.ts +123 -0
- package/src/output.ts +32 -8
- package/src/package-boundaries.ts +110 -0
- package/src/page-pins.ts +23 -0
- package/src/plain-routes.ts +13 -0
- package/src/prerender-entries.ts +59 -0
- package/src/prerender-heaviest.ts +27 -0
- package/src/prerender-preload.ts +6 -0
- package/src/prerender-report.ts +12 -0
- package/src/prerender-seo-reads.ts +38 -0
- package/src/prerender-served.ts +22 -7
- package/src/prerender-vitals.ts +42 -0
- package/src/prerender.ts +61 -32
- package/src/pwa-artifacts.ts +4 -3
- package/src/pwa-manifest-locales.ts +1 -1
- package/src/registry.ts +18 -0
- package/src/repro-bundle.ts +65 -0
- package/src/repro-errors.ts +47 -0
- package/src/repro-job.ts +72 -0
- package/src/repro-literal.ts +41 -0
- package/src/repro-request.ts +48 -0
- package/src/repro-write.ts +100 -0
- package/src/retain-sweep.ts +27 -0
- package/src/retained-assets-boot.ts +113 -0
- package/src/retained-assets-layout.ts +110 -0
- package/src/retained-assets-read.ts +142 -0
- package/src/retained-assets-sweep.ts +70 -0
- package/src/retained-assets.ts +171 -0
- package/src/revalidated-response.ts +8 -5
- package/src/role-replicator.ts +7 -1
- package/src/role-start-devices.ts +119 -0
- package/src/role-start-types.ts +6 -0
- package/src/role-start.ts +22 -5
- package/src/role-sync.ts +7 -1
- package/src/route-islands.ts +42 -0
- package/src/route-table.ts +2 -1
- package/src/row-security-boot.ts +58 -0
- package/src/row-security-wanted.ts +32 -0
- package/src/runtime-admin.ts +0 -1
- package/src/runtime-assets-site-cache.ts +55 -0
- package/src/runtime-assets-site.ts +136 -0
- package/src/runtime-assets.ts +99 -55
- package/src/runtime-bindings.ts +19 -3
- package/src/runtime-cache.ts +23 -14
- package/src/runtime-isr-outcome.ts +114 -60
- package/src/runtime-purge.ts +14 -2
- package/src/runtime-queue.ts +31 -4
- package/src/runtime-render-document.ts +204 -0
- package/src/runtime-render.ts +48 -198
- package/src/runtime-replica-position.ts +62 -0
- package/src/runtime-replica.ts +18 -3
- package/src/runtime-s3-path-style.ts +17 -0
- package/src/runtime-services.ts +19 -6
- package/src/runtime-statement-bound.ts +22 -0
- package/src/runtime-storage-upload.ts +9 -0
- package/src/runtime-storage.ts +42 -138
- package/src/schema-diff-views.ts +54 -0
- package/src/schema-diff.ts +21 -3
- package/src/schema-drift.ts +28 -9
- package/src/schema-dump-drift.ts +91 -7
- package/src/script-csp.ts +1 -1
- package/src/seo-cards.ts +26 -0
- package/src/seo-routes.ts +22 -8
- package/src/serve-boot.ts +54 -21
- package/src/serve-drain.ts +1 -0
- package/src/serve-env.ts +26 -8
- package/src/serve-retire.ts +12 -6
- package/src/serve-web.ts +42 -4
- package/src/serve.ts +9 -7
- package/src/site-asset-routes.ts +17 -27
- package/src/site-config.ts +4 -0
- package/src/site-llms-pages.ts +106 -0
- package/src/site-llms.ts +64 -0
- package/src/site-seo-write.ts +4 -2
- package/src/site-seo.ts +55 -6
- package/src/source-mtime.ts +50 -0
- package/src/static-capture-errors.ts +41 -0
- package/src/static-capture.ts +56 -0
- package/src/static-cards-serve.ts +39 -0
- package/src/static-cards.ts +93 -0
- package/src/static-document.ts +14 -13
- package/src/static-images.ts +115 -0
- package/src/stream-hole-scope.ts +55 -0
- package/src/style-bundle.ts +1 -1
- package/src/style-routes.ts +3 -2
- package/src/sw-precache-plan.ts +2 -2
- package/src/sw-routes.ts +2 -2
- package/src/templates/action-tests.ts +306 -0
- package/src/templates/action.ts +81 -269
- package/src/templates/backfill.ts +1 -1
- package/src/templates/entity.ts +21 -5
- package/src/templates/github/ci.yml.ts +8 -9
- package/src/templates/guard-bare-error.ts +2 -2
- package/src/templates/guard-error-declared.ts +263 -0
- package/src/templates/guard-native-confirm.ts +132 -0
- package/src/templates/guard-raw-fetch.ts +151 -0
- package/src/templates/guard-raw-interval.ts +121 -0
- package/src/templates/guard-repo-raw-sql.ts +71 -2
- package/src/templates/job.ts +1 -1
- package/src/templates/naming.ts +5 -0
- package/src/templates/pinned/docker-compose.yml +6 -0
- package/src/templates/pinned-images.ts +8 -0
- package/src/templates/policy.ts +85 -2
- package/src/templates/query.ts +70 -13
- package/src/templates/resource-create.ts +3 -1
- package/src/templates/resource-form-island.ts +15 -4
- package/src/templates/resource-page-db.ts +83 -0
- package/src/templates/resource-page.ts +161 -52
- package/src/templates/route.ts +4 -6
- package/src/templates/scaffold-app.ts +3 -0
- package/src/templates/scaffold-auth.ts +2 -1
- package/src/templates/scaffold-bin.ts +5 -52
- package/src/templates/scaffold-claude-agents.ts +19 -19
- package/src/templates/scaffold-claude-commands.ts +28 -31
- package/src/templates/scaffold-claude.ts +26 -23
- package/src/templates/scaffold-container-compose.ts +6 -1
- package/src/templates/scaffold-container.ts +24 -4
- package/src/templates/scaffold-dashboard-bare.ts +3 -3
- package/src/templates/scaffold-dashboard-example-tests.ts +49 -9
- package/src/templates/scaffold-dashboard-example-view.ts +156 -0
- package/src/templates/scaffold-dashboard-example.ts +43 -131
- package/src/templates/scaffold-dashboard-shared.ts +3 -7
- package/src/templates/scaffold-db-client.ts +20 -6
- package/src/templates/scaffold-db-package.ts +5 -14
- package/src/templates/scaffold-docs.ts +73 -57
- package/src/templates/scaffold-domain-package.ts +18 -14
- package/src/templates/scaffold-entries.ts +7 -2
- package/src/templates/scaffold-exercise.ts +37 -0
- package/src/templates/scaffold-guards.ts +13 -1
- package/src/templates/scaffold-helm-alerts.ts +234 -0
- package/src/templates/scaffold-helm-templates.ts +7 -5
- package/src/templates/scaffold-helm.ts +46 -12
- package/src/templates/scaffold-i18n.ts +6 -2
- package/src/templates/scaffold-mcp-endpoint.ts +182 -0
- package/src/templates/scaffold-mcp-json.ts +31 -0
- package/src/templates/scaffold-mcp-package.ts +59 -56
- package/src/templates/scaffold-repo.ts +7 -2
- package/src/templates/scaffold-shell.ts +45 -79
- package/src/templates/scaffold-test-services.ts +13 -5
- package/src/templates/shapes/api-keys.ts +170 -0
- package/src/templates/shapes/api.ts +101 -0
- package/src/templates/shapes/blog-site.ts +340 -0
- package/src/templates/shapes/blog-slice.ts +265 -0
- package/src/templates/shapes/blog.ts +114 -0
- package/src/templates/shapes/dashboard-detail.ts +242 -0
- package/src/templates/shapes/dashboard.ts +53 -0
- package/src/templates/shapes/index.ts +17 -0
- package/src/templates/shapes/overlay.ts +99 -0
- package/src/templates/shapes/shared-edits.ts +46 -0
- package/src/templates/sign-in-action-tests.ts +237 -0
- package/src/templates/sign-in-authenticator.ts +112 -0
- package/src/templates/sign-in-oidc.ts +189 -0
- package/src/templates/sign-in-pages.ts +148 -0
- package/src/templates/sign-in-password.ts +216 -0
- package/src/templates/sign-in-shared.ts +268 -0
- package/src/templates/slice-foundation.ts +70 -15
- package/src/test-batches.ts +1 -1
- package/src/test-database.ts +1 -1
- package/src/test-shards.ts +7 -1
- package/src/test-workers.ts +18 -12
- package/src/tool-gate-names.ts +34 -0
- package/src/upgrade-agents-link.ts +49 -0
- package/src/upgrade-bun-floor.ts +225 -0
- package/src/upgrade-gate.ts +228 -0
- package/src/upgrade-pins.ts +63 -0
- package/src/upgrade-plan.ts +214 -0
- package/src/upgrade-registry.ts +53 -0
- package/src/upgrade-report.ts +139 -0
- package/src/upgrade-root.ts +74 -0
- package/src/verify-budgets-build.ts +118 -0
- package/src/verify-checks.ts +67 -24
- package/src/verify-coverage-run.ts +1 -1
- package/src/verify-e2e-lane.ts +41 -0
- package/src/verify-exercise.ts +64 -0
- package/src/verify-floor.ts +41 -0
- package/src/verify-merge.ts +19 -4
- package/src/verify-role-load.ts +2 -0
- package/src/verify-run.ts +28 -8
- package/src/verify-shard.ts +12 -2
- package/src/verify-skip-ceiling.ts +87 -0
- package/src/verify-step.ts +5 -0
- package/src/verify-tests.ts +25 -5
- package/src/view-drift.ts +55 -0
- package/src/worker-bundle.ts +3 -2
- package/src/workspace-graph.ts +38 -19
- package/src/write-generated.ts +43 -0
- package/src/zone-boot.ts +20 -0
- package/wiki/Actions.md +342 -0
- package/wiki/Admin-Dashboard.md +432 -0
- package/wiki/Agents.md +958 -0
- package/wiki/Auth.md +195 -0
- package/wiki/Bare-VM.md +111 -0
- package/wiki/Batching-And-Preloading.md +136 -0
- package/wiki/Building-Your-Own-Base.md +203 -0
- package/wiki/CI-Parallel-Gate.md +220 -0
- package/wiki/CLI-Reference.md +1894 -0
- package/wiki/Caching-And-Invalidation.md +255 -0
- package/wiki/Client-Data.md +194 -0
- package/wiki/Client-Navigation.md +513 -0
- package/wiki/Compared.md +93 -0
- package/wiki/Configuration.md +868 -0
- package/wiki/Contributing.md +12 -0
- package/wiki/Deploy-Recipes.md +122 -0
- package/wiki/Deployment.md +508 -0
- package/wiki/Entities-And-Migrations.md +1116 -0
- package/wiki/FAQ.md +175 -0
- package/wiki/Feature-Flags.md +65 -0
- package/wiki/Getting-Started.md +221 -0
- package/wiki/Home.md +160 -0
- package/wiki/I18n.md +239 -0
- package/wiki/Installation.md +206 -0
- package/wiki/Interface-Rules.md +339 -0
- package/wiki/Jobs-And-Workflows.md +783 -0
- package/wiki/Known-Gaps.md +185 -0
- package/wiki/MCP-And-AI.md +753 -0
- package/wiki/Mail.md +138 -0
- package/wiki/Migrating-An-Existing-App.md +594 -0
- package/wiki/Migrating-From-Django.md +54 -0
- package/wiki/Migrating-From-Laravel.md +53 -0
- package/wiki/Migrating-From-Node-Drizzle.md +53 -0
- package/wiki/Migrating-From-Rails.md +53 -0
- package/wiki/Migrations-And-Backfills.md +552 -0
- package/wiki/Money.md +338 -0
- package/wiki/N-Plus-One-Detection.md +197 -0
- package/wiki/Notify.md +208 -0
- package/wiki/Observability.md +245 -0
- package/wiki/Outbound-HTTP.md +109 -0
- package/wiki/PWA-And-Offline.md +343 -0
- package/wiki/Policies-And-Authz.md +251 -0
- package/wiki/Project-Layout.md +150 -0
- package/wiki/Queries-And-Live-Queries.md +429 -0
- package/wiki/Realtime.md +734 -0
- package/wiki/Resource-Management.md +121 -0
- package/wiki/Routes-And-Render-Modes.md +521 -0
- package/wiki/SEO.md +288 -0
- package/wiki/Scheduled-Tasks.md +175 -0
- package/wiki/Scraping.md +493 -0
- package/wiki/Shapes.md +63 -0
- package/wiki/Stability.md +97 -0
- package/wiki/Static-Assets.md +166 -0
- package/wiki/Storage-And-Uploads.md +404 -0
- package/wiki/Testing.md +1313 -0
- package/wiki/The-Eight-Primitives.md +247 -0
- package/wiki/Theming.md +362 -0
- package/wiki/Timezones-And-Dates.md +194 -0
- package/wiki/Troubleshooting.md +163 -0
- package/wiki/Tutorial-01-First-App.md +289 -0
- package/wiki/Tutorial-02-First-Feature.md +339 -0
- package/wiki/Tutorial-03-Auth-And-Admin.md +379 -0
- package/wiki/Tutorial-04-Jobs-And-Realtime.md +263 -0
- package/wiki/Tutorial-05-Deploy-Free.md +231 -0
- package/wiki/Tutorial-06-Growing-Up.md +158 -0
- package/wiki/UI-Components.md +156 -0
- package/wiki/Upgrading.md +3667 -0
- package/src/stored-object-headers.ts +0 -43
package/wiki/Mail.md
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
# Mail
|
|
2
|
+
|
|
3
|
+
Transactional email as **data**. One template renders an HTML part and a plain-text part, every
|
|
4
|
+
string is an i18n key, every colour is a design token, and delivery is a [job](Jobs-And-Workflows).
|
|
5
|
+
Package: `@ultimat3/mail` (tier 4) — the full reference is
|
|
6
|
+
[`packages/mail/README.md`](https://github.com/developerz-ai/ultimate/blob/main/packages/mail/README.md).
|
|
7
|
+
|
|
8
|
+
```ts
|
|
9
|
+
import { blocks, defineMail, send, t } from '@ultimat3/mail';
|
|
10
|
+
|
|
11
|
+
export const receiptMail = defineMail({
|
|
12
|
+
id: 'receipt',
|
|
13
|
+
subject: 'mail.receipt.subject', // an i18n key, never a literal
|
|
14
|
+
input: t.object({ name: t.string, url: t.url }),
|
|
15
|
+
template: ({ data }) => [
|
|
16
|
+
blocks.heading('mail.receipt.heading', { name: data.name }),
|
|
17
|
+
blocks.paragraph('mail.receipt.body'),
|
|
18
|
+
blocks.button('mail.receipt.cta', data.url),
|
|
19
|
+
],
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
await send(receiptMail, { name: user.name, url }, { to: user.email, locale: ctx.locale });
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
`send` validates the data through the mail's schema, renders, and enqueues `mail.send` through the
|
|
26
|
+
jobs facade, so inside a transaction the job commits or rolls back with it. It delivers inline only
|
|
27
|
+
with `{ sync: true }` or when no job driver is configured.
|
|
28
|
+
|
|
29
|
+
## Rules
|
|
30
|
+
|
|
31
|
+
| Rule | Why |
|
|
32
|
+
|---|---|
|
|
33
|
+
| `locale` is required by the type | a mail is read hours later; there is no ambient request locale. `X_MAIL_LOCALE_MISSING` backs it for JS callers |
|
|
34
|
+
| a text part is mandatory, derived from the blocks | HTML-only mail scores as spam and cannot be read by a screen reader; deriving it from blocks means the two parts cannot drift (`X_MAIL_TEXT_MISSING`) |
|
|
35
|
+
| every string is a `mail.<id>.<slot>` key | English ships in the package catalog; an app catalog overrides it — translating the framework mails is shipping keys, never editing a template |
|
|
36
|
+
| a mail with an unresolved key is not sent | `send()` throws `X_MAIL_KEY_MISSING` before the transform and the queue, and it is terminal: a key the locale lacks, a catalog value still holding `⟦…⟧`, or a `{slot}` with no value (`meta.keys`, `meta.slots`). A framework mail counts, so a locale that has not translated `mail.*` is refused. `renderMessage()` still returns the `⟦…⟧`, because a preview should show it. A `⟦` from your data sends. `x verify` catches the app's own keys earlier: `blocks.*`, `subject:` ([I18n](I18n)). A framework template's keys live in the package, so no source scan reads them |
|
|
37
|
+
| every date takes an IANA zone | `options.tz`, else `ctx.tz`, else `UTC` |
|
|
38
|
+
| no CR/LF in a header-bound field | refused in rendering and again in the send job (`X_MAIL_HEADER_INVALID`), so every driver refuses the same message |
|
|
39
|
+
| one recipient rule, inline and queued | `to`, `cc`, `bcc`, `replyTo` take the display form; a control character or a non-ASCII mailbox is `X_MAIL_ADDRESS_INVALID` at `send()`, whichever driver is installed |
|
|
40
|
+
| `unsubscribeUrl` is one-click, and one-click is the supported path | it emits `List-Unsubscribe: <url>` plus `List-Unsubscribe-Post: List-Unsubscribe=One-Click` (RFC 8058), and the footer links the same url. Serve it as ONE page: `GET` confirms and never unsubscribes (scanners prefetch), `POST` unsubscribes — `defineRoute({ render: 'ssr', …, post: 'unsubscribe' })` binds the POST to an action with the url's query merged into its input (→ [Routes](Routes-And-Render-Modes)). `unsubscribeOneClick: false` is the fallback for a url that cannot take a POST: the `-Post` line goes |
|
|
41
|
+
| sending is a job | `retry: { attempts: 5, backoff: 'exponential' }`, and an idempotency key derived from the mail id and the rendered message, so a retry is one email |
|
|
42
|
+
|
|
43
|
+
## Which driver a boot installs
|
|
44
|
+
|
|
45
|
+
`selectMailDriver(env)` is the one answer; `x dev` and every role call it. The app does not change
|
|
46
|
+
between environments — the credential does.
|
|
47
|
+
|
|
48
|
+
| env | driver |
|
|
49
|
+
|---|---|
|
|
50
|
+
| nothing set, `development` / `test` | `memoryMailDriver()` — caught in the `/_x` mail panel, never sent |
|
|
51
|
+
| nothing set, `staging` / `production` | `unconfiguredMailDriver(...)` — every send is `X_MAIL_CREDENTIAL_MISSING`; the boot still succeeds, so an app that sends no mail deploys |
|
|
52
|
+
| `SMTP_URL` + `MAIL_FROM` | `smtpMailDriver(...)` — ESMTP over `Bun.connect`, STARTTLS required unless `allowInsecure` |
|
|
53
|
+
| `RESEND_API_KEY` + `MAIL_FROM` | `resendMailDriver(...)` — one `POST /emails` with an `Idempotency-Key` |
|
|
54
|
+
| `SES_REGION` + `SES_ACCESS_KEY_ID` + `SES_SECRET_ACCESS_KEY` + `MAIL_FROM` | `sesMailDriver(...)` — one SES v2 `SendEmail` with the raw MIME, SigV4-signed, no SDK. `SES_SESSION_TOKEN`, `SES_ENDPOINT`, `SES_CONFIGURATION_SET` optional |
|
|
55
|
+
|
|
56
|
+
Every key, with what it means: [Configuration → `mail`](Configuration#mail). More than one of
|
|
57
|
+
`SMTP_URL`, `RESEND_API_KEY` and `SES_REGION` is `X_CONFIG_INVALID` rather than a silent winner.
|
|
58
|
+
`setMailDriver(driver)` is the one seam for a host that builds its own.
|
|
59
|
+
|
|
60
|
+
## Keeping the sent bytes: `mail.retainMime`
|
|
61
|
+
|
|
62
|
+
**Off by default; SMTP and SES only.** `As of 2026-10`. The transports that build the MIME can keep
|
|
63
|
+
the exact bytes they handed the provider, on `SendResult.mime`.
|
|
64
|
+
|
|
65
|
+
```ts
|
|
66
|
+
// app.config.ts
|
|
67
|
+
mail: { retainMime: { maxBytes: 262_144 } },
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
| Rule | Detail |
|
|
71
|
+
|---|---|
|
|
72
|
+
| `true` | mail's default cap, `DEFAULT_RETAIN_MIME_MAX_BYTES` (256 KiB) |
|
|
73
|
+
| over the cap | `{ kind: 'digest-only', sha256, byteLength }` — never a truncated message |
|
|
74
|
+
| `maxBytes` | a whole number above 0 (`X_CONFIG_INVALID` at `defineConfig`), at most `RETAIN_MIME_CEILING_BYTES` (10 MiB; `X_CONFIG_INVALID` at boot, judged before a transport is chosen) |
|
|
75
|
+
| `RESEND_API_KEY` selected | any retention refuses the boot: Resend builds the MIME on its own side |
|
|
76
|
+
| durable copy | `onRetained` is code, not config — a queued send's result is not persisted. Build the driver with `selectMailDriver(env, { retainMime: { onRetained } })` and hand its `.driver` to the `mail` runtime override ([Configuration](Configuration)) |
|
|
77
|
+
|
|
78
|
+
A throwing `onRetained` is logged (`mail.retain_mime.failed`) and does not fail the send.
|
|
79
|
+
|
|
80
|
+
## Delivery events: `@ultimat3/mail/events`
|
|
81
|
+
|
|
82
|
+
**A subpath, never the barrel**: only a webhook route needs it, and every serving role loads
|
|
83
|
+
`@ultimat3/mail` to send. One receiver per provider; each verifies the request, then normalises it to
|
|
84
|
+
`DeliveryEvent` — `delivered` · `bounced` (`bounce: 'hard' | 'soft'`) · `complained` · `delayed`,
|
|
85
|
+
with `messageId` (the sending driver's `SendResult.id`), `recipient`, `at`, `eventId` and `raw`. One
|
|
86
|
+
event per recipient; `(eventId, recipient)` is the dedupe key.
|
|
87
|
+
|
|
88
|
+
```ts
|
|
89
|
+
import { resendEventReceiver, sesEventReceiver } from '@ultimat3/mail/events';
|
|
90
|
+
|
|
91
|
+
export const ses = sesEventReceiver({ topicArns: [Bun.env['SES_EVENTS_TOPIC_ARN'] ?? ''] });
|
|
92
|
+
export const resend = resendEventReceiver({ secret: Bun.env['RESEND_WEBHOOK_SECRET'] ?? '' });
|
|
93
|
+
// const outcome = await ses.receive(request); // { type: 'events' | 'ignored' | 'subscription', … }
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
| Receiver | Verifies |
|
|
97
|
+
|---|---|
|
|
98
|
+
| `sesEventReceiver` | SNS: the topic is in `topicArns`, the certificate URL is `https://sns.<topic region>.amazonaws.com/…pem`, `SignatureVersion` 1 or 2, `Timestamp` within `toleranceMs` (1 h). A `SubscriptionConfirmation` returns its `confirmUrl`; it is fetched only with `confirmSubscriptions: true` |
|
|
99
|
+
| `resendEventReceiver` | Svix: HMAC-SHA256 under the `whsec_` secret, constant-time, within `toleranceMs` (5 min) |
|
|
100
|
+
|
|
101
|
+
Mount a receiver through `webhookReceiver()` ([Jobs and workflows](Jobs-And-Workflows#inbound-webhooks)),
|
|
102
|
+
the one door for an inbound webhook. Its `verify` calls `ses.receive(inbound.request())` — a fresh
|
|
103
|
+
`Request` over the exact bytes, verified there — and returns `{ eventId, payload }` with the events
|
|
104
|
+
as the payload (the SNS `MessageId` as the id), or `{ answer: Response }` for a subscription
|
|
105
|
+
confirm. Its `handle` applies the stored events in the worker without verifying again, so a retry
|
|
106
|
+
an hour later is not refused by the signature window. Declaring it is its registration: the boot
|
|
107
|
+
mounts its route. `api/` holds no route file.
|
|
108
|
+
`SES_CONFIGURATION_SET` names the SES configuration set sent on every message (`ConfigurationSetName`) — the set whose event destination publishes to that SNS topic.
|
|
109
|
+
|
|
110
|
+
## Previews
|
|
111
|
+
|
|
112
|
+
`As of 2026-11-06`. `defineMail({ preview })` declares the data a template is checked by: a value,
|
|
113
|
+
parsed by `input` where it is written (`X_MAIL_PREVIEW_INVALID`), or `(locale) => data`, parsed at
|
|
114
|
+
each render. Never production rows. `x mail preview <id> [--locale <tag>] [--tz <zone>] [--out <dir>] [--json]`
|
|
115
|
+
renders it per locale with nothing sent, naming every key a locale lacks; `x verify`'s `i18n` step
|
|
116
|
+
renders every declared preview in every locale, so a preview is also a test. `previewMail(id,
|
|
117
|
+
{ locale })` is the same render for a host. A mail with none: `X_MAIL_PREVIEW_MISSING`.
|
|
118
|
+
|
|
119
|
+
The `/_x` mail panel lists every registered mail beside the caught ones (`templates`): each declared
|
|
120
|
+
`preview` rendered in each of the app's locales, dates in UTC, with the keys a locale lacks as
|
|
121
|
+
`templateGaps` and a preview its schema refuses shown as that refusal. A mail with no `preview` is
|
|
122
|
+
listed with `hasPreview: false`. `?template=<id>` selects one; `x dev`'s panel and its `--json`
|
|
123
|
+
payload are the same facts.
|
|
124
|
+
|
|
125
|
+
## Framework mails
|
|
126
|
+
|
|
127
|
+
`welcome`, `verify-email`, `reset-password`, `invite`, `mfa-enrolled`, `security-alert` —
|
|
128
|
+
registered by importing them (`FRAMEWORK_MAILS`). `registeredMails()` lists them with an app's
|
|
129
|
+
own; `renderMessage()` renders one without sending.
|
|
130
|
+
|
|
131
|
+
## Errors
|
|
132
|
+
|
|
133
|
+
Every `X_MAIL_*` code is in [Error codes](Error-Codes). The two an app meets first:
|
|
134
|
+
`X_MAIL_LOCALE_MISSING` (pass `locale: ctx.locale`) and `X_MAIL_CREDENTIAL_MISSING` (set `SMTP_URL`,
|
|
135
|
+
`RESEND_API_KEY`, or `SES_REGION` with its key pair — and `MAIL_FROM` — in the deployment).
|
|
136
|
+
|
|
137
|
+
Related: [Notify](Notify) fans one event out to mail, the in-app inbox and your own channels;
|
|
138
|
+
[Configuration](Configuration) lists the env vars.
|