Bay 12Bring your forms
#Moving from Formspree
sendm8 reads the same field names as Formspree, so a plain HTML form moves with a one-line change: the action URL. Code that sends forms with JavaScript needs a small change to how it reads errors.
Moving a form
- Get a sendm8 endpoint. Create a form in the dashboard and copy its URL, or skip signing up and use
https://sendm8.com/f/you@example.com. - Swap the URL everywhere the form posts to
https://formspree.io/f/…. - Send a test submission and check it arrives where you expect. Then add Discord, Slack, Telegram or webhooks if you like.
contact.html
<form action="https://sendm8.com/f/k3x9q2m7ab" method="POST">
<input name="email" type="email" required>
<textarea name="message"></textarea>
<input type="hidden" name="_subject" value="New enquiry">
<input type="text" name="_gotcha" style="display:none">
<button>Send</button>
</form>contact.html
<form action="https://formspree.io/f/xyzabcde" method="POST">
<input name="email" type="email" required>
<textarea name="message"></textarea>
<input type="hidden" name="_subject" value="New enquiry">
<input type="text" name="_gotcha" style="display:none">
<button>Send</button>
</form>What maps to what
| Formspree | sendm8 |
|---|---|
https://formspree.io/f/{id} | https://sendm8.com/f/{id}, or https://sendm8.com/f/{your email} with no account |
_replyto or email, _subject, _next, _gotcha, _cc | The same. _next must be on one of the form’s allowed domains, and _cc only copies addresses verified in your account. |
| Other underscore fields | Ignored and not stored. |
JSON success: { ok: true } | { ok: true, id } |
JSON errors: an errors array | { ok: false, error: { code, message } } with a matching HTTP status. AJAX / fetch. |
@formspree/react (useForm) | A few lines of fetch, below. The package only talks to Formspree. |
@formspree/ajax and data-fs-* attributes | The helper script and data-sendm8 attributes. AJAX without writing JavaScript. |
JavaScript and React
contact.js
const res = await fetch("https://sendm8.com/f/k3x9q2m7ab", {
method: "POST",
body: new FormData(form),
headers: { Accept: "application/json" },
});
const result = await res.json();
if (result.ok) {
showThanks();
} else {
// Formspree: result.errors.map((e) => e.message)
showError(result.error.message);
}ContactForm.jsx
// Before: import { useForm, ValidationError } from "@formspree/react";
import { useState } from "react";
export function ContactForm() {
const [state, setState] = useState({ submitting: false, succeeded: false, error: null });
async function handleSubmit(event) {
event.preventDefault();
setState({ submitting: true, succeeded: false, error: null });
const res = await fetch("https://sendm8.com/f/k3x9q2m7ab", {
method: "POST",
body: new FormData(event.currentTarget),
headers: { Accept: "application/json" },
});
const result = await res.json();
setState({ submitting: false, succeeded: result.ok, error: result.ok ? null : result.error.message });
}
if (state.succeeded) return <p>Thanks! It's on its way.</p>;
return (
<form onSubmit={handleSubmit}>
<input name="email" type="email" required />
<textarea name="message" required />
{state.error && <p role="alert">{state.error}</p>}
<button disabled={state.submitting}>Send</button>
</form>
);
}Migrating with an AI assistant
These docs are also published as Markdown for AI assistants: /llms.txt lists every section, and /llms-full.txt has all of them in one file. Create your sendm8 forms first (an assistant can’t do that for you), then give it a prompt like this in the app you’re migrating.
Migrate this app's forms from Formspree to sendm8. Read https://sendm8.com/llms-full.txt first.
Endpoint mapping (Formspree → sendm8):
- https://formspree.io/f/xyzabcde → https://sendm8.com/f/k3x9q2m7ab
1. Find every Formspree usage: "formspree.io", "@formspree/react", "@formspree/ajax",
data-fs-* attributes, and environment variables holding Formspree form IDs.
2. HTML forms: change only the action URL. Field names stay the same.
3. JavaScript submissions: POST to the sendm8 URL with "Accept: application/json".
Success is { ok: true, id }. Failure is { ok: false, error: { code, message } }:
show error.message. Update any code that reads Formspree's errors array.
4. Replace @formspree/react and @formspree/ajax with fetch (or the sendm8 helper script),
keeping the same loading, success and error states. Then remove the packages.
5. Don't change styling, copy or field names.
6. List every file you changed, and anything you couldn't migrate.