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

Spam protection

Captcha providers, rate limits, blocked IPs and the spam check on contact-form email.

SecureSMTP checks form submissions and outgoing email in several ways. This page lists each check, what it looks at and what happens when it fires.

CheckApplies toWhen it fires
CaptchaForm submissionsSubmission dropped: blocked: true.
IP limit and IP blocksForm submissionsSubmission dropped: blocked: true.
Spam scoreForm submissionsStored as spam, not emailed.
Spam scoreOutgoing emailRefused: 400 content_flagged.
Contact-form spam holdContact-form notifications sent through the API or SMTPNot sent: held: true.
AI checkOutgoing email (sampled)Refused: 400 ai_flagged.

Captcha

Each site has one captcha setting:

providerWhat it is
sharedCloudflare Turnstile run by SecureSMTP. The default for new sites. No keys needed.
turnstileCloudflare Turnstile with your own keys.
hcaptchahCaptcha with your own keys.
recaptchaGoogle reCAPTCHA v2 (the “I’m not a robot” checkbox) with your own keys.
recaptcha_v3Google reCAPTCHA v3 with your own keys. Invisible. A score below 0.5 is rejected.
noneNo captcha. The other checks still run.

Change it in the dashboard (Sites → Spam protection), on the WordPress plugin’s Spam Protection page, or with POST /sites/captcha.

Read the setting

curl https://securessmtp.com/api/v1/sites/captcha \
  -H "x-securessmtp-api-key: $SECURESSMTP_API_KEY"
Response
{
  "ok": true,
  "provider": "turnstile",
  "site_key": "0x4AAAAAAAxxxxxxxxxxxxxx",
  "widget_js": "https://challenges.cloudflare.com/turnstile/v0/api.js",
  "widget_class": "cf-turnstile",
  "response_field": "cf-turnstile-response"
}

With none, every field except provider is null. The secret key is never returned.

Show the widget

providerwidget_jswidget_classresponse_field
shared, turnstilehttps://challenges.cloudflare.com/turnstile/v0/api.jscf-turnstilecf-turnstile-response
hcaptchahttps://js.hcaptcha.com/1/api.jsh-captchah-captcha-response
recaptchahttps://www.google.com/recaptcha/api.jsg-recaptchag-recaptcha-response
recaptcha_v3https://www.google.com/recaptcha/api.jsg-recaptcha-v3g-recaptcha-response

For Turnstile, hCaptcha and reCAPTCHA v2, load widget_js and put a div with widget_class and data-sitekey inside the form. When the visitor passes, the widget adds a hidden input named response_field to the form.

HTML
<script src="https://challenges.cloudflare.com/turnstile/v0/api.js" async defer></script>

<form method="post" action="/contact">
  <input name="name" required>
  <input name="email" type="email" required>
  <textarea name="message" required></textarea>
  <div class="cf-turnstile" data-sitekey="YOUR_SITE_KEY"></div>
  <button type="submit">Send</button>
</form>

reCAPTCHA v3 has no visible widget and adds no field. Load the script with your site key, ask for a token when the form is submitted, and put it in a hidden field yourself:

HTML
<script src="https://www.google.com/recaptcha/api.js?render=YOUR_SITE_KEY"></script>

<form id="contact" method="post" action="/contact">
  <input name="name" required>
  <input name="email" type="email" required>
  <textarea name="message" required></textarea>
  <input type="hidden" name="g-recaptcha-response">
  <button type="submit">Send</button>
</form>

<script>
  document.getElementById('contact').addEventListener('submit', function (e) {
    e.preventDefault();
    var form = this;
    grecaptcha.ready(function () {
      grecaptcha.execute('YOUR_SITE_KEY', { action: 'submit' }).then(function (token) {
        form.elements['g-recaptcha-response'].value = token;
        form.submit();
      });
    });
  });
</script>

Pass the token

On your server, read the field named response_field from the posted form and send it as captcha_token in POST /forms/submit. SecureSMTP checks it with the provider using the secret key saved for the site.

  • Rejected token: { "ok": true, "blocked": true }. Nothing is stored.
  • No token: the captcha check is skipped. Always send the token.
  • Provider down or slow: the submission goes through and the other checks still run.

Use your own keys

curl -X POST https://securessmtp.com/api/v1/sites/captcha \
  -H "x-securessmtp-api-key: $SECURESSMTP_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "provider": "turnstile",
  "site_key": "0x4AAAAAAAxxxxxxxxxxxxxx",
  "secret_key": "0x4AAAAAAAyyyyyyyyyyyyyyyyyyyyyyyyy"
}'
  • site_key is required for turnstile, hcaptcha, recaptcha and recaptcha_v3.
  • secret_key is required the first time. Later you can leave it out to keep the saved one. It is stored encrypted (AES-256-GCM) and never returned.
  • Switching to shared or none deletes any saved keys.
  • The response has the same shape as the GET response, with the new setting.
  • Errors: site_key_required, secret_key_required_on_first_save (400), encryption_failed (500), invalid_payload (400).

IP limit and IP blocks

These checks use visitor.ip from POST /forms/submit. If you do not send it, they do not run.

  • 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 with the reason rate_limit.
  • While an IP is blocked, its submissions are dropped and the answer is { "ok": true, "blocked": true }.
  • You can lift a rate_limit block yourself. Blocks with other reasons are set by SecureSMTP and need support. See Blocks.

Spam score on form submissions

Each submission gets a score from 0 to 100. Points are added for three or more links (more for six or more), for common spam words, for some domain endings often used by spammers, and for long text written in capitals.

ScoreClassWhat happens
0–29legitimateStored and emailed.
30–59suspiciousStored and emailed. An AI check also reads it in the background and can change the class to spam in the Submissions list.
60–100spamStored, not emailed. The answer is {"ok":true,"id":"…"} with no email_sent.

If at least 20 submissions reach a site in 24 hours and half or more of them are spam, the site is disabled automatically. You can turn it back on yourself — see Blocks.

Spam score on outgoing email

Email sent with POST /mail/send or SMTP gets the same score, worked out from the subject and the body.

  • 60 or more: refused with 400 content_flagged and logged as flagged.
  • 30–59: sent. The email log shows warn:content_spam_score: and the score next to it.

Contact-form spam hold

Contact-form notifications relayed through POST /mail/send or SMTP are checked by the same spam classifier that checks hosted-form submissions. An email counts as a contact-form notification when:

  • source_plugin is a known form plugin, such as contact-form-7, wpforms, elementor-pro, gravityforms, fluentform, ninja-forms, forminator or jetpack; or
  • the subject reads like one, for example “New message”, “Contact form”, “New enquiry” or “Message from …”.

Subjects that look like an auto-reply to the visitor (“Thank you…”, “We received…”, “Your enquiry…”) are not checked.

If the classifier is at least 90% sure the message is spam, the email is held: it is not sent, it is logged as flagged with the reason, and the API answers:

Response
{
  "ok": true,
  "sent": true,
  "held": true,
  "reason": "held_as_spam"
}

The answer says sent: true on purpose, so the WordPress plugin does not send the spam another way. Over SMTP a held message gets 250. If the classifier fails, the email is sent.

AI check on outgoing email

  • It runs on every send while the site has fewer than 100 entries in its email log. After that it runs on about 1 email in 20, chosen from the subject, so the same subject always gets the same decision.
  • It does not run on contact-form notifications; those get the spam hold above.
  • If it is at least 85% sure the email is spam, the send is refused with 400 ai_flagged and logged as flagged.
  • If the check fails or times out, the email is sent.

See Deliverability for what else is checked before an email leaves.