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="_honey" 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.