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

  1. Get an access key — enter your email and click the confirmation link.
  2. Set your form's action to https://silentforms.com/api/submit and method to POST.
  3. 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.

contact.html
<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.

contact.js
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:

terminal
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.

ContactForm.jsx
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.

FieldDescription
accessKeyrequiredYour access key. Tells us which inbox receives the submission.
emailThe sender's email. Used as the Reply-To address so you can answer directly. Must be a valid address if present.
subjectCustom subject line for the notification email.
redirectAbsolute URL to send the visitor to after a successful plain HTML submission. Defaults to our thank-you page.
webhookUrlAbsolute URL that receives the submission as a JSON POST in addition to the email.
botcheckHoneypot. 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:

400Invalid submission: no fields, an invalid email, or a bad redirect / webhook URL.
401The accessKey field is missing.
403The access key is unknown, the form is paused, or the submission came from a domain the form does not allow.
413The submission is larger than 1 MB.
429Rate 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.
500Something 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.
  • webhookUrl must resolve to a public address. Private, loopback and link-local targets are refused.
  • Keep the botcheck honeypot 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 Origin header. A bare domain covers its subdomains. Requests with no Origin (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 webhookUrl and redirect fields 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 403 until 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