Skip to content

The recommended way to integrate Postbox with an HTML form is using JavaScript fetch(). This gives you full control over the user experience - inline validation errors, success messages, and no page navigation.

<form id="contact-form">
<label for="name">Name</label>
<input type="text" id="name" name="name" required />
<label for="email">Email</label>
<input type="email" id="email" name="email" required />
<label for="message">Message</label>
<textarea id="message" name="message"></textarea>
<!-- honeypot field: hidden from users, catches bots -->
<div style="position:absolute; left:-9999px; top:-9999px; opacity:0; pointer-events:none;">
<input type="text" name="website" tabindex="-1" autocomplete="off" />
</div>
<button type="submit">Send</button>
<div id="form-errors" style="display:none; color:red;"></div>
<div id="form-success" style="display:none; color:green;">Submitted successfully!</div>
</form>
<script>
document
.getElementById("contact-form")
.addEventListener("submit", async (e) => {
e.preventDefault();
const errorsEl = document.getElementById("form-errors");
const successEl = document.getElementById("form-success");
errorsEl.style.display = "none";
successEl.style.display = "none";
const data = Object.fromEntries(new FormData(e.target));
try {
const res = await fetch("https://usepostbox.com/api/.../f/contact", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(data),
});
if (res.ok) {
successEl.style.display = "block";
e.target.reset();
} else {
const body = await res.json();
if (body.error?.details) {
const messages = Object.entries(body.error.details)
.map(([field, errs]) => `${field}: ${errs.join(", ")}`)
.join("\n");
errorsEl.textContent = messages;
} else {
errorsEl.textContent =
body.error?.message || "Something went wrong.";
}
errorsEl.style.display = "block";
}
} catch (err) {
errorsEl.textContent = "Network error. Please try again.";
errorsEl.style.display = "block";
}
});
</script>

A <form action> submission triggers a full page navigation. The browser leaves your page, and you lose the ability to:

  • Show inline per-field validation errors (Postbox returns error.details with field-level messages)
  • Keep the form state so users can correct and resubmit
  • Show a success message in context

With fetch(), you get the full response object, can parse errors, and give users a smooth experience.

Postbox endpoints have CORS enabled. You can submit from any origin - no proxy needed.

  • Match field names to your Postbox form schema (the name attributes in your HTML must match)
  • Use HTML validation (required, type="email", etc.) for client-side checks - Postbox validates server-side too
  • Hidden fields can pass metadata (e.g., page URL, referrer) alongside user input
  • Honeypot fields should use robust CSS hiding (not just display:none which some bots detect)