Guides

Lovable contact form that sends email via your SMTP

Lovable can send email through its built-in Emails feature or a Resend connection, and both need a domain you control and verify. If you’d rather send from a mailbox you already have, one fetch() call to FormSMTP does it.

Last updated November 2, 2026

How Lovable apps send email

RouteWhat you needCatches
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.comHourly caps per workspace, for example 100 app emails an hour on Pro. Some DNS providers can’t hold subdomain NS records.
Resend connectorA Resend account, an API key, and a domain verified in ResendResend’s test sender, onboarding@resend.dev, only delivers to the Resend account owner.
FormSMTPA mailbox with SMTP access, such as Gmail, Microsoft 365, Zoho or your host’s emailThe 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.

1Create a FormSMTP project

Sign up free 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, Microsoft 365, Zoho, 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.

2Lock 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.

3Paste 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.

4Or 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>
  );
}

5Publish 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). 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

Get a Founding Pass

10,000 emails a month, free for your first year. Pass No. 0008 is next: 1,993 of 2,000 left, no card.