React contact form to email, no server
React is a rendering library, not a mail server. A component can collect a name, email, and message, but it has nowhere to deliver them. The usual fix is standing up an API route with an email SDK and spam checks. The lighter one: point your fetch at an endpoint that already does all three.
A React contact form needs somewhere to send its data. React only renders the UI. In your submit handler, fetch() a MakeTheForm endpoint: it accepts the POST from the browser, filters spam, emails you each submission, and stores it in a searchable inbox. No API route or server to run.
The minimal integration
This is a client component. The endpoint URL lives safely in the browser, like a mailto link. Drop it in as-is, or copy just the submit handler into your existing form and keep your own markup and styles.
import { useState } from 'react';
export function ContactForm() {
const [status, setStatus] = useState({ state: 'idle' });
async function handleSubmit(event) {
event.preventDefault();
const form = event.currentTarget;
setStatus({ state: 'submitting' });
try {
const response = await fetch('https://mtform.co/f/your-form-key', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(Object.fromEntries(new FormData(form))),
});
const result = await response.json();
if (!response.ok) {
throw new Error(result.error?.message ?? 'Unable to send your message.');
}
form.reset();
setStatus({ state: 'success' });
} catch (error) {
// Entered values stay put; we only reset on success.
setStatus({ state: 'error', message: error.message });
}
}
return (
<form onSubmit={handleSubmit}>
<input name="name" placeholder="Name" required />
<input type="email" name="email" placeholder="Email" required />
<textarea name="message" placeholder="Message" required />
{/* Spam trap: hidden from people, tempting to bots. */}
<input
type="text"
name="_mtf_honeypot"
tabIndex={-1}
autoComplete="off"
aria-hidden="true"
style={{ position: 'absolute', left: '-9999px' }}
/>
<button type="submit" disabled={status.state === 'submitting'}>
{status.state === 'submitting' ? 'Sending…' : 'Send'}
</button>
{status.state === 'success' && <p role="status">Thanks. We got your message.</p>}
{status.state === 'error' && <p role="alert">{status.message}</p>}
</form>
);
}Drop in as a component, or copy the handler into your existing form. Disables the button while submitting and preserves entered values on error.
Step by step
- 01
Create the endpoint
Create a form in MakeTheForm to get a public endpoint URL and an inbox. The URL is an address, not a secret, so it’s fine in client-side React.
- 02
Wire the submit handler
In onSubmit, call preventDefault, then fetch() the endpoint with your form data as JSON. Disable the button while the request is in flight.
- 03
Handle success and error
Treat any 2xx as success and reset the form. On error, show the returned message and leave the entered values in place: never reset on failure.
- 04
Verify and ship
Click the link in the verification email once, then submit the form. It lands in your inbox and by email with the sender in Reply-To.
Common pitfalls
Resetting the form on error
form.reset() belongs only in the success branch. If you reset in a finally block, a failed send wipes everything the visitor typed. Keep values on error so they can retry without re-entering the whole message.
Reaching for a server you don’t need
A plain React app is client-side, and the endpoint accepts browser POSTs directly. Standing up a Node/Express relay just to forward the request adds a server to deploy and an email service to keep alive: for no gain.
Troubleshooting
| Symptom | What is happening | Fix |
|---|---|---|
| CORS error | The request isn’t a simple JSON POST, or a wrapper added a disallowed header. | Send Content-Type: application/json with a plain fetch; the endpoint answers the browser’s CORS preflight. |
| button stuck disabled | The submitting state never resets because an error threw before it cleared. | Set state back to idle/error inside catch, not only on success, so the button re-enables. |
| 422 | A field the form requires wasn’t included in the POST body. | Confirm your input name attributes match the form’s configured fields; the JSON error names the missing one. |
Frequently asked questions
→How do I send a React form to email?
Point the form’s submit handler at a hosted form endpoint with fetch(). The endpoint receives the POST, filters spam, and emails you each submission, so you never write or host any mail-sending server code in React.
→Does React have built-in form handling?
React manages form state and input in the browser, but has no built-in backend to receive or email submissions. You either build a server route yourself or POST to a form endpoint that handles delivery and storage.
→Is it safe to put the form endpoint in React client code?
Yes. The public form key is a non-secret address meant for the browser, like a mailto link. Your destination email never appears in the page source, and there’s no API key to leak.
Keep going
- Next.js contact form
- Send an HTML form to email
- Contact form without a backend
- Endpoint docs and field reference
Last reviewed July 23, 2026
Ship this form for real
Create an endpoint, paste it in, and watch the first submission land in your inbox, in under three minutes.
Create free endpoint