# Lovable contact form that sends email via your SMTP

> Make a Lovable app send email through Gmail, Microsoft 365 or any SMTP: a ready-to-paste prompt, the React code, and fixes for when Lovable isn’t sending email.

Source: https://formsmtp.com/guides/lovable-contact-form · Last updated November 2, 2026

## How Lovable apps send email

| Route | What you need | Catches |
| --- | --- | --- |
| **Lovable Emails** (built in) | A paid Lovable plan, and a domain whose DNS can hold `NS` records for a sending subdomain such as `notify.yourdomain.com` | Hourly caps per workspace, for example 100 app emails an hour on Pro. Some DNS providers can’t hold subdomain `NS` records. |
| **Resend connector** | A Resend account, an API key, and a domain verified in Resend | Resend’s test sender, `onboarding@resend.dev`, only delivers to the Resend account owner. |
| **FormSMTP** | A mailbox with SMTP access, such as Gmail, Microsoft 365, Zoho or your host’s email | The form endpoint only emails you. Emailing your users needs the server route below. |

## Why your Lovable app isn’t sending email

- **It uses Resend’s test sender.** `onboarding@resend.dev` only reaches the Resend account owner, so tests to yourself work and everyone else gets nothing.
- **The sending domain isn’t verified.** The Resend connector doesn’t set up DNS for you, and Lovable Emails needs its `NS` records in place at your DNS provider before it can send.
- **A secret is missing.** Open the function and click *View logs* to see each failed run’s error.
- **You’re on the free plan.** Lovable Emails is for paid plans only.
- **An hourly limit was reached.** Lovable rejects new emails until the hour resets.

For a contact form, there’s a shorter path: the browser posts the form to FormSMTP, and FormSMTP sends it through your own SMTP account. No edge function, no domain verification.

## 1. Create a FormSMTP project

