• v0.9.3 91d7b1af19

    v0.9.3 Stable

    Robin released this 2026-09-30 18:15:47 +02:00 | 6 commits to main since this release

    A release shaped by real use: three requests from the beta test, and four things a production site taught us in one day. Update as soon as you can if your site sits behind a page cache or a web application firewall — the forms section explains why.

    ✨ Subscribers: export, and a re-import that behaves

    • Export CSV — a button on the Subscribers page. It exports the rows of the tab and filters you are looking at (search, list, WordPress users, sort), with the same columns the import reads back. Sort or correct in a spreadsheet, import again. Opens cleanly in Excel (accents included, ; separator on French sites); a cell that a spreadsheet would run as a formula is neutralised, phone numbers stay phone numbers.
    • A re-import updates the name of an address already here, like every other column — it was the one thing a file could not correct.
    • A re-import never re-enables someone who left: unsubscribed, refused, bounced or trashed addresses found in the file are left exactly as they are, and counted as such. A Re-enable… box (off by default) is there for a migration where the file is the truth.

    ✨ Newsletters: exclude a list from a send

    Under Recipients, an Excluding picker with the same lists. Send to "News" minus everyone in "General" — typically the people who already received this email. The count tells you how many are left out, the pace estimate follows, and a list ticked on one side leaves the other.

    🔧 Forms: page caches and firewalls

    • Forms work behind page caches. Three per-visitor values used to be baked into the page's HTML, which a cache (W3 Total Cache, WP Rocket, a CDN) freezes for everyone for hours: the security token (stale after a day → "this form has expired"), the timing check, and — since 0.9.2 — the built-in anti-spam question, whose one-time token the first visitor burned for all the others. Now the question is fetched by each visitor when the page loads, and renewed in place if it expires (it lasts an hour); the other two values are measured client-side or only required of signed-in visitors. Nothing per-visitor is left in the page.
    • A form's own error message always reaches the visitor. On hosts running the Comodo web application firewall, an expired form or a refused anti-spam answer came back as a "server returned a non-JSON response" error: the firewall replaced the plugin's answer with its own page. The answer no longer trips it.

    💬 Conversations

    • A reply sent from the site never shows your email address. It goes out under the form's sending identity, with your name as the sender name — "Robin (Site name)" — and no Reply-To. Before, a visitor who hit Reply in their mail client wrote straight to the admin's own mailbox, outside the conversation.
    • Visitors are answered in the language of the page they wrote from. The form's messages, your reply's wording and buttons, the subscribe confirmation, the reminders, the email-change emails and the newsletter footer follow the language of the page (what TranslatePress, WPML or Polylang declare), when your site has that language installed.
    • The View the conversation and reply button in a reply email now lands on the conversation, not at the top of the page.

    🧹 Under the hood

    SubscribersPage::collect_rows() shared by the list and the export; exclude_list_ids in the newsletter's audience spec, retargeted on list merge; Support\VisitorLocale; the homemade captcha placeholder is a signed request; the e2e driver never leaves a Chromium behind.

    Downloads