No server to bolt onto your SPA
A Vite or CRA build is static files. Because the endpoint accepts cross-origin requests, the browser talks to mailcontact directly. No proxy, no serverless function, nothing extra to deploy.
integrations · framework
A React SPA has nowhere to send a contact form: there's no server behind a Vite build. You don't need to add one. POST the fields to your mailcontact endpoint straight from the browser (CORS is open, so it works from any origin) and messages land in your project's inbox, with a notification in Discord, Slack, email, or a webhook.
The component below is the whole integration: a form, a fetch call, and a useState flag for the success state. No Express server, no serverless function, no email provider SDK. Your build stays a pile of static files.
Prefer zero JavaScript? A plain <form action="…" method="POST"> posts to the same endpoint. See the FAQ for the trade-off.
the snippet
Point the form at your project's mailcontact endpoint. Submissions land in your project inbox. No server, no API route, no email service to wire up.
import { useState } from "react";
export default function ContactForm() {
const [status, setStatus] = useState<"idle" | "sending" | "sent" | "error">("idle");
async function handleSubmit(event: React.FormEvent<HTMLFormElement>) {
event.preventDefault();
const data = new FormData(event.currentTarget);
setStatus("sending");
try {
const res = await fetch("https://mailcontact.app/api/forms/your-form-id", {
method: "POST",
body: data,
});
setStatus(res.ok ? "sent" : "error");
} catch {
setStatus("error");
}
}
if (status === "sent") {
return <p>Thanks! Your message is on its way.</p>;
}
return (
<form onSubmit={handleSubmit}>
<input type="text" name="name" placeholder="Your name" />
<input type="email" name="email" placeholder="you@email.com" required />
<textarea name="message" placeholder="What's on your mind?" required />
<button type="submit" disabled={status === "sending"}>
{status === "sending" ? "Sending…" : "Send"}
</button>
{status === "error" && <p>Something went wrong. Please try again.</p>}
</form>
);
}how it works
One inbox per project. Name it after your app and you get a form endpoint and a project email address immediately.
Paste ContactForm.tsx, swap in your form id, and render it anywhere: a route, a footer, a modal. The markup is yours to restyle.
Add a Discord, Slack, email, or webhook channel. Every submission pings you where you already are.
why mailcontact
A Vite or CRA build is static files. Because the endpoint accepts cross-origin requests, the browser talks to mailcontact directly. No proxy, no serverless function, nothing extra to deploy.
fetch plus useState keeps visitors inside your app: disable the button while sending, swap in a thank-you message on success. No full-page navigation out of your client-side router.
It's a plain form component, not an embedded widget or iframe. Style it with Tailwind, CSS Modules, styled-components, whatever the rest of your app uses.
faq
Correct. The endpoint accepts cross-origin POSTs (CORS is open), so the browser submits directly to mailcontact. Your React app can be deployed as pure static files with no functions at all.
Submit with fetch as in the snippet and flip a useState flag when the response comes back ok. Native form posts currently return a JSON response instead of redirecting back to your site (a configurable redirect is coming with the launch), so fetch is the right fit for a SPA today.
Send email and message (both required) plus optional name and subject, as FormData or JSON. Requests are limited to 20 per minute per IP with a 64KB max body, plenty for a contact form.
Yes. mailcontact only sees the POST, so build the form however you like (react-hook-form, controlled inputs, a UI kit), as long as the request carries the name, email, and message fields.
related
Forms and a real email address, together, with notifications where you live. Launching soon. Be the first to know.