Why your Lovable form looks done but isn't
Lovable is an AI app builder: you describe an app, it generates a working React front end with Tailwind styling. It's genuinely good at the part you can see. The trap is the part you can't see. When Lovable generates a "contact form," it produces the markup, the inputs, the validation, and a submit button — but there is no server on the other end of that button. Hit submit on a freshly generated Lovable contact form and, by default, one of three things happens: nothing, a console log, or a fake success toast that never sends anything anywhere.
This is the single most common "my Lovable app is broken" moment, and it isn't a bug. A form is a front-end element; receiving a form submission requires a backend that accepts an HTTP POST, stores it, and notifies you. Lovable knows this, which is why its own guidance points you at connecting Supabase for storage and Resend for email, glued together with an edge function.
That works. It's also a lot of machinery for "email me when someone fills out my contact form." You end up creating a database table you'll never query, writing and deploying an edge function, signing up for a second service (Resend), configuring an API key, and debugging CORS and cold starts — all to move three fields from a form into your inbox. There's a shorter path.
Supabase + Resend vs a hosted form backend
Here's the honest comparison so you can pick deliberately rather than by default.
| What you need to do | Supabase + Resend | splitforms |
|---|---|---|
| Create a database table | Yes — schema, columns, RLS policy | No |
| Write + deploy an edge function | Yes — and redeploy on every change | No |
| Sign up for an email provider | Yes — Resend account + API key + domain verify | No — email included |
| Spam protection | Build it yourself | Honeypot + AI classifier, included |
| Dashboard to read submissions | Query the table or build a UI | Built-in, searchable |
| Lines of app code | Dozens, across several files | One endpoint URL |
The rule of thumb: keep Supabase for things that are genuinely your app's data — user accounts, records your product reads and writes. Use a form backend for the thing that is just "a form went off, tell me about it." A contact form, a waitlist, a lead-capture form, a demo request — these don't need to live in your app database, and putting them there is busywork.
Step 1: Get a free splitforms access key (1 minute)
splitforms is the form backend we'll point your Lovable app at. The free tier is 500 submissions/month with AI spam filtering and dashboard capture; Starter adds email forwarding and webhooks — no credit card for Free.
- Go to splitforms.com/login
- Enter your email and paste the 6-digit code
- Copy the access key the dashboard generates (it looks like
sf_live_a1b2c3d4...) - Set your notification email under Settings → Notifications — that's where submissions get forwarded
The access key is a public identifier, not a secret. It will live in your Lovable app's client code, which is fine and expected — abuse is handled server-side with rate limiting, Allowed Domains, and spam classification. If you're comparing options first, the free form backend comparison covers the field; splitforms has the largest free tier in it.
Step 2: Tell Lovable to use the endpoint (the easy way)
The fastest path is to let Lovable rewire its own form. Paste this prompt into Lovable's chat, swapping in your access key:
Change the contact form so that on submit it sends the form data
as a POST request to https://splitforms.com/api/submit using fetch.
Include a hidden field "access_key" with the value "YOUR_ACCESS_KEY".
Send the remaining fields (name, email, message) as form data.
On a successful response, show the existing success state and
navigate to /thanks. On error, show an inline error message.
Do not change the visual design or styling of the form.Lovable will rewrite the submit handler and keep your design intact. This is the whole integration — no table, no edge function, no second service. If you'd rather wire it by hand (or want to understand exactly what Lovable generates), the next section has the code.
Step 3 (manual): The exact submit handler
If you prefer to edit the code yourself, here's a clean React submit handler that posts to splitforms. It works in any Lovable-generated React app and doesn't care how the inputs are styled.
import { useState } from "react";
const ACCESS_KEY = "YOUR_ACCESS_KEY"; // public identifier, safe in client code
export function ContactForm() {
const [status, setStatus] = useState<"idle" | "sending" | "ok" | "error">("idle");
async function handleSubmit(e: React.FormEvent<HTMLFormElement>) {
e.preventDefault();
setStatus("sending");
const form = e.currentTarget;
const data = new FormData(form);
data.append("access_key", ACCESS_KEY);
try {
const res = await fetch("https://splitforms.com/api/submit", {
method: "POST",
body: data,
});
if (!res.ok) throw new Error("Request failed");
setStatus("ok");
form.reset();
// navigate to a thank-you route if you have one:
// navigate("/thanks");
} catch {
setStatus("error");
}
}
return (
<form onSubmit={handleSubmit}>
{/* keep all of Lovable's generated inputs and classes exactly as-is */}
<input name="name" type="text" required />
<input name="email" type="email" required />
<textarea name="message" required />
{/* honeypot: hidden from humans, bots fill it */}
<input name="botcheck" type="checkbox" tabIndex={-1}
autoComplete="off" style={{ display: "none" }} />
<button type="submit" disabled={status === "sending"}>
{status === "sending" ? "Sending..." : "Send message"}
</button>
{status === "ok" && <p>Thanks — we'll be in touch shortly.</p>}
{status === "error" && <p>Something went wrong. Please try again.</p>}
</form>
);
}Three things to keep: the access_key appended to the FormData, the name attributes on each input (these become the field labels in your dashboard and Starter notification emails), and the hidden botcheck honeypot. Everything else is yours — keep Lovable's exact markup and Tailwind classes so the design is untouched.
The name attributes are flexible. Rename them to company, budget, project_type — whatever your form collects. splitforms preserves whatever keys you send, so the same handler works for a contact form, a quote request, a waitlist, or a demo request. For a deeper dive on the fetch-and-FormData pattern, see send form data to email with JavaScript fetch.
Step 4: Test and publish
Use Lovable's preview to submit a real test entry. Within about 5 seconds you should see two things:
- A notification email in the inbox you set under Settings → Notifications
- The submission in your splitforms dashboard, with all fields captured
If both land, publish your Lovable app as normal. One detail worth setting before you ship widely: open the splitforms security tab and add your published Lovable domain (and any custom domain) to Allowed Domains. That ensures only your app can use the key, and it's the right time to do it — once, before launch.
If the submission doesn't arrive, check the dashboard first. If it's in the dashboard but no email came, the issue is forwarding (check spam, verify the notification address). If it's not in the dashboard at all, the POST never reached splitforms — open the browser console and confirm the fetch URL and that the access_key is present. The full checklist is in contact form not working.
Step 5 (optional): Fan out to Slack, Notion, or Sheets
This is where the hosted-backend approach pays off versus Supabase + Resend. Every submission can be delivered to email plus any number of signed webhooks in parallel, configured in the dashboard — not in your Lovable app. So adding a Slack alert or a Notion row later doesn't mean regenerating or redeploying the app.
- Real-time team alerts: submissions to Slack or to Discord
- Lightweight CRM: submissions to Notion or to Airtable
- Spreadsheet of leads: to Google Sheets without Zapier
If you're building with AI tooling more broadly, splitforms also ships an MCP server — so an agent in Cursor or Claude can read submissions and configure forms through a typed protocol, not a scraped dashboard. That's the part generic "works with AI builders" backends can't match.
What to do next
- Doing the same in a Vercel app? Add a form backend to a v0 app.
- Grab a ready-made form: free HTML contact form.
- Compare the backend options: splitforms vs Forminit (the AI-builder backend) and vs Formspree.
- Ready to wire it up: get a free access key. Reference: /docs.
FAQ
Why doesn't my Lovable contact form do anything when I submit it?
Because Lovable generates the front end — the React, the Tailwind, the validation — but it doesn't stand up a server to receive the POST. By default a generated form either does nothing on submit or logs to the console. To actually capture submissions you need a backend: either Supabase + an email service like Resend wired through an edge function, or a hosted form backend you point the form at. The hosted route is one line of code; the Supabase + Resend route is several files and two dashboards.
Do I need Supabase and Resend to collect form submissions in Lovable?
No. That's Lovable's default recommendation, but it's three moving parts (a database table, an edge function, and an email provider) for what is fundamentally 'receive a POST and email it to me.' A hosted form backend collapses all of it into a single endpoint URL. You keep Supabase for the parts that actually need a database — auth, app data — and let the form backend handle form delivery, spam filtering, and notifications. Fewer parts, fewer things to break.
Will telling Lovable to use a custom endpoint break the generated design?
No. You're only changing where the form sends data, not how it looks. Keep Lovable's generated markup and styling exactly as-is and change the submit handler to POST to the splitforms endpoint. The visual design, the Tailwind classes, the layout — all untouched. You can even paste the prompt in this guide directly into Lovable's chat and it will rewire the handler for you.
Is the access key safe to expose in a Lovable app's client code?
Yes. The splitforms access key is a public identifier, not a secret — it's designed to live in client-side code where anyone can view-source it. Abuse is prevented server-side with rate limiting, the Allowed Domains setting in the security tab, and an AI spam classifier. If a key ever gets abused, rotate it from the dashboard in one click and redeploy. This is the same model Formspree, Web3Forms, and every client-side form backend use.
Can I still send submissions to Slack, Discord, or Notion from Lovable?
Yes. Once the form posts to splitforms, every submission can fan out to email plus any number of signed webhooks in parallel — Slack, Discord, Notion, Google Sheets, Zapier, or your own server. You wire those destinations once in the splitforms dashboard, not in your Lovable app, so adding a new destination later doesn't require regenerating or redeploying the app.
Does this work with Lovable apps that are connected to Supabase already?
Yes. The two don't conflict. Keep Supabase for auth and app data; route the contact form to splitforms. You can even store a copy of each submission in Supabase via a splitforms webhook if you want everything in one database — but for a contact or lead form, most teams just let splitforms own it and skip the table entirely.
How do I redirect to a thank-you page after the Lovable form submits?
If you submit via fetch (the AJAX approach in this guide), handle the success response in JavaScript and navigate with your router — for a Lovable React app that's usually a React Router or Next navigation call to a /thanks route. If you use a plain HTML form post instead, add a hidden input named redirect with your thank-you URL and splitforms will 302 the browser there automatically.