void 0.21.9 → 0.23.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (293) hide show
  1. package/README.md +5 -1
  2. package/dist/{account-cmd-DZZGK80W.mjs → account-cmd-C84Ee8cO.mjs} +5 -5
  3. package/dist/{scan-ClYmX3sa.mjs → application-analysis-BVsVqc11.mjs} +135 -18
  4. package/dist/application-routing-B7QEjqSR.mjs +27 -0
  5. package/dist/{auth-CZuiVsFh.mjs → auth-CCjt0hcq.mjs} +1 -1
  6. package/dist/{auth-DLNN0D3Z.mjs → auth-CSkdO2Bb.mjs} +4 -47
  7. package/dist/{auth-link-B_CeugBw.mjs → auth-link-CioEg6uY.mjs} +5 -5
  8. package/dist/{auth-router-BgEFRuvZ.mjs → auth-router-BsR981d4.mjs} +5 -5
  9. package/dist/{better-auth-shared-rsBGBvWJ.mjs → better-auth-shared-hy6RPh9W.mjs} +13 -2
  10. package/dist/{build-cmd-Dpt0jd-x.mjs → build-cmd-LzvNJORH.mjs} +19 -7
  11. package/dist/{cache-7_UeZdTk.mjs → cache-C4MvnrMH.mjs} +4 -4
  12. package/dist/{cancel-deploy-D23R1RXT.mjs → cancel-deploy-abUxpP2n.mjs} +4 -4
  13. package/dist/{cf-build-output-CGT03qCD.mjs → cf-build-output-BPqOT964.mjs} +141 -63
  14. package/dist/cf-build-output-_0HNWysu.mjs +2 -0
  15. package/dist/cli/cli.mjs +114 -460
  16. package/dist/cli/cloudflare-operation-process.mjs +1103 -0
  17. package/dist/cli/env-schema-probe.d.mts +2 -1
  18. package/dist/cli/env-schema-probe.mjs +3 -3
  19. package/dist/client-Cu7jWiF1.mjs +2 -0
  20. package/dist/{client-QTl6ko_D.mjs → client-RV8NVeB8.mjs} +165 -21
  21. package/dist/{cloudflare-auth-C4_GPZr0.mjs → cloudflare-auth-Qdc7tw8F.mjs} +41 -43
  22. package/dist/{cloudflare-cmd-bSmZ1d5L.mjs → cloudflare-cmd-BcrVyTmJ.mjs} +9 -10
  23. package/dist/cloudflare-config-Bktvwtpf.mjs +182 -0
  24. package/dist/{cloudflare-connect-jAwacxxP.mjs → cloudflare-connect-B8uPZ4nx.mjs} +4 -4
  25. package/dist/{cloudflare-operations-BipGMJ5O.mjs → cloudflare-operations-AiWashgg.mjs} +1 -1
  26. package/dist/{cloudflare-operations-sMZXpk_S.mjs → cloudflare-operations-fxHb-byx.mjs} +118 -203
  27. package/dist/{preset-UHj9ARyP.mjs → cloudflare-process-B-wekeR6.mjs} +93 -6
  28. package/dist/{config-CF69HgXc.d.mts → config-BMHb8RCj.d.mts} +2 -2
  29. package/dist/{config-CafTW6Cz.mjs → config-Br_JZD6u.mjs} +2 -7
  30. package/dist/config-C_XRIPx2.mjs +89 -0
  31. package/dist/{config-s7Xj7tPb.mjs → config-CyQ-wVd7.mjs} +1 -1
  32. package/dist/config-entry.d.mts +1 -1
  33. package/dist/{config-write-BSduPMY8.mjs → config-write-B1f88wJA.mjs} +1 -1
  34. package/dist/{connect-BsUSRzln.mjs → connect-Bd4kJd9U.mjs} +6 -6
  35. package/dist/{create-project-BniEV0OW.mjs → create-project-Boczwj5r.mjs} +1 -1
  36. package/dist/{create-project-DBfFSZKY.mjs → create-project-bMf6ffLZ.mjs} +9 -5
  37. package/dist/{db-hRrvZaq_.mjs → db-8uG64XLl.mjs} +26 -26
  38. package/dist/{delete-D6dZ9B6B.mjs → delete-NvbiyeJf.mjs} +4 -4
  39. package/dist/{deploy-WaAQez1O.mjs → deploy-DzAIwqNU.mjs} +944 -607
  40. package/dist/{deploy-DDz7c8LK.mjs → deploy-bjXdFCtn.mjs} +1 -1
  41. package/dist/{dist-BR1quN_w.mjs → dist-AoCzRTJE.mjs} +226 -60
  42. package/dist/{dist-C5fND3R0.mjs → dist-BuDuKZJv.mjs} +1 -1
  43. package/dist/{dist-Dn6nn2IU.mjs → dist-CTBk70IR.mjs} +42 -42
  44. package/dist/{domain-Dmhvb2oU.mjs → domain-y5Tvydvo.mjs} +5 -5
  45. package/dist/{email-DFi-s2t4.mjs → email-B3umsW75.mjs} +15 -30
  46. package/dist/{env-Csi-tMbT.mjs → env-BS6qYHDb.mjs} +6 -6
  47. package/dist/{env-public-D_6u46fX.d.mts → env-public-BX_r8HR6.d.mts} +2 -1
  48. package/dist/{env-validation-BB4GkLxn.mjs → env-validation-BPn7vk-V.mjs} +18 -71
  49. package/dist/{env-validation-BXge7uyK.mjs → env-validation-DbTg7-ar.mjs} +1 -1
  50. package/dist/{fetch-CXDChK7B.mjs → fetch-BIZJh7vR.mjs} +2 -2
  51. package/dist/{fetch-stream-AOByI7Ki.mjs → fetch-stream-IvCYKyQL.mjs} +24 -4
  52. package/dist/{gen-Dz3X1Qab.mjs → gen-B580--vC.mjs} +5 -5
  53. package/dist/gen-BVaUUumi.mjs +2 -0
  54. package/dist/{github-cmd-BED3_9JD.mjs → github-cmd-v45BdfKX.mjs} +6 -8
  55. package/dist/{handler-BXJTXd02.d.mts → handler-CZ4nAylQ.d.mts} +2 -1
  56. package/dist/{headers-BOg_velo.mjs → headers-DWi2IXWx.mjs} +1 -1
  57. package/dist/help-DofyZuY7.mjs +2 -0
  58. package/dist/{help-DC7gdz7L.mjs → help-daGKjXGk.mjs} +268 -666
  59. package/dist/index.d.mts +1 -1
  60. package/dist/index.mjs +305 -1490
  61. package/dist/info-B97bTX9N.mjs +113 -0
  62. package/dist/{init-WO0JlPx8.mjs → init-Bvy7zrBo.mjs} +51 -20
  63. package/dist/limits-Bq5LG8Id.d.mts +27 -0
  64. package/dist/limits-Cjuk2VPm.mjs +68 -0
  65. package/dist/{link-CsHOinF7.mjs → link-CDqqCjFl.mjs} +5 -5
  66. package/dist/{list-CDb-4bZ1.mjs → list-CZj0dzKY.mjs} +5 -5
  67. package/dist/{live-CKiJilLr.d.mts → live-Chw1eIMv.d.mts} +1 -1
  68. package/dist/{local-d1-CC8sKFGu.mjs → local-d1-2CMnpuW_.mjs} +2 -2
  69. package/dist/login-DYt_An22.mjs +2 -0
  70. package/dist/{login-DvqXsfGs.mjs → login-UZKFM_u7.mjs} +5 -5
  71. package/dist/{logs-BdfiOezj.mjs → logs-CUZ6t9t3.mjs} +5 -5
  72. package/dist/migrate-8_2u55MD.mjs +2 -0
  73. package/dist/{migrate-B8KuoYsO.mjs → migrate-DHul7PRV.mjs} +5 -4
  74. package/dist/{node-BM43oz4G.mjs → node-BkyRWRx8.mjs} +2 -2
  75. package/dist/operator-args-CLgKGlwU.mjs +690 -0
  76. package/dist/{operator-cmd-03819ATr.mjs → operator-cmd-DBA6dl0m.mjs} +69 -23
  77. package/dist/output-Dm_A4Tbv.mjs +81 -0
  78. package/dist/pages/client.d.mts +34 -2
  79. package/dist/pages/client.mjs +59 -3
  80. package/dist/pages/index.d.mts +1 -1
  81. package/dist/pages/index.mjs +3 -3
  82. package/dist/pages/islands-plugin.d.mts +16 -6
  83. package/dist/pages/islands-plugin.mjs +2 -2
  84. package/dist/pages/protocol.d.mts +2 -2
  85. package/dist/pages/protocol.mjs +2 -308
  86. package/dist/{parse-filename-DioPHiR9.mjs → parse-filename-CUbj-1MP.mjs} +35 -1
  87. package/dist/{output-CCH48AMM.mjs → picocolors-BTps1_gs.mjs} +2 -76
  88. package/dist/plan-D1Q5rf-r.mjs +2 -0
  89. package/dist/plan-NPpwZ_kc.mjs +58 -0
  90. package/dist/platform-args-BJdRtlLq.mjs +506 -0
  91. package/dist/platform-args-D3RXyR6h.mjs +2 -0
  92. package/dist/{platform-auth-config-z92h63q5.mjs → platform-auth-config-B56E9YP1.mjs} +4 -4
  93. package/dist/{platform-auth-protection-DeE3yr2R.mjs → platform-auth-protection-BDWO_aER.mjs} +3 -3
  94. package/dist/{platform-auth-recovery-o_340Ixx.mjs → platform-auth-recovery-1gg4CSRe.mjs} +4 -4
  95. package/dist/{platform-cmd-C4ZV3Vpy.mjs → platform-cmd-Bt-1w6pY.mjs} +1 -1
  96. package/dist/{platform-cmd-DNl8WosH.mjs → platform-cmd-DMcQStSc.mjs} +26 -5
  97. package/dist/{platform-domain-CU1JtJkW.mjs → platform-domain-BNkcz0OB.mjs} +36 -9
  98. package/dist/{platform-lifecycle-Bc047IeT.mjs → platform-lifecycle-9OyBALhH.mjs} +850 -276
  99. package/dist/{platform-lifecycle-Bs2qx1E3.mjs → platform-lifecycle-BE6C_jxh.mjs} +1 -1
  100. package/dist/{platform-management-CVpGI9Y5.mjs → platform-management-CjwLVQwN.mjs} +59 -11
  101. package/dist/{platform-management-C8tt6D8h.mjs → platform-management-CnyTdcWX.mjs} +1 -1
  102. package/dist/platform-plans-config-BNGKGr4P.mjs +359 -0
  103. package/dist/{platform-recovery-dvUaptLr.mjs → platform-recovery-D6KSpuFm.mjs} +2 -2
  104. package/dist/{plugin-inference-BMfKRSqE.mjs → plugin-inference-CXWnn79A.mjs} +174 -64
  105. package/dist/{prepare-DyZ-Yok5.mjs → prepare-B5Mkic5u.mjs} +3 -3
  106. package/dist/{prepare-C3kt3Rst.mjs → prepare-DOsL0CC9.mjs} +4 -20
  107. package/dist/prepare-cgvDMtSb.mjs +2 -0
  108. package/dist/{project-cmd-B44I8J_W.mjs → project-cmd-CNzrqvBv.mjs} +30 -16
  109. package/dist/{project-team-BkbYIsXN.mjs → project-team-DQOWPfQT.mjs} +4 -4
  110. package/dist/{project-token-C8xEEQnB.mjs → project-token-CTDYU0Cc.mjs} +4 -4
  111. package/dist/project-zero-trust-BNAkW-_0.mjs +63 -0
  112. package/dist/{protocol-ZH3jP4a7.d.mts → protocol-CjF_iI9X.d.mts} +2 -2
  113. package/dist/protocol-U7bfjHmA.mjs +331 -0
  114. package/dist/{provision-DNtrtaVD.mjs → provision-C4ORE7G7.mjs} +1 -1
  115. package/dist/{provision-BhreDAOS.mjs → provision-CJFgTZY8.mjs} +111 -215
  116. package/dist/{requests-DFyhBMaf.mjs → requests-MhxYnau8.mjs} +4 -4
  117. package/dist/resource-name-C7LVpcRm.mjs +11 -0
  118. package/dist/{rollback-DHHxXZiS.mjs → rollback-CJ6iSDoU.mjs} +5 -5
  119. package/dist/route-url-CG7U-cRN.mjs +15 -0
  120. package/dist/{runner-B8wXwWlo.mjs → runner-CXA9Fh8h.mjs} +1 -1
  121. package/dist/{runner-p-dMs2UN.mjs → runner-Ol0TNjk6.mjs} +2 -2
  122. package/dist/runtime/ai.d.mts +12 -8
  123. package/dist/runtime/ai.mjs +84 -17
  124. package/dist/runtime/better-auth-mysql.mjs +1 -1
  125. package/dist/runtime/better-auth-pg.mjs +1 -1
  126. package/dist/runtime/better-auth.mjs +1 -1
  127. package/dist/runtime/client-react.mjs +2 -2
  128. package/dist/runtime/client-solid.mjs +2 -2
  129. package/dist/runtime/client-svelte.mjs +2 -2
  130. package/dist/runtime/client-vue.mjs +2 -2
  131. package/dist/runtime/client.mjs +2 -2
  132. package/dist/runtime/durable.d.mts +3 -1
  133. package/dist/runtime/durable.mjs +4 -1
  134. package/dist/runtime/email/testing.mjs +1 -1
  135. package/dist/runtime/env-public.d.mts +1 -1
  136. package/dist/runtime/fetch-stream.mjs +1 -1
  137. package/dist/runtime/fetch.mjs +1 -1
  138. package/dist/runtime/handler.d.mts +1 -1
  139. package/dist/runtime/kv.mjs +0 -1
  140. package/dist/runtime/limits.d.mts +2 -0
  141. package/dist/runtime/limits.mjs +2 -0
  142. package/dist/runtime/live-client.d.mts +1 -1
  143. package/dist/runtime/live-server.mjs +25 -16
  144. package/dist/runtime/live.d.mts +1 -1
  145. package/dist/runtime/migration-handler.mjs +62 -42
  146. package/dist/runtime/route-url.d.mts +4 -0
  147. package/dist/runtime/route-url.mjs +2 -0
  148. package/dist/runtime/routing.d.mts +180 -0
  149. package/dist/runtime/routing.mjs +1082 -0
  150. package/dist/runtime/sandbox-container.d.mts +3 -0
  151. package/dist/runtime/sandbox-container.mjs +2 -0
  152. package/dist/runtime/sandbox.d.mts +4 -32
  153. package/dist/runtime/sandbox.mjs +151 -75
  154. package/dist/runtime/sse.mjs +1 -1
  155. package/dist/runtime/validator.d.mts +1 -1
  156. package/dist/runtime/ws-server.d.mts +4 -2
  157. package/dist/runtime/ws-server.mjs +27 -2
  158. package/dist/runtime/ws.d.mts +2 -2
  159. package/dist/runtime/ws.mjs +8 -6
  160. package/dist/sandbox-XZAqzFlG.d.mts +52 -0
  161. package/dist/sandbox-container-Bo0eiFKz.d.mts +73 -0
  162. package/dist/sandbox-container-C6ItmVuN.mjs +281 -0
  163. package/dist/{scan-4tfN-PSn.mjs → scan-C7okrLyM.mjs} +4 -35
  164. package/dist/{secret-DN9sSNiV.mjs → secret-Dli5fP0B.mjs} +6 -6
  165. package/dist/{skills-O6FUaizK.mjs → skills-D1II1Juz.mjs} +1 -1
  166. package/dist/{sse-BaC1jXko.mjs → sse-CQNaDFFV.mjs} +6 -3
  167. package/dist/{subcommand-prompt-BuGYkAkC.mjs → subcommand-prompt-CY1C4fvl.mjs} +2 -2
  168. package/dist/validate-Dq_L3s0S.mjs +2 -0
  169. package/dist/{validate-CIUwFpjB.mjs → validate-ctOrgiS3.mjs} +2 -1
  170. package/dist/{wrangler-BymcxrRa.mjs → wrangler-7K-bW_DL.mjs} +14 -235
  171. package/dist/{ws-BwcqizuH.d.mts → ws-CL1w7GXU.d.mts} +13 -2
  172. package/package.json +48 -33
  173. package/sandbox.Dockerfile +4 -0
  174. package/schema.json +10 -22
  175. package/skills/migrate-vite-cloudflare-to-void/SKILL.md +34 -157
  176. package/skills/void/SKILL.md +50 -133
  177. package/skills/void/docs/guide/ai.md +94 -84
  178. package/skills/void/docs/guide/app-types.md +3 -32
  179. package/skills/void/docs/guide/auth.md +12 -116
  180. package/skills/void/docs/guide/database/d1.md +9 -54
  181. package/skills/void/docs/guide/database/mysql.md +1 -1
  182. package/skills/void/docs/guide/database/postgresql.md +5 -26
  183. package/skills/void/docs/guide/database.md +23 -75
  184. package/skills/void/docs/guide/deployment.md +27 -113
  185. package/skills/void/docs/guide/durable-state.md +43 -18
  186. package/skills/void/docs/guide/edge/headers.md +3 -47
  187. package/skills/void/docs/guide/edge/prerendering.md +5 -20
  188. package/skills/void/docs/guide/edge/redirects.md +11 -64
  189. package/skills/void/docs/guide/edge/revalidation.md +6 -19
  190. package/skills/void/docs/guide/edge/rewrites.md +56 -284
  191. package/skills/void/docs/guide/edge/static-assets.md +23 -72
  192. package/skills/void/docs/guide/email/domains.md +112 -0
  193. package/skills/void/docs/guide/email/receiving.md +139 -0
  194. package/skills/void/docs/guide/email/sending.md +231 -0
  195. package/skills/void/docs/guide/email.md +13 -619
  196. package/skills/void/docs/guide/env-migration.md +11 -11
  197. package/skills/void/docs/guide/env-vars.md +9 -29
  198. package/skills/void/docs/guide/index.md +0 -15
  199. package/skills/void/docs/guide/jobs.md +3 -18
  200. package/skills/void/docs/guide/kv.md +5 -11
  201. package/skills/void/docs/guide/live.md +5 -56
  202. package/skills/void/docs/guide/pages-routing/actions-and-forms.md +78 -125
  203. package/skills/void/docs/guide/pages-routing/head.md +10 -10
  204. package/skills/void/docs/guide/pages-routing/islands.md +6 -36
  205. package/skills/void/docs/guide/pages-routing/layouts.md +6 -128
  206. package/skills/void/docs/guide/pages-routing/loaders.md +3 -19
  207. package/skills/void/docs/guide/pages-routing/markdown.md +13 -171
  208. package/skills/void/docs/guide/pages-routing/overview.md +7 -17
  209. package/skills/void/docs/guide/pages-routing/view-transitions.md +1 -1
  210. package/skills/void/docs/guide/platform/administration/access.md +1 -4
  211. package/skills/void/docs/guide/platform/administration/email.md +35 -8
  212. package/skills/void/docs/guide/platform/administration/operations.md +26 -4
  213. package/skills/void/docs/guide/platform/administration/plans.md +126 -0
  214. package/skills/void/docs/guide/platform/administration/projects.md +5 -2
  215. package/skills/void/docs/guide/platform/administration/zero-trust.md +189 -0
  216. package/skills/void/docs/guide/platform/development/local.md +2 -2
  217. package/skills/void/docs/guide/platform/development/runtime.md +3 -13
  218. package/skills/void/docs/guide/platform/development/schema-ci.md +0 -58
  219. package/skills/void/docs/guide/platform/installation/credentials.md +6 -4
  220. package/skills/void/docs/guide/platform/installation/domains.md +31 -3
  221. package/skills/void/docs/guide/platform/installation/first-deployment.md +6 -0
  222. package/skills/void/docs/guide/platform/installation/maintenance.md +3 -1
  223. package/skills/void/docs/guide/platform/installation/prerequisites.md +21 -16
  224. package/skills/void/docs/guide/platform/installation/setup.md +9 -5
  225. package/skills/void/docs/guide/platform/installation/uninstall.md +17 -2
  226. package/skills/void/docs/guide/platform-administration.md +2 -0
  227. package/skills/void/docs/guide/queues.md +7 -9
  228. package/skills/void/docs/guide/quickstart.md +38 -37
  229. package/skills/void/docs/guide/remote-dev.md +4 -9
  230. package/skills/void/docs/guide/sandboxes.md +78 -41
  231. package/skills/void/docs/guide/server-routing.md +9 -72
  232. package/skills/void/docs/guide/sse.md +4 -18
  233. package/skills/void/docs/guide/ssg.md +3 -15
  234. package/skills/void/docs/guide/ssr.md +14 -62
  235. package/skills/void/docs/guide/storage.md +9 -4
  236. package/skills/void/docs/guide/type-safety.md +3 -14
  237. package/skills/void/docs/guide/typed-fetch.md +3 -7
  238. package/skills/void/docs/guide/websockets.md +68 -40
  239. package/skills/void/docs/integrations/agents.md +3 -3
  240. package/skills/void/docs/integrations/cloudflare.md +85 -316
  241. package/skills/void/docs/integrations/frameworks/analog.md +5 -64
  242. package/skills/void/docs/integrations/frameworks/astro.md +4 -73
  243. package/skills/void/docs/integrations/frameworks/nuxt.md +5 -62
  244. package/skills/void/docs/integrations/frameworks/overview.md +11 -54
  245. package/skills/void/docs/integrations/frameworks/react-router.md +5 -60
  246. package/skills/void/docs/integrations/frameworks/sveltekit.md +6 -65
  247. package/skills/void/docs/integrations/frameworks/tanstack-start.md +4 -62
  248. package/skills/void/docs/integrations/nodejs-bun-deno.md +5 -69
  249. package/skills/void/docs/reference/api/auth.md +156 -0
  250. package/skills/void/docs/reference/api/client.md +87 -0
  251. package/skills/void/docs/reference/api/database.md +95 -0
  252. package/skills/void/docs/reference/api/durable.md +46 -0
  253. package/skills/void/docs/reference/api/env.md +50 -0
  254. package/skills/void/docs/reference/api/handlers.md +254 -0
  255. package/skills/void/docs/reference/api/pages.md +241 -0
  256. package/skills/void/docs/reference/api/plugin.md +39 -0
  257. package/skills/void/docs/reference/api/resources.md +109 -0
  258. package/skills/void/docs/reference/api/rewrites.md +76 -0
  259. package/skills/void/docs/reference/api/types.md +92 -0
  260. package/skills/void/docs/reference/api.md +56 -1218
  261. package/skills/void/docs/reference/cli/auth.md +88 -0
  262. package/skills/void/docs/reference/cli/database.md +128 -0
  263. package/skills/void/docs/reference/cli/deploy.md +85 -0
  264. package/skills/void/docs/reference/cli/domains.md +41 -0
  265. package/skills/void/docs/reference/cli/email.md +129 -0
  266. package/skills/void/docs/reference/cli/generate.md +116 -0
  267. package/skills/void/docs/reference/cli/github.md +189 -0
  268. package/skills/void/docs/reference/cli/platform-config.md +92 -0
  269. package/skills/void/docs/reference/cli/platform-email.md +81 -0
  270. package/skills/void/docs/reference/cli/platform-installation.md +127 -0
  271. package/skills/void/docs/reference/cli/platform-operations.md +90 -0
  272. package/skills/void/docs/reference/cli/platform-users.md +89 -0
  273. package/skills/void/docs/reference/cli/platform-zero-trust.md +45 -0
  274. package/skills/void/docs/reference/cli/platform.md +70 -0
  275. package/skills/void/docs/reference/cli/project.md +214 -0
  276. package/skills/void/docs/reference/cli/secrets.md +76 -0
  277. package/skills/void/docs/reference/cli/setup.md +70 -0
  278. package/skills/void/docs/reference/cli.md +33 -1621
  279. package/skills/void/docs/reference/config.md +26 -32
  280. package/skills/void/docs/reference/resource-inference.md +3 -58
  281. package/skills/void/docs/reference/structure.md +14 -41
  282. package/dist/canonical-json-DuDiiUsQ.mjs +0 -13
  283. package/dist/cli/cf-compat.mjs +0 -968
  284. package/dist/client-4cDVv7BO.mjs +0 -2
  285. package/dist/gen-Vnv2f65C.mjs +0 -2
  286. package/dist/help-DQMfeKMz.mjs +0 -2
  287. package/dist/login-DFQk7rbW.mjs +0 -2
  288. package/dist/migrate-TsHGBnDA.mjs +0 -2
  289. package/dist/plan-BEZ8VJW0.mjs +0 -256
  290. package/dist/plan-DpuOr14e.mjs +0 -2
  291. package/dist/prepare-BZXkjdNe.mjs +0 -2
  292. package/dist/validate-EKmJWxmy.mjs +0 -2
  293. /package/dist/cli/{cf-compat.d.mts → cloudflare-operation-process.d.mts} +0 -0
