Connecting an HTML Form to Postbox — The Complete Guide
From creating an account to handling validation errors in the browser. A step-by-step guide to connecting your HTML form to Postbox using fetch and JSON.
This guide walks through connecting an HTML form to Postbox, from creating your account to handling validation errors in the browser. Start to finish, it takes about two minutes.
Why JSON over form-data
Postbox accepts both application/json and multipart/form-data. We recommend JSON, for two reasons.
Error handling. When a submission fails validation — a required field is missing, a type is wrong — Postbox returns a structured JSON error response. With fetch, your frontend can parse that response and show field-level errors inline. With a traditional HTML form POST, the browser navigates away from the page, and the error response replaces your entire UI.
No redirects. A traditional <form action="..." method="POST"> sends the user away from your page. You either redirect them back with a query parameter or lose them entirely. With fetch, the submission happens in the background. The user stays on your page. You control what happens next — show a success message, clear the form, trigger an animation, whatever fits your product.
The pattern is simple: build your form in HTML, intercept the submit event, send JSON via fetch, handle the response.
Step 1: Create an account
Head to usepostbox.com and sign up. You can use Google, GitHub, or email. Takes ten seconds.
Once you’re in, Postbox creates a “Try Me” form automatically so you can see how everything works before creating your own.
Step 2: Create a form
From the dashboard, click New Form. Give it a name and a slug. The slug becomes part of your endpoint URL — contact, feedback, waitlist, whatever describes what the form collects.
Step 3: Define your schema
This is what makes Postbox different from every other form backend. You define the fields your endpoint accepts — names, types, and which ones are required. Read why this matters.
Go to the Schema tab on your form and define your fields:
{
"fields": [
{ "name": "name", "type": "string", "required": true },
{ "name": "email", "type": "email", "required": true },
{ "name": "message", "type": "string", "required": false }
]
}
Supported types: string, email, number, boolean, date. Every submission is validated against this schema. Anything that doesn’t match gets rejected with a clear error.
Step 4: Get your endpoint URL
Go to the Integration tab. You’ll see your endpoint URL and auto-generated code snippets for every language. It looks like this:
https://usepostbox.com/api/{opaque_segment}/f/{slug}
When you update your schema, you get a new endpoint URL — but old URLs keep working. Nothing breaks.
Step 5: Build your form
Here’s a complete, working example. No framework, no dependencies, just HTML and vanilla JavaScript:
<form id="contact-form">
<div>
<label for="name">Name</label>
<input type="text" id="name" name="name" required />
<span class="error" data-field="name"></span>
</div>
<div>
<label for="email">Email</label>
<input type="email" id="email" name="email" required />
<span class="error" data-field="email"></span>
</div>
<div>
<label for="message">Message</label>
<textarea id="message" name="message"></textarea>
<span class="error" data-field="message"></span>
</div>
<button type="submit">Send</button>
</form>
<script>
const POSTBOX_URL = "<POSTBOX_FORM_URL>";
document
.getElementById("contact-form")
.addEventListener("submit", async (e) => {
e.preventDefault();
// Clear previous errors
document
.querySelectorAll(".error")
.forEach((el) => (el.textContent = ""));
const form = e.target;
const data = {
name: form.name.value,
email: form.email.value,
message: form.message.value,
};
const res = await fetch(POSTBOX_URL, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(data),
});
if (res.ok) {
form.reset();
alert("Sent! Thank you.");
return;
}
// Handle validation errors
const errors = await res.json();
for (const [field, message] of Object.entries(errors)) {
const el = document.querySelector(`.error[data-field="${field}"]`);
if (el) el.textContent = message;
}
});
</script>
Replace <POSTBOX_FORM_URL> with the endpoint URL from your Integration tab. That’s it.
What happens when you submit
When the form submits, here’s the sequence:
-
Validation — Postbox checks the payload against your schema. If
nameis missing oremailisn’t a valid email, the submission is rejected with a422response containing field-level errors:
{
"name": "is required",
"email": "invalid email"
}-
Storage — if validation passes, the submission is stored and you get a
201response:
{
"id": "abc123",
"data": { "name": "Jane", "email": "jane@example.com", "message": "Hello!" },
"created_at": "2026-03-09T12:00:00Z"
}- Processing — in the background, Postbox runs your configured pipeline: spam detection, auto-translation, and smart replies if enabled. The submitter gets an instant response regardless.
Using with React, Vue, or any framework
The pattern is identical in any framework. Intercept the submit, send JSON via fetch, handle the response. Here’s a React example:
async function handleSubmit(e) {
e.preventDefault();
setErrors({});
const res = await fetch(POSTBOX_URL, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ name, email, message }),
});
if (res.ok) {
setSubmitted(true);
return;
}
setErrors(await res.json());
}
The error response shape is always { field_name: "error message" }, so mapping errors to form fields is straightforward in any framework.
CORS
Postbox endpoints have CORS enabled by default. Your frontend can submit from any origin — localhost, your production domain, a Vercel preview deploy. No configuration needed.
What’s next
Your form is connected. Submissions flow into your Postbox dashboard in real time. From here, you can:
- Enable spam filtering to catch junk submissions automatically
- Set up auto-translation if you serve a global audience
- Add a knowledge base to auto-reply to common questions
- Access submissions through the API or MCP for agent workflows
The same endpoint that your HTML form submits to also works with cURL, scripts, and AI agents. One endpoint, any source. That’s the point.