AntiSpam
AntiSpam protects the official craftcms/contact-form plugin from automated and human spam submissions. It does not add a storefront UI of its own — customers never see it — it sits between the contact form and the mailer/database and silently rejects submissions that look like spam before they reach an inbox or the submissions list.
Why it matters
Every public contact form is a target for bots and spam farms. Left unprotected, a store's inbox fills with junk leads, sales staff waste time triaging fake enquiries, and (if Contact Form Extensions is used to store submissions as elements) the database accumulates spam rows. AntiSpam runs a configurable chain of checks on every submission and blocks the ones that fail, without adding friction (like a visible CAPTCHA) for legitimate visitors unless one of the optional CAPTCHA integrations is turned on.
How it works
On every contact form submission, AntiSpam runs an ordered chain of checks and stops at the first one that flags the submission as spam:
- Rate limiting — too many submissions from the same IP in a time window.
- IP blocking — the sender IP is on the banned list (manually or auto-banned).
- Submission fingerprint — a JS-injected fingerprint that flags submissions with no mouse/touch activity (typical of headless bots).
- DNSBL lookup (premium) — the sender IP is listed on a DNS-based blocklist (e.g. Spamhaus).
- Country / phone validation — the sender's IP resolves to a disallowed country, or a phone field fails a per-country pattern.
- Honeypot — a hidden form field, invisible to humans, was filled in.
- Submission timing — the form was completed faster than a human plausibly could.
- Disposable email domain — the submitted email uses a known throwaway/disposable domain.
- Content scoring — message text scores too high on link density, spam keywords, all-caps ratio, or a language/country mismatch.
- CAPTCHA / third-party classifiers (premium) — Cloudflare Turnstile, hCaptcha, Akismet, CleanTalk, OOPSpam.
- Custom rulesets (premium) — admin-defined regex rules with a per-rule score.
A submission is rejected as soon as any enabled check fails; later checks in the chain are skipped. Every enabled check can be turned on or off independently, so a store can start with the free, invisible checks (rate limiting, honeypot, timing, disposable-email blocklist, content scoring) and only add CAPTCHA-style checks if spam still gets through.
Blocked submissions are logged (if logging is enabled) and can trigger auto-banning of the sender IP and, on a licensed install, a spam-wave alert to Slack/Telegram/webhook.
AntiSpam has no effect unless the official craftcms/contact-form plugin is installed and active. If Contact Form isn't detected, AntiSpam logs a warning and does nothing.
Admin use cases
- Stop bot-generated contact form spam without adding a CAPTCHA that hurts conversion.
- Automatically ban IPs that repeatedly trigger spam checks.
- Reject signups/enquiries that use disposable/throwaway email addresses.
- Restrict form submissions to a set of allowed countries (e.g. only accept enquiries from the markets the store serves).
- Get an early warning (Slack/Telegram/webhook) when a spam wave hits the site.
- Review a searchable log of every blocked submission and its rejection reason.
Installation
Require the plugin via Composer (a valid AntiSpam license is needed for private-packager access — see the Project Manager license registry):
composer require yui/craft-antispam
Install it:
php craft plugin/install antispam
If Craft doesn't run migrations automatically on install:
php craft migrate/all
php craft clear-caches/all
Then open the Craft Control Panel and go to AntiSpam in the main navigation.
Optional: to resolve the sender's country from a local MaxMind GeoLite2 database instead of the rate-limited ip-api.com fallback, require geoip2/geoip2 via Composer and point Settings → Geo → GeoLite2 DB Path at the .mmdb file.
Frequently asked questions
Does AntiSpam protect anything besides the contact form?
No. It only hooks into craftcms/contact-form (and, transparently, Contact Form Extensions submissions built on top of it). Custom Formie/Freeform forms or checkout are not covered.
Will legitimate customers ever get blocked? It's possible if a check is misconfigured — for example, an allowed-countries list that's too narrow, or a submission-time minimum set too high for a genuinely fast typist. Start with the free, low-friction checks (rate limiting, honeypot, timing, disposable email, content scoring) and review the Spam Logs after enabling anything new before turning on stricter checks like country or CAPTCHA gating.
What happens to a blocked submission? It never reaches the mailer (and, with Contact Form Extensions, is never saved as a submission element). The event is written to the spam log if logging is enabled, and the sender's IP can be auto-banned once it crosses the configured threshold.
Do I need a license to use AntiSpam? The free checks (rate limiting, IP blocking, honeypot, submission timing, submission fingerprint, disposable-email blocklist, content scoring, geo/phone validation) work without a license. DNSBL lookups, Cloudflare Turnstile, hCaptcha, Akismet, CleanTalk, OOPSpam, custom rulesets, and spam-wave notifications require an active AntiSpam license (Premium features).
Can I manually ban or unban an IP? Yes, from AntiSpam → Banned IPs in the control panel, or in code — see Developer below.
Developer
Manually ban an IP from custom code (e.g. from a queue job or another event handler):
use Yui\AntiSpam\Plugin as AntiSpam;
AntiSpam::banIp('192.168.1.1', 'Repeated spam attempts');
The plugin registers its checks and services as components on the plugin instance, reachable via AntiSpam::getInstance():
| Accessor | Service |
|---|---|
getLicense() | License validation |
getRateLimiter() | Per-IP rate limiting and recent-spam counting |
getEmailDomain() | Disposable/throwaway email domain detection |
getContentScoring() | Link density / keyword / all-caps / locale-mismatch scoring |
getGeo() | IP → country lookup (GeoLite2 or ip-api.com fallback) |
getPremium() | Licensed-only checks: DNSBL, Turnstile, hCaptcha, Akismet, CleanTalk, OOPSpam, custom rulesets |
getNotifications() | Spam-wave alerting (Slack/Telegram/generic webhook) |
AntiSpam hooks craft\contactform\Mailer::EVENT_BEFORE_SEND and, when Contact Form Extensions is active, craft\services\Elements::EVENT_BEFORE_SAVE_ELEMENT on ContactFormSubmission/Submission elements — it runs the same check chain in either case and does not run it twice for the same submission.
See Settings for the full configuration reference.