@@ -0,0 +1,112 @@
1
+ ---
2
+ outline: deep
3
+ ---
4
+
5
+ # Email Domains and Setup
6
+
7
+ Choose the setup for your deployment: register a domain on a Void platform, or configure email in your own Cloudflare account. For the platform’s shared sender, follow [Sending Email](./sending.md#setup).
8
+
9
+ ## Your own domain on the platform {#your-own-domain-on-the-platform}
10
+
11
+ 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:
12
+
13
+ ```sh
14
+ void email domain add acme.com
15
+ ```
16
+
17
+ `add` opens a Cloudflare API-token template. Restrict the token to the selected account and mail zone before creating it; Workers Scripts permission applies across that account. The CLI accepts a masked paste or a newly copied token and asks you to confirm those restrictions. The platform encrypts the credential for the zone connection, so projects sharing that zone do not need separate ingress Workers.
18
+
19
+ Platform-managed custom inbound email requires the apex of a Cloudflare zone. Cloudflare’s catch-all only covers that apex, so a subdomain of the selected zone cannot receive arbitrary addresses. Choose an apex without another mail provider, or use the platform’s shared mail address. Void preserves foreign MX records and enabled catch-alls. Each custom apex belongs to one project; a project can register more than one apex.
20
+
21
+ If setup is interrupted, use `void email domain status <domain>` to inspect the saved operation before retrying.
22
+
23
+ `void email domain status <domain>` reports three independent results:
24
+
25
+ - **Inbound**: whether mail can reach the project's handlers.
26
+ - **Outbound**: whether sending is ready, restricted to verified destinations, pending, or blocked.
27
+ - **Management**: whether the stored Cloudflare credential can manage the connection.
28
+
29
+ Use `void email domain sync <domain>` to finish setup and refresh readiness. The status lists any dashboard or credential steps still needed. Use `rotate-secret` to rotate the connection secret after inbound is ready, or `remove` to detach the domain. Revoke unused API tokens in Cloudflare. See [Email domain commands](../../reference/cli/email.md#void-email-domain).
30
+
31
+ Once inbound is ready, mail to any address on that domain reaches your `email/` handlers. Sending to arbitrary recipients also needs outbound readiness; a domain limited to verified destinations still requires recipient verification. Platform quotas and suspension apply to both shared and custom senders.
32
+
33
+ On an administrator-managed platform, `add` prints the administrator command for new domains. Existing owner-managed connections remain available to their owner. Your administrator may permit shared-sender mail to specific recipient domains or any recipient; destinations on the platform's shared mail domain still require explicit verification.
34
+
35
+ ## Your own Cloudflare account {#your-own-cloudflare-account}
36
+
37
+ Set a sender on a Cloudflare zone you own:
38
+
39
+ ```ts
40
+ import { defineConfig } from 'void/config';
41
+
42
+ export default defineConfig({
43
+ email: { from: 'Acme <support@mail.acme.com>' },
44
+ });
45
+ ```
46
+
47
+ Use a mail subdomain to keep existing mail on `acme.com` with its current provider. Void refuses to replace another provider's MX records.
48
+
49
+ ### Setup {#setup-1}
50
+
51
+ Sign in with `void cloudflare login`, or use a `CLOUDFLARE_API_TOKEN` with **Email Routing Edit** and **Email Sending Edit** in addition to deploy permissions. If your browser session lacks email permissions, log out and sign in again. Global API Keys aren't supported.
52
+
53
+ Run `void deploy --platform cloudflare`. Void shows the domain, routing, sending, DNS, and handler addresses it would configure. Accept to set up email and deploy. Later deploys reuse the setup. Without `email.from`, or if you decline the initial setup, the app deploys without email.
54
+
55
+ If subdomain setup needs a dashboard step, follow the checklist under **Email → Settings → Subdomains**, then retry. After DNS changes, check readiness with:
56
+
57
+ ```sh
58
+ void email status --platform cloudflare
59
+ ```
60
+
61
+ ### Inbound addresses {#inbound-addresses}
62
+
63
+ With `email.from` on `mail.acme.com`, handlers receive:
64
+
65
+ | Handler | Subdomain address | Zone apex address |
66
+ | ----------------------------------- | ----------------------- | ------------------ |
67
+ | `support.ts`, `support+[ticket].ts` | `support@mail.acme.com` | `support@acme.com` |
68
+ | `_default.ts` | No catch-all rule | `*@acme.com` |
69
+ | `[user].ts`, `[user]+[tag].ts` | Not supported | `*@acme.com` |
70
+
71
+ Cloudflare catch-alls cover only a zone apex. On a subdomain, `_default.ts` can handle mail admitted by an explicit rule, but cannot receive arbitrary addresses.
72
+
73
+ Setup enables the zone's plus addressing so `support+T-42@mail.acme.com` reaches `support+[ticket].ts`. This setting also applies to the apex and other subdomains.
74
+
75
+ `replyEmail()` defaults its sender to the address that received the message. Forwarding requires a verified Cloudflare destination.
76
+
77
+ ### Sending {#sending}
78
+
79
+ On Workers Free, `sendEmail()` can send to verified destinations listed under **Email Routing → Destination addresses** in Cloudflare.
80
+
81
+ Sending to arbitrary recipients requires [Workers Paid](https://dash.cloudflare.com/?to=/:account/workers/plans) and Email Sending onboarding. After upgrading, run `void email setup --platform cloudflare`. Void doesn't retry onboarding during ordinary deploys.
82
+
83
+ Existing DMARC or bounce-domain records can block onboarding; the checklist identifies conflicts to resolve. Inbound mail remains available.
84
+
85
+ The `void email usage`, `logs`, `destinations`, `allow`, and `disallow` commands apply to Void platforms.
86
+
87
+ ### Configuration changes {#configuration-changes}
88
+
89
+ Commit `void.lock.json` after setup. Void derives routing addresses from your handlers and creates their rules during deployment.
90
+
91
+ Existing rules owned by another Worker or forwarding service are left in place. If you manage `cloudflare.addresses` yourself, follow the checklist rather than combining it with automatic email setup.
92
+
93
+ After deleting a handler, review the reported stale address and set the desired `cloudflare.addresses`. Deleting a routing rule requires confirmation in an interactive terminal.
94
+
95
+ To remove all email setup, remove `addresses`, `send_email`, and `vars.__VOID_EMAIL_FROM` from `resolved` in `void.lock.json`, remove corresponding overrides in `void.config.ts`, and delete the routing rules in Cloudflare.
96
+
97
+ ### CI {#ci}
98
+
99
+ Set up email locally first:
100
+
101
+ ```sh
102
+ void email setup --platform cloudflare
103
+ void email status --platform cloudflare
104
+ ```
105
+
106
+ Commit `void.lock.json`, then deploy in CI with:
107
+
108
+ ```sh
109
+ void deploy --platform cloudflare --require-email
110
+ ```
111
+
112
+ CI cannot answer setup prompts. Without `--require-email`, an unconfigured app can deploy without email. If setup is saved but DNS hasn't propagated, deployment waits for a later retry. Workers Free setup for verified recipients satisfies `--require-email`.
@@ -0,0 +1,139 @@
1
+ ---
2
+ outline: deep
3
+ ---
4
+
5
+ # Receiving Email {#inbound}
6
+
7
+ Receive mail with handlers in the top-level `email/` directory. Inbound handlers require a native Void app; framework and Node.js, Bun, or Deno builds do not support them.
8
+
9
+ ```ts
10
+ // email/_default.ts — fallback for any unmatched recipient
11
+ import { defineEmail, parseEmail } from 'void/email';
12
+
13
+ export default defineEmail(async (message, env, ctx) => {
14
+ const parsed = await parseEmail(message);
15
+
16
+ if (parsed.subject?.startsWith('STOP')) {
17
+ message.setReject('Use the unsubscribe link.');
18
+ return;
19
+ }
20
+
21
+ await message.forward('archive@example.com');
22
+ });
23
+ ```
24
+
25
+ The handler receives Cloudflare's native `ForwardableEmailMessage` plus an optional fourth `info` arg with `params` populated for dynamic / tagged route segments:
26
+
27
+ | Method | Purpose |
28
+ | ---------------------------------- | ------------------------------------------ |
29
+ | `message.setReject(reason)` | Reject the message with an SMTP error. |
30
+ | `message.forward(to, headers?)` | Forward to a verified destination. |
31
+ | `replyEmail(message, options)` | Send a threaded reply. |
32
+ | `message.reply({ from, to, raw })` | Reply using your own MIME bytes or stream. |
33
+
34
+ Returning without an action accepts the message. Use bare email addresses for `forward` and `message.reply`.
35
+
36
+ `parseEmail(message)` returns `from`, `to`, `subject`, `text`, `html`, `attachments`, and `headers`. Call it at most once per message — the underlying stream can only be read once.
37
+
38
+ ## Per-recipient routing {#per-recipient-routing}
39
+
40
+ ```
41
+ email/
42
+ support.ts — handles support@<your-domain>
43
+ billing.ts — handles billing@<your-domain>
44
+ support+[ticket].ts — handles support+ticket-123@... → info.params.ticket = "ticket-123"
45
+ support+vip.ts — handles exactly support+vip@..., ahead of support+[ticket].ts
46
+ [user].ts — dynamic local-part → info.params.user
47
+ [user]+[tag].ts — dynamic + subaddress → info.params.user, info.params.tag
48
+ _default.ts — fallback when no per-address pattern matches
49
+ ```
50
+
51
+ Files are matched against the local-part of the `To` address (the portion before `@`), case-insensitive. **Match precedence (most specific wins):**
52
+
53
+ 1. `support+vip.ts` (static + literal tag)
54
+ 2. `support+[ticket].ts` (static + captured tag)
55
+ 3. `support.ts` (static)
56
+ 4. `[user]+vip.ts` (dynamic + literal tag)
57
+ 5. `[user]+[tag].ts` (dynamic + captured tag)
58
+ 6. `[user].ts` (dynamic)
59
+ 7. `_default.ts` (fallback)
60
+
61
+ The first pattern that matches wins. Within a shape, a literal tag beats a captured tag — `support+vip@` reaches `support+vip.ts`, and every other `support+…@` reaches `support+[ticket].ts`. Handlers of the same shape are tried alphabetically.
62
+
63
+ If nothing matches and there's no `_default.ts`, control returns to Cloudflare and the sender receives a non-delivery report. Nested folders under `email/` are ignored — the recipient is a flat string, not a path.
64
+
65
+ ## Threaded replies {#threaded-replies}
66
+
67
+ ```ts
68
+ // email/support+[ticket].ts
69
+ import { defineEmail, parseEmail, replyEmail } from 'void/email';
70
+
71
+ export default defineEmail(async (message, env, ctx, info) => {
72
+ const parsed = await parseEmail(message);
73
+ await ticketStore.append(info!.params.ticket, parsed.text ?? '');
74
+
75
+ await replyEmail(message, {
76
+ text: `Got your message on ticket ${info!.params.ticket}.`,
77
+ });
78
+ });
79
+ ```
80
+
81
+ `replyEmail()` replies to the original sender with a threaded subject. You can override `subject`.
82
+
83
+ On a Void platform, the default sender is your project's shared address, or the address that received mail on your registered domain. Use that default for shared mail; `message.to` has the project prefix removed. Sender overrides must belong to your project.
84
+
85
+ Replies follow your project's recipient policy. Verify shared-sender recipients with `void email allow <address>`. Check `void email logs` for the provider outcome after the handler finishes.
86
+
87
+ Forwarding requires a native Cloudflare email event. For custom domains relayed through the platform, use replies instead.
88
+
89
+ ## Configuring inbound delivery {#configuring-inbound-delivery}
90
+
91
+ On an email-enabled platform, `<slug>+anything@<mail domain>` reaches your
92
+ deployed `email/` handlers without per-project DNS setup once the platform has verified your recipient rule and enabled plus addressing. Void sets up that rule when you deploy handlers; creating a project or only sending mail requires no inbound rule. Deployment reports a routing conflict or capacity limit if setup cannot complete. A
93
+ [registered custom domain](./domains.md#your-own-domain-on-the-platform) reaches those
94
+ handlers once its inbound readiness is `ready`. Use `void email domain status`
95
+ to check current provider routing and any remaining setup steps.
96
+
97
+ For direct deployments, follow [Your own Cloudflare account](./domains.md#your-own-cloudflare-account) to configure inbound addresses.
98
+
99
+ There is no local inbound trigger yet — `void dev` serves the outbound dev inbox only, so test inbound handlers with `createInboundTestHarness` below.
100
+
101
+ ## Testing inbound handlers {#testing-inbound-handlers}
102
+
103
+ Use `createInboundTestHarness` to capture replies, forwards, and rejections. For shared platform mail, pass the full address including the project prefix, such as `acme+support+abc-123@acme.dev`. For direct Cloudflare mail, set `slug: null` and use `support+abc-123@mail.acme.com`.
104
+
105
+ ```ts
106
+ import { describe, it, expect } from 'vitest';
107
+ import { createInboundTestHarness } from 'void/email/testing';
108
+ import support from './email/support+[ticket]';
109
+ import defaultHandler from './email/_default';
110
+
111
+ describe('email handlers', () => {
112
+ it('routes to support+[ticket] and extracts the tag', async () => {
113
+ const harness = createInboundTestHarness({
114
+ routes: { 'support+[ticket]': support, _default: defaultHandler },
115
+ });
116
+ const result = await harness.deliver({
117
+ from: 'user@example.com',
118
+ to: 'acme+support+abc-123@acme.dev',
119
+ subject: 'Help',
120
+ text: 'I have a problem',
121
+ });
122
+ expect(result.handler).toBe('support+[ticket]');
123
+ expect(result.params).toEqual({ ticket: 'abc-123' });
124
+ });
125
+
126
+ it('rejects STOP messages via _default', async () => {
127
+ const harness = createInboundTestHarness({ routes: { _default: defaultHandler } });
128
+ await harness.deliver({
129
+ from: 'user@example.com',
130
+ to: 'acme+unknown@acme.dev',
131
+ subject: 'STOP',
132
+ text: 'bye',
133
+ });
134
+ expect(harness.rejects).toEqual(['Use the unsubscribe link.']);
135
+ });
136
+ });
137
+ ```
138
+
139
+ The harness matches handlers in the same order as deployed email routes.
@@ -0,0 +1,231 @@
1
+ ---
2
+ outline: deep
3
+ ---
4
+
5
+ # Sending Email
6
+
7
+ Use `sendEmail` to send transactional email. During development, Void captures messages in a local inbox.
8
+
9
+ ```ts
10
+ import { sendEmail } from 'void/email';
11
+
12
+ const result = await sendEmail({
13
+ from: 'Acme <acme+noreply@mail.example.com>', // use your project's sender address
14
+ to: 'user@example.com',
15
+ subject: 'Welcome',
16
+ text: 'Thanks for signing up!',
17
+ // html: '<p>Thanks for signing up!</p>', // optional — sent as multipart/alternative when paired with text
18
+ });
19
+
20
+ if (!result.ok) {
21
+ if ('error' in result) {
22
+ // A request-level failure; OUTCOME_UNKNOWN may already have been submitted.
23
+ console.error(result.error.code, result.error.message);
24
+ } else {
25
+ // Some recipients failed. `deliveries` says which.
26
+ for (const d of result.deliveries.filter((d) => !d.ok)) {
27
+ console.error(d.recipient, d.error.code, d.error.message);
28
+ }
29
+ }
30
+ }
31
+ ```
32
+
33
+ Check both failure shapes: `result.error` reports a request failure, while `result.deliveries` reports failures for individual recipients. An unverified recipient fails in `deliveries`.
34
+
35
+ ## Setup {#setup}
36
+
37
+ On a platform with email enabled, deploy without additional email configuration. Ask your administrator for its shared mail domain. Administrators [enable email during installation or upgrade](/guide/platform/installation/credentials#runtime-token-permissions); Void Cloud uses `mail.void.cloud`.
38
+
39
+ Each project on an email-enabled platform has:
40
+
41
+ - **Default sender:** `<your-slug>+noreply@<mail-domain>`. Older projects with slugs longer than 56 characters must pass `from` explicitly.
42
+ - **Your account email as a recipient:** it is registered when the project is created. If Cloudflare has not verified it for the platform, follow the emailed verification link and run `void email destinations` before sending to it.
43
+
44
+ The shared sender can send only to verified recipients. Verify your own address or [add another recipient](#adding-recipients) before sending.
45
+
46
+ Deploying to your own Cloudflare account instead (`void deploy --platform cloudflare`) requires a sender in `void.config.ts` and email setup — see [Your own Cloudflare account](./domains.md#your-own-cloudflare-account).
47
+
48
+ ## Adding recipients {#adding-recipients}
49
+
50
+ Cloudflare's `send_email` binding only delivers to addresses you've registered as recipients. Add them with the CLI:
51
+
52
+ ```sh
53
+ void email allow user@acme.com
54
+ ```
55
+
56
+ Cloudflare emails the recipient with a verification link. Once they click it and `void email destinations` has picked the click up, you can send to that address from your project. If the link did not arrive or has expired, run `void email allow <address>` again while the address is still pending — the CLI re-sends the link, or tells you how to get a fresh one. List the project's recipients:
57
+
58
+ ```sh
59
+ void email destinations
60
+ ```
61
+
62
+ The project owner’s email is added automatically, but still needs verification as described in [Setup](#setup).
63
+
64
+ ::: warning When this is the right fit
65
+ The shared sender suits team alerts, project notifications, and replies to inbound mail.
66
+
67
+ For mail to arbitrary users, [register your own domain](./domains.md#your-own-domain-on-the-platform), use [Workers Paid on your own Cloudflare account](./domains.md#your-own-cloudflare-account), or call another email provider from your handler.
68
+ :::
69
+
70
+ ## Options {#options}
71
+
72
+ | Option | Type | Notes |
73
+ | ---------------- | ---------------------------- | --------------------------------------------------------------------------------------------------------------- |
74
+ | `from` | `string \| { email, name? }` | Optional. Pinned to the project sender — see below. |
75
+ | `to` | `Address \| Address[]` | Required. One or more recipients. |
76
+ | `subject` | `string` | Required. UTF-8 supported (encoded as RFC 2047). |
77
+ | `text` | `string` | At least one of `text` / `html` is required. |
78
+ | `html` | `string` | Sent as `multipart/alternative` if both are provided. |
79
+ | `replyTo` | `Address` | Optional `Reply-To` header. |
80
+ | `cc`, `bcc` | `Address \| Address[]` | Optional. Each recipient is sent its own message envelope. |
81
+ | `headers` | `Record<string, string>` | Custom headers; reserved headers (From, Date, etc.) are ignored. |
82
+ | `attachments` | `Attachment[]` | See [Attachments](#attachments). |
83
+ | `idempotencyKey` | `string` | Optional on the Void Platform: 1–128 printable, non-space ASCII characters. Reuse for retries of the same send. |
84
+
85
+ You can send to at most 50 recipients across `to`, `cc`, and `bcc`. Use standard email addresses with ASCII local parts, and punycode for internationalized domains.
86
+
87
+ `Address` accepts either a string (`"hello@acme.dev"` or `"Name <hello@acme.dev>"`) or an object (`{ email, name? }`). Unicode display names are supported.
88
+
89
+ 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](./domains.md#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.
90
+
91
+ 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`.
92
+
93
+ ## Attachments {#attachments}
94
+
95
+ ```ts
96
+ await sendEmail({
97
+ to: 'user@example.com',
98
+ subject: 'Your receipt',
99
+ text: 'Receipt attached.',
100
+ attachments: [
101
+ {
102
+ filename: 'receipt.pdf',
103
+ content: pdfBytes, // string | Uint8Array | ArrayBuffer | Blob
104
+ contentType: 'application/pdf',
105
+ },
106
+ ],
107
+ });
108
+ ```
109
+
110
+ For inline images (e.g. logos referenced from HTML), set `disposition: 'inline'` and `contentId`:
111
+
112
+ ```ts
113
+ await sendEmail({
114
+ to: 'user@example.com',
115
+ subject: 'Hello',
116
+ html: '<img src="cid:logo" alt="Acme">',
117
+ attachments: [
118
+ {
119
+ filename: 'logo.png',
120
+ content: logoBytes,
121
+ contentType: 'image/png',
122
+ contentId: 'logo',
123
+ disposition: 'inline',
124
+ },
125
+ ],
126
+ });
127
+ ```
128
+
129
+ Void infers `contentType` from the filename if you omit it. An explicit value must be a valid media type. `contentId` names the image referenced by `cid:<id>` in HTML; use an ID such as `logo` or `logo@acme.dev`. The encoded message is limited to 5 MiB and custom headers to 16 KiB. Invalid attachment fields or oversized messages return `MIME_ERROR`.
130
+
131
+ ## Result and errors {#result-and-errors}
132
+
133
+ `sendEmail` returns a result instead of throwing. A successful result means the provider accepted the send; it does not confirm delivery to the recipient's mailbox.
134
+
135
+ `ok: true` includes recipient IDs. A failed result has either a request-level `error` or per-recipient `deliveries`. Platform results include an `operationId` when available.
136
+
137
+ A timeout can return `OUTCOME_UNKNOWN`; the provider may still accept the message. Inspect the outcome before retrying.
138
+
139
+ Use an idempotency key for sends you may retry:
140
+
141
+ ```ts
142
+ const result = await sendEmail({
143
+ to: 'user@example.com',
144
+ subject: 'Invoice ready',
145
+ text: 'Your invoice is available in your account.',
146
+ idempotencyKey: 'invoice:42:ready',
147
+ });
148
+
149
+ if (result.ok) {
150
+ console.log('Accepted by the provider', result.operationId);
151
+ } else {
152
+ console.log('Inspect the outcome before retrying', result.operationId, result);
153
+ }
154
+ ```
155
+
156
+ For 30 days, repeating a key with the same payload returns its recorded outcome without another provider submission. Reusing it with a different payload returns `IDEMPOTENCY_CONFLICT`. A lost response or interrupted provider request can return `OUTCOME_UNKNOWN`; check `void email logs` or repeat the same key. A new key creates a new send and can produce a duplicate. Native Cloudflare binding sends do not support platform idempotency keys.
157
+
158
+ | Code | Meaning |
159
+ | ------------------------ | -------------------------------------------------------------- |
160
+ | `BINDING_MISSING` | No email transport is configured. |
161
+ | `INVALID_FROM` | The sender is invalid or not authorized for the project. |
162
+ | `INVALID_TO` | The recipient list is invalid or exceeds 50 recipients. |
163
+ | `UNVERIFIED_DESTINATION` | The recipient needs verification under the active policy. |
164
+ | `MIME_ERROR` | The message is invalid or exceeds a size limit. |
165
+ | `QUOTA_EXCEEDED` | A platform or provider quota has been reached. |
166
+ | `IDEMPOTENCY_CONFLICT` | The key was already used for another payload. |
167
+ | `OUTCOME_UNKNOWN` | The send may have reached the provider; do not blindly resend. |
168
+ | `UPSTREAM_ERROR` | The provider or platform refused the request. |
169
+
170
+ The default platform allowance is 200 recipient attempts per UTC month and 10 per rolling minute. Failed or uncertain attempts can count toward these limits. Check `void email usage` for your current allowance.
171
+
172
+ `void email usage` shows monthly recipient attempts, inbound receipts, and the remaining allowance. `void email logs --limit 50` shows up to 100 recent metadata entries retained for 30 days: operation, recipient, direction, state, provider reference, and error code. It does not store subjects, bodies, or attachments. Receiving a message and sending a reply are separate events.
173
+
174
+ ## Local development {#local-development}
175
+
176
+ During `void dev`, sends are captured to an in-memory inbox instead of going to Cloudflare. The dev server prints the inbox URL when it starts:
177
+
178
+ ```
179
+ [void] Email inbox: http://localhost:5173/__void/inbox?token=<printed-token>
180
+ ```
181
+
182
+ Open a message to preview its HTML, inspect attachments, or download it as an `.eml` file.
183
+
184
+ Every inbox route requires a local access token. Void includes it in the printed inbox URL and browser links. For command-line requests, send it in the `x-void-dev-trigger` header as shown below; requests without it return `401`. The token is stored in `.void/dev-trigger-token`. Treat inbox URLs as credentials, especially when exposing your dev server to a network.
185
+
186
+ ```bash
187
+ # download a message as .eml
188
+ curl http://localhost:5173/__void/inbox/<id>/raw \
189
+ -H "x-void-dev-trigger: <printed-token>" -o message.eml
190
+
191
+ # clear the inbox
192
+ curl -X DELETE http://localhost:5173/__void/inbox \
193
+ -H "x-void-dev-trigger: <printed-token>"
194
+ ```
195
+
196
+ The inbox keeps the most recent 100 messages through HMR and clears on a full server restart.
197
+
198
+ The dev inbox works in native Void apps, TanStack Start, and React Router. It is unavailable in SvelteKit, Nuxt, Analog, and Astro; sends from those apps return `BINDING_MISSING`. Use `createEmailTestHarness` from `void/email/testing` to capture sends in tests. If a configured inbox cannot be reached, `sendEmail` returns `UPSTREAM_ERROR` without sending.
199
+
200
+ `sendInDev: true` skips the inbox, but returns `BINDING_MISSING` during `void dev`. Deploy the app to test real delivery.
201
+
202
+ ## Testing {#testing}
203
+
204
+ Use the test harness to assert on outgoing messages without mocking the binding:
205
+
206
+ ```ts
207
+ import { describe, it, expect } from 'vitest';
208
+ import { sendEmail } from 'void/email';
209
+ import { createEmailTestHarness } from 'void/email/testing';
210
+
211
+ describe('signup flow', () => {
212
+ it('sends a welcome email', async () => {
213
+ const inbox = createEmailTestHarness();
214
+
215
+ await sendEmail({
216
+ from: 'acme+noreply@mail.example.com', // use your project sender
217
+ to: 'user@example.com',
218
+ subject: 'Welcome',
219
+ text: 'Hi!',
220
+ });
221
+
222
+ expect(inbox.messages).toHaveLength(1);
223
+ expect(inbox.messages[0].subject).toBe('Welcome');
224
+ inbox.dispose();
225
+ });
226
+ });
227
+ ```
228
+
229
+ The harness intercepts every `sendEmail` call until `dispose()` is called. `clear()` empties the captured list without releasing the sink.
230
+
231
+ To handle incoming messages, see [Receiving Email](./receiving.md).