Does this work for project sites, not just username.github.io?
Yes. Project pages live under the same host, and since origins exclude the path, the single `yourname.github.io` entry covers every repository at once.
Pages ships HTML, CSS, and JS from a repository, but a pull request is the only thing it accepts. Forms need a destination, so this guide wires a Pages site to SubmitHarbor's endpoint — paste-ready markup included, with the redirect and bot controls done properly.
GitHub's servers will serve your index.html all day, but nothing on that infrastructure is listening for a POST with your name on it. Historically the workarounds were grim: a mailto: link that opens a desktop client, or a third-party widget that injects scripts and styling you do not control. A hosted endpoint sidesteps both — your markup stays yours, and the receiving half runs elsewhere.
The whole integration fits in one file with no build tooling, which suits the way Pages already works.
Drop this into any .html file Pages serves. Jekyll processes templates, but ordinary HTML passes through untouched — no front matter, plugins, or _config.yml edits required.
<form
action="https://www.submitharbor.com/api/submit/sh_your_project_key"
method="POST"
>
<p>
<label for="email">Email</label>
<input id="email" name="email" type="email" required />
</p>
<p>
<label for="message">Message</label>
<textarea id="message" name="message" required></textarea>
</p>
<input
type="hidden"
name="_redirect"
value="https://yourname.github.io/your-repo/thanks.html"
/>
<!-- Bots fill this; people never see it. -->
<input type="text" name="_gotcha" tabindex="-1" autocomplete="off" hidden />
<button>Send</button>
</form>Create a stub thanks.html alongside it, swap the key for your own, and the form is production-shaped. The _redirect input turns the raw JSON answer into a 303 bounce to your page — without it, a successful send leaves the visitor staring at a JSON document.
With an allow-list configured, the endpoint refuses browser posts from unlisted sites with 403 — worth setting before strangers find your key.
https://yourname.github.io
https://your-custom-domain.com
https://www.your-custom-domain.comAn Origin header carries scheme, host, and port — never a path — so one yourname.github.io entry vouches for every repository you publish there. Custom domains resolve differently with and without www, hence both lines. While iterating locally with jekyll serve or a plain Python server, temporarily add http://localhost:4000 or http://localhost:8000, and remove it afterwards.
thanks.html. In the studio inbox the arrival appears with its fields, timestamp, and sending origin, flagged unread.required attributes in place to spare most visitors the trip._gotcha receives an unremarkable success while nothing is recorded — no puzzle, no challenge page, no JavaScript involved.sh_your_project_key is a placeholder. A malformed or unknown key answers 404 regardless of how perfect the rest is.www variant included where applicable.not-an-email and read the 422 page. If the sentence would embarrass you, style an error route — or lean harder on HTML validation attributes.curl -i -X POST https://www.submitharbor.com/api/submit/demo_contact_7x2p \
-H "Content-Type: application/json" \
-d '{"email":"you@example.com","message":"Testing the demo endpoint."}'The shared demo key validates a submission and echoes it back, then discards it. It never writes to the database, because a stored demo submission would leave a stranger's details in an inbox nobody owns. Create your own endpoint to keep what arrives.
Worth reading before you build on it. These limits apply today.
Yes. Project pages live under the same host, and since origins exclude the path, the single `yourname.github.io` entry covers every repository at once.
List each host exactly as browsers will visit it. Redirecting one to the other server-side also works, leaving a single entry to maintain.
Technically yes, and the combined inbox will interleave everything. Separate projects per site cost nothing and keep exports clean, so most people split them.
It is JavaScript-free by construction — a native POST, a server-side honeypot, and a 303 redirect. Enhancing it with fetch later is optional, never required.