Until today, Postboi assumed you had a server: the library reads your form's FormData and mail() sends it. But an enormous number of sites don't have a
server at all — static sites, landing pages, documentation, portfolios, anything on GitHub Pages or
a CDN. Their "contact form" options were a mailto link or a third-party form service.
Not any more. Postboi Forms turns any HTML form into email with one attribute. No backend, no library, no JavaScript.
Point a form at a URL. That's it.
Create a form in the dashboard and you get an endpoint URL. Point any plain HTML form at it:
<form action="https://postboi.email/f/form_k3v9x2m1p8q4" method="POST">
<input name="name" placeholder="Your name" required>
<input name="email" type="email" placeholder="Your email" required>
<textarea name="message" placeholder="Your message" required></textarea>
<!-- honeypot: hidden from humans; bots fill it and get dropped -->
<input name="🍯" style="position:absolute;left:-9999px;height:0;width:0;opacity:0"
tabindex="-1" autocomplete="off" aria-hidden="true">
<button>Send</button>
</form>Submit it, and the message lands in your inbox as the same tidy HTML email the Postboi library
renders — fields as a table, an email field automatically wired to Reply-To (so answering happens in your mail client), and file inputs arriving as attachments.
After submitting, visitors land on a hosted thank-you page — or set a redirect back to your own site in the dashboard.
Spam handled, invisibly
The honeypot field in the snippet is the same zero-config spam trap the library uses: humans never see it, bots fill it in, and filled submissions are silently dropped — the bot even gets a success response, so it learns nothing. Endpoints are also rate-limited per form.
Want more? Enable managed captcha on the form and add one script tag to your page — Postboi provisions an invisible Cloudflare Turnstile challenge and verifies every submission server-side. No Cloudflare account needed.
Grouping, subjects, attachments
The endpoint speaks the library's full FormData dialect — special fields set the
subject, and fieldset→field names group the email into sections:
<!-- special fields tune the email; fieldset→field groups the table -->
<input type="hidden" name="_subject" value="New enquiry from the pricing page">
<input name="contact→name" placeholder="Your name">
<input name="contact→email" type="email" placeholder="Your email">
<textarea name="details→message" placeholder="What can we help with?"></textarea>
<input name="details→attachment" type="file">One thing to remember for file uploads: give the <form> itself enctype="multipart/form-data", or the browser sends only the filename. With it, files
arrive as attachments — up to 5 MB per submission.
Or fetch(), if you'd rather stay on the page
// Prefer to stay on the page? Post the same endpoint with fetch —
// ask for JSON and you get { ok: true } back instead of a redirect.
async function submit(event: SubmitEvent) {
event.preventDefault()
const form = event.target as HTMLFormElement
const response = await fetch(form.action, {
method: "POST",
body: new FormData(form),
headers: { accept: "application/json" },
})
const result = await response.json()
if (result.ok) form.replaceWith("Thanks — message sent!")
}The fine print (it's short)
- Form submissions are ordinary sends: they show up in your message log and count towards your plan — the free tier's 3,000 emails a month covers a lot of contact form.
- Submissions can only be delivered to a team member's verified sign-in email, so a public endpoint can never be pointed at someone else's inbox.
- Everything is live today, on every plan, including free. Full guide: docs.postboi.email/forms.
If you do have a backend, the library route is still the nicest way to do a contact form in SvelteKit (and Next, Astro, Nuxt, Remix…). Now the sites without one get the same treatment.