Set up with an AI agent
Hand the integration to your AI coding agent. Paste one prompt, and it adds the form code to your website the way FormRoute expects.
The prompt
Paste this into a coding agent that can read web pages and work in your website's code:
Follow the instructions verbatim at https://form-route.com/agent-setup.md to connect my website's form to FormRoute. My FormRoute site key is fr_your_site_key. The fields set up in FormRoute are: name (required, up to 120 characters), email (required, up to 254 characters), message (required, up to 5000 characters).The prompt holds no secret: the site key identifies your form and is meant to be public.
Your own prompt, filled in
Your FormRoute account writes the prompt for you, with your site key and your form's fields. On your Overview page, under Send from your website, open the AI agent tab and copy it.
What the agent does
- Finds your form, or creates one if there isn't one.
- Matches the inputs' names to the fields set up in FormRoute.
- Adds the hidden honeypot input.
- Sends each submission from the visitor's browser, with the time the form was open, and shows a sent or error message.
- Tells you which website addresses to allow, any field changes to make, and how to test.
It is told not to proxy submissions through a server, not to use your host's own form handling, not to add a CAPTCHA or tracking scripts, and not to send test submissions without asking you.
What's left for you
- Add the addresses the agent lists under Allowed websites in your form's settings. An agent can't change your FormRoute account.
- Review the change like any other, then publish it.
- Send one real submission from your website and check Recent submissions. Testing and troubleshooting explains every result.
The instructions
The agent reads form-route.com/agent-setup.md, a plain Markdown file written for it. If your agent can't open web pages, paste the instructions into the conversation instead:
Show the full instructions
# Connect a website form to FormRoute
These instructions are for an AI coding agent working in a website's codebase on behalf of a FormRoute customer. Follow them in order. Do not skip steps, and do not improvise a different integration.
FormRoute is a form-to-email service. The website's form posts JSON from the visitor's browser to one endpoint; FormRoute checks the submission and emails it to the customer from the customer's own domain. FormRoute does not store what visitors write.
## 0. Inputs
The customer's prompt gives you:
- **Site key**: starts with `fr_`. It identifies the form. It is public by design: it may appear in client-side code and in public environment variables. It is not a secret.
- **Fields**: the field names set up in FormRoute, and which are required.
If the site key is missing or still `fr_your_site_key`, stop and ask the customer for it; it is on the Overview page of their FormRoute account. Never invent a site key.
## 1. The contract
```
POST https://form-route.com/api/v1/send
Content-Type: application/json
```
JSON body:
- `siteKey` (string, required): the site key.
- `t` (integer, required): milliseconds between the form being shown to the visitor and the submission being sent. Measure it in the browser. A missing value, or one under 1200, is treated as automated and refused with `captcha_required`. Never send a constant or made-up value.
- `hp` (string): the value of the hidden honeypot input. It must be empty for real visitors. Any other value makes FormRoute silently drop the submission.
- One key per configured field, with the input's value as a string. Send optional fields even when they are empty (`""`). Keys must match the configured field names exactly (case-sensitive). Keys FormRoute doesn't know are dropped. These names are reserved and must not be used as field names: siteKey, siteId, hp, t, captchaToken, recipient.
The body must be under 20 KB. FormRoute carries text only: no file uploads.
Responses are JSON with a `message`. Treat any 2xx as success (`200 {"message":"sent","id":"…"}`). Anything else is a refusal; show the visitor a short, friendly error and let them try again.
## 2. Find the form
Search the codebase for the form the customer means (contact, quote request or booking request). If there are several and the prompt doesn't say which, ask. If there is none, create one with the configured fields, in the site's existing style.
If the form already sends somewhere (a `mailto:` action, a server action, an API route, a mail plugin), replace that path with FormRoute and remove the old handler if nothing else uses it. Don't uninstall packages or delete environment variables yourself: tell the customer which ones are now unused.
Detect the stack (plain HTML, Next.js, React, Vue, Astro, WordPress theme, Webflow or Framer export, Hugo, Jekyll, Eleventy, …). The platform guides show the exact pattern for each:
- HTML: https://form-route.com/guides/html
- Next.js: https://form-route.com/guides/nextjs
- React: https://form-route.com/guides/react
- Vue: https://form-route.com/guides/vue
- Astro: https://form-route.com/guides/astro
- WordPress: https://form-route.com/guides/wordpress
- Webflow: https://form-route.com/guides/webflow
- Framer: https://form-route.com/guides/framer
- Hugo, Jekyll and Eleventy: https://form-route.com/guides/static-site-generators
If a guide doesn't load, carry on: the reference implementation in step 5 is enough.
## 3. Match the field names
Every configured field must have an input whose `name` is exactly that field name. Only the `name` attribute matters to FormRoute: keep the site's own markup, labels, ids and classes. Rename inputs in the code to match; do not ask the customer to rename fields in FormRoute unless the form clearly needs different fields. In that case, finish the code and tell the customer which fields to add or change in their FormRoute account under their form's settings, and which should be required.
For the visitor's benefit, add `required` to the fields the prompt marks as required, and `maxlength` with the exact limit the prompt gives ("up to N characters"). If the prompt gives no limit for a field, add no `maxlength`. FormRoute enforces its own rules either way.
## 4. Add the honeypot
Add this input inside the form, unchanged. It must stay hidden from people and from browser autofill:
```html
<div aria-hidden="true" style="position:absolute;left:-10000px">
<label>Leave this empty <input name="hp" tabindex="-1" autocomplete="off"></label>
</div>
```
## 5. Send from the browser
Intercept the form's submit event, prevent the default navigation, and `fetch` the endpoint from the browser. Start the timer when the form is shown (page load, or component mount in a framework), not when the visitor clicks Send.
Reference implementation for plain HTML, with the fields name, email and message. Adapt the field lines to the configured fields and the selectors to the real form:
```html
<form id="contact-form">
<label>Name <input name="name" required maxlength="120"></label>
<label>Email <input name="email" type="email" required maxlength="254"></label>
<label>Message <textarea name="message" required maxlength="5000"></textarea></label>
<!-- Honeypot: hidden from people, must stay empty -->
<div aria-hidden="true" style="position:absolute;left:-10000px">
<label>Leave this empty <input name="hp" tabindex="-1" autocomplete="off"></label>
</div>
<button type="submit">Send</button>
<p id="contact-status" role="status"></p>
</form>
<script>
const ENDPOINT = "https://form-route.com/api/v1/send";
const SITE_KEY = "fr_your_site_key"; // from your FormRoute account
const form = document.querySelector("#contact-form");
const status = document.querySelector("#contact-status");
const shownAt = Date.now();
form.addEventListener("submit", async (event) => {
event.preventDefault();
const data = new FormData(form);
const button = form.querySelector("button[type=submit]");
button.disabled = true;
status.textContent = "Sending…";
try {
const response = await fetch(ENDPOINT, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
siteKey: SITE_KEY,
t: Date.now() - shownAt, // ms since the form was shown
hp: data.get("hp") ?? "", // honeypot, empty for people
name: data.get("name"),
email: data.get("email"),
message: data.get("message"),
}),
});
if (response.ok) {
form.reset();
status.textContent = "Thanks, your message was sent.";
} else {
status.textContent = "Sorry, that didn't go through. Please try again.";
}
} catch {
status.textContent = "Sorry, that didn't go through. Please try again.";
} finally {
button.disabled = false;
}
});
</script>
```
In a framework, build the same thing as a client-side component (see the platform guide). You may reword the two visitor messages in the site's own voice; keep the request, the timer, the honeypot and the success check exactly as shown.
## 6. Do not
- Do not send submissions from a server, API route, serverless function or server action. FormRoute's rate limits count the sender's IP address, so a server would make every visitor share one limit. The request must come from the visitor's browser.
- Do not use the hosting platform's own form handling (Netlify Forms, Webflow's form block, Framer's form element) for this form; they cannot send the values FormRoute needs.
- In Framer, do not use the Embed element: it runs in an iframe whose requests FormRoute cannot match to the customer's website. Use a code component.
- Do not add a CAPTCHA widget, analytics, or any other third-party script for this.
- Do not remove or rename the honeypot, and do not fake `t`.
- Do not store the site key as a server secret. A constant in client code is simplest. If you use a public environment variable (`NEXT_PUBLIC_`, `VITE_`, `PUBLIC_`), tell the customer to set it in their host's environment settings too, because local files such as `.env.local` are not deployed.
## 7. Allowed websites
FormRoute only accepts browser submissions from origins the customer has listed in their account (up to 10). An origin is scheme + host + port, matched exactly: `https://example.com` and `https://www.example.com` are different.
You cannot change this setting. Work out every origin the form will be served from: production, with and without `www` if both answer, and any staging or preview hosts you find in the code or its config. List them for the customer, telling them to add them under **Allowed websites** in their form's settings in FormRoute. Allowed websites has no wildcards: for preview deployments whose address changes (such as `*.vercel.app`), tell the customer to add one specific preview address only while they test on it. If you can't tell whether the site also answers with or without `www`, list that origin as conditional. List a local development origin separately and mark it optional: it uses one of the 10 slots, and `localhost` and `127.0.0.1` are different origins, so give the exact address the dev server prints.
## 8. Test
Do not send test submissions yourself unless the customer asks: each one emails the customer's real inbox. Instead, tell the customer to:
1. Open the page in a browser on an allowed origin.
2. Fill the form in at a human pace and send it.
3. Check **Recent submissions** on their FormRoute Overview page. It shows **Sent**, or the reason it was refused.
If it was refused:
- **Rejected: website not allowed**: the page's origin isn't listed (step 7).
- **Rejected: invalid fields**: a required field was empty, a name doesn't match, or a value is too long (step 3).
- **CAPTCHA required**: `t` was missing or too small (step 5).
- **Filtered as spam**: `hp` had a value (step 4).
- **Rate limited**: too many test sends in a minute; wait and retry.
- **Not sent: account cannot deliver**: the subscription isn't active or the sending domain isn't verified; the customer fixes that in their account.
## 9. Report back
When you are done, tell the customer:
1. Which files you changed or created.
2. The exact origins to add under Allowed websites.
3. Any fields to add or change in FormRoute.
4. How to test (step 8).
Reference: https://form-route.com/docs/api · Support for the customer: [email protected]