Skip to main content

Frameworks

Cloudflare Workers

Send email from a Worker. Bindings are read automatically, so there's nothing to pass in.


Workers pass configuration as bindings rather than ambient env vars, but Postboi reads them off cloudflare:workers for you — a POSTBOI_TOKEN binding is picked up exactly like a POSTBOI_TOKEN env var anywhere else, so there’s nothing to wire up. Read the request’s FormData and hand it to mail(). Postboi extracts the special fields and renders the rest into a tidy HTML table.

// src/index.ts
import { mail } from 'postboi'

export default {
	async fetch(request: Request): Promise<Response> {
		const url = new URL(request.url)

		if (request.method === 'POST' && url.pathname === '/contact') {
			await mail({ body: request.formData(), to: 'team@example.com' })
			return Response.redirect(new URL('/?sent=1', url).toString(), 303)
		}

		return new Response(/* the contact form */)
	},
}
// src/index.ts
import { mail } from 'postboi'

export default {
	async fetch(request: Request): Promise<Response> {
		const url = new URL(request.url)

		if (request.method === 'POST' && url.pathname === '/contact') {
			await mail({ body: request.formData(), to: 'team@example.com' })
			return Response.redirect(new URL('/?sent=1', url).toString(), 303)
		}

		return new Response(/* the contact form */)
	},
}

Point a multipart/form-data form at /contact. Include hidden _subject and _reply_to fields, and mirror the email into _reply_to with a one-line oninput so replying reaches the sender. Field names use the fieldset→field syntax.

Set the token as a secret (wrangler secret put POSTBOI_TOKEN, or .dev.vars for local dev), and turn on nodejs_compat in wrangler.jsonc. Swap providers with a POSTBOI_PROVIDER binding plus that provider’s credential — or construct one explicitly, which still works: new Postboi({ token: env.POSTBOI_TOKEN }). See Providers.

The config file

Bindings arrive on their own, but a Worker has no filesystem, so postboi.config.ts can’t be read at runtime. If you build with Vite — SvelteKit, Nuxt, Astro, Remix, or plain Vite — add the plugin and it travels in the bundle instead:

// vite.config.ts
import { postboi } from 'postboi/vite'

export default defineConfig({ plugins: [sveltekit(), postboi()] })
// vite.config.ts
import { postboi } from 'postboi/vite'

export default defineConfig({ plugins: [sveltekit(), postboi()] })

That’s the whole setup: mail() picks up your default.from, hooks and captcha settings with nothing imported anywhere. The plugin also adds the optimizeDeps exclude that remote forms need, so it replaces that line too.

Building with wrangler alone (no Vite), import the config file once from your entry point — config() registers it as a side effect and esbuild inlines it:

// src/index.ts
import '../postboi.config'
import { mail } from 'postboi'
// src/index.ts
import '../postboi.config'
import { mail } from 'postboi'

Or skip the file and call configure() at startup.

Runnable example: examples/cloudflare-workers-provider-postboi.