Bay 12Bring your forms
Moving from Formspree
sendm8 reads Formspree’s usual field names (email, _replyto, _gotcha and friends), so a plain HTML form usually moves with a one-line change: the action URL. If yours sets the subject with a plain subject field, rename it_subject. Code that sends forms with JavaScript needs a small change to how it reads errors.
Still deciding? sendm8 vs Formspree compares the free plans side by side, andFormspree alternatives covers the other options too.
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.
<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><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, plus the same errors array when a field is at fault. AJAX / fetch. |
@formspree/react (useForm, ValidationError) | @sendm8/react, with the same API. Change the import and the form id. |
@formspree/ajax and data-fs-* attributes | The helper script and data-sendm8 attributes. AJAX without writing JavaScript. |
React and JavaScript
In a React app, swap @formspree/react for @sendm8/react. It has the sameuseForm and ValidationError, so the import and the form id are the only changes. Field errors work the same, and every failure also comes with one error for the whole form (some, like the monthly limit, aren’t about a field), so keep a <ValidationError> without a field somewhere in the form too.
import { useForm, ValidationError } from "@sendm8/react"; // was "@formspree/react"
export function ContactForm() {
const [state, handleSubmit] = useForm("k3x9q2m7ab"); // was useForm("xyzabcde")
if (state.succeeded) return <p>Thanks! It's on its way.</p>;
return (
<form onSubmit={handleSubmit}>
<input name="email" type="email" required />
<ValidationError field="email" errors={state.errors} />
<textarea name="message" required />
<ValidationError errors={state.errors} />
<button disabled={state.submitting}>Send</button>
</form>
);
}npm uninstall @formspree/react
npm install @sendm8/reactconst 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 {
// One sentence for the whole form. Field problems are also in
// result.errors, shaped like Formspree's: [{ field, code, message }].
showError(result.error.message);
}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 }, errors? }:
error.message is always there. errors (field problems only) matches Formspree's
[{ field, code, message }], so code reading it per field can stay.
4. Replace @formspree/react with @sendm8/react (same useForm and ValidationError API):
change the import and the form id. Keep field-level <ValidationError field="..." />, and
make sure there's also one without a field prop: errors like the monthly limit aren't
about a field.
Replace @formspree/ajax with fetch or the sendm8 helper script. Then remove the old packages.
5. Don't change styling, copy or field names.
6. List every file you changed, and anything you couldn't migrate.