FORMS · Published 2026-10-06 · Updated 2026-10-06 · SecureSMTP

Forms API

Send submissions from your own form to SecureSMTP: notification emails, auto-replies and spam checks.

Use POST /forms/submit when you have your own form and want SecureSMTP to store each submission, check it for spam and email it to you. It works with any site or framework.

Call the API from your server, never from the browser. The request needs your API key, and anyone who sees the key can send email as your site.

How it works

  1. The visitor’s browser posts the form to your server.
  2. Your server calls POST /forms/submit with the key, the field values and the visitor’s IP address.
  3. SecureSMTP checks the submission, stores it and emails it to you.
  4. Your server shows the visitor a thank-you message.

Full example

The form posts to your own /contact route:

HTML
<form method="post" action="/contact">
  <label>Name <input name="name" required></label>
  <label>Email <input name="email" type="email" required></label>
  <label>Message <textarea name="message" required></textarea></label>
  <button type="submit">Send</button>
</form>

Your server forwards it to SecureSMTP:

// server.mjs — run with: npm install express && node server.mjs
import express from 'express';

const app = express();
app.use(express.urlencoded({ extended: false }));

app.post('/contact', async (req, res) => {
  const field = (name) => String(req.body[name] ?? '').slice(0, 10000);

  const r = await fetch('https://securessmtp.com/api/v1/forms/submit', {
    method: 'POST',
    headers: {
      'x-securessmtp-api-key': process.env.SECURESSMTP_API_KEY,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      form_id: 1,
      form_name: 'Contact',
      fields: { name: field('name'), email: field('email'), message: field('message') },
      visitor: {
        ip: req.ip ?? '',
        user_agent: (req.get('user-agent') ?? '').slice(0, 500),
        referrer: (req.get('referer') ?? '').slice(0, 500),
      },
    }),
  });
  const result = await r.json();

  if (!r.ok) {
    console.error('SecureSMTP error', r.status, result.error, result.details);
    return res.status(500).send('Sorry, something went wrong. Please try again later.');
  }
  // 200: stored, blocked or held as spam. Show every visitor the same message.
  res.send('Thanks. We got your message.');
});

app.listen(3000);
Set SECURESSMTP_API_KEY in the server environment before you start it.

If your server runs behind a proxy or load balancer, make sure the IP you send is the visitor’s, not the proxy’s (in Express, set trust proxy). The IP limit and IP blocks only work when you send it.

Request fields

form_idintegerrequired
A positive whole number that names the form on your side. Use a different number for each form.
form_namestringoptional
Up to 200 characters. Used in the notification subject: “New form_name submission — site domain”. Default Form.
fieldsobjectoptional
Field key to value. Every value must be a string of up to 10,000 characters. Keys are shown as labels in the email (first_name becomes “First Name”).
notify_emailsstring[]optional
Who gets the notification. Up to 10 entries; the first 3 valid addresses are used. If none is valid, notify_email is used, then the site’s notify email.
notify_emailstringoptional
A single recipient. Older form of notify_emails.
from_namestringoptional
Sender name of the notification, up to 120 characters. Default: the site’s from name, then the site name.
visitorobjectoptional
ip (up to 64), user_agent (up to 500), referrer (up to 500). Send ip: the IP limit and blocks use it. Longer values are rejected, so cut them first.
captcha_tokenstringoptional
The captcha token from the visitor’s browser, up to 6,000 characters. See Spam protection.
captcha_providerstringoptional
Which widget you showed. A hint only: the site’s saved captcha setting decides how the token is checked.
confirmobjectoptional
Auto-reply to the visitor: enabled (boolean), subject (up to 200), body (HTML, up to 20,000), from_name. See below.
site_urlstringoptional
The page’s site URL. Must be a full URL such as https://example.com when sent.
templatestringoptional
A label of up to 50 characters, stored with the submission.

