Bodholdt Contact
A contact widget that knows your customers
Freeforever
Everything in this plugin is free: no license key, no paid tiers, no locked features. Download the ZIP, then install it from your WordPress admin.
- Floating contact widget for any theme
- Exact-email signed-in account recognition, with active-entitlement checks in Licensing mode
- Spam-resistant email code, honeypot, and database-backed rate limiting
- Optional weekly activity summary after weeks with contact traffic
- Themeable brand, accent, and copy, with a live preview
- Creates trackable tickets with a compatible Bodholdt Tickets release
- Working the Door turns contact activity while you are away into a private Door Report
- Translation-ready visitor widget with configurable brand and copy
- Automatic updates from your dashboard, with no license key to enter
- Free and GPL, with no license key, paid tiers, or locked features
No account and no email address needed. Updates arrive in your WordPress dashboard.
Turn those messages into tracked tickets
Pair Contact with a compatible Bodholdt Tickets release and accepted submissions become support tickets your customers can follow.
- Four channels in one desk: email, form, portal, and widget
- A license-aware customer panel, so you see what they own
- An AI co-pilot that drafts replies with your own Claude key
Contact stays free either way. Nothing here is locked behind Tickets.
Real screens from Bodholdt Contact
These are real admin screens, not mockups. Click any one to view it full size.
A small floating contact widget for WordPress. A signed-in WordPress account can skip the email code only when its email exactly matches; Licensing recognition also requires an active entitlement. Everyone else verifies by email to help reduce automated spam. It emails accepted messages on its own; with a compatible Bodholdt Tickets release, it attempts ticket delivery first and retains the configured email route as a safe fallback.
Floating contact widget for any theme
Exact-email signed-in account recognition, with active-entitlement checks in Licensing mode
Spam-resistant email code, honeypot, and database-backed rate limiting
Optional weekly activity summary after weeks with contact traffic
Themeable brand, accent, and copy, with a live preview
Creates trackable tickets with a compatible Bodholdt Tickets release
Working the Door turns contact activity while you are away into a private Door Report
Translation-ready visitor widget with configurable brand and copy
Automatic updates from your dashboard, with no license key to enter
Free and GPL, with no license key, paid tiers, or locked features
- Floating contact widget for any theme
- Exact-email signed-in account recognition, with active-entitlement checks in Licensing mode
- Spam-resistant email code, honeypot, and database-backed rate limiting
- Optional weekly activity summary after weeks with contact traffic
- Themeable brand, accent, and copy, with a live preview
- Creates trackable tickets with a compatible Bodholdt Tickets release
- Working the Door turns contact activity while you are away into a private Door Report
- Translation-ready visitor widget with configurable brand and copy
- Automatic updates from your dashboard, with no license key to enter
- Free and GPL, with no license key, paid tiers, or locked features
- The changelog now reads like a changelog. Several entries from August described release machinery rather than the plugin: package validators, audit receipts, hash-bound tool inputs and publishing gates. None of it is anything you can see or use, and it was sitting on the product page and the site changelog where people decide whether to install. Removed rather than reworded, because there is no reader-facing version of most of it. Everything describing an actual change to the plugin is untouched.
- One of those bullets also claimed the translation template pointed at our support address. It did not; the file carried the WordPress.org default, which goes nowhere for a plugin that is not hosted there. The claim is withdrawn from the release it was attached to.
- The note about turning the arcade back on pointed at an "Arcade settings" screen that does not exist. It is under Settings, then Bodholdt Contact, in the row headed "Working the Door".
- The full download no longer contains a second readme describing the WordPress.org build, which creates a different set of database tables. Two readmes disagreeing about the same product is confusing on its own; the build now refuses to ship it.
- Confetti in the admin now really does start switched off, the way the documentation has always said it does. The setting was never written down when the plugin was activated, so a new site fell back to the checkbox's own default and started with it on. If you already had confetti, you keep it; nothing is switched off underneath you.
- The Door Report no longer looks broken before it has anything to report. On the first open after you turn on Working the Door, all four counters were zero, and the display font draws a zero with a diagonal bar through it, which reads as the browser's broken-image icon. A panel that has not counted anything now says so in a sentence, and once real counts exist the big numerals come back. A single zero sitting among real counts is drawn in the plain admin font instead.
- You can now see the bot trap working. The contact form has always hidden a trap field that only automated senders fill in, but nothing ever showed you it was doing anything unless you had switched the activity report on. The settings status card now carries a line for it, and there is a matching entry under Tools, Site Health. It counts trap trips over the last 7 days and says nothing more than that, because a sender turned away earlier by the request checks never reaches the trap and cannot be counted.
- The verification email says who it is from. The subject was "Your site name: verification code" and is now "Your {site name} verification code", so it reads as a sentence and is easier to find in a crowded inbox. Your site name, or the brand you set in the plugin, is what appears.
- The widget stops offering categories that need a support ticket, and stops quietly turning one into an email. When Bodholdt Tickets is installed with its paid support model switched on, the widget used to mirror every category, including the ones that cost a credit. If someone picked one, the ticket engine refused, this plugin threw the reason away, sent the message to you as plain email instead, and told the visitor it had been sent. So the same question was paid on one page and free on another, and you got it as mail with no ticket to track. The widget now leaves those categories out, and in the rare case one is submitted anyway, the visitor is told plainly to open it on the Support page instead of being quietly rerouted.
- The readme now describes the one request this plugin makes. The External services section said the plugin made no external requests and had no update checker. It has had an update checker since 5.2.6, and that section was never updated. It now says exactly what the check asks for, what it sends (the plugin version and your site address, and nothing else), how long each answer is cached, and how to switch it off.
- The update check now has an off switch. Add `add_filter( 'bodholdt_contact_update_check_enabled', '__return_false' );` and the plugin makes no outside requests at all.
- Uninstall now removes the update-check cache it had been leaving behind.
- The arcade is fully translatable now, and its toasts can be dismissed. Several labels the arcade shows (the dismiss button, the "Nice" message on a plain points toast, and the level badge) stayed in English no matter what language your site runs in, because the shared arcade code never passed them through for translation. They now translate like everything else, the level number is formatted for your locale, and every celebration has a close button. Toasts also pause while you hover or focus them, and skip their timer entirely if you have asked your system to reduce motion.
- A site that cannot create the arcade tables now keeps trying. On a host where the database user is not allowed to create tables, the plugin recorded the arcade as installed anyway and never tried again, so the arcade stayed silently broken forever. It now confirms the tables really exist before recording success, and retries later if they do not.
- The arcade settings box now matches the arcade. Since 5.2.8 the arcade has been off on a fresh install, but the checkbox on the Settings screen still drew itself as switched on, and saving your settings for any reason turned the arcade on for real. The box now shows the truth, and saving your settings leaves the arcade exactly as you found it. If you had turned it on yourself, it stays on.
- Gamification is now off by default. The arcade (XP, ranks, the HUD, and confetti) starts off. Turn it on any time under Settings, then Bodholdt Contact, in the row headed "Working the Door". Nothing else changes.
- American spellings throughout. The plugin and this changelog used British spellings in places (licence, colour, behaviour, cancelled). They now match the rest of Bodholdt Labs. Wording only, nothing behaves differently.
- This plugin can now update itself. Until today it had no update client at all, so a new version, including a security fix, only reached you if you happened to visit the site and reinstall by hand. Bodholdt Contact now checks for a newer version and offers it on your Plugins screen like any other plugin. It carries no license key and sends none: the check asks a public address for the current version number, and the download is the same public link a person would click.
- The contact card now takes keyboard focus when it opens. The card is hidden behind a short fade, and focus was being moved into it before that fade had started, so the browser discarded it and focus stayed on the button behind the card. Anyone using a keyboard or a screen reader landed outside a dialog that announces itself as modal, and pressing Tab walked into the page behind instead of moving through the form. Focus now waits for the card to be on screen, which also restores the Tab trap that keeps you inside the form until you close it.
- Failed-code accounting is proven or unavailable. The OTP repository validates the exact database write, follow-up row, counter, state, and database error before returning a remaining-attempt count. Storage uncertainty returns a visible 503 without an invented attempt budget, while a concurrent fifth failure returns the proven locked count.
- Legacy IPv6 rate rows have a complete transition. Before the 5.2.4 database marker can advance, the bounded upgrade deletes every Contact-owned pre-canonical ephemeral rate row and verifies the table is empty. New collection, export, and erasure use one canonical `inet_pton()`/`inet_ntop()` representation for every equivalent IPv6 spelling.
- Identifier matching is canonical end to end. Collection, export, and erasure share one validated lowercase email normalizer. Source addresses use `inet_pton()`/`inet_ntop()` at collection and lookup, with a bounded compatibility set for still-active pre-5.2.3 IPv6 HMAC rows.
- Rate-limit storage faults fail closed. The deciding counter read and its write accept only exact database result types and reject false, null, non-integer, negative, or last-error states with a visible storage-unavailable response.
- Diagnostics cannot retain subject data. Mail health stores only allowlisted context/status timestamps for at most seven days, and an upgrade removes the older arbitrary-error field. Debug logs accept only request-independent event codes; ticket IDs, challenge IDs, addresses, inputs, and exception text are not accepted by the logger.
- Cookie wording matches runtime. The inventory now distinguishes the JavaScript-unreadable HttpOnly cookie from flow values returned in same-origin JSON to the widget.
- Pseudonymous counters are classified and handled truthfully. The privacy inventory, suggested policy, exporter, and eraser now cover still-active email-keyed abuse counters. A bounded operator API handles a separately supplied IP address without scanning or exposing unrelated rows. Database read, delete, and partial-delete faults remain visible rather than reporting a completed request.
- Delivery diagnostics are subject-free. A rejected message logs only a request-independent transport error; no stable email-derived identifier or raw sender address is written to the server log.
- The necessary flow cookie is disclosed. Operator copy names `bodholdt_contact_flow`, its opaque purpose, 10-minute maximum, `HttpOnly`/`SameSite=Lax` controls, HTTPS-only Secure flag, and early deletion.
- Privacy requests now fail visibly if storage fails. Export and erase callbacks return a WordPress error when the verification table cannot be read, a delete fails, or only part of the selected page is deleted. They no longer report a completed request that never reached the data.
- Failed resends no longer spend delivery quota. When the mail transport returns false or throws, the destination and source-address hits are refunded to the exact windows originally charged while global anti-abuse counters remain in place.
- No held-product promotion. The settings-page Ticketing cross-sell and its sales link were removed. Passive local compatibility with already-installed Tickets and Licensing releases remains documented without a purchase link.
Show the full release history (21 older releases)
- The plugin can now actually be translated. Every screen was already written for translation, but the download carried no template to translate against and no folder for finished translations, so it stayed English in every language. Both now ship. The download includes `languages/bodholdt-contact.pot`, and a finished `.mo` file placed in `languages/` is loaded automatically.
- Right-to-left languages. Every style now follows the reading direction, so the widget, the settings page and the activity report lay out correctly in Arabic, Hebrew, Farsi and any other right-to-left locale.
- You are told when your site email stops working. A broken mail transport takes the contact form down for every visitor, and it also swallows the weekly summary that would otherwise have warned you, so a dead mailbox used to look like a quiet week. The settings page and an admin notice now say plainly what could not be sent, when, and what the mail server said about it. The warning clears itself as soon as mail works again.
- A warning when a proxy hides your visitors. If a CDN or load balancer sits in front of your site and your web server does not pass the real visitor address through, every visitor shares one hourly limit and the contact form stops for everybody after fifteen codes. Tools → Site Health now reports this, the settings page says so too, and both explain how to fix it. Forwarded headers are still never trusted on their own, because anyone can send one.
- Numbers and waiting times read correctly in every language. "You can try again in about 1 minutes" is gone. Waits, counts and percentages are now formatted for the reader's language.
- Smaller fixes. "Wrong email? Go back" keeps what the visitor typed again, which is what the widget always said it did. Handing back an unused verification code now credits the correct hour. The public token-refresh endpoint follows the same on/off and storage checks as the rest of the form and is rate limited. Deleting the plugin no longer leaves two internal markers behind. Decorative characters in the interface were replaced with drawn icons.
- The minimum WordPress version is now 6.4, lowered from 6.5 to match the oldest release this code actually needs.
- Messages now reach you exactly as they were typed. A link containing encoded characters used to lose part of itself on the way to you, so a pasted address could arrive broken. Quoted markup, such as a customer showing you the HTML on their page, could vanish from the message entirely. Both are fixed. Messages routed into Bodholdt Tickets are now escaped as well, so nothing a visitor sends can render as live content in your Support Desk.
- Asking for a fresh code works again. The hourly limit on codes sent to one address was exactly the number the widget itself could spend, so "start again to request a new code" was always refused. The limit now leaves room to start over, and when a visitor does have to wait, the widget says roughly how long.
- Clearer answers when something is wrong. If the plugin cannot create its database tables, the settings page and an admin notice now say so plainly instead of reporting that the widget is live. If a saved setting had to be shortened, or had formatting removed, the page now names the field that changed instead of only saying "Settings saved".
- Friendlier recovery in the widget. After too many incorrect codes the widget returns the visitor to the email step, rather than offering a Resend button that could never work. The resend countdown now follows real time, so it no longer looks stuck after someone switches to their mail app on a phone.
- Sites behind a proxy or CDN. Added a `bodholdt_contact_client_ip` filter so operators whose web server does not pass through the real visitor address can supply it. Without it, every visitor could end up sharing a single rate limit bucket. Forwarded headers are still never trusted by default.
- Smaller fixes. A three-digit color such as #0af no longer produces invalid styling. The message box now shows its length limit before you send. The suggested privacy policy wording and the erase result describe the ticket store accurately when Bodholdt Tickets is handling messages. The Licensing note no longer contradicts the status card. Gamification labels can now be translated. A failed table creation no longer repeats a full migration on every page load, and no longer disables the activity report permanently.
- Privacy tools (GDPR export and erase). The plugin now integrates with the built-in WordPress privacy tools. Tools → Export Personal Data returns the verification records held for an email address, and Tools → Erase Personal Data deletes them, so you can answer data subject requests the standard way. The plugin also adds suggested wording to Settings → Privacy for your privacy policy. No change to how the contact form works.
- Accessible contact form (WCAG 2.1 AA). The visitor widget now passes accessibility review: readable success and error text in dark mode, visible field borders and focus outlines, autocomplete on the email and verification-code fields, a properly announced delivery confirmation, and correct focus handling on Resend. This matters if your site serves visitors in the EU, where the European Accessibility Act applies to consumer-facing forms.
- Licensing and packaging. The bundled confetti library now ships its required copyright notice, and the release archive uses correct, non-world-writable file permissions. The readme now names the database tables the plugin creates.
- Stronger verification state. One-time codes and opaque flow tokens are stored as HMAC hashes in plugin-owned per-site tables. Each challenge is bound to its browser flow, expires quickly, enforces exact six-digit input and attempt/resend limits, and can authorize at most one delivered message through atomic state transitions.
- Bounded abuse handling. Atomic database-backed limits now cover source addresses, destination emails, global mail volume, widget opens, and message attempts. An hourly security task removes expired challenges and counters; a failed delivery releases the verified flow for a safe retry instead of silently consuming it.
- Precise customer recognition. Skipping the emailed code requires a signed-in WordPress account whose email exactly matches the address entered. Licensing mode additionally requires a currently active, non-expired Bodholdt license, checked again when the message is submitted. A known email address alone is never proof of identity.
- Safer request boundaries. Each public action has its own nonce, cached pages can refresh the complete nonce set, malformed non-scalar fields are rejected cleanly, and message length is validated rather than silently truncated.
- Safer delivery. A support ticket is considered delivered only after its first message is stored. If that partial write fails, direct-email fallback occurs only after the empty ticket is confirmed removed, preventing duplicate or orphaned customer conversations.
- Deliberate first run and upgrades. Fresh installations start with the widget hidden and weekly summary email off, then show a direct setup notice. Existing installations retain their prior live/hidden behavior and automatic reports even if they never opened the old settings page. In-place and automatic updates create required storage on the first request instead of waiting for an administrator visit.
- Operator controls that match the documentation. The settings page now exposes the weekly activity email and the private Fun Pass, sound, and confetti switches. Blank display copy falls back consistently in both the preview and live accessible dialog.
- Complete lifecycle support. Activation creates or upgrades per-site security storage and schedules cleanup; network activation covers existing sites and sites added later. Deactivation clears short-lived security state and scheduled tasks without deleting configuration. Uninstall removes this plugin's settings, owned tables, legacy verification remnants, scheduled tasks, and only its own rows from shared Bodholdt Arcade tables.
- Requires WordPress 6.4 or newer and PHP 8.0 or newer.
- Sturdier under odd input. The contact endpoints and the settings save now shrug off malformed requests (for example a field sent in the wrong shape) and return a normal validation response instead of erroring. This only ever affected hand crafted requests, never a real visitor or a normal save, but it keeps your error log clean.
- Precise multisite cleanup. On a multisite network, turning the plugin off on a single site now leaves the other sites' weekly report untouched; only a full network deactivation clears them all.
- Housekeeping. Trimmed a few unused entries from the "Working the Door" scoreboard so its catalog only lists rewards you can actually earn.
- Cleaner activity readout. The "last 7 days" line and the weekly email always show sensible figures now. Percentages stay between 0 and 100, the counts never read backwards, and the wording reads correctly even when a number is 1.
- Tidier multisite cleanup. On a multisite network, turning the plugin off now clears its scheduled weekly report on every site, leaving nothing behind.
- True open-rate. The activity summary now also counts how many people opened the widget, so you can see the whole funnel: opened, entered an email, and finished. It reports the overall share of opens that turned into a message, which tells you at a glance whether people are dropping off before they even start.
- See how your contact form is doing. The settings page now shows a short "last 7 days" activity line (how many people started, how many were sent a code, and how many finished after verifying), and you get an optional weekly email summary. This makes it easy to tell whether the email step is turning visitors away, so you can switch recognition mode if it is.
- Stronger proof before the code is skipped. A returning customer now skips the email code only when they are signed in to the matching account. A visitor who simply types a known customer's email address is asked to verify by code like anyone else. This closes a way someone could send you a message posing as one of your customers.
- Tougher on abuse. Verification codes are now capped per destination address as well as per sender, a valid code can be used only once even under a rapid burst of requests, and automated "bot" traffic is rate limited before it can touch anything. Together these bound spam and reduce the widget's usefulness for sending unwanted code emails to other people.
- Off means off. When you switch the widget off, its background endpoints stop responding too, not just the visible button.
- Accurate tallies. The "Working the Door" counts tick over only after a message is actually delivered, so your Door Report reflects reality even when mail is misconfigured.
- Polish. Very long messages are trimmed to a sensible length, the celebration animations stay within the screen, and the arcade labels are translatable (the shared level-up HUD badges stay in English, since a shared component cannot carry a fixed text domain).
- The code is checked the moment you enter it. When a visitor types their verification code and continues, the widget now confirms it right away, so a wrong or mistyped code is caught before they write their whole message instead of after.
- The widget keeps working on cached sites. If your pages are served from a cache and the security token in the page has gone stale, the widget quietly refreshes it and carries on, so the contact form no longer breaks with a "network error" on heavily cached sites.
- You are told when a code cannot be emailed. If your site cannot send the verification email (for example mail is not configured yet), the visitor now sees a clear "please try again in a few minutes" message instead of being stranded on a code screen with no code ever arriving. Submitted messages are handled the same way, so a message is never quietly lost.
- Clean removal. Deleting the plugin now removes its settings, its "Working the Door" tallies, and its rows in the shared arcade tables. The four shared `bod_arcade_*` tables and the `bod_arcade_db_version` option are deliberately left alone, because uninstall has no safe way to tell whether another Bodholdt plugin still needs them. If Contact was the only Bodholdt plugin on the site, those four tables stay behind empty and you can drop them yourself.
- Polish. The notification address now tells you if what you entered was not a valid email instead of silently ignoring it, the header, intro and success text show exactly what you typed, those fields gained sensible length limits, and the "Working the Door" operator text is translatable.
- The widget stays readable with any accent color. Button text and links now choose their color from the accent's real contrast, so a light accent (like a bright cyan) gets dark, legible text instead of washing out on the white card. The admin live preview reflects the same result.
- Working the Door. A calm, optional arcade for you, the operator. Because contact activity happens while you are away, the settings page now greets you with a Door Report of who you waved through, the strangers who verified, the bots you bounced, and the messages handled since your last visit, plus a rank and XP that grow as your door gets busier. It sits behind a Fun Pass switch (on by default, sound off) and stores everything in your own database.
- A clearer settings page and a preview you can walk through. The page now opens with a status card that answers "is my widget live and doing what I set up?" in plain sentences: whether it shows, where messages go, who skips verification, and the address you are notified at. The live preview steps through all three screens a visitor sees (email, then the 6-digit code, then the message and its success note), so the verification flow you configure is no longer invisible. On wide screens the form and preview sit side by side. Accent color gains curated swatches and a readability note, and the appearance and corner-style choices became tidy segmented controls.
- The verification step no longer dead-ends. If a visitor's code does not arrive or they typed the wrong address, they can now "Resend code" (with a short countdown that respects the anti-spam limit) or go back and fix their email. Running out of attempts now says so and offers a fresh code instead of failing silently. The opening line also explains why the email is asked for first.
- Added a dismissible panel on the settings page inviting you to turn contact messages into trackable Bodholdt Tickets when that plugin is not active. This mirrors the confirmation shown when it is active. Nothing about the widget changed.
- White-label appearance. The widget now offers Light, Dark, or Auto (match the visitor's system) display modes plus a corner-style choice, all derived from your single accent color with readable text and contrast shades computed automatically, so it blends into any site. The settings page got a matching refresh and the live preview reflects every change.
- WordPress.org readiness pass: added translator comments for placeholder strings, removed the manual text-domain load (WordPress loads translations automatically), and hardened a server-variable read. No change to how the widget behaves.
- Renamed the Bodholdt Tickets integration to its current name and refined the verification step layout. The email address and the "enter your code" instruction now sit on their own lines.
- Settings page now shows a live preview of the widget that updates as you change the accent color, header, and intro (§7.4 Steve Jobs pass).
- Bodholdt Tickets front-door. With a compatible Bodholdt Tickets release active, contact submissions can create a support ticket (and notify you by email) instead of a plain email, so customers can track and reply to them, and you work them in the Support Desk. If Tickets has categories, the widget shows a “What’s this about?” picker. Without a compatible Tickets API, it behaves exactly as before (emails you). All recognition + anti-spam unchanged.
- Reconfigured for distribution: renamed to Bodholdt Contact, added a multi-tenant settings page (brand name, accent color, copy, notification email, customer-recognition mode), made visitor-facing branding configurable, internationalized the interface (text domain `bodholdt-contact`), and packaged it for release. All anti-spam protections (one-time code, honeypot, rate limiting, brute-force lockout, timing-safe comparison) retained.