Connect any HTML form to your inbox
SilentForms is a single HTTP endpoint: https://silentforms.com/api/submit. Post a form to it with your access key and the submission is emailed to you. This page lists everything the endpoint accepts and returns, with copy-paste examples for plain HTML, JavaScript and React.
Quick start
- Get an access key — enter your email and click the confirmation link.
- Set your form's
actiontohttps://silentforms.com/api/submitandmethodtoPOST. - Add the hidden access key input:
<input type="hidden" name="accessKey" value="YOUR_ACCESS_KEY_HERE">Every input needs a name attribute. All fields except the special ones below are included in the email exactly as submitted.
HTML form
The simplest integration: no JavaScript at all. After a successful submission the visitor is redirected to our thank-you page, or to your own via the redirect field.
<form action="https://silentforms.com/api/submit" method="POST">
<!-- Your access key: tells us which inbox gets the submission -->
<input type="hidden" name="accessKey" value="YOUR_ACCESS_KEY_HERE">
<!-- Any inputs you like. Each one needs a name="" attribute -->
<input type="text" name="name" required>
<input type="email" name="email" required>
<textarea name="message" required></textarea>
<!-- Honeypot: hidden from people, filled in by bots -->
<input type="checkbox" name="botcheck" style="display: none;" tabindex="-1" autocomplete="off">
<!-- Optional: send visitors to your own thank-you page -->
<!-- <input type="hidden" name="redirect" value="https://example.com/thanks.html"> -->
<button type="submit">Send</button>
</form>JavaScript / fetch
Send JSON with the Content-Type: application/json and Accept: application/json headers and you get a JSON response instead of a redirect. Cross-origin requests are allowed from any site.
const form = document.getElementById('contact-form');
form.addEventListener('submit', async (event) => {
event.preventDefault();
const data = Object.fromEntries(new FormData(form));
data.accessKey = 'YOUR_ACCESS_KEY_HERE';
const response = await fetch('https://silentforms.com/api/submit', {
method: 'POST',
headers: { 'Content-Type': 'application/json', Accept: 'application/json' },
body: JSON.stringify(data),
});
const result = await response.json();
if (result.status === 'success') {
form.reset();
console.log('Sent!');
} else {
console.error(result.message);
}
});Or from the command line:
curl -X POST https://silentforms.com/api/submit \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{"accessKey":"YOUR_ACCESS_KEY_HERE","name":"Jane","email":"[email protected]","message":"Hello!"}'React
The same JSON request wrapped in a component. Works in Next.js, Remix, Vite, Astro islands or plain React.
import { useState } from 'react';
export default function ContactForm() {
const [status, setStatus] = useState('');
async function handleSubmit(event) {
event.preventDefault();
setStatus('Sending…');
const data = Object.fromEntries(new FormData(event.target));
data.accessKey = 'YOUR_ACCESS_KEY_HERE';
const response = await fetch('https://silentforms.com/api/submit', {
method: 'POST',
headers: { 'Content-Type': 'application/json', Accept: 'application/json' },
body: JSON.stringify(data),
});
const result = await response.json();
setStatus(result.status === 'success' ? 'Thanks, we got it!' : result.message);
}
return (
<form onSubmit={handleSubmit}>
<input type="text" name="name" required />
<input type="email" name="email" required />
<textarea name="message" required />
<button type="submit">Send</button>
<p>{status}</p>
</form>
);
}Special fields
These field names have a meaning to SilentForms. Anything else is passed through to your email.
| Field | Description |
|---|---|
accessKeyrequired | Your access key. Tells us which inbox receives the submission. |
email | The sender's email. Used as the Reply-To address so you can answer directly. Must be a valid address if present. |
subject | Custom subject line for the notification email. |
redirect | Absolute URL to send the visitor to after a successful plain HTML submission. Defaults to our thank-you page. |
webhookUrl | Absolute URL that receives the submission as a JSON POST in addition to the email. |
botcheck | Honeypot. Keep it hidden and empty; if a bot fills it the submission is silently dropped. honeypot is accepted as an alias. |
Responses & redirects
The endpoint accepts application/x-www-form-urlencoded, multipart/form-data and application/json bodies.
If the request sends JSON or includes Accept: application/json, the response is JSON:
{
"status": "success",
"message": "Form submitted successfully."
}Otherwise (a plain HTML form post) the visitor is redirected with a 303 to https://silentforms.com/success, or to the URL in your redirect field. Failed submissions redirect to https://silentforms.com/error with the reason in the query string.
Error codes
Errors return { "status": "error", "message": "…" } with one of these HTTP status codes:
400 | Invalid submission: no fields, an invalid email, or a bad redirect / webhook URL. |
401 | The accessKey field is missing. |
403 | The access key is unknown, the form is paused, or the submission came from a domain the form does not allow. |
413 | The submission is larger than 1 MB. |
429 | Rate limit hit: more than 10 submissions per minute from one IP, or more than 60 per minute for one access key. The Retry-After header says how long to wait. |
500 | Something failed on our side. Try again in a moment. |
Limits & spam protection
- Submissions are unlimited and free. There are no monthly quotas.
- Each IP address may submit up to 10 times per minute, and each access key accepts up to 60 submissions per minute.
- Bodies larger than 1 MB are rejected. File uploads are not supported.
webhookUrlmust resolve to a public address. Private, loopback and link-local targets are refused.- Keep the
botcheckhoneypot in your form. Bots fill every field they find; real visitors never see it. - Values are delivered exactly as submitted. They are HTML-escaped in the notification email and sent verbatim as JSON to webhooks, so nothing you collect is altered.
Dashboard settings
Signing in is optional — every form works without it. When you do, the dashboard adds per-form settings that override what your HTML sends, so they can stay off your public pages:
- Allowed domains. Submissions are accepted only from domains you list, checked against the
Originheader. A bare domain covers its subdomains. Requests with noOrigin(curl, server-to-server) still pass unless you switch that off too. - Key rotation. A new key takes effect immediately while the old one keeps working for 24 hours, so a live page never breaks mid-deploy. Revoke on the spot if a key has leaked.
- Webhook and redirect. Saved server-side, they replace the
webhookUrlandredirectfields entirely. - Email notifications. Turn the per-submission email off and read submissions in the dashboard instead.
- Pause. A paused form rejects every submission with a
403until you resume it.
Ready to connect your form?
Get a free access key and paste it into the example above.
Get your free access key