Documentation
Last updated: August 21, 2026
Formgopher relays static-site form submissions to a real inbox: create a recipient, get an API key instantly, and paste a snippet into your form. No account or verification is required from whoever receives the mail.
Getting started
- Create a free account.
- Add a recipient: give it a name and the destination email that should receive submissions. A key is generated instantly, nothing is required from that inbox's owner.
- Copy one of the snippets below into your site's contact form, using the key shown on that recipient's Overview page.
- Optionally lock the key to your site's domain under Origin locking.
- Test-submit the live form, then check the recipient's Submissions log for a “delivered” entry.
Embed snippet
Every recipient has its own key and ready-made snippets on its Overview page in the dashboard. The examples below show the same two variants with a placeholder key.
Plain HTML form
<form action="https://app.formgopher.com/api/submit" method="POST">
<input type="hidden" name="access_key" value="fg_live_your_key_here">
<input type="text" name="name" placeholder="Your Name" required>
<input type="email" name="email" placeholder="Your Email" required>
<textarea name="message" placeholder="Your Message" required></textarea>
<input type="checkbox" name="_honeypot" style="display:none" tabindex="-1" autocomplete="off">
<!-- Optional: send visitors to your own page after they submit -->
<!-- <input type="hidden" name="redirect" value="https://yoursite.com/thanks"> -->
<button type="submit">Send</button>
</form>
fetch() form
<form id="contact-form">
<input type="text" name="name" placeholder="Your Name" required>
<input type="email" name="email" placeholder="Your Email" required>
<textarea name="message" placeholder="Your Message" required></textarea>
<input type="checkbox" name="_honeypot" style="display:none" tabindex="-1" autocomplete="off">
<button type="submit">Send</button>
</form>
<script>
document.getElementById('contact-form').addEventListener('submit', async function (e) {
e.preventDefault()
const form = e.target
const button = form.querySelector('button[type="submit"]')
const originalLabel = button.textContent
button.disabled = true
button.textContent = 'Sending…'
try {
const formData = new FormData(form)
formData.append('access_key', 'fg_live_your_key_here')
const response = await fetch('https://app.formgopher.com/api/submit', {
method: 'POST',
body: formData,
})
if (response.ok) {
alert('Message sent!')
form.reset()
} else {
const result = await response.json().catch(function () { return null })
alert((result && result.error) || 'Something went wrong. Please try again.')
}
} catch {
alert('Something went wrong. Please check your connection and try again.')
} finally {
button.disabled = false
button.textContent = originalLabel
}
})
</script>
File attachments
Warren-tier recipients can accept file uploads with a submission, like resumes, signed documents, or reference photos, sent straight through with the notification email.
Free plan: a form can still include a file input and submit normally; the submission itself isn't rejected. Every attached file is stripped before the email is sent, and the recipient gets the rest of the submission as usual, plus a short note that an attachment couldn't be delivered because file attachments require Warren.
Limits (Warren plan)
| Limit | Value |
|---|---|
| Files per submission | Up to 6 |
| Size per file | Up to 5MB |
| Combined size per submission | Up to 20MB |
If a submission exceeds either limit, the excess files, in the order they were selected, are stripped the same way as on the Free plan; the rest of the submission still delivers normally. Since files can be silently dropped this way, consider adding a client-side check (file count, running total size) before submit so visitors get instant feedback instead of finding out later that a file didn't arrive.
Sending files
A plain HTML form needs enctype="multipart/form-data" on the
<form> tag itself; a type="file" input on its
own isn't enough to switch the encoding. Building the fetch() variant
instead? Submit a FormData object as the body and don't set a
Content-Type header yourself; the browser sets the multipart boundary for
you.
The name on a file input doesn't matter to Formgopher, any file
part in the request is accepted regardless of field name. Add the
multiple attribute to let a visitor attach more than one file from a
single input. Whatever file input(s) you use must sit inside the
<form> element, carry a name attribute (browsers omit
unnamed controls when building the submission), and not be disabled at
submit time.
Plain HTML form
<form action="https://app.formgopher.com/api/submit" method="POST" enctype="multipart/form-data">
<input type="hidden" name="access_key" value="fg_live_your_key_here">
<input type="text" name="name" placeholder="Your Name" required>
<input type="email" name="email" placeholder="Your Email" required>
<textarea name="message" placeholder="Your Message" required></textarea>
<input type="file" name="attachments" multiple>
<input type="checkbox" name="_honeypot" style="display:none" tabindex="-1" autocomplete="off">
<button type="submit">Send</button>
</form>
fetch() form
<form id="contact-form">
<input type="text" name="name" placeholder="Your Name" required>
<input type="email" name="email" placeholder="Your Email" required>
<textarea name="message" placeholder="Your Message" required></textarea>
<input type="file" name="attachments" multiple>
<input type="checkbox" name="_honeypot" style="display:none" tabindex="-1" autocomplete="off">
<button type="submit">Send</button>
</form>
<script>
document.getElementById('contact-form').addEventListener('submit', async function (e) {
e.preventDefault()
const form = e.target
const button = form.querySelector('button[type="submit"]')
const originalLabel = button.textContent
button.disabled = true
button.textContent = 'Sending…'
try {
const formData = new FormData(form)
formData.append('access_key', 'fg_live_your_key_here')
const response = await fetch('https://app.formgopher.com/api/submit', {
method: 'POST',
body: formData,
})
if (response.ok) {
alert('Message sent!')
form.reset()
} else {
const result = await response.json().catch(function () { return null })
alert((result && result.error) || 'Something went wrong. Please try again.')
}
} catch {
alert('Something went wrong. Please check your connection and try again.')
} finally {
button.disabled = false
button.textContent = originalLabel
}
})
</script>
Delivered attachments stay downloadable from the dashboard's File Manager for 90 days after submission, then are automatically deleted.
API reference
https://app.formgopher.com/api/submit
Accepts application/json, application/x-www-form-urlencoded,
or multipart/form-data (required only when attaching files; see
File attachments).
Fields
| Field | Required | Notes |
|---|---|---|
access_key |
Required | The recipient's API key. |
name |
Optional | Included in the notification email. |
email |
Optional | Used as the reply-to address, so replying to the notification goes straight to the visitor. |
message |
Optional | Included in the notification email. |
subject |
Optional | Sets the notification email's subject line (default: “New submission from {recipient name}”). Also included in the email body like any other field. |
_honeypot |
Optional | Leave this field empty in your form. If it arrives non-empty, the submission is treated as spam and silently dropped. |
redirect |
Optional | Where to send the visitor after a plain HTML form POST. Must match one of the recipient's allowed origins; if the recipient has no origins configured yet, it must match the domain the request came from instead. A non-matching URL falls back to a built-in “message sent” page. |
(file inputs) |
Optional | Multipart only, any field name. Warren plan only, up to 6 files per submission (5MB each, 20MB combined); see File attachments. |
| Any other field | Optional | Forwarded as-is into the notification email. |
Response
{
"success": true
}
{
"success": false,
"error": "Origin not allowed for this key"
}
A plain HTML form POST (no JavaScript) instead receives an HTML redirect to your
redirect URL, or a built-in “message sent” page, rather than
JSON.
Error codes
| Status | Meaning |
|---|---|
| 400 | Missing access_key. |
| 404 | Invalid access_key. |
| 403 | Origin not allowed, recipient inactive, plan's monthly submission limit reached, or blocked as possible spam. |
| 429 | Rate-limited: more than 20 requests/minute from the same visitor to this key. Scoped per visitor, so one abusive visitor can't exhaust the budget for everyone else submitting through the same key. |
| 502 | Delivery to the recipient's inbox failed. |
Spam protection (Warren plan)
Warren-tier recipients get a lightweight spam check on every submission, looking at things like suspicious links, spam-like keywords, and bursts of submissions from the same visitor. Per recipient, you choose whether flagged submissions still get delivered and are just tagged in the log (“flag”, the default), or are dropped before ever being sent (“block”). Free-tier recipients skip this check entirely.
Auto-confirmation emails (Warren plan)
Warren-tier recipients can send a second email straight back to the visitor confirming their message got through, so they get an immediate “we got it” instead of silence while they wait on a reply.
Enabling it
Auto-confirmation is off by default and toggled per recipient, from that recipient's settings in the dashboard. Turning it on for one recipient doesn't touch any others on the account, so you can enable it for one client's site without changing how the rest of your forms behave.
Subject & message
Both fields are optional. Leave either blank and Formgopher fills in a default:
| Field | Default |
|---|---|
| Subject | “Thanks for reaching out to {Business Name}.” |
| Message | “Thanks for reaching out! We've received your message and will get back to you soon.” |
Every confirmation email also carries a fixed disclaimer noting that it's automated and shouldn't be replied to. This can't be turned off or edited, so a visitor never mistakes it for a real reply from the recipient.
If a submission gets flagged by spam protection, the confirmation email is skipped, even though the primary notification to the recipient still goes through as normal, keeping Formgopher's sending reputation clean by not emailing addresses likely to belong to a bot. The recipient's Submissions log shows whether each confirmation sent successfully or bounced, which doubles as a handy signal that a visitor may have mistyped their own email address.
Auto-confirmation is a distinct, Formgopher-generated email, not a relay of the recipient's own autoresponder or out-of-office reply; that would require inbound email infrastructure Formgopher doesn't have. A fixed, editable confirmation keeps the feature simple and fully within Formgopher's control.
Origin locking
By default, a key works from any site that submits to it. Adding one or more domains to a recipient's allowed origins restricts it to only accept submissions whose request origin matches, useful once a client's site is live. This is a separate, optional step from creating the recipient, so you can generate a key before a site even has a domain.
Plans & limits
| Plan | Recipients | Submissions/mo | CC emails | Attachments | Spam detection | Auto-confirmation | Price |
|---|---|---|---|---|---|---|---|
| Free | 5 | ~150 | None | None | None | None | $0 |
| Warren | Unlimited | ~5,000 | Up to 4 per recipient | Up to 6 files/submission, 5MB each (20MB total) | Flag or block, per recipient | Optional, per recipient | $12/mo or $120/yr |