A form is what submissions are filed under. Every submission it receives is a row with its fields as data, readable in the dashboard, exportable as a CSV or spreadsheet with a column per field, and emailed on a schedule if you want it to be. A form is fed one of two ways.
Named from your code
Your app already sends the submission with the library; name the form and it’s filed:
Or on any mail() call: mail({ to, body: request.formData(), form: "Home Ownership Query" }).
A name is created on first use and matched case-insensitively; bunx postboi sync makes
your account’s forms autocomplete on form, and a name it doesn’t know yet is still fine. See Naming the form for the details, including what a rename does.
A hosted endpoint
For a page with no code behind it, a form can be a URL instead. Create one in the dashboard, choose which team member’s inbox
receives submissions, and point a plain <form> at the endpoint you get, like https://postboi.app/f/form_k3v9x2m1p8q4. Submissions arrive as the same tidy email the
library renders, and are filed under the form like any other.
Submissions can only be delivered to a team member’s sign-in email, an address verified at sign-in (magic link or SSO), so a public endpoint can never be pointed at someone else’s inbox. If that member leaves the team, the form stops delivering until you point it at a current member.
The HTML
That’s the whole integration. On submit, the visitor lands on a hosted thank-you page, or set a redirect URL (https) on the form in the dashboard to send them back to your own site.
The email arrives with fields rendered as a table, and an email field is automatically
used as the Reply-To: hitting Reply in your inbox goes straight back to the sender.
File inputs (<input type="file"> with enctype="multipart/form-data") become
attachments, up to 5 MB per submission.
Special fields
The endpoint speaks the library’s FormData dialect:
_to, _from, _cc and _bcc are ignored: the recipient is fixed by the form’s
configuration, so a bot can’t repoint the mail.
Spam protection
Two layers, same as the library’s spam protection:
- Honeypot: include the hidden
_honeyfield and bot submissions are silently dropped. The bot even receives a success response, so it learns nothing. - Managed captcha: enable Require captcha on the form and add the captcha
<script>tag (from the dashboard) to the page. Postboi renders an invisible Turnstile challenge and verifies every submission server-side. No Cloudflare account needed.
Endpoints are also rate-limited per form (10 submissions a minute, 500 a day) on top of your plan’s limits, so a bot can’t drain your quota (or your overage bill) through a public URL.
Open & click tracking
Tick Track opens & clicks on a form and its notification emails opt into engagement tracking: opens and link clicks show up on the message in your log, exactly like a tracked API send. Left unticked, the form follows your account’s tracking default.
Posting with fetch
Prefer to stay on the page? Ask for JSON and you get { ok: true } instead of a redirect
(CORS is open, so static sites can post from their own origins):
A JSON request body works too: POST with content-type: application/json and a flat
object of fields.
Errors come back as { ok: false, code, message } with a matching HTTP status:
Submissions as data
Every submission, named or posted, keeps its fields as [name, value] pairs beside the
email. The form’s page in the dashboard shows them as a table with a column per field,
its Exports tab downloads them as a CSV or spreadsheet, and a schedule emails that file
every day, week or month: new submissions since the last run, or the previous period
whole. The same is on the API: mail.exports.download({ filter: { form: "Home Ownership Query" } }) for the file now, mail.exports.create({ filter: { form: "Home Ownership Query" }, recipients, schedule: "weekly" }) for the calendar. On the CLI it is bunx postboi exports download --form "Home Ownership Query" and bunx postboi exports add "Weekly queries" --to ops@acme.example --form "Home Ownership Query" --weekly. Every webhook event about a submission names the form and carries the fields.
Notes
- Submissions are ordinary sends: they appear in the dashboard’s message log and count towards your plan’s volume. The free tier’s 3,000/mo covers a lot of contact form.
- Pause a form from the dashboard and sends naming it are refused, or its endpoint stops accepting (410), until it’s resumed. Delete a hosted form and the URL is gone for good; delete a named one and the next send naming it starts a fresh form.