Name your email field email. SecureSMTP looks for it to set the notification’s Reply-To and to send the auto-reply. If there is no such key, the first value that looks like an email address is used.

Captcha

Show the captcha widget set for your site, read its token from the posted form and pass it on. With the Turnstile example, the token arrives in the cf-turnstile-response field:

Node.js
body: JSON.stringify({
  form_id: 1,
  form_name: 'Contact',
  fields: { name: field('name'), email: field('email'), message: field('message') },
  visitor: { ip: req.ip ?? '' },
  captcha_token: String(req.body['cf-turnstile-response'] ?? ''),
}),
  • If the captcha provider rejects the token, the answer is { "ok": true, "blocked": true } and the submission is not stored.
  • A submission sent without a token is not checked by the captcha. Always pass the token.
  • If the provider cannot be reached, the submission goes through.

Which widget to show and which field holds the token: Spam protection.

Auto-reply

Add confirm to send the visitor an email. It goes out only if enabled is true, the submission contains an email address, the submission is not classified as spam and the notification was not skipped for the monthly limit. It is never sent to a suppressed address or to someone who unsubscribed from your site. Replies go to the first notification recipient.

curl -X POST https://securessmtp.com/api/v1/forms/submit \
  -H "x-securessmtp-api-key: $SECURESSMTP_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "form_id": 1,
  "form_name": "Contact",
  "fields": {
    "name": "Ana Silva",
    "email": "[email protected]",
    "message": "Do you deliver on Saturdays?"
  },
  "notify_emails": [
    "[email protected]"
  ],
  "visitor": {
    "ip": "203.0.113.7"
  },
  "confirm": {
    "enabled": true,
    "subject": "Thanks for contacting {site_name}",
    "body": "<p>Hi {name},</p><p>We got your message and will reply soon.</p>",
    "from_name": "Example Support"
  }
}'

Placeholders work in the subject and the body:

PlaceholderReplaced with
{name}The first non-empty value of a field called name, full_name, first_name, your-name or similar; otherwise the first field whose key contains “name”.
{email}The submitter’s email address (see below).
{form_name}The form name.
{site_name}The site’s display name.
{site_domain}The site’s domain, for example example.com.
{field:KEY}The value of the field with key KEY, for example {field:phone}.

The auto-reply is sent after the response, so the response does not say whether it went out. If the subject is empty it is “Thanks — site name”.

Responses

Every outcome that the visitor should see as a success comes back as HTTP 200:

ResultBodyWhat happened
Sent{"ok":true,"id":"…","email_sent":true}Stored and emailed.
Email not sent{"ok":true,"id":"…","email_sent":false}Stored. The email failed, or your plan’s monthly form-submission limit is used up.
Spam{"ok":true,"id":"…"}Stored as spam. No email. There is no email_sent field.
Blocked{"ok":true,"blocked":true}Not stored, no email. The captcha token was rejected, the IP is blocked, or the IP went over the limit.

id is the submission’s ID (a UUID). Show the visitor the same message for all four, so bots learn nothing.

Errors use an error field:

HTTPerrorMeaning
400invalid_jsonThe body is not valid JSON.
400invalid_payloadA field failed validation. details says which.
400no_notify_email_configuredNo valid recipient in the request and the site has no notify email.
401missing_api_key, invalid_api_keyNo key, or the key is wrong.
403site_disabledThe site is disabled. See Blocks.
400 response
{
  "error": "invalid_payload",
  "details": {
    "formErrors": [],
    "fieldErrors": {
      "form_id": [
        "Required"
      ]
    }
  }
}

Limits

  • Per visitor IP: when an IP already has more than 3 submissions in the last 60 seconds, the next one is dropped and the IP is blocked for 1 hour. You can lift that block yourself — see Blocks.
  • Per month: your plan’s form-submission count, for all your sites together. Spam and submissions over the limit do not count. See Plans and limits.
  • Field values: 10,000 characters each.

Every field and response is also listed in the API reference.