[Sign up free](https://app.formsmtp.com/#/signup) and add a project. Pick your email provider so the SMTP host and port fill in, enter your login, and click *Test connection*. Any mailbox with SMTP access works: [Gmail or Google Workspace](https://formsmtp.com/guides/gmail-smtp-contact-form), [Microsoft 365](https://formsmtp.com/guides/outlook-smtp-contact-form), [Zoho](https://formsmtp.com/guides/zoho-smtp-contact-form), your web host’s mailbox, or a sending service. Personal Outlook.com and Hotmail addresses can’t be used, because Microsoft only lets them sign in with OAuth.

## 2. Lock it to your site

In *Settings → Security*, set **Allowed websites** to your published Lovable address and any custom domain, each written out in full: `https://your-app.lovable.app` and `https://yourdomain.com`, and **Allowed recipients** to your own address. Then copy the **form key** from *API & code*. It starts with `fk_`, it only works on the form endpoint, and it can only email you, so it’s safe in your page.

The editor preview runs on a different address than your published app. Test on the published URL, or add the address shown in the error to Allowed websites while you test.

## 3. Paste this prompt into Lovable

Replace `fk_YOUR_FORM_KEY` with your form key and send this in the project chat:

```text
Add a contact form that emails submissions to me through FormSMTP.

- Add VITE_FORMSMTP_FORM_KEY=fk_YOUR_FORM_KEY to the .env file. It's a
  public key made for browser code, so it belongs in .env, not in Secrets.
- Create a ContactForm component with name, email and message fields,
  using the project's existing UI components and styles.
- On submit, POST JSON to
  https://app.formsmtp.com/api/v1/form/ + VITE_FORMSMTP_FORM_KEY
  with the headers Content-Type: application/json and
  Accept: application/json. Include the fields plus
  _subject: "New message from my Lovable app" and
  _t: the time the form first rendered (Date.now()).
- Add a hidden honeypot input named _gotcha (display none, tabIndex -1,
  autoComplete off) and send its value with the rest.
- The response is { success: true } or { success: false, error }.
  Disable the button while sending, show a thank-you message on success
  and the error text on failure.
- Don't add an edge function or any backend code for this. The browser
  posts directly to FormSMTP.
- Put the form in the contact section of the home page.
```

Lovable keeps `VITE_` values in `.env`, where they’re built into the browser code, and it refuses to store them as Secrets. That’s fine for a form key, which is public by design.

## 4. Or add the component yourself

If you edit code directly, or want to check what Lovable wrote, this is the whole component:

```tsx
// src/components/ContactForm.tsx
import { useRef, useState, type FormEvent } from "react";

const ENDPOINT = "https://app.formsmtp.com/api/v1/form/" + import.meta.env.VITE_FORMSMTP_FORM_KEY;

export default function ContactForm() {
  const [status, setStatus] = useState<"idle" | "sending" | "sent" | "error">("idle");
  const [error, setError] = useState("");
  const loadedAt = useRef(Date.now());

  async function handleSubmit(e: FormEvent<HTMLFormElement>) {
    e.preventDefault();
    const fields = Object.fromEntries(new FormData(e.currentTarget));
    setStatus("sending");
    try {
      const res = await fetch(ENDPOINT, {
        method: "POST",
        headers: { "Content-Type": "application/json", Accept: "application/json" },
        body: JSON.stringify({ ...fields, _subject: "New message from my Lovable app", _t: loadedAt.current }),
      });
      const data = await res.json();
      if (!data.success) throw new Error(data.error);
      setStatus("sent");
    } catch (err) {
      setError(err instanceof Error ? err.message : "Something went wrong");
      setStatus("error");
    }
  }

  if (status === "sent") return <p>Thanks! We'll be in touch soon.</p>;

  return (
    <form onSubmit={handleSubmit}>
      <input name="name" placeholder="Name" autoComplete="name" required />
      <input name="email" type="email" placeholder="Email" autoComplete="email" required />
      <textarea name="message" placeholder="Message" rows={5} required />
      <input name="_gotcha" style={{ display: "none" }} tabIndex={-1} autoComplete="off" />
      <button disabled={status === "sending"}>{status === "sending" ? "Sending…" : "Send"}</button>
      {status === "error" && <p role="alert">{error}</p>}
    </form>
  );
}
```

## 5. Publish and test

`VITE_` values are read when the app is built, so publish again after changing `.env`. Then submit the form on your published URL. The message arrives from your own address and shows up under *Logs* in FormSMTP.

## When you need server-side code instead

The form endpoint only ever emails you. Use the send API with your secret API key (`fs_`) when the app emails someone else, such as an order confirmation to a customer, or when you want HTML templates or attachments. That call must run on a server: an edge function on older React + Vite projects with Lovable’s built-in backend, or the app’s own server code on TanStack Start, Lovable’s default for new projects since May 13, 2026.

```text
When an order is placed, send the customer a confirmation email through
the FormSMTP API.

- Do this in server code, never in the browser.
- Ask me for the API key as a secret named FORMSMTP_API_KEY.
- POST https://app.formsmtp.com/api/v1/send with the header
  Authorization: Bearer FORMSMTP_API_KEY and a JSON body with
  to, subject, html and replyTo.
- Take the recipient and the order details from the order record in
  the database, not from the request body.
- The response is { success: true, messageId } or
  { success: false, error }. Log the error if it fails.
```

Let Lovable ask for the key through its secure secret input rather than pasting it into the chat. Use a separate FormSMTP project for this, with a sensible *Max API emails per hour*, so your contact form project stays locked to your own inbox.

Never build a function that takes the recipient and message straight from the request. Anyone could call it to send email from your address.

## Troubleshooting

- **“This form isn’t allowed on …”.** Add that exact address to *Allowed websites*. The preview and the published app have different addresses.
- **“Invalid form key”.** The key didn’t make it into the build. Check that `VITE_FORMSMTP_FORM_KEY` is in `.env`, then publish again.
- **“That is a form key”.** Server code is calling `/send` with the `fk_` key. Use the API key there.
- **“Recipient not allowed for this project”.** The server route emails customers, but *Allowed recipients* is set to your address. Use a separate project for customer email.

## FAQ

### Do I need Lovable Cloud or Supabase for a contact form?

No. The browser posts straight to FormSMTP, so the form works without any backend. You only need server code to email people other than yourself.

### Is it safe to put the form key in .env?

Yes. Lovable builds `VITE_` values into the browser code, so they must be safe to be public, and the form key is: it can only email you. Never put the `fs_` API key there.

### Can I keep using Resend?

Yes. Connect Resend as the SMTP server in your FormSMTP project (see the [Resend SMTP guide](https://formsmtp.com/guides/resend-smtp-contact-form)). You still need a domain verified in Resend to email anyone but yourself.

### Can the visitor get a confirmation email?

Not from the form endpoint: it only emails you, so a copied form key can’t be used to spam other people. To email the visitor, call the send API with your API key from server code (see “When you need server-side code” above).

## More guides

- [Webflow form to email: custom action with your SMTP](https://formsmtp.com/guides/webflow-contact-form)
- [Framer form to email: webhook or code component](https://formsmtp.com/guides/framer-contact-form)
- [Carrd contact form that emails you from your address](https://formsmtp.com/guides/carrd-contact-form)
- [Bolt.new contact form that sends email (no backend)](https://formsmtp.com/guides/bolt-contact-form)
