{"id":342811,"date":"2026-07-22T05:57:48","date_gmt":"2026-07-22T05:57:48","guid":{"rendered":"https:\/\/wordpress.org\/plugins\/mdl-spam-filter\/"},"modified":"2026-07-22T05:57:21","modified_gmt":"2026-07-22T05:57:21","slug":"mdl-spam-filter","status":"publish","type":"plugin","link":"https:\/\/wordpress.org\/plugins\/mdl-spam-filter\/","author":23485821,"comment_status":"closed","ping_status":"closed","template":"","meta":{"version":"1.0.0","stable_tag":"1.0.0","tested":"7.0.2","requires":"6.2","requires_php":"7.4","requires_plugins":null,"header_name":"MDL Spam Filter","header_author":"Monday Digital Lab","header_description":"Block contact form spam server-side with keywords, honeypot, rate limiting and optional AI detection \u2014 before it reaches your inbox, form entries, or email log. Works with Contact Form 7, WPForms, Gravity Forms, Elementor and more.","assets_banners_color":"e9e2fa","last_updated":"2026-07-22 05:57:21","external_support_url":"","external_repository_url":"","donate_link":"","header_plugin_uri":"https:\/\/lab.mondaydigital.com\/plugins\/mdl-spam-filter\/","header_author_uri":"https:\/\/lab.mondaydigital.com","rating":0,"author_block_rating":0,"active_installs":0,"downloads":39,"num_ratings":0,"support_threads":0,"support_threads_resolved":0,"author_block_count":0,"sections":["description","installation","faq","changelog"],"tags":{"1.0.0":{"tag":"1.0.0","author":"mondaydigitallab","date":"2026-07-22 05:57:21"}},"upgrade_notice":[],"ratings":[],"assets_icons":{"icon-128x128.png":{"filename":"icon-128x128.png","revision":3619892,"resolution":"128x128","location":"assets","locale":"","width":128,"height":128},"icon-256x256.png":{"filename":"icon-256x256.png","revision":3618555,"resolution":"256x256","location":"assets","locale":"","width":256,"height":256}},"assets_banners":{"banner-1544x500.png":{"filename":"banner-1544x500.png","revision":3618555,"resolution":"1544x500","location":"assets","locale":"","width":1544,"height":500},"banner-772x250.png":{"filename":"banner-772x250.png","revision":3619892,"resolution":"772x250","location":"assets","locale":"","width":772,"height":250},"banner.svg":{"filename":"banner.svg","revision":3618557,"resolution":false,"location":"assets","locale":false}},"assets_blueprints":{},"all_blocks":[],"tagged_versions":["1.0.0"],"block_files":[],"assets_screenshots":{"screenshot-1.png":{"filename":"screenshot-1.png","revision":3618008,"resolution":"1","location":"assets","locale":"","width":1757,"height":1834},"screenshot-2.png":{"filename":"screenshot-2.png","revision":3618008,"resolution":"2","location":"assets","locale":"","width":1734,"height":740},"screenshot-3.png":{"filename":"screenshot-3.png","revision":3618008,"resolution":"3","location":"assets","locale":"","width":1731,"height":920},"screenshot-4.png":{"filename":"screenshot-4.png","revision":3618057,"resolution":"4","location":"assets","locale":"","width":1749,"height":1628},"screenshot-5.png":{"filename":"screenshot-5.png","revision":3618008,"resolution":"5","location":"assets","locale":"","width":1753,"height":1107},"screenshot-6.png":{"filename":"screenshot-6.png","revision":3618008,"resolution":"6","location":"assets","locale":"","width":1732,"height":444}},"screenshots":{"1":"Dashboard with the setup checklist, period picker (7\/30\/90 days), trend deltas, blocked-submission chart, and system-health card.","2":"Settings page with WooCommerce-style sections, detection mode, and AI provider options.","3":"Notifications section: one central address, per-notification toggles and overrides with a tag-style multi-email picker, and a send-test button.","4":"Keywords page: built-in keywords grouped by category, with search and per-group toggles, plus your own custom keywords.","5":"Filter Log page with search, filters, date sorting, expandable excerpts, bulk delete, and CSV export.","6":"Review queue: flagged submissions held before anything is stored or emailed \u2014 accept, block, or turn a phrase into a keyword."}},"plugin_section":[],"plugin_tags":[2353,2656,358,599,10877],"plugin_category":[54],"plugin_contributors":[265282],"plugin_business_model":[],"class_list":["post-342811","plugin","type-plugin","status-publish","hentry","plugin_tags-ai","plugin_tags-anti-spam","plugin_tags-contact-form","plugin_tags-spam","plugin_tags-spam-filter","plugin_category-security-and-spam-protection","plugin_contributors-mondaydigitallab","plugin_committers-mondaydigitallab"],"banners":{"banner":"https:\/\/ps.w.org\/mdl-spam-filter\/assets\/banner-772x250.png?rev=3619892","banner_2x":"https:\/\/ps.w.org\/mdl-spam-filter\/assets\/banner-1544x500.png?rev=3618555","banner_rtl":false,"banner_2x_rtl":false},"icons":{"svg":false,"icon":"https:\/\/ps.w.org\/mdl-spam-filter\/assets\/icon-128x128.png?rev=3619892","icon_2x":"https:\/\/ps.w.org\/mdl-spam-filter\/assets\/icon-256x256.png?rev=3618555","generated":false},"screenshots":[{"src":"https:\/\/ps.w.org\/mdl-spam-filter\/assets\/screenshot-1.png?rev=3618008","caption":"Dashboard with the setup checklist, period picker (7\/30\/90 days), trend deltas, blocked-submission chart, and system-health card."},{"src":"https:\/\/ps.w.org\/mdl-spam-filter\/assets\/screenshot-2.png?rev=3618008","caption":"Settings page with WooCommerce-style sections, detection mode, and AI provider options."},{"src":"https:\/\/ps.w.org\/mdl-spam-filter\/assets\/screenshot-3.png?rev=3618008","caption":"Notifications section: one central address, per-notification toggles and overrides with a tag-style multi-email picker, and a send-test button."},{"src":"https:\/\/ps.w.org\/mdl-spam-filter\/assets\/screenshot-4.png?rev=3618057","caption":"Keywords page: built-in keywords grouped by category, with search and per-group toggles, plus your own custom keywords."},{"src":"https:\/\/ps.w.org\/mdl-spam-filter\/assets\/screenshot-5.png?rev=3618008","caption":"Filter Log page with search, filters, date sorting, expandable excerpts, bulk delete, and CSV export."},{"src":"https:\/\/ps.w.org\/mdl-spam-filter\/assets\/screenshot-6.png?rev=3618008","caption":"Review queue: flagged submissions held before anything is stored or emailed \u2014 accept, block, or turn a phrase into a keyword."}],"raw_content":"<!--section=description-->\n<p><strong>Stop contact form spam before it reaches your inbox.<\/strong> MDL Spam Filter is a lightweight anti-spam plugin that protects Contact Form 7, WPForms, Gravity Forms, Elementor forms, and any other contact form \u2014 using keyword filtering, a honeypot, rate limiting, a language guard, and optional AI spam detection (Anthropic Claude, OpenAI GPT, or any OpenAI-compatible provider). No CAPTCHA, no visitor friction, and nothing is stored or emailed until a submission passes the filter.<\/p>\n\n<p>MDL Spam Filter scans front-end contact form submissions on the server before any form handler runs. When a submission is detected as spam, it is blocked: no email is sent \u2014 including the admin notification of a new submission and any autoresponder \u2014 and nothing is written to your form plugin's entries or email log. You choose whether blocked submissions are recorded in this plugin's own private log.<\/p>\n\n<p>Detection works two ways, used alone or together:<\/p>\n\n<ul>\n<li><strong>Keywords<\/strong> \u2014 a built-in list of common spam phrases plus your own custom keywords.<\/li>\n<li><strong>AI<\/strong> \u2014 optional classification by Anthropic (Claude), OpenAI, or any custom OpenAI-compatible provider using your own API key.<\/li>\n<\/ul>\n\n<p>The filter is form-agnostic. It inspects any POST request that carries an email address \u2014 or that is recognisably from a supported form plugin even without an email field \u2014 so it works with Contact Form 7, WPForms, Gravity Forms, Elementor forms, raw HTML forms, and most others without per-plugin configuration. AJAX and REST submissions (such as Contact Form 7) receive a proper response, so the submit spinner never hangs.<\/p>\n\n<p>When AI detection is enabled, the verdict is requested as a strict yes\/no decision and, where the provider supports it, constrained to a structured JSON response \u2014 so a confident block\/allow is the only possible answer. If the AI provider is unreachable (outage, timeout, bad key) the plugin can fall back to a keyword scan instead of letting the submission through, and an optional per-visitor rate limit throttles floods before any AI call is made.<\/p>\n\n<p>When spam is caught you choose what the sender sees:<\/p>\n\n<ul>\n<li><strong>Silently discard<\/strong> \u2014 the form shows its normal success message while the submission is quietly dropped, giving bots no signal.<\/li>\n<li><strong>Show a blocked message<\/strong> \u2014 the form displays a clear \"flagged as spam\" notice.<\/li>\n<\/ul>\n\n<h4>Key features<\/h4>\n\n<ul>\n<li>Server-side blocking before the submission reaches form handlers or email.<\/li>\n<li>Works with AJAX and REST forms (Contact Form 7, WPForms, Gravity Forms, Elementor, and more) without hanging the submit spinner.<\/li>\n<li>Silent discard that mimics a successful send, or a visible blocked message.<\/li>\n<li>Keyword detection with 200+ built-in keywords organised into collapsible categories (pharma, SEO outreach, scams, gambling, and more) \u2014 each toggleable individually, per category, or all at once \u2014 plus unlimited custom keywords.<\/li>\n<li>Activate, pause, or remove individual keywords without losing them \u2014 custom keywords use the same chip interface as the built-in list.<\/li>\n<li>Regular-expression keywords: wrap a custom term in slashes (\/pattern\/) for advanced matching \u2014 invalid patterns are skipped safely.<\/li>\n<li>Per-keyword hit counters show how many submissions each keyword has blocked, so dead keywords are easy to prune.<\/li>\n<li>Review-queue feedback loop: one-click Allow sender \/ Block sender on held items, plus an inline \"add keyword\" box that turns a held spam's telltale phrase into a blocking keyword.<\/li>\n<li>Optional AI detection via Anthropic, OpenAI, or a custom OpenAI-compatible provider (bring your own API key).<\/li>\n<li>AI verdicts constrained to a structured yes\/no response (json_schema \/ json_object) where the provider supports it, so the result can't be ambiguous free text.<\/li>\n<li>Custom AI detection rules: keep the built-in rules, write your own, or combine both \u2014 with a \"Generate rules with AI\" assistant and ready-made templates that draft rules from a plain-language description of your business.<\/li>\n<li>Keyword fallback when the AI provider is unavailable, so an API outage does not silently let spam through (AI-only mode; on by default).<\/li>\n<li>Optional per-visitor submission rate limit to throttle floods before any AI call is made.<\/li>\n<li>Honeypot field and minimum-submit-time check that stop obvious bots before any keyword or AI work (no cost, no false success signal).<\/li>\n<li>Optional language guard: block the languages and alphabets you choose (Cyrillic, Chinese, Arabic, and more) or allow only the ones you choose \u2014 plus an always-on trap for words mixing Latin with look-alike Cyrillic letters, a common keyword-filter evasion trick. Accented Latin languages are never affected.<\/li>\n<li>Allow list (never block) and deny list (always block) by email address, domain, or IP \u2014 with a one-click \"Block sender\" action on every Filter Log and Review row.<\/li>\n<li>Form targeting: scan all forms, or only selected forms identified by their id, class, or name.<\/li>\n<li>Outgoing-email safety net: when a submission is spam, every email it triggers is suppressed \u2014 including the admin \"new submission\" notification and any autoresponder.<\/li>\n<li>Review queue (on by default): hold keyword\/AI-flagged submissions for manual review \u2014 the submission is stopped before anything is recorded or emailed. View the fields, then Accept to email it to the site admin (kept out of the filter log) or Block to drop it (recorded in the filter log). Optionally get an email notification when a submission is held.<\/li>\n<li>Optional email alert to an admin when the plugin hits a provider error \u2014 invalid or expired API key, exhausted credit or quota, or a provider outage \u2014 throttled to one email per distinct error every 10 minutes.<\/li>\n<li>Private, opt-in log of blocked submissions in a dedicated database table.<\/li>\n<li>Automatic retention cleanup: filter-log and review-queue entries older than a configurable number of days (default 90, 0 = keep forever) are pruned daily, so a spam flood cannot grow the database forever.<\/li>\n<li>Optional weekly email digest: blocked totals, detection-method breakdown, top blocked senders, and the review-queue backlog \u2014 skipped automatically when there is nothing to report.<\/li>\n<li>A dedicated Notifications settings section: one central notification email address for every alert (review holds, provider errors, weekly digest), an on\/off toggle and optional override address per notification, a tag-style picker for adding multiple recipients to any field (plain comma-separated input still works), and a send-test button that resolves the recipient exactly like the real notification; everything falls back to the site admin email.<\/li>\n<li>\"Spam blocked\" counter in the WordPress dashboard At a Glance widget.<\/li>\n<li>Analytics dashboard with a 7\/30\/90-day period picker: daily blocked-submission chart, trend deltas against the previous period, busiest-day and awaiting-review tiles, detection-method breakdown, and top blocked senders and targeted pages \u2014 every bar, method, sender, and page clicks through to the pre-filtered Filter Log.<\/li>\n<li>System-health card on the dashboard: protection state, detection engine and API-key source, AI-provider status (from the circuit breaker \u2014 no live call), review-queue backlog, scheduled maintenance (with your retention period), and weekly-digest status, each with a fix-it link when something needs attention. The cron rows diagnose real scheduling problems: an overdue event (WP-Cron not firing on a quiet site) or a stale queue entry gets an amber warning with the fix.<\/li>\n<li>Dismissable setup checklist on the dashboard that tracks protection, detection, notifications, and the first test email \u2014 each step deep-links to the right screen.<\/li>\n<li>Dashboard aggregates are cached for five minutes so the overview never runs heavy queries on every load.<\/li>\n<li>Enterprise-grade admin polish: every screen opens with a one-line purpose statement and a primary action, a persistent banner warns on every screen when protection is off, the Filter Log, Review, and Activity lists all sort by date, table headers stay visible while scrolling long lists, and the whole UI follows your chosen WordPress admin color scheme.<\/li>\n<li>Accessible by design: keyboard-visible focus on every custom control (toggles, keyword chips, recipient pickers, chart bars), screen-reader table behind the dashboard chart, WCAG AA contrast on badges and status colors, and reduced-motion support.<\/li>\n<li>Contextual help on every screen: the WordPress Help flyout carries the screen's purpose and the FAQ entries relevant to that page.<\/li>\n<li>One-click dashboard report export (CSV): totals, daily counts, method breakdown, and top senders\/pages for the selected period.<\/li>\n<li>Filter Log excerpts expand in place, so long submissions can be read without leaving the list.<\/li>\n<li>Searchable, filterable log with date range and method filters, sortable dates, bulk delete, and CSV export.<\/li>\n<li>Settings import\/export as JSON for moving configuration between sites (API keys are never exported).<\/li>\n<li>Keywords-only import\/export for sharing keyword lists between sites without touching other settings.<\/li>\n<li>Activity audit log: who changed settings and keywords, imports\/exports, log purges, AI connection tests, and AI provider outages \u2014 with automatic 90-day retention.<\/li>\n<li>Stores submitter IP as a one-way hash for privacy.<\/li>\n<li>WooCommerce-style navigation: a dedicated top-level MDL Spam Filter menu with Dashboard, Settings, Keywords, Review, Filter Log, Activity Log, Help, and Import\/Export pages. The Settings page is organised into sections (Protection, Detection engine, AI engine, Advanced protection, Form targeting, Email &amp; logging, Notifications) and the Help page into Getting started \/ FAQ \/ About \u2014 each with its own section links.<\/li>\n<\/ul>\n\n<h3>External Services<\/h3>\n\n<p>This plugin connects to third-party AI services <strong>only when AI detection is enabled<\/strong>, or when an admin uses the optional \"Generate rules with AI\" assistant on the settings page. Keyword-only mode with the assistant unused contacts no external service.<\/p>\n\n<p><strong>Anthropic (Claude API)<\/strong>\nUsed to classify submission text as spam or genuine when the AI provider is set to Anthropic, and to draft custom detection rules when an admin uses the \"Generate rules with AI\" assistant.\nWhat is sent: the text content of the contact form submission and your API key, sent to the endpoint at the time a submission is scanned; and, only when an admin clicks \"Generate rules with AI\", the business description that admin types into the assistant.\nEndpoint: https:\/\/api.anthropic.com\/v1\/messages\nTerms of Service: https:\/\/www.anthropic.com\/legal\/commercial-terms\nPrivacy Policy: https:\/\/www.anthropic.com\/legal\/privacy<\/p>\n\n<p><strong>OpenAI (Chat Completions API)<\/strong>\nUsed to classify submission text as spam or genuine when the AI provider is set to OpenAI, and to draft custom detection rules when an admin uses the \"Generate rules with AI\" assistant.\nWhat is sent: the text content of the contact form submission and your API key, sent to the endpoint at the time a submission is scanned; and, only when an admin clicks \"Generate rules with AI\", the business description that admin types into the assistant.\nEndpoint: https:\/\/api.openai.com\/v1\/chat\/completions\nTerms of Use: https:\/\/openai.com\/policies\/terms-of-use\nPrivacy Policy: https:\/\/openai.com\/policies\/privacy-policy<\/p>\n\n<p><strong>Custom provider (optional)<\/strong>\nIf you set the AI provider to \"Custom\", submission text and your API key are sent to the OpenAI-compatible endpoint URL you enter in the settings \u2014 and, only when an admin clicks \"Generate rules with AI\", the business description that admin types into the assistant. The endpoint must be an https:\/\/ URL that is not a private or loopback address (an http:\/\/ endpoint can be enabled with the <code>mdlsf_allow_insecure_custom_endpoint<\/code> filter for a trusted local model). No request is made unless you configure this. Because the endpoint is supplied by you, review the terms and privacy policy of whichever provider you point it at (for example OpenRouter, Groq, Together AI, or a self-hosted model).<\/p>\n\n<!--section=installation-->\n<ol>\n<li>Upload the <code>mdl-spam-filter<\/code> folder to <code>\/wp-content\/plugins\/<\/code>, or install through the Plugins screen.<\/li>\n<li>Activate the plugin through the Plugins screen.<\/li>\n<li>Go to the MDL Spam Filter menu in the admin sidebar, then open Settings to configure detection.<\/li>\n<li>If using AI detection, choose a provider (Anthropic, OpenAI, or a custom OpenAI-compatible endpoint) and enter your API key.<\/li>\n<li>Optionally, under Form targeting, limit scanning to specific forms by id, class, or name.<\/li>\n<\/ol>\n\n<!--section=faq-->\n<dl>\n<dt id=\"does%20this%20work%20with%20my%20contact%20form%20plugin%3F\"><h3>Does this work with my contact form plugin?<\/h3><\/dt>\n<dd><p>It scans any POST submission that contains an email address \u2014 or that carries a recognised form plugin's identifier (Contact Form 7, WPForms, Gravity Forms, Ninja Forms, and similar) even without an email field \u2014 so it works with most form plugins and plain HTML forms without extra setup. AJAX and REST forms such as Contact Form 7 get a proper response, so the submit spinner does not hang.<\/p><\/dd>\n<dt id=\"what%20does%20the%20visitor%20see%20when%20a%20submission%20is%20blocked%3F\"><h3>What does the visitor see when a submission is blocked?<\/h3><\/dt>\n<dd><p>Your choice, under Settings \u2192 Protection. \"Silently discard\" shows the form's normal success message while the submission is dropped, so bots get no clue. \"Show a blocked message\" displays a clear notice that the message was flagged as spam.<\/p><\/dd>\n<dt id=\"is%20an%20api%20key%20required%3F\"><h3>Is an API key required?<\/h3><\/dt>\n<dd><p>No. Keyword detection works with no external service. An API key is only needed if you enable AI detection.<\/p><\/dd>\n<dt id=\"the%20keywords%20page%20says%20keywords%20are%20not%20in%20use.%20why%3F\"><h3>The Keywords page says keywords are not in use. Why?<\/h3><\/dt>\n<dd><p>Detection mode is set to \"AI only\" and the AI-unavailable keyword fallback is turned off, so keywords play no part. Switch to \"Keywords only\" or \"Keywords, then AI\", or turn the fallback back on in Settings \u2192 AI engine, to manage and use keywords. With the fallback on, keywords are still editable and are used only when the AI provider is unreachable.<\/p><\/dd>\n<dt id=\"what%20happens%20if%20my%20ai%20provider%20is%20down%3F\"><h3>What happens if my AI provider is down?<\/h3><\/dt>\n<dd><p>By default, when detection mode is \"AI only\" and the provider cannot be reached (outage, timeout, rate limit, or a bad key), the plugin falls back to a keyword scan so spam is not silently let through. You can turn this off in Settings \u2192 AI engine. In \"Keywords, then AI\" mode the keyword scan always runs first, so an outage never opens the gate. A blocked submission caught this way is logged with the reason \"Matched keyword (AI unavailable)\".<\/p>\n\n<p>You can also be told about provider problems as they happen: turn on the AI Engine provider-error notification in Settings \u2192 Notifications and the plugin emails you when it hits a provider error \u2014 an invalid or expired API key, exhausted credit or quota, or an outage. Alerts are throttled to one email per distinct error every 10 minutes.<\/p><\/dd>\n<dt id=\"can%20i%20customise%20what%20the%20ai%20treats%20as%20spam%3F\"><h3>Can I customise what the AI treats as spam?<\/h3><\/dt>\n<dd><p>Yes. Under Settings \u2192 AI engine, \"Detection rules\" offers three modes: the built-in default rules, your own custom rules, or both combined. Custom rules are plain-language lines such as \"Treat requests for electrical or roofing work as spam \u2014 we only do plumbing.\" If you'd rather not write them yourself, the \"Generate rules with AI\" assistant drafts rules from a short description of your business (ready-made templates included) \u2014 review the draft, adjust it, and save.<\/p><\/dd>\n<dt id=\"can%20i%20rate-limit%20submissions%3F\"><h3>Can I rate-limit submissions?<\/h3><\/dt>\n<dd><p>Yes. Under Settings \u2192 Protection set \"Submission rate limit\" to the maximum number of submissions allowed per minute from one visitor. Submissions beyond that are blocked without an AI call, which throttles bursts and caps API cost. It defaults to 20 per minute; set it to 0 to disable.<\/p><\/dd>\n<dt id=\"what%20are%20the%20honeypot%20and%20minimum%20submit%20time%3F\"><h3>What are the honeypot and minimum submit time?<\/h3><\/dt>\n<dd><p>Two zero-cost bot traps under Settings \u2192 Advanced protection. The honeypot adds an invisible form field that humans never see; any submission that fills it is blocked. The minimum submit time blocks forms submitted faster than a human could plausibly fill them (bots typically submit instantly). Both checks fail open: a visitor without JavaScript or on a cached page is never blocked by them.<\/p><\/dd>\n<dt id=\"can%20i%20block%20submissions%20written%20in%20a%20foreign%20language%3F\"><h3>Can I block submissions written in a foreign language?<\/h3><\/dt>\n<dd><p>Yes. Turn on the language guard under Settings \u2192 Advanced protection and pick a mode. <strong>Block selected languages &amp; alphabets<\/strong> blocks submissions written mostly in any language or alphabet you tick (the default selection is everything except Latin). <strong>Allow only selected languages &amp; alphabets<\/strong> inverts it: anything written mostly in a language or alphabet you did not tick is blocked, including ones not listed. Either way the guard also blocks words that mix Latin with look-alike Cyrillic letters \u2014 a common trick to sneak terms like \"v\u0456\u0430gra\" past keyword filters. Accented Latin languages such as French, German, or Spanish always count as Latin, and a foreign name or city in an otherwise fine message does not trip it. The check is deterministic and costs nothing \u2014 it runs before any keyword or AI work and fails open on content it cannot judge.<\/p>\n\n<p>The guard detects the alphabet, not the exact language \u2014 it cannot tell apart languages that share an alphabet, such as Russian vs Ukrainian or English vs Indonesian. For per-language policy, use AI detection instead: add a custom rule under Settings \u2192 AI engine \u2192 Detection rules such as \"We only serve English-speaking customers \u2014 treat submissions written entirely in another language as spam.\" The AI reads every language, so it classifies foreign-language spam on meaning either way.<\/p><\/dd>\n<dt id=\"can%20i%20always%20allow%20or%20always%20block%20specific%20senders%3F\"><h3>Can I always allow or always block specific senders?<\/h3><\/dt>\n<dd><p>Yes. Under Settings \u2192 Advanced protection, add email addresses, domains, or IP addresses to the allow list (matching submissions skip every check, including AI) or the deny list (matching submissions are blocked immediately, without an AI call).<\/p>\n\n<p>You can also manage senders in one click: every Filter Log and Review row with a valid email shows a \"Block sender\" action that adds that address to the deny list, and Review rows additionally show an \"Allow sender\" action that adds it to the allow list (rows whose sender is already listed show \"On deny list\" or \"On allow list\" instead). Each change is recorded in the Activity Log, and both lists stay editable under Settings \u2192 Advanced protection.<\/p><\/dd>\n<dt id=\"what%20is%20the%20review%20queue%3F\"><h3>What is the Review queue?<\/h3><\/dt>\n<dd><p>A hold-for-review step for submissions flagged by keywords or AI, on by default \u2014 turn it off with \"Hold flagged mail for review\" under Settings \u2192 Email &amp; logging. When on, a flagged submission is captured and the request is stopped before any form handler runs \u2014 so nothing is stored in your form plugin and no email is sent until you decide. The visitor still sees the form's normal success message. You can also have the plugin email you when a submission is held: turn on the review notification under Settings \u2192 Notifications. It goes to the central notification email address, or a review-specific override address if you set one; either falls back to the site admin email. For each held submission you can:<\/p>\n\n<ul>\n<li><strong>View<\/strong> \u2014 see all submitted fields, the sender, source, and reason. The detail view also includes a quick \"add keyword\" box: paste a telltale phrase from the spam and it is added to your custom keyword list immediately, so future submissions containing it are caught without an AI call.<\/li>\n<li><strong>Accept<\/strong> \u2014 email the submitted fields to the site administrator (goes out through wp_mail like any other mail). Not added to the filter log.<\/li>\n<li><strong>Block<\/strong> \u2014 discard the submission (never delivered) and record it in the filter log.<\/li>\n<li><strong>Allow sender \/ Block sender<\/strong> \u2014 add the sender to the allow or deny list in one click, so their future submissions are always let through or always blocked.<\/li>\n<\/ul>\n\n<p>This applies only to keyword\/AI verdicts. Deterministic bot blocks (rate limit, deny list, honeypot, minimum submit time, language guard) are always blocked immediately and never enter the queue.<\/p>\n\n<p>Because the submission is held before the form plugin processes it, the accepted email is a notification the plugin composes from the submitted fields \u2014 it is not the form plugin's own templated email, and no form-plugin entry is created. Note: forms that submit over admin-ajax (for example WPForms) are processed too early for this pre-handler capture; their flagged mail is blocked and logged rather than queued.<\/p><\/dd>\n<dt id=\"what%20does%20the%20activity%20log%20page%20show%3F\"><h3>What does the Activity Log page show?<\/h3><\/dt>\n<dd><p>A plugin-level audit trail, separate from the Filter Log: settings and keyword changes (with which settings changed \u2014 never their values), imports and exports (settings and keywords), log purges and deletions, automatic retention pruning, review-queue decisions, senders blocked or allowed from the Filter Log or Review pages, keywords added from the review queue, weekly digests sent, AI connection tests, notification test emails, plugin activation, and AI provider errors (throttled, so an outage produces one entry per window). Entries record the acting user and are pruned automatically after 90 days. Setting values and API keys are never stored in the activity log.<\/p><\/dd>\n<dt id=\"can%20i%20export%20the%20log%20or%20my%20settings%3F\"><h3>Can I export the log or my settings?<\/h3><\/dt>\n<dd><p>Yes. The Filter Log page has a CSV export that honours your current search and filters. The Import\/Export page has a JSON settings export\/import for moving configuration between sites \u2014 API keys are never included in the export \u2014 plus a separate keywords-only export\/import for sharing keyword lists without touching other settings.<\/p><\/dd>\n<dt id=\"can%20i%20limit%20which%20forms%20are%20scanned%3F\"><h3>Can I limit which forms are scanned?<\/h3><\/dt>\n<dd><p>Yes. Under Settings \u2192 Form targeting choose \"Selected forms\" and add each form by its id, class, or name (for example #contact-form or .wpcf7-form). A lightweight front-end script tags each form with its id, class, and name so only the forms you list are scanned.<\/p><\/dd>\n<dt id=\"does%20the%20site%20admin%20still%20get%20a%20notification%20email%20for%20spam%3F\"><h3>Does the site admin still get a notification email for spam?<\/h3><\/dt>\n<dd><p>No. When a submission is flagged as spam, every email it would send is suppressed, including the admin \"new submission\" notification and any autoresponder to the visitor. This applies to forms that send through both standard page submits and AJAX. The outgoing-email safety net (Settings \u2192 Email &amp; logging) must be enabled, which it is by default.<\/p><\/dd>\n<dt id=\"where%20are%20blocked%20submissions%20stored%3F\"><h3>Where are blocked submissions stored?<\/h3><\/dt>\n<dd><p>Only in this plugin's own database table, and only if you enable logging. They are never written to your form plugin's entries or email log.<\/p>\n\n<p>Entries do not pile up forever: filter-log and review-queue rows older than the retention period (Settings \u2192 Email &amp; logging, default 90 days) are removed automatically once a day. Set the retention to 0 to keep entries indefinitely.<\/p><\/dd>\n<dt id=\"why%20does%20system%20health%20say%20the%20daily%20cleanup%20is%20overdue%3F\"><h3>Why does System health say the daily cleanup is overdue?<\/h3><\/dt>\n<dd><p>WordPress cron is not a real cron \u2014 it only fires when someone visits the site. On a quiet, staging, or local site, scheduled events sit in the queue past their due time, and the dashboard's System health card flags them as overdue rather than pretending all is well. Visit the front end once (the overdue job runs and reschedules itself), or for a reliable setup add <code>define( 'DISABLE_WP_CRON', true );<\/code> to wp-config.php and point a real system cron at wp-cron.php every few minutes. If the card instead reports the cron entry as stale (a daily event scheduled more than a day out), deactivating and reactivating the plugin rebuilds the schedule.<\/p><\/dd>\n<dt id=\"is%20submission%20data%20sent%20anywhere%3F\"><h3>Is submission data sent anywhere?<\/h3><\/dt>\n<dd><p>Only when AI detection is enabled, or when an admin uses the \"Generate rules with AI\" assistant. With AI detection on, the submission text is sent to the AI provider you select (Anthropic, OpenAI, or a custom OpenAI-compatible endpoint you configure) for classification. The rule assistant additionally sends the business description an admin types into it, so the provider can draft custom detection rules. See External Services below. Fields whose name indicates a secret or payment detail (passwords, API tokens, card numbers, CVV, account\/routing numbers, and similar) are excluded from both the text sent to the provider and the stored log excerpt.<\/p><\/dd>\n<dt id=\"where%20do%20the%20plugin%27s%20notification%20emails%20go%3F\"><h3>Where do the plugin's notification emails go?<\/h3><\/dt>\n<dd><p>Set one central address under Settings \u2192 Notifications and every alert the plugin sends \u2014 review-queue holds, AI provider errors, and the weekly digest \u2014 goes there. Leave it blank to use the site admin email. Each notification also has its own on\/off toggle and an optional override field, so you can send, say, the digest to a different mailbox. Every field accepts several addresses through a tag-style picker \u2014 press Enter or type a comma after each \u2014 and anything that is not a valid email address is flagged when you save instead of being dropped silently. A \"Send test email\" button on the same section delivers a test message to whoever would receive the selected notification, so you can confirm delivery before a real alert depends on it.<\/p><\/dd>\n<dt id=\"can%20i%20get%20a%20summary%20email%20instead%20of%20checking%20the%20dashboard%3F\"><h3>Can I get a summary email instead of checking the dashboard?<\/h3><\/dt>\n<dd><p>Yes. Turn on the weekly digest under Settings \u2192 Notifications. Once a week the plugin emails a summary \u2014 how many submissions were blocked in the last 7 days, the detection-method breakdown, the top blocked senders, and how many items are waiting in the review queue \u2014 to the address you set there (the central notification email address, or a digest-specific override; defaults to the site admin email). Weeks with nothing to report send no email.<\/p><\/dd>\n<dt id=\"can%20i%20use%20regular%20expressions%20in%20keywords%3F\"><h3>Can I use regular expressions in keywords?<\/h3><\/dt>\n<dd><p>Yes. Wrap a custom keyword in forward slashes to have it matched as a regular expression, for example \/\\bcheap +rolex\\b\/. Patterns are always case-insensitive, and an invalid pattern is skipped safely rather than blocking anything. Each keyword \u2014 plain or regex \u2014 also shows a hit counter on the Keywords page telling you how many submissions it has blocked, so ineffective keywords are easy to spot and prune.<\/p><\/dd>\n<dt id=\"how%20are%20my%20api%20keys%20stored%3F\"><h3>How are my API keys stored?<\/h3><\/dt>\n<dd><p>Your provider API keys are never included in a settings export. For maximum safety you can keep them out of the database entirely by defining them in wp-config.php:<\/p>\n\n<pre><code>define( 'MDLSF_ANTHROPIC_API_KEY', '...' ); (or `MDLSF_OPENAI_API_KEY` \/ `MDLSF_CUSTOM_API_KEY`).\n<\/code><\/pre>\n\n<p>When a constant is defined it overrides and hides the stored option, and the matching field shows a read-only note instead of an input.<\/p><\/dd>\n<dt id=\"can%20developers%20customise%20it%3F\"><h3>Can developers customise it?<\/h3><\/dt>\n<dd><p>Yes. These hooks are available:<\/p>\n\n<ul>\n<li><code>mdlsf_predefined_keywords<\/code> (filter) \u2014 modify the built-in keyword list.<\/li>\n<li><code>mdlsf_excluded_requests<\/code> (filter) \u2014 mark additional requests (by your own logic) as non-contact-form so they are never scanned.<\/li>\n<li><code>mdlsf_log_max_rows<\/code> \/ <code>mdlsf_quarantine_max<\/code> (filters) \u2014 adjust the filter-log row cap and review-queue size cap.<\/li>\n<li><code>mdlsf_allow_insecure_custom_endpoint<\/code> (filter) \u2014 permit an http:\/\/ custom AI endpoint (e.g. a trusted local model).<\/li>\n<li><code>mdlsf_sensitive_field_keys<\/code> (filter) \u2014 modify the list of field-name substrings excluded from scanning, AI, and logging (passwords, tokens, card numbers, etc.).<\/li>\n<li><code>mdlsf_language_guard_threshold<\/code> (filter) \u2014 adjust the ratio of offending-script letters (0\u20131, default 0.3) above which the language guard blocks.<\/li>\n<li><code>mdlsf_submission_blocked<\/code> (action) \u2014 fires when a submission is blocked, passing the submitter email and the detector verdict.<\/li>\n<li><code>mdlsf_submission_quarantined<\/code> (action) \u2014 fires when a flagged submission is held in the review queue, passing the submitter email and the detector verdict.<\/li>\n<\/ul><\/dd>\n\n<\/dl>\n\n<!--section=changelog-->\n<h4>1.0.0<\/h4>\n\n<ul>\n<li>Initial release.<\/li>\n<\/ul>","raw_excerpt":"Block contact form spam server-side with keywords, honeypot, rate limiting and optional AI \u2014 works with Contact Form 7, WPForms, Gravity Forms.","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin\/342811","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin"}],"about":[{"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wp\/v2\/types\/plugin"}],"replies":[{"embeddable":true,"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wp\/v2\/comments?post=342811"}],"author":[{"embeddable":true,"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wporg\/v1\/users\/mondaydigitallab"}],"wp:attachment":[{"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wp\/v2\/media?parent=342811"}],"wp:term":[{"taxonomy":"plugin_section","embeddable":true,"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_section?post=342811"},{"taxonomy":"plugin_tags","embeddable":true,"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_tags?post=342811"},{"taxonomy":"plugin_category","embeddable":true,"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_category?post=342811"},{"taxonomy":"plugin_contributors","embeddable":true,"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_contributors?post=342811"},{"taxonomy":"plugin_business_model","embeddable":true,"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_business_model?post=342811"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}