void 0.20.2 → 0.21.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/README.md +2 -2
- package/dist/agents-gni4eqBu.mjs +111 -0
- package/dist/auth-3sGB0zJ1.mjs +22 -0
- package/dist/{auth-DPl6kck4.mjs → auth-B9_D6ygs.mjs} +6 -241
- package/dist/auth-CAw7zHqj.mjs +2 -0
- package/dist/auth-client-BWBT8HLp.mjs +6 -0
- package/dist/auth-client-Bawdj38n.d.mts +7 -0
- package/dist/auth-client-react-7GFffu8R.d.mts +7 -0
- package/dist/auth-client-react-CMLwT2xI.mjs +6 -0
- package/dist/auth-client-solid-DyTFL4cq.d.mts +7 -0
- package/dist/auth-client-solid-UoreJWZG.mjs +6 -0
- package/dist/auth-client-svelte-Cv_WvrRk.mjs +6 -0
- package/dist/auth-client-svelte-zetW0KIH.d.mts +7 -0
- package/dist/auth-client-vue-CFk7Xbv3.mjs +6 -0
- package/dist/auth-client-vue-bChShbQP.d.mts +7 -0
- package/dist/{auth-cmd-gniL2fNt.mjs → auth-cmd-MkBG2u1f.mjs} +5 -5
- package/dist/{auth-link-NZdjCmSc.mjs → auth-link-Devmv96E.mjs} +5 -5
- package/dist/{better-auth-shared-DSCeohOK.d.mts → better-auth-shared-BoVwA5Vm.d.mts} +1 -6
- package/dist/{better-auth-shared-CYw1T3k4.mjs → better-auth-shared-syGYYmnY.mjs} +1 -1
- package/dist/{build-cmd-sI18tX_O.mjs → build-cmd-DsBGwtfs.mjs} +6 -4
- package/dist/{cache-IHn5MwBC.mjs → cache-BMw8eMyF.mjs} +6 -4
- package/dist/{cancel-deploy-C5qTOdLi.mjs → cancel-deploy-B3_s9NIN.mjs} +5 -3
- package/dist/cf-access-rAZxzMkL.mjs +181 -0
- package/dist/cf-build-output-CTdlo6Rt.mjs +520 -0
- package/dist/cli/cf-compat.d.mts +1 -0
- package/dist/cli/cf-compat.mjs +934 -0
- package/dist/cli/cli.mjs +61 -38
- package/dist/cli/env-schema-probe.mjs +3 -3
- package/dist/client-DAAivdid.mjs +2 -0
- package/dist/{client-dHfSJvAN.mjs → client-PdAJ-F0t.mjs} +148 -89
- package/dist/{cloudflare-auth-6M5llVPC.mjs → cloudflare-auth-BCmTc1X_.mjs} +13 -44
- package/dist/{cloudflare-cmd-4RPGN3KB.mjs → cloudflare-cmd-D6PrKn7o.mjs} +4 -7
- package/dist/{cloudflare-connect-t1UU5svD.mjs → cloudflare-connect-DloREDlc.mjs} +6 -6
- package/dist/cloudflare-operations-BHNJVDbN.mjs +2 -0
- package/dist/{cloudflare-operations-BzWnlC1_.mjs → cloudflare-operations-CPEdHYhM.mjs} +37 -39
- package/dist/cloudflare-user-output-Q-k0QoNQ.mjs +20 -0
- package/dist/collect-DAUItMDS.mjs +2 -0
- package/dist/collect-DXHNWHcT.mjs +48 -0
- package/dist/config--T87TD8T.mjs +2 -0
- package/dist/{config-NOG_U1aK.mjs → config-BSe70f4T.mjs} +4 -6
- package/dist/config-DDIFxQYx.mjs +2 -0
- package/dist/{config-uNGuFsI2.mjs → config-Dxr6cTXn.mjs} +22 -37
- package/dist/config-DywxBLQC.d.mts +185 -0
- package/dist/config-entry.d.mts +6 -0
- package/dist/config-entry.mjs +7 -0
- package/dist/config-uP7ZDtVZ.mjs +21 -0
- package/dist/config-write-y3CBI_ux.mjs +60 -0
- package/dist/{connect-Bfk31O_8.mjs → connect-BvC9kolB.mjs} +8 -6
- package/dist/{create-project-Bk9Z0-Jg.mjs → create-project-C9lQhRzj.mjs} +9 -25
- package/dist/create-project-CI-17RVJ.mjs +2 -0
- package/dist/database-provider-Cx525bwX.mjs +6 -0
- package/dist/database-provider.mjs +1 -5
- package/dist/{db-BkRoptAt.mjs → db-R7IQgOv5.mjs} +48 -44
- package/dist/{delete-DouASY9P.mjs → delete-CghI_NXn.mjs} +6 -4
- package/dist/deploy-CYPymbtG.mjs +2 -0
- package/dist/{deploy-DTaWUS1S.mjs → deploy-H968Lo4T.mjs} +765 -197
- package/dist/discover-BBzDZe_o.mjs +2 -0
- package/dist/{discover-xvfrgJeo.mjs → discover-C4O6YxVS.mjs} +3 -8
- package/dist/{dist-BrsS7cai.mjs → dist-4WkAWhWx.mjs} +1 -15
- package/dist/{dist-m40_XgNh.mjs → dist-Bn8Kodjp.mjs} +1 -1
- package/dist/dist-C5fND3R0.mjs +2 -0
- package/dist/dist-D7-nEOXi.mjs +2 -0
- package/dist/{output-B0cfNSx5.mjs → dist-Dn6nn2IU.mjs} +3 -926
- package/dist/{domain-1RhhOVrC.mjs → domain-CITt8lC-.mjs} +7 -5
- package/dist/{route-types-Da-DpyUp.mjs → drizzle-Beb2Am5S.mjs} +1 -280
- package/dist/{email-Ce6SQq-i.mjs → email-C5kaZXzJ.mjs} +2 -2
- package/dist/{email-C-lGh51B.mjs → email-_8VmyX7V.mjs} +14 -12
- package/dist/{entry-DU3oDoQ3.mjs → entry-DdRFGK0y.mjs} +2 -2
- package/dist/{env-DJHsPE7Z.mjs → env-DV4r3nHz.mjs} +9 -7
- package/dist/{env-DBKmK4vc.mjs → env-DX_v-Q-v.mjs} +1 -1
- package/dist/env-Yz4HcvHs.mjs +78 -0
- package/dist/env-helpers--wFmQ_5Q.mjs +136 -0
- package/dist/{env-types-BNPhro-M.mjs → env-types-CWDtqHgw.mjs} +6 -2
- package/dist/{env-validation-CF6KvTRf.mjs → env-validation-BsFEXps5.mjs} +19 -28
- package/dist/env-validation-DIDGM7h4.mjs +2 -0
- package/dist/fetch-CXDChK7B.mjs +18 -0
- package/dist/fetch-_SeGZao9.d.mts +57 -0
- package/dist/fetch-stream-AOByI7Ki.mjs +81 -0
- package/dist/fetch-stream-Bjf0hoZb.d.mts +49 -0
- package/dist/gen-BBiIZw6g.mjs +2 -0
- package/dist/{gen-B_wPnVTK.mjs → gen-CFOEEc-t.mjs} +13 -11
- package/dist/{generate-RTK8_kK1.mjs → generate-C0VY6RVf.mjs} +2 -2
- package/dist/{github-cmd-PW7ZnWTp.mjs → github-cmd-jYjha2LO.mjs} +18 -22
- package/dist/{handler-D1hLsObx.d.mts → handler-BXJTXd02.d.mts} +6 -1
- package/dist/handler-DghKr6dU.mjs +150 -0
- package/dist/head-D_QRR5Yd.mjs +112 -0
- package/dist/head-client-DzmJGN4C.mjs +90 -0
- package/dist/{headers-BAHwgHdW.mjs → headers-B_HBMgi0.mjs} +2 -2
- package/dist/{help-CwOX-zmI.mjs → help-C46mnlUS.mjs} +17 -12
- package/dist/help-CS_nAsWu.mjs +2 -0
- package/dist/index.d.mts +2 -20
- package/dist/index.mjs +116 -60
- package/dist/{init-BWZ7q5Z4.mjs → init-CSXmvxKu.mjs} +49 -50
- package/dist/{link-Rmvu2Wl_.mjs → link-DxMOALXk.mjs} +7 -5
- package/dist/{list-DEE2S6mY.mjs → list-0wQs0oYg.mjs} +7 -5
- package/dist/live-CB1y5IuC.mjs +411 -0
- package/dist/live-CKiJilLr.d.mts +105 -0
- package/dist/local-d1-DzykTWY8.mjs +104 -0
- package/dist/{login-Uvferzmm.mjs → login-CKk5NX4d.mjs} +6 -11
- package/dist/login-Ch9cgWRF.mjs +2 -0
- package/dist/{logs-27FenuiC.mjs → logs-hxWBSoej.mjs} +6 -4
- package/dist/migrate-CLty4mZR.mjs +2 -0
- package/dist/migrate-DcW8F2W8.mjs +285 -0
- package/dist/migration-handler-DM4clYj5.d.mts +51 -0
- package/dist/{neon-DHwd2zvC.mjs → neon-n74ta1Pr.mjs} +1 -1
- package/dist/{node-Ez5KW5rn.mjs → node-Cupyf7-s.mjs} +6 -6
- package/dist/{operator-auth-B3e08unv.mjs → operator-auth-BkVgJqv-.mjs} +2 -2
- package/dist/{operator-client-LUZnlnYk.mjs → operator-client-A0iex2yi.mjs} +2 -1
- package/dist/{operator-cmd-CjOTmAYE.mjs → operator-cmd-BEUYhaHB.mjs} +6 -6
- package/dist/output-urU86XeT.mjs +146 -0
- package/dist/{package-json-CPoWX79C.mjs → package-json-iCbMg5XF.mjs} +1 -1
- package/dist/pages/client.d.mts +5 -2
- package/dist/pages/client.mjs +5 -3
- package/dist/pages/head-client.mjs +1 -89
- package/dist/pages/head.mjs +1 -111
- package/dist/pages/index.d.mts +2 -3
- package/dist/pages/index.mjs +7 -7
- package/dist/pages/islands-plugin.mjs +2 -2
- package/dist/pages/prefetch.d.mts +2 -30
- package/dist/pages/prefetch.mjs +1 -89
- package/dist/pages/protocol.d.mts +2 -2
- package/dist/pages/protocol.mjs +3 -3
- package/dist/pages/serialize.d.mts +2 -9
- package/dist/pages/serialize.mjs +1 -13
- package/dist/plan-BEZ8VJW0.mjs +256 -0
- package/dist/plan-DpuOr14e.mjs +2 -0
- package/dist/{platform-auth-config-DrbQXXiW.mjs → platform-auth-config-Df6yVw-e.mjs} +6 -6
- package/dist/{platform-auth-protection-Bhtvp0B_.mjs → platform-auth-protection-Jb0yftay.mjs} +7 -5
- package/dist/{platform-auth-recovery-CeOKGVeJ.mjs → platform-auth-recovery-DmRyIfW0.mjs} +6 -5
- package/dist/platform-cmd-C9Vt7m-d.mjs +2 -0
- package/dist/{platform-cmd-BFhieCdV.mjs → platform-cmd-DRxCOTwy.mjs} +8 -5
- package/dist/{platform-domain-C74PULqV.mjs → platform-domain-IjiXgrXJ.mjs} +4 -3
- package/dist/{platform-lifecycle-BwAIgz-t.mjs → platform-lifecycle-BquAaU-5.mjs} +263 -56
- package/dist/platform-lifecycle-CuJIZNvA.mjs +2 -0
- package/dist/{platform-management-COogu_Se.mjs → platform-management-CCQKGsq4.mjs} +12 -7
- package/dist/platform-management-Cgyed0WY.mjs +2 -0
- package/dist/{platform-recovery-ewqLefp1.mjs → platform-recovery-DS8ih1SW.mjs} +3 -3
- package/dist/platform-registry-BJbgZLS1.mjs +431 -0
- package/dist/{plugin-inference-BDRfZngg.mjs → plugin-inference-DsvtJLll.mjs} +4 -4
- package/dist/prefetch-Bsc_Pb6c.mjs +90 -0
- package/dist/prefetch-Ce6la4EI.d.mts +31 -0
- package/dist/{prepare-blNRQvQl.mjs → prepare-CaUxODOU.mjs} +3 -2
- package/dist/prepare-D4CkM3_v.mjs +2 -0
- package/dist/{prepare-CtDJjoOj.mjs → prepare-pcWcxSCh.mjs} +13 -11
- package/dist/prerender-render-Cf_WDE9W.mjs +111 -0
- package/dist/prerender-render.mjs +1 -110
- package/dist/{preset-lAy0B0BQ.mjs → preset-Dowh9tTt.mjs} +16 -118
- package/dist/project-BEBFDFLz.mjs +2 -0
- package/dist/project-CWNIPoXc.mjs +209 -0
- package/dist/{project-cmd-DmZK9Hxf.mjs → project-cmd-2lN--YAX.mjs} +18 -16
- package/dist/{project-paths-SK8nMHPp.mjs → project-paths-CKQ-Q5JS.mjs} +47 -14
- package/dist/project-slug-23TpquG4.mjs +8 -0
- package/dist/project-slug-DofjTFd-.mjs +2 -0
- package/dist/{project-team-D8jOJMUJ.mjs → project-team-Dapn9HZn.mjs} +9 -5
- package/dist/{project-token-DA34bf-C.mjs → project-token-C90v9SJK.mjs} +6 -4
- package/dist/{project-tsconfig-Ql2XsSQp.mjs → project-tsconfig-CwfqUnVp.mjs} +2 -2
- package/dist/{protocol-C-pqYJjE.d.mts → protocol-ZH3jP4a7.d.mts} +1 -1
- package/dist/providers-BNKRacMr.d.mts +7 -0
- package/dist/provision-CNgEBkVA.mjs +3 -0
- package/dist/{provision-CSJOjjQk.mjs → provision-Cck2m3jJ.mjs} +11 -25
- package/dist/queues-BWKt1Xo4.d.mts +7 -0
- package/dist/{requests-CUExwGQQ.mjs → requests-CzX46I0i.mjs} +5 -3
- package/dist/resolve-project-BTotl8Nn.mjs +2 -0
- package/dist/{resolve-project--Vxawf7z.mjs → resolve-project-Xvis70DG.mjs} +2 -8
- package/dist/response-Tn7rU0MV.mjs +30 -0
- package/dist/{rollback-CDNGU1gr.mjs → rollback-BEeyc73H.mjs} +6 -4
- package/dist/{rolldown-runtime-rQ84J-ij.mjs → rolldown-runtime-DXIUcv95.mjs} +1 -10
- package/dist/route-types-Id82-veQ.mjs +280 -0
- package/dist/{local-d1-D2I6Ox5F.mjs → runner-BGVsGgkb.mjs} +6 -114
- package/dist/runner-CQs_cDSG.mjs +2 -0
- package/dist/runner-mysql-1o47achK.mjs +2 -0
- package/dist/{runner-mysql-7BPUNGmL.mjs → runner-mysql-BhwMk2Bm.mjs} +2 -9
- package/dist/{runner-pg-BkEza-dX.mjs → runner-pg-CJ_JeGF6.mjs} +2 -9
- package/dist/runner-pg-D7z01mQS.mjs +2 -0
- package/dist/runtime/ai.mjs +1 -1
- package/dist/runtime/auth-client-react.d.mts +2 -6
- package/dist/runtime/auth-client-react.mjs +1 -5
- package/dist/runtime/auth-client-solid.d.mts +2 -6
- package/dist/runtime/auth-client-solid.mjs +1 -5
- package/dist/runtime/auth-client-svelte.d.mts +2 -6
- package/dist/runtime/auth-client-svelte.mjs +1 -5
- package/dist/runtime/auth-client-vue.d.mts +2 -6
- package/dist/runtime/auth-client-vue.mjs +1 -5
- package/dist/runtime/auth-client.d.mts +2 -6
- package/dist/runtime/auth-client.mjs +1 -5
- package/dist/runtime/auth.mjs +1 -21
- package/dist/runtime/better-auth-mysql.d.mts +1 -1
- package/dist/runtime/better-auth-mysql.mjs +1 -1
- package/dist/runtime/better-auth-pg.d.mts +1 -1
- package/dist/runtime/better-auth-pg.mjs +1 -1
- package/dist/runtime/better-auth.d.mts +1 -1
- package/dist/runtime/better-auth.mjs +1 -1
- package/dist/runtime/client-react.d.mts +3 -3
- package/dist/runtime/client-react.mjs +3 -3
- package/dist/runtime/client-solid.d.mts +3 -3
- package/dist/runtime/client-solid.mjs +3 -3
- package/dist/runtime/client-svelte.d.mts +3 -3
- package/dist/runtime/client-svelte.mjs +3 -3
- package/dist/runtime/client-vue.d.mts +3 -3
- package/dist/runtime/client-vue.mjs +3 -3
- package/dist/runtime/client.d.mts +3 -3
- package/dist/runtime/client.mjs +3 -3
- package/dist/runtime/db.mjs +1 -1
- package/dist/runtime/durable.mjs +1 -1
- package/dist/runtime/email/testing.mjs +1 -1
- package/dist/runtime/env-helpers.mjs +1 -135
- package/dist/runtime/env-public-client.mjs +1 -1
- package/dist/runtime/env-public.mjs +2 -2
- package/dist/runtime/env.mjs +1 -77
- package/dist/runtime/fetch-stream.d.mts +2 -49
- package/dist/runtime/fetch-stream.mjs +2 -80
- package/dist/runtime/fetch.d.mts +2 -57
- package/dist/runtime/fetch.mjs +2 -17
- package/dist/runtime/handler.d.mts +1 -1
- package/dist/runtime/handler.mjs +1 -149
- package/dist/runtime/kv.mjs +1 -1
- package/dist/runtime/live-client.d.mts +1 -1
- package/dist/runtime/live-server.mjs +2 -2
- package/dist/runtime/live.d.mts +2 -104
- package/dist/runtime/live.mjs +1 -410
- package/dist/runtime/migration-handler-mysql.d.mts +1 -1
- package/dist/runtime/migration-handler-pg.d.mts +1 -1
- package/dist/runtime/migration-handler.d.mts +2 -50
- package/dist/runtime/queues.d.mts +2 -6
- package/dist/runtime/queues.mjs +1 -1
- package/dist/runtime/remote/index.mjs +49 -8
- package/dist/runtime/response.mjs +1 -29
- package/dist/runtime/sandbox.d.mts +4 -56
- package/dist/runtime/sandbox.mjs +82 -221
- package/dist/runtime/sse.mjs +1 -171
- package/dist/runtime/storage.mjs +1 -1
- package/dist/runtime/validator.d.mts +1 -1
- package/dist/runtime/validator.mjs +1 -71
- package/dist/runtime/ws-server.d.mts +2 -2
- package/dist/runtime/ws-server.mjs +2 -2
- package/dist/runtime/ws.d.mts +2 -121
- package/dist/{scan-CpK-57ug.mjs → scan-DJbooZm2.mjs} +3 -3
- package/dist/{scan-BMH4rzlv.mjs → scan-DdDvRCU1.mjs} +8 -26
- package/dist/{secret-Bzzi2e9E.mjs → secret-wnTel5Yw.mjs} +8 -6
- package/dist/serialize-BPvnNQuA.mjs +14 -0
- package/dist/serialize-CfSwWfF2.d.mts +10 -0
- package/dist/{skills-C0RvGjeE.mjs → skills-B-690E7h.mjs} +3 -2
- package/dist/sse-BaC1jXko.mjs +172 -0
- package/dist/{subcommand-prompt-Bmyn5Rlc.mjs → subcommand-prompt-Gj3VzLIh.mjs} +2 -1
- package/dist/sveltekit.d.mts +2 -1
- package/dist/sveltekit.mjs +3 -2
- package/dist/validate-DqJ33oHj.mjs +2 -0
- package/dist/validate-qNhV00PD.mjs +180 -0
- package/dist/validator-BTOu0fB0.mjs +72 -0
- package/dist/{wrangler--imS8n0d.mjs → wrangler-D01qs6VB.mjs} +259 -71
- package/dist/ws-BwcqizuH.d.mts +122 -0
- package/dist/{yarn-pnp-DxSInkzL.mjs → yarn-pnp-CVEc3gE7.mjs} +1 -1
- package/package.json +19 -8
- package/skills/void/SKILL.md +7 -5
- package/skills/void/docs/guide/ai.md +3 -3
- package/skills/void/docs/guide/app-types.md +12 -11
- package/skills/void/docs/guide/auth.md +2 -2
- package/skills/void/docs/guide/database/d1.md +1 -1
- package/skills/void/docs/guide/database/mysql.md +1 -1
- package/skills/void/docs/guide/database/postgresql.md +3 -3
- package/skills/void/docs/guide/deployment.md +16 -16
- package/skills/void/docs/guide/durable-state.md +2 -2
- package/skills/void/docs/guide/edge/headers.md +3 -3
- package/skills/void/docs/guide/edge/prerendering.md +1 -1
- package/skills/void/docs/guide/edge/redirects.md +4 -4
- package/skills/void/docs/guide/edge/revalidation.md +6 -6
- package/skills/void/docs/guide/edge/rewrites.md +28 -27
- package/skills/void/docs/guide/edge/static-assets.md +1 -1
- package/skills/void/docs/guide/email.md +27 -25
- package/skills/void/docs/guide/env-migration.md +1 -1
- package/skills/void/docs/guide/index.md +1 -1
- package/skills/void/docs/guide/pages-routing/head.md +1 -1
- package/skills/void/docs/guide/platform/administration/access.md +171 -0
- package/skills/void/docs/guide/platform/administration/email.md +121 -0
- package/skills/void/docs/guide/platform/administration/operations.md +97 -0
- package/skills/void/docs/guide/platform/administration/projects.md +56 -0
- package/skills/void/docs/guide/platform/development/local.md +119 -0
- package/skills/void/docs/guide/platform/development/runtime.md +124 -0
- package/skills/void/docs/guide/platform/development/schema-ci.md +95 -0
- package/skills/void/docs/guide/platform/installation/ci.md +58 -0
- package/skills/void/docs/guide/platform/installation/credentials.md +90 -0
- package/skills/void/docs/guide/platform/installation/domains.md +68 -0
- package/skills/void/docs/guide/platform/installation/first-deployment.md +82 -0
- package/skills/void/docs/guide/platform/installation/maintenance.md +137 -0
- package/skills/void/docs/guide/platform/installation/prerequisites.md +88 -0
- package/skills/void/docs/guide/platform/installation/setup.md +169 -0
- package/skills/void/docs/guide/platform/installation/uninstall.md +54 -0
- package/skills/void/docs/guide/platform-administration.md +6 -414
- package/skills/void/docs/guide/platform-development.md +5 -316
- package/skills/void/docs/guide/project-collaboration.md +1 -1
- package/skills/void/docs/guide/remote-dev.md +2 -2
- package/skills/void/docs/guide/sandboxes.md +10 -25
- package/skills/void/docs/guide/self-hosted-platform.md +11 -694
- package/skills/void/docs/guide/ssg.md +1 -1
- package/skills/void/docs/guide/websockets.md +1 -1
- package/skills/void/docs/integrations/cloudflare.md +86 -84
- package/skills/void/docs/integrations/frameworks/analog.md +14 -9
- package/skills/void/docs/integrations/frameworks/astro.md +14 -10
- package/skills/void/docs/integrations/frameworks/nuxt.md +14 -9
- package/skills/void/docs/integrations/frameworks/overview.md +35 -29
- package/skills/void/docs/integrations/frameworks/react-router.md +1 -1
- package/skills/void/docs/integrations/frameworks/sveltekit.md +33 -30
- package/skills/void/docs/integrations/frameworks/tanstack-start.md +1 -1
- package/skills/void/docs/integrations/nodejs-bun-deno.md +5 -5
- package/skills/void/docs/reference/api.md +55 -38
- package/skills/void/docs/reference/cli.md +60 -34
- package/skills/void/docs/reference/config.md +57 -34
- package/skills/void/docs/reference/resource-inference.md +10 -10
- package/skills/void/docs/reference/structure.md +2 -2
- package/dist/validate-tBBN_dXH.mjs +0 -505
|
@@ -10,7 +10,7 @@ Send transactional email from your app via [Cloudflare's `send_email` binding](h
|
|
|
10
10
|
import { sendEmail } from 'void/email';
|
|
11
11
|
|
|
12
12
|
const result = await sendEmail({
|
|
13
|
-
from: 'Acme <acme+noreply@mail.
|
|
13
|
+
from: 'Acme <acme+noreply@mail.example.com>', // use your project's sender address
|
|
14
14
|
to: 'user@example.com',
|
|
15
15
|
subject: 'Welcome',
|
|
16
16
|
text: 'Thanks for signing up!',
|
|
@@ -36,16 +36,20 @@ hits first, because a recipient you have not verified yet fails per-recipient.
|
|
|
36
36
|
|
|
37
37
|
## Setup
|
|
38
38
|
|
|
39
|
-
|
|
39
|
+
On a platform with email enabled, your app needs no email configuration before
|
|
40
|
+
`void deploy`. Ask your administrator for the platform's shared mail domain. A
|
|
41
|
+
self-hosted administrator [enables email during installation or upgrade](/guide/platform/installation/credentials#runtime-token-permissions); installations without it do not offer platform email. Void Cloud uses `mail.void.cloud`.
|
|
40
42
|
|
|
41
|
-
|
|
43
|
+
Each project on an email-enabled platform has:
|
|
44
|
+
|
|
45
|
+
- **Sender** — `<your-slug>+noreply@<mail-domain>`. Used as the default `from` if you omit it. The platform administrator configures the mail zone and its Email Routing, DKIM, SPF, and DMARC records. Project slugs are capped at 56 characters so this local part fits RFC 5321's 64 octets; a project created before the cap with a longer slug must pass `from` explicitly.
|
|
42
46
|
- **No worker binding to add** — outbound mail is sent by the Void proxy, which holds the
|
|
43
47
|
platform `send_email` binding. Your worker never gets one, so there is nothing to configure.
|
|
44
48
|
- **Your own address as a recipient** — the email on your Void account is registered as a recipient when the project is created. It is verified at once when Cloudflare already holds it verified for the platform (you clicked its link for an earlier project of yours); otherwise Cloudflare mails it a verification link, and until you click that link and run `void email destinations` — the listing is what records the click — a send to yourself comes back `ok: false` with a per-recipient `UNVERIFIED_DESTINATION` in `result.deliveries`.
|
|
45
49
|
|
|
46
50
|
That's it for configuration. Delivery is gated separately: outbound mail only reaches verified recipients, so `sendEmail({ to: '...', subject: '...', text: '...' })` works on first deploy for your own address once it is verified, and for anyone else after `void email allow` — see [Adding recipients](#adding-recipients).
|
|
47
51
|
|
|
48
|
-
Deploying to your own Cloudflare account instead (`void deploy --platform cloudflare`) takes one line of `void.
|
|
52
|
+
Deploying to your own Cloudflare account instead (`void deploy --platform cloudflare`) takes one line of `void.config.ts` and one Enter on the first deploy — see [Your own Cloudflare account](#your-own-cloudflare-account).
|
|
49
53
|
|
|
50
54
|
## Adding recipients
|
|
51
55
|
|
|
@@ -61,7 +65,7 @@ Cloudflare emails the recipient with a verification link. Once they click it and
|
|
|
61
65
|
void email destinations
|
|
62
66
|
```
|
|
63
67
|
|
|
64
|
-
The project owner's email
|
|
68
|
+
The project owner's account email is added automatically when the project is created on an email-enabled platform, so it skips `void email allow` — not the verification. See [Setup](#setup) for when it is verified at once and when there is a link to click.
|
|
65
69
|
|
|
66
70
|
::: warning When this is the right fit
|
|
67
71
|
The shared sender is great for: ops alerts to the team, notifications to the project owner, reply-by-email flows on top of inbound, internal/app-internal mail.
|
|
@@ -71,7 +75,7 @@ For SaaS sending to arbitrary end-users (every signup gets a welcome email), the
|
|
|
71
75
|
|
|
72
76
|
## Your own domain on the platform
|
|
73
77
|
|
|
74
|
-
The shared sender
|
|
78
|
+
The shared sender uses the platform's configured mail domain. To send — and receive — at a domain you own, register its Cloudflare zone with the project:
|
|
75
79
|
|
|
76
80
|
```sh
|
|
77
81
|
void email domain add acme.com
|
|
@@ -120,15 +124,14 @@ At most 50 recipients across `to`, `cc` and `bcc` per call. Each address is chec
|
|
|
120
124
|
|
|
121
125
|
`Address` accepts either a string (`"hello@acme.dev"` or `"Name <hello@acme.dev>"`) or an object (`{ email, name? }`). Display names with non-ASCII characters are RFC 2047 encoded automatically.
|
|
122
126
|
|
|
123
|
-
On the platform the sender is pinned to your project. `from` must be your project's own platform address — `<project-slug
|
|
127
|
+
On the platform the sender is pinned to your project. `from` must be your project's own platform address — `<project-slug>@<mail-domain>` or `<project-slug>+<tag>@<mail-domain>`, optionally with a display name — or any address on a domain registered with `void email domain add` (see [Your own domain on the platform](#your-own-domain-on-the-platform)). For example, if your platform's mail domain is `mail.example.com`, you can use `Acme <acme+noreply@mail.example.com>`. Anything else is rejected with `INVALID_FROM`. Omit `from` and Void fills in `<project-slug>+noreply@<mail-domain>` for you.
|
|
124
128
|
|
|
125
|
-
On your own Cloudflare account, `from` defaults to `email.from` from `void.
|
|
129
|
+
On your own Cloudflare account, `from` defaults to `email.from` from `void.config.ts` and must be on a domain your account can send from; Cloudflare rejects any other sender and `sendEmail` reports it as `INVALID_FROM`.
|
|
126
130
|
|
|
127
131
|
## Attachments
|
|
128
132
|
|
|
129
133
|
```ts
|
|
130
134
|
await sendEmail({
|
|
131
|
-
from: 'Acme <acme+noreply@mail.void.cloud>',
|
|
132
135
|
to: 'user@example.com',
|
|
133
136
|
subject: 'Your receipt',
|
|
134
137
|
text: 'Receipt attached.',
|
|
@@ -146,7 +149,6 @@ For inline images (e.g. logos referenced from HTML), set `disposition: 'inline'`
|
|
|
146
149
|
|
|
147
150
|
```ts
|
|
148
151
|
await sendEmail({
|
|
149
|
-
from: 'Acme <acme+noreply@mail.void.cloud>',
|
|
150
152
|
to: 'user@example.com',
|
|
151
153
|
subject: 'Hello',
|
|
152
154
|
html: '<img src="cid:logo" alt="Acme">',
|
|
@@ -237,11 +239,11 @@ Under the hood, your code runs in workerd (a separate process from the Vite dev
|
|
|
237
239
|
|
|
238
240
|
The dev inbox is **not** available under a Class B or C framework — SvelteKit, Nuxt, Analog, Astro. Those adapters own their own dev server and worker build, so Void never installs the Cloudflare Vite plugin for them and has nowhere to register the inbox. Declaring an `assets` binding does not help: SvelteKit, Nuxt and Analog do not run your code in a workerd instance fronted by Vite at all, so there is no loopback to bind to. Sends from those apps return `BINDING_MISSING`. Use `createEmailTestHarness` from `void/email/testing` instead, which captures in-process and works everywhere. When the inbox is configured but unreachable, `sendEmail` returns `UPSTREAM_ERROR` — nothing is captured and nothing is sent.
|
|
239
241
|
|
|
240
|
-
`sendInDev: true` bypasses the dev inbox for a single send. `void dev` binds no send transport at all — the platform's `__VOID_PROXY` service binding is added only on a deployed worker, and the own-account `SEND_EMAIL` binding is stripped under `serve` so miniflare cannot write stray `.eml` files or send real mail (only that entry: a `send_email` binding of your own under another name is left exactly as `
|
|
242
|
+
`sendInDev: true` bypasses the dev inbox for a single send. `void dev` binds no send transport at all — the platform's `__VOID_PROXY` service binding is added only on a deployed worker, and the own-account `SEND_EMAIL` binding is stripped under `serve` so miniflare cannot write stray `.eml` files or send real mail (only that entry: a `send_email` binding of your own under another name is left exactly as `cloudflare.send_email` in `void.config.ts` declares it). With the inbox skipped there is nothing left to fall through to, so the call returns `BINDING_MISSING`: it proves the inbox was bypassed, it does not deliver. To verify real delivery, deploy and send from the deployed worker.
|
|
241
243
|
|
|
242
244
|
```ts
|
|
243
245
|
const result = await sendEmail({
|
|
244
|
-
from: 'acme+noreply@mail.
|
|
246
|
+
from: 'acme+noreply@mail.example.com', // replace with your project sender
|
|
245
247
|
to: 'verified@acme.dev',
|
|
246
248
|
subject: 'Skips the dev inbox',
|
|
247
249
|
text: 'Not captured — and not delivered either.',
|
|
@@ -264,7 +266,7 @@ describe('signup flow', () => {
|
|
|
264
266
|
const inbox = createEmailTestHarness();
|
|
265
267
|
|
|
266
268
|
await sendEmail({
|
|
267
|
-
from: 'acme+noreply@mail.
|
|
269
|
+
from: 'acme+noreply@mail.example.com', // use your project sender
|
|
268
270
|
to: 'user@example.com',
|
|
269
271
|
subject: 'Welcome',
|
|
270
272
|
text: 'Hi!',
|
|
@@ -406,7 +408,7 @@ deployed `email/` handlers without per-project DNS setup. A
|
|
|
406
408
|
handlers once its inbound readiness is `ready`. Use `void email domain status`
|
|
407
409
|
to check current provider routing and any remaining setup steps.
|
|
408
410
|
|
|
409
|
-
On your own Cloudflare account there is no shared facility: the deploy derives one Email Routing rule per handler and
|
|
411
|
+
On your own Cloudflare account there is no shared facility: the deploy derives one Email Routing rule per handler and records it in `void.lock.json` for you — see [Your own Cloudflare account](#your-own-cloudflare-account).
|
|
410
412
|
|
|
411
413
|
There is no local inbound trigger yet — `void dev` serves the outbound dev inbox only, so test inbound handlers with `createInboundTestHarness` below.
|
|
412
414
|
|
|
@@ -476,7 +478,7 @@ The harness uses the same precedence rules as the production dispatcher. Reserve
|
|
|
476
478
|
|
|
477
479
|
### Two things to type, once
|
|
478
480
|
|
|
479
|
-
1. **`email.from` in `void.
|
|
481
|
+
1. **`email.from` in `void.config.ts`** — the default sender, and the domain Void sets up:
|
|
480
482
|
|
|
481
483
|
```json
|
|
482
484
|
{
|
|
@@ -486,17 +488,17 @@ The harness uses the same precedence rules as the production dispatcher. Reserve
|
|
|
486
488
|
}
|
|
487
489
|
```
|
|
488
490
|
|
|
489
|
-
The host (`mail.acme.com`) must be a zone in the Cloudflare account your deploy is pinned to, or sit under one. Without `email.from`, a deploy that uses email
|
|
491
|
+
The host (`mail.acme.com`) must be a zone in the Cloudflare account your deploy is pinned to, or sit under one. Without `email.from`, a deploy that uses email asks you to set it in `void.config.ts` and deploys without email. Void does not pick a zone for you or write the setting automatically.
|
|
490
492
|
|
|
491
493
|
2. **`void cloudflare login`**, or **`CLOUDFLARE_API_TOKEN`** set to a token with **Email Routing Edit** and **Email Sending Edit** (zone and account) alongside the deploy permissions. A browser session created by older Cloudflare tooling lacks the two email scopes; the deploy tells you to run `void cloudflare logout`, then `void cloudflare login` (one browser Allow) and skips the email step. A token's permissions cannot be listed up front, so a token that lacks one shows up as a row Void could not read (`unknown`), with the permission named. A Global API Key pair (`CLOUDFLARE_API_KEY`) is refused: it has no single bearer for the two calls wrangler has no command for.
|
|
492
494
|
|
|
493
495
|
### The first deploy
|
|
494
496
|
|
|
495
|
-
Before any of your project code runs, the deploy **reads** — the session, the zone, public DNS (MX on the mail domain and on the apex, `_dmarc`, `cf-bounce`), Email Routing status, the zone's subaddressing setting, the routing rules on your addresses, Email Sending, and what
|
|
497
|
+
Before any of your project code runs, the deploy **reads** — the session, the zone, public DNS (MX on the mail domain and on the apex, `_dmarc`, `cf-bounce`), Email Routing status, the zone's subaddressing setting, the routing rules on your addresses, Email Sending, and what the resolved Cloudflare config already holds — and prints what it found and what it would change:
|
|
496
498
|
|
|
497
499
|
```
|
|
498
500
|
Email in use email/support+[ticket].ts · email/_default.ts · sendEmail() in 2 files
|
|
499
|
-
domain mail.acme.com (void.
|
|
501
|
+
domain mail.acme.com (void.config.ts email.from)
|
|
500
502
|
zone acme.com account Acme (f721b8e5…) · session dev@acme.com
|
|
501
503
|
apex MX aspmx.l.google.com left alone — mail lives on the subdomain
|
|
502
504
|
routing mail.acme.com not enabled · subaddressing off
|
|
@@ -509,7 +511,7 @@ Before any of your project code runs, the deploy **reads** — the session, the
|
|
|
509
511
|
+ enable Email Routing on mail.acme.com Cloudflare writes and locks 3 MX + 1 SPF record there
|
|
510
512
|
+ turn on subaddressing for acme.com support+anything@ reaches support@
|
|
511
513
|
+ onboard mail.acme.com for Email Sending MX/SPF/DKIM on cf-bounce.mail.acme.com, _dmarc.mail.acme.com (p=reject)
|
|
512
|
-
+
|
|
514
|
+
+ Cloudflare config send_email: [{ name: "SEND_EMAIL" }], addresses: ["support@mail.acme.com"], vars.__VOID_EMAIL_FROM: "noreply@mail.acme.com"
|
|
513
515
|
+ routing rule (created on deploy) support@mail.acme.com → acme-support
|
|
514
516
|
! email/_default.ts a catch-all exists only on an apex; other @mail.acme.com mail bounces
|
|
515
517
|
|
|
@@ -528,7 +530,7 @@ On the second deploy every row reads ready: no prompt, no account call, and wran
|
|
|
528
530
|
|
|
529
531
|
DNS can take a few minutes to become visible to the resolver running the deploy. If `void email setup` has already written the exact `addresses` plan but that resolver still sees no subdomain MX, Void preserves the plan and stops before the build or upload. Run `void email status --platform cloudflare`, then retry the deploy after it sees Cloudflare's MX records.
|
|
530
532
|
|
|
531
|
-
Two rows live in
|
|
533
|
+
Two rows live in the generated Cloudflare config rather than in your account, and a deploy that finds every account row ready reconciles them with a plain file write, no prompt: the `addresses` array is rewritten whenever it is not the current derivation (an entry pruned since, a worker rename, a new handler), and `vars.__VOID_EMAIL_FROM` follows a changed `email.from`. Routing rules are `wrangler deploy`'s own work, so a committed `addresses` entry whose rule does not exist yet — the state right after `void email setup` — still reads ready; the address map marks it `(rule created by this deploy)`.
|
|
532
534
|
|
|
533
535
|
A subdomain is added to a zone that already routes. When Email Routing is **off** on the apex (`Enabled: false`), the subdomain step is not attempted at all — Void never enables routing on the apex from the subdomain path, since that would lock MX records over the apex's live mail — and the checklist prints the dashboard step (`Email → Settings → Subdomains → add mail.acme.com`) instead; `addresses` is withheld until routing on the subdomain reads ready.
|
|
534
536
|
|
|
@@ -544,7 +546,7 @@ The apex path (`email.from` on `acme.com` itself) is allowed with the same one E
|
|
|
544
546
|
|
|
545
547
|
**Subaddressing** is a per-zone Cloudflare setting, and it is off by default. Until it is on, a rule for `support@mail.acme.com` does not match `support+T-42@mail.acme.com`. The checklist's `turn on subaddressing for acme.com` row flips it for the whole zone — on the apex and every subdomain — so `email/support+[ticket].ts` works the way it does on the platform.
|
|
546
548
|
|
|
547
|
-
### What Void
|
|
549
|
+
### What Void records in `void.lock.json`
|
|
548
550
|
|
|
549
551
|
```jsonc
|
|
550
552
|
{
|
|
@@ -563,7 +565,7 @@ The apex path (`email.from` on `acme.com` itself) is allowed with the same one E
|
|
|
563
565
|
- An address already routed to another worker or to a forwarding rule is **pruned** from the array and reported (`! support@mail.acme.com already routed to …; left alone, not in addresses`). wrangler's plan is never destructive on your account because of something Void derived.
|
|
564
566
|
- If the array holds entries Void did not derive, the deploy prints which ones and **skips the email step for that deploy** — the deploy itself continues and the array is left untouched. Remove them, or manage `addresses` by hand and leave `email.from` unset.
|
|
565
567
|
|
|
566
|
-
Deleting a handler leaves its address in `addresses`: the next deploy names it in a `✘` row as an entry Void no longer derives, and skips the email step until you
|
|
568
|
+
Deleting a handler leaves its address in `addresses`: the next deploy names it in a `✘` row as an entry Void no longer derives, and skips the email step until you override `cloudflare.addresses` in `void.config.ts` with the desired list (the row says which). Once removed, Cloudflare's plan drops the rule with its own y/n (default No) in a terminal, an error in CI. Removing the last handler and every `sendEmail()` call leaves the whole setup in `void.lock.json`; the deploy warns which addresses are still routed to a worker with no `email()` export and how to detach them, and deploys as-is. To detach fully, remove `addresses`, `send_email`, and `vars.__VOID_EMAIL_FROM` from `resolved` in `void.lock.json` and remove any authored overrides in `void.config.ts`, then delete the routing rules in Cloudflare.
|
|
567
569
|
|
|
568
570
|
How handlers become addresses, with `email.from` on `mail.acme.com` under the zone `acme.com`:
|
|
569
571
|
|
|
@@ -591,7 +593,7 @@ Workers Paid needed for arbitrary recipients. Inbound works; sendEmail() to veri
|
|
|
591
593
|
|
|
592
594
|
Everything else — routing, rules, the binding — still goes through, and the address map ends with `outbound sendEmail() from support@mail.acme.com (verified destinations only)`. Verified destinations are the addresses under **Email Routing → Destination addresses** in your Cloudflare dashboard; a handler that `forward()`s to a new address needs the same verification click.
|
|
593
595
|
|
|
594
|
-
The refusal is remembered by the `send_email` binding that same run
|
|
596
|
+
The refusal is remembered by the `send_email` binding that same run records in `void.lock.json`: Void records the binding only after it has attempted the onboarding, so a committed binding next to a domain that is still not onboarded means "tried, refused". Later deploys ask nothing about it, `--require-email` passes, `void email status` reads the domain as set up (its sending row says `not onboarded — verified destinations only` and names the retry), and the address map keeps ending with `(verified destinations only)`. The deploy never retries the onboarding on its own. After upgrading to Workers Paid, run `void email setup --platform cloudflare` once: it asks `Onboard mail.acme.com for Email Sending?` and, on Yes, onboards the domain — from then on the map ends without the marker.
|
|
595
597
|
|
|
596
598
|
If `_dmarc.mail.acme.com` or `cf-bounce.mail.acme.com` already has a TXT record, sending onboarding is refused — it writes its own `_dmarc` (`p=reject`) and DKIM records and Cloudflare would answer with a conflict. Inbound is unaffected; remove the records or keep sending off.
|
|
597
599
|
|
|
@@ -601,12 +603,12 @@ The email prompt follows wrangler's own interactivity rule: a CI environment as
|
|
|
601
603
|
|
|
602
604
|
```
|
|
603
605
|
deploy: email on mail.acme.com is not set up, and this shell cannot ask.
|
|
604
|
-
Run `void email setup --platform cloudflare` once locally, commit
|
|
606
|
+
Run `void email setup --platform cloudflare` once locally, commit void.lock.json, then redeploy — deploying without email.
|
|
605
607
|
```
|
|
606
608
|
|
|
607
609
|
then deploys **without** email. Pass `--require-email` to fail instead. When setup has already committed the exact subdomain `addresses` plan and only its MX records are not visible yet, every deploy stops and preserves that plan until DNS can be verified. Automatic resource provisioning is not an escape hatch: it creates D1/KV/R2/Queue/Hyperdrive resources, never a mail setup. To read the rows without deploying or being asked anything, run `void email status --platform cloudflare`.
|
|
608
610
|
|
|
609
|
-
So the CI story is: run `void email setup --platform cloudflare` once on your machine (it runs the same preflight, checklist and prompt as the first deploy, then
|
|
611
|
+
So the CI story is: run `void email setup --platform cloudflare` once on your machine (it runs the same preflight, checklist and prompt as the first deploy, then updates `void.lock.json`, without deploying), commit `void.lock.json`, and let CI run `void deploy --platform cloudflare --require-email`. Once the MX records are visible, the committed binding and `addresses` make every row read ready and nothing is asked — on Workers Free too, where the committed binding is what remembers the refused sending onboarding (see [Sending](#sending)).
|
|
610
612
|
|
|
611
613
|
### If the subdomain step is refused
|
|
612
614
|
|
|
@@ -80,7 +80,7 @@ void secret list
|
|
|
80
80
|
|
|
81
81
|
Remote stores return names only; plaintext values are validated during upload and again at worker startup. If an upload fails or a name is missing, retain the source values and retry before continuing.
|
|
82
82
|
|
|
83
|
-
For direct Cloudflare deployments, remove the migrated server keys from `vars` in `
|
|
83
|
+
For direct Cloudflare deployments, remove the migrated server keys from `cloudflare.vars` in `void.config.ts` after confirming the upload. Void now treats any schema-declared server key in Worker `vars` as an error.
|
|
84
84
|
|
|
85
85
|
## 4. Supply client values at build time
|
|
86
86
|
|
|
@@ -36,7 +36,7 @@ Types follow your data through the app: from a Drizzle schema to a route handler
|
|
|
36
36
|
|
|
37
37
|
Void brings these pieces together:
|
|
38
38
|
|
|
39
|
-
- **Resources from your code:** imports tell Void which supported resources to provision. Use `void.
|
|
39
|
+
- **Resources from your code:** imports tell Void which supported resources to provision. Use `void.config.ts` and its `cloudflare` field when you need to customize them.
|
|
40
40
|
- **Types from database to frontend:** your Drizzle schema defines DB types, route handlers infer return types, and the [typed fetch client](./typed-fetch.md) checks calls at the usage site. One [Standard Schema](https://standardschema.dev/) validator can drive both runtime validation and compile-time types.
|
|
41
41
|
- **Local Cloudflare development:** native Void apps run server code in `workerd`, with local database, KV, and storage.
|
|
42
42
|
- **Deploy that understands the app:** `void deploy` reads your migrations, provisions the resources you actually use, and ships the result to the edge.
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
---
|
|
2
|
+
outline: deep
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# Sign-in and Access
|
|
6
|
+
|
|
7
|
+
## Signing In
|
|
8
|
+
|
|
9
|
+
For the browser admin UI, open `<API URL>/admin` and use the administrator login method selected during setup. On a new installation, follow the printed administrator setup instructions to create the first admin user. You do not need to create an app or sign in through the CLI first.
|
|
10
|
+
|
|
11
|
+
Connect to your platform's API URL, then sign in as an administrator:
|
|
12
|
+
|
|
13
|
+
```sh
|
|
14
|
+
void connect https://platform.example.com --no-login
|
|
15
|
+
void platform auth login
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
The first command saves the connection. The second opens your browser to sign in and saves an administrator session in your system keychain. Sessions last for one hour and are stored separately for each platform.
|
|
19
|
+
|
|
20
|
+
You can check which account is signed in at any time:
|
|
21
|
+
|
|
22
|
+
```sh
|
|
23
|
+
void platform auth status
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
This shows your account, the session's expiry, and the features available on the platform. To end the session, run `void platform auth logout`.
|
|
27
|
+
|
|
28
|
+
If the platform is protected by Cloudflare Access, `void connect <url>` handles
|
|
29
|
+
the company sign-in before Void login. Interactive Access authentication uses a
|
|
30
|
+
locally installed `cloudflared` and saves its short-lived credential in your
|
|
31
|
+
system keychain for that platform origin. You do not need to copy browser cookies.
|
|
32
|
+
|
|
33
|
+
For CI, Access credentials do not replace Void deployment credentials. A project
|
|
34
|
+
owner creates the latter with `void project token create`; it is independently
|
|
35
|
+
revocable, expires within 90 days, and authorizes only that project's deploy
|
|
36
|
+
workflow. Human and operator tokens cannot be renewed by Access service proof.
|
|
37
|
+
|
|
38
|
+
Store Access credentials in origin-keyed `VOID_ACCESS_CREDENTIALS`. A deploy
|
|
39
|
+
that uses prerendering or remote bindings needs entries for both the exact API
|
|
40
|
+
and proxy HTTPS origins, even when both entries contain the same admitted
|
|
41
|
+
service-token pair. `VOID_ACCESS_ORIGIN` selects only one recipient and therefore
|
|
42
|
+
cannot cover both calls. Keep the JSON value in your secret manager, not in
|
|
43
|
+
application configuration. See [CI deployment setup](/guide/platform/installation/first-deployment)
|
|
44
|
+
for the required shape.
|
|
45
|
+
|
|
46
|
+
## Choosing a Platform
|
|
47
|
+
|
|
48
|
+
If you manage more than one platform, list your connections and choose a default:
|
|
49
|
+
|
|
50
|
+
```sh
|
|
51
|
+
void platform list
|
|
52
|
+
void platform use <connection-id>
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
You can also select a platform for a single command with `--connection`:
|
|
56
|
+
|
|
57
|
+
```sh
|
|
58
|
+
void platform user list --connection <connection-id>
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Use an ID or URL from the connection list. Administrative commands use this selection even when you run them inside an app with a different deployment destination.
|
|
62
|
+
|
|
63
|
+
## Recovering Administrator Login
|
|
64
|
+
|
|
65
|
+
If no administrator can use the configured identity provider, the installation
|
|
66
|
+
owner can recover an existing administrator with Cloudflare management access,
|
|
67
|
+
direct database access, and the original encrypted recovery credentials:
|
|
68
|
+
|
|
69
|
+
```sh
|
|
70
|
+
void platform config auth recover company --installation <id> --file recovery.json
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
The file identifies the existing account, for example
|
|
74
|
+
`{"administratorUserId":"existing-admin-id"}`. Recovery opens a real login test
|
|
75
|
+
and asks you to confirm the exact identity before restoring access. It does not
|
|
76
|
+
create a new administrator. To use a replacement provider, include its nonsecret
|
|
77
|
+
`configuration` with a new connection ID and supply the secret through
|
|
78
|
+
`--client-secret-env <name>`. To rotate an existing provider's secret, keep its
|
|
79
|
+
connection ID and identity configuration.
|
|
80
|
+
|
|
81
|
+
For `--yes`, also pin `expectedIdentity` with the exact `issuer` and `subject` in
|
|
82
|
+
the file. A successful browser login is still required. Use `--plan` to preview
|
|
83
|
+
the affected administrator and connection before starting recovery.
|
|
84
|
+
|
|
85
|
+
## Giving People Access
|
|
86
|
+
|
|
87
|
+
Open **Settings** in the administrator UI, or run `void platform config auth`,
|
|
88
|
+
to add, test, enable, or disable login methods. Access protection and login
|
|
89
|
+
methods are separate settings. A provider test shows the authenticated account
|
|
90
|
+
before you explicitly link it or enable the configuration.
|
|
91
|
+
|
|
92
|
+
To change the company gate after installation, use the installing workstation
|
|
93
|
+
with its saved recovery credentials:
|
|
94
|
+
|
|
95
|
+
```sh
|
|
96
|
+
void platform config auth protection show
|
|
97
|
+
void platform config auth protection enable --installation <id>
|
|
98
|
+
void platform config auth protection disable --installation <id>
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
Enabling offers application creation or connection to an existing application.
|
|
102
|
+
It checks every API, proxy, and configured dashboard origin and requires a company
|
|
103
|
+
user sign-in. Changing protection ends all current human sessions; sign in again
|
|
104
|
+
afterwards. Before removing a gate used for company signup, select another
|
|
105
|
+
verified company rule or restricted signup. Removing protection retains the
|
|
106
|
+
Cloudflare applications for deliberate cleanup. Disabling an Access login method
|
|
107
|
+
does not remove the gate.
|
|
108
|
+
|
|
109
|
+
Users can add another enabled login to their existing account with
|
|
110
|
+
`void auth link <connection-id>` or **Account** in the optional
|
|
111
|
+
dashboard. Sign in again first if prompted, then authenticate with the additional
|
|
112
|
+
provider and confirm the identity shown. Matching email addresses alone do not
|
|
113
|
+
link accounts.
|
|
114
|
+
|
|
115
|
+
In the admin dashboard, open **People → Access** to choose who can join, invite
|
|
116
|
+
people, and manage individual signup grants. Choose **Company-approved or
|
|
117
|
+
individually approved users** to create accounts automatically for people who
|
|
118
|
+
meet your configured company rules while still allowing explicit invitations
|
|
119
|
+
and allowlist grants. Company protection at the platform edge still applies
|
|
120
|
+
before anyone reaches a login page.
|
|
121
|
+
|
|
122
|
+
With invited/allowlisted signup selected, let a teammate join with GitHub by adding their login to the allowlist:
|
|
123
|
+
|
|
124
|
+
```sh
|
|
125
|
+
void platform signup allow github teammate
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
Void shows the change and asks you to confirm it. The teammate can then run `void connect` with the platform's URL and sign in.
|
|
129
|
+
|
|
130
|
+
You can allow an email address or a whole email domain in the same way:
|
|
131
|
+
|
|
132
|
+
```sh
|
|
133
|
+
void platform signup allow email teammate@example.com
|
|
134
|
+
void platform signup allow email '*@example.com'
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
Email patterns apply across the platform's sign-in providers. Quote a domain pattern so your shell passes the `*` to Void.
|
|
138
|
+
|
|
139
|
+
For an OIDC login without a verified email, allow the identity by its login connection and stable provider subject instead:
|
|
140
|
+
|
|
141
|
+
```sh
|
|
142
|
+
void platform signup allow identity company-sso 'Employee-42'
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
Use the connection ID shown by `void platform config auth list` and the exact subject reported by your identity provider. The match is case-sensitive and does not infer an email address or link another account. Any domain or group restrictions configured for that login method still apply, and the account joins as an ordinary user. Remove the grant with `void platform signup disallow identity company-sso 'Employee-42'`.
|
|
146
|
+
|
|
147
|
+
To invite someone else by email, use:
|
|
148
|
+
|
|
149
|
+
```sh
|
|
150
|
+
void platform invitation send alex@example.org
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
An invitation grants signup access for that exact email address. The person must
|
|
154
|
+
sign in with a login method that supplies the same verified email address.
|
|
155
|
+
When platform email delivery is configured, Void also sends connection
|
|
156
|
+
instructions. If delivery is unavailable or fails, the grant still works:
|
|
157
|
+
share the platform's `/invite` page or `void connect '<platform URL>'` command
|
|
158
|
+
with the person yourself. The admin dashboard shows the delivery outcome
|
|
159
|
+
separately from the invitation's pending, accepted, or revoked status.
|
|
160
|
+
|
|
161
|
+
Inspect the current access settings and invitations with:
|
|
162
|
+
|
|
163
|
+
```sh
|
|
164
|
+
void platform signup show
|
|
165
|
+
void platform invitation list
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
Invitation history remains available after an involved account is removed. The stored actor ID
|
|
169
|
+
remains visible when that account's login no longer exists.
|
|
170
|
+
|
|
171
|
+
`void platform signup open` allows anyone to sign up. Use `void platform signup restrict` to require an allowlist match again. Disallowing an entry affects future signup; it does not suspend an existing account.
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
---
|
|
2
|
+
outline: deep
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# Email
|
|
6
|
+
|
|
7
|
+
## Managing Email
|
|
8
|
+
|
|
9
|
+
These commands apply to a platform that enables email. Every project can send from and receive at `<slug>+tag@<mail domain>` on the platform's shared mail domain; the [email guide](/guide/email) describes what applications do with that.
|
|
10
|
+
|
|
11
|
+
### Deciding who mail may reach
|
|
12
|
+
|
|
13
|
+
By default, mail from the shared sender reaches only the recipients each project has verified. For a team whose applications mainly mail colleagues, widen that once for the whole installation:
|
|
14
|
+
|
|
15
|
+
```sh
|
|
16
|
+
void platform email policy
|
|
17
|
+
void platform email policy-set domains --domains example.com,corp.example.net
|
|
18
|
+
void platform email policy-set any
|
|
19
|
+
void platform email policy-set verified
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
`domains` lets every project mail any address on the listed domains in addition to its verified recipients; `any` lifts the check. Cloudflare still refuses a recipient it has not verified until your mail domain is onboarded for Email Sending, which needs Workers Paid on the platform's account. Until then such sends come back as `UNVERIFIED_DESTINATION` for that recipient, whatever the policy says. Two things the policy never changes: sends from a project's own custom domain, and mail to any address on the platform's own mail domain — those stay verified-recipient-only, so no project reaches another project's inbox without its consent. A tightening applies to new send admissions as soon as it commits; previously admitted attempts may finish. The same setting is on the admin UI's **Email** page.
|
|
23
|
+
|
|
24
|
+
### Project caps
|
|
25
|
+
|
|
26
|
+
Each project has 200 recipient submissions per UTC calendar month and 10 in a rolling 60-second window by default. An attempt stays charged once it starts, including a failed or uncertain outcome. Raise or lower a project's caps by id or slug:
|
|
27
|
+
|
|
28
|
+
```sh
|
|
29
|
+
void platform email limit hr-portal --monthly 2000 --burst 30
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
The project's page in the admin UI shows the current values and clears an override.
|
|
33
|
+
|
|
34
|
+
### Recovering an interrupted send
|
|
35
|
+
|
|
36
|
+
If a sender stops after beginning a provider call, its unresolved attempt blocks
|
|
37
|
+
removal of the project or sender domain. Inspect the attempt ID and start time:
|
|
38
|
+
|
|
39
|
+
```sh
|
|
40
|
+
void platform email attempts --project hr-portal
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
First confirm that the original execution has ended through your Worker logs or
|
|
44
|
+
incident records. Once the attempt is at least 24 hours old, resolve it with an
|
|
45
|
+
audit reason that contains no recipient address or message content:
|
|
46
|
+
|
|
47
|
+
```sh
|
|
48
|
+
void platform email attempt-resolve <attempt-id> --ended --reason "Original Worker execution ended; provider outcome could not be verified" --plan
|
|
49
|
+
void platform email attempt-resolve <attempt-id> --ended --reason "Original Worker execution ended; provider outcome could not be verified"
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
This records the send as `outcome_unknown` and releases its cleanup fence. The
|
|
53
|
+
attempt remains charged and is never retried. If its 30-day delivery record has
|
|
54
|
+
expired, only its recipient-free fence is removed. Do not resolve an attempt
|
|
55
|
+
while its original execution may still be active.
|
|
56
|
+
|
|
57
|
+
Inspect a project's retained receipt and recipient outcomes with:
|
|
58
|
+
|
|
59
|
+
```sh
|
|
60
|
+
void platform email logs hr-portal --page 1 --limit 50 --json
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Logs include operation IDs, recorded outcomes, provider references, and error codes.
|
|
64
|
+
After project deletion, use its project ID until the 30-day metadata retention
|
|
65
|
+
period expires. Message bodies, subjects, attachments, and credentials are not logged.
|
|
66
|
+
|
|
67
|
+
### Registering email domains for projects
|
|
68
|
+
|
|
69
|
+
Your users may hold no Cloudflare account. Tell the platform that administrators register email domains, so `void email domain add` prints the command to ask you for instead of opening a token-creation page:
|
|
70
|
+
|
|
71
|
+
```sh
|
|
72
|
+
void platform email settings-set --domains admin
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
Then register a domain for a project. The zone must be in the platform's Cloudflare account; the platform's own credential does the setup, and no Cloudflare credential is stored per domain:
|
|
76
|
+
|
|
77
|
+
```sh
|
|
78
|
+
void platform email domain-add mail.example.com --project hr-portal
|
|
79
|
+
void platform email domain-status mail.example.com
|
|
80
|
+
void platform email domain-rotate-secret mail.example.com
|
|
81
|
+
void platform email domains
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Name the exact mail domain: a subdomain such as `mail.example.com` when the apex already receives mail, otherwise the apex itself. `domain-status` reports inbound, outbound, and management readiness separately. Follow any required DNS or Cloudflare dashboard step, then use `domain-sync` to reconcile. Use `domain-rotate-secret` when you need to replace the zone ingress credential explicitly. Email Sending onboarding needs Workers Paid; after upgrading, `domain-sync` re-attempts it. For a zone in another Cloudflare account, pipe an API token for that account on standard input:
|
|
85
|
+
|
|
86
|
+
```sh
|
|
87
|
+
void platform email domain-add mail.other.example --project hr-portal --token-stdin --yes < token.txt
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Project owners see administrator-registered domains in `void email domain list` and `status`; `status` names the `void platform email` command for any step they cannot take themselves, and `sync` and `remove` point them at `domain-sync` and `domain-remove`. A domain an owner registered before you switched to `admin` stays theirs to renew, sync and remove. The runtime token needs the email permissions listed in the [self-hosting guide](/guide/platform/installation/credentials#runtime-token-permissions) for this to work.
|
|
91
|
+
|
|
92
|
+
A permission refusal leaves setup blocked. After correcting it, run `domain-sync`
|
|
93
|
+
to resume that same setup operation; repeat `domain-rotate-secret` to resume a
|
|
94
|
+
blocked rotation without replacing its staged secret. If project deletion leaves
|
|
95
|
+
a blocked domain cleanup, correct the permission and run `domain-remove <domain>`
|
|
96
|
+
to finish the retained cleanup. If its token has expired or been revoked, provide
|
|
97
|
+
a replacement scoped to the same account and zone:
|
|
98
|
+
|
|
99
|
+
```sh
|
|
100
|
+
void platform email domain-remove mail.other.example --token-stdin --yes < token.txt
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
This recovery is available only after project deletion has begun and no other
|
|
104
|
+
project uses the connection. For a live project, renew its token through
|
|
105
|
+
`domain-add`. A timed-out Cloudflare
|
|
106
|
+
mutation stays stopped because replay may duplicate a provider write. After
|
|
107
|
+
confirming the original execution ended, wait 24 hours, inspect the exact
|
|
108
|
+
routing, Worker, secret, catch-all, or Sending resource named by the preview,
|
|
109
|
+
and use its recovery fence:
|
|
110
|
+
|
|
111
|
+
```sh
|
|
112
|
+
void platform email operation-resolve <operation-id> --ended --outcome <applied|not-applied> --reason "Provider state verified" --plan
|
|
113
|
+
void platform email operation-resolve <operation-id> --ended --outcome <applied|not-applied> --reason "Provider state verified"
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
Run recovery with the same platform version that created the persisted intent.
|
|
117
|
+
`applied` continues without replaying the write; `not-applied` permits that exact
|
|
118
|
+
step to retry. Secret rotation keeps its staged generation, and removal resumes
|
|
119
|
+
from the unresolved cleanup step. Age alone never authorizes a retry.
|
|
120
|
+
|
|
121
|
+
Register administrator domains only after a platform upgrade has completed, and remove them before rolling the platform back to a version that predates this feature: an earlier runtime cannot use the platform credential for them and does not distinguish administrator-registered domains from owner-registered ones.
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
---
|
|
2
|
+
outline: deep
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# Operations
|
|
6
|
+
|
|
7
|
+
## Previewing Changes
|
|
8
|
+
|
|
9
|
+
Commands that change the platform show the affected objects before asking for confirmation. To inspect a change without applying it, add `--plan`:
|
|
10
|
+
|
|
11
|
+
```sh
|
|
12
|
+
void platform user suspend <user-id> --reason "Investigating unexpected traffic" --plan
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
The preview shows which account will be suspended and the effect on its projects. Run the command again without `--plan` to confirm interactively, or use `--yes` when the change is ready to apply:
|
|
16
|
+
|
|
17
|
+
```sh
|
|
18
|
+
void platform user suspend <user-id> --reason "Investigating unexpected traffic" --yes
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Scripts must use `--yes` to apply these changes. The `void platform auth` session commands run directly and do not use `--plan` or `--yes`; changes under `void platform config auth` do use previews and confirmation. After a change, `void platform system events` shows the administrator, affected objects, and recorded outcome.
|
|
22
|
+
|
|
23
|
+
Changes can take longer when an account owns many resources. Requests that change the platform allow five minutes by default; use `--timeout` to set a different limit in seconds:
|
|
24
|
+
|
|
25
|
+
```sh
|
|
26
|
+
void platform user delete <user-id> --timeout 600 --plan
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
If a request times out or loses its connection, some work may already have finished. Check the affected objects and `system events` before retrying. Void reports partial results when it can and does not automatically repeat the change.
|
|
30
|
+
|
|
31
|
+
## Following Logs
|
|
32
|
+
|
|
33
|
+
To investigate a deployment, find its ID and follow its runtime logs:
|
|
34
|
+
|
|
35
|
+
```sh
|
|
36
|
+
void platform deployment list --project <project-id>
|
|
37
|
+
void platform deployment logs <deployment-id> --since 10m --follow
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Runtime logs include application messages, exceptions, and HTTP status codes. Without `--follow`, the command reads a page of historical logs. Use `--since` to choose a duration, an ISO date, or a timestamp in milliseconds.
|
|
41
|
+
|
|
42
|
+
Logs can become available after a request finishes. Following checks a five-minute overlap to pick up delayed records without printing them again. Records delayed longer than that may need a later historical query.
|
|
43
|
+
|
|
44
|
+
Build logs use the build's ID:
|
|
45
|
+
|
|
46
|
+
```sh
|
|
47
|
+
void platform build logs <build-id> --follow
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Following waits for the final logs before stopping, including diagnostics written after the build's status changes. For a GitHub Actions build, the command gives you the URL of its logs.
|
|
51
|
+
|
|
52
|
+
## Checking the Platform
|
|
53
|
+
|
|
54
|
+
Use the overview to see recent activity, or run a health check to test the platform's services and database:
|
|
55
|
+
|
|
56
|
+
```sh
|
|
57
|
+
void platform system overview
|
|
58
|
+
void platform system health
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Health checks use the services configured for the selected platform. An unhealthy result exits with a nonzero status, so the same command can be used in a script.
|
|
62
|
+
|
|
63
|
+
The browser dashboard's **System Status** checks refresh every 30 seconds. Failed checks show an HTTP status or connection error beside the service name. If the dashboard cannot refresh the checks, it reports that status is unavailable instead of displaying stale results.
|
|
64
|
+
|
|
65
|
+
Use `void platform upgrade`, `repair`, `disable`, and `enable` to maintain your platform. See [platform maintenance](/guide/platform/installation/maintenance#resume-repair-recover-and-upgrade).
|
|
66
|
+
|
|
67
|
+
For disaster recovery, keep a matching PostgreSQL backup, provider data backups, installation identity and complete project-encryption keyring, and immutable runtime artifacts. Keep traffic disabled while restoring data, run `discover` to reconstruct verified local metadata, and review `repair --plan` before recreating missing installer-owned infrastructure. Neither command restores backed-up data. See [Prepare for disaster recovery](/guide/platform/installation/maintenance#prepare-for-disaster-recovery) for the full sequence.
|
|
68
|
+
|
|
69
|
+
## Using Scripts
|
|
70
|
+
|
|
71
|
+
Add `--json` to read a command's result from another program:
|
|
72
|
+
|
|
73
|
+
```sh
|
|
74
|
+
void platform user list --page 1 --limit 50 --json
|
|
75
|
+
void platform system health --json
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Results go to standard output. Command errors go to standard error as JSON and exit with a nonzero status. With `--follow`, each log response is a separate JSON line.
|
|
79
|
+
|
|
80
|
+
Operator tokens expire after one hour. For CI, mint a new operator token at the start of each job from a valid full API login session belonging to an active administrator. Choose the platform explicitly, inject the full session as `VOID_TOKEN` from your secret manager, and capture the exchange output directly into the protected job environment:
|
|
81
|
+
|
|
82
|
+
```sh
|
|
83
|
+
export VOID_API_URL=https://platform.example.com
|
|
84
|
+
VOID_OPERATOR_TOKEN="$(
|
|
85
|
+
printf '%s' "$VOID_TOKEN" | void platform auth token --token-stdin
|
|
86
|
+
)" || exit 1
|
|
87
|
+
export VOID_OPERATOR_TOKEN
|
|
88
|
+
unset VOID_TOKEN
|
|
89
|
+
void platform system health --json
|
|
90
|
+
unset VOID_OPERATOR_TOKEN
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Disable shell tracing for the exchange and do not write either token to logs or plaintext files. A full API login session expires after 30 days and can be revoked sooner; renew it through the normal authenticated login flow and update the protected CI secret. A job that runs longer than one hour must repeat the exchange while its full API session is still valid. An expired operator token cannot refresh itself, and Void does not issue permanent service tokens for administrator automation.
|
|
94
|
+
|
|
95
|
+
`auth login --token-stdin` performs the same elevation and saves the one-hour operator token in the system keychain for interactive use. See [operator authentication](/reference/cli#operator-authentication) for the full command syntax.
|
|
96
|
+
|
|
97
|
+
An email zone has one connection and ingress Worker shared by its exact domain assignments. Removing one assignment preserves resources used by the others. The Email administration page can explicitly rotate that connection secret; ordinary synchronization does not rotate it. Setup and cleanup outcomes include operation IDs, and uncertain provider writes remain recorded until reconciled.
|