• v0.9.4 c240029f39

    v0.9.4 Stable

    Robin released this 2026-10-01 12:10:35 +02:00 | 2 commits to main since this release

    A fix release for everyone who imports subscribers. If you ever imported a file of more than ~330 rows, part of it never arrived — run it again after updating.

    🐛 Importing subscribers

    • Large files were silently cut. A CSV of 749 rows reported "333 updated" and nothing more: PHP caps the number of fields a request may carry (1 000), and every cell travelled as its own field, so everything past row 333 was dropped before the plugin saw it — and the list you had ticked went with it. Rows now travel in batches of 200 with a progress counter, the lists ride along, and if a batch ever comes back incomplete the import stops and says so instead of ending on a false "Done".
    • The list is never a guess. The list the Subscribers page is filtered by is pre-ticked in the import window, and the result names the lists the addresses were added to — "Added to: General." If you forgot to tick one, you see it at once.

    🐛 Subscribers

    • Bulk "Delete permanently" did nothing outside the trash — and said nothing. The bulk menu now follows the tab: Move to trash everywhere, Restore and Delete permanently in the Trash tab, next to Empty trash. Any bulk action that could not apply to every selected row tells you how many it touched.
    • Deleting a subscriber also removes them from their lists; stray list memberships no longer inflate the list sizes.
    Downloads
  • 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
  • v0.9.2 653ee43126

    v0.9.2 Stable

    Robin released this 2026-09-28 19:19:54 +02:00 | 14 commits to main since this release

    A security release first, a feature release second. Update as soon as you can — the security section below is honest about what was exposed in 0.9.1 and earlier. Then the good news: popups that behave like any other form, a Newsletters dashboard that actually tells you something, and a list view when the cards get in the way.

    🔒 Security — please read

    We ran a full audit of the plugin (output escaping, every server entry point, the public forms and links, SQL, stored content). Good news: no cross-site scripting and no SQL injection were found. Less good news: several real problems were, and this release closes all of them. In order of seriousness:

    • Your newsletters, email templates and forms were readable by anyone through the WordPress REST API (/wp-json/wp/v2/lrob-etk-…), without logging in — including the content of unsent newsletters and the recipient addresses configured on your contact forms. WordPress does not hide "private" post types from REST; the plugin now does. Gutenberg and the block pickers keep working.
    • The signup form could be turned into an email cannon: no rate limit, and one confirmation email per request to any address you typed. There is now a budget per visitor, one confirmation email per address every ten minutes (five a day at most), and the same rule on address changes from the preferences page.
    • Anyone could subscribe any WordPress account of your site — typing an account's email opted it in on the spot, added it to lists and recorded a "consent granted" entry in its name. Now an account joins either signed in, or through a confirmation email sent to the account itself.
    • The per-visitor rate limit could be bypassed by sending a fake X-Forwarded-For header. The plugin trusts the real connection address unless you tell it which proxy header to trust (LROB_ETK_TRUSTED_PROXY_HEADER in wp-config.php, e.g. for Cloudflare).
    • The built-in captchas (math question, picture) carried their own answer in a hidden field a bot could decode, and never expired. Tokens no longer reveal anything, last fifteen minutes and work once.
    • A forged bounce report mailed to your sending address could unsubscribe or suppress any subscriber. Every email now carries a signed recipient tag; a report that doesn't prove it answers one of your emails counts, at most, as one soft bounce.
    • Link previews could act on someone's behalf: a mail scanner prefetching the "decline" or "confirm my new address" link did the deed. Both land on a page with a button now.
    • The open pixel counted people who had not consented to be measured (only the "deliverability" pixel was allowed for them). Opens follow the same consent rule as clicks, and count at most once per hour.
    • A visitor's uploaded SVG opened inline as your site when an administrator clicked it in the inbox — a scripted SVG would have run with admin rights. Only images and PDFs open inline; everything else downloads, and every file response is sandboxed.
    • A visitor's name could add hidden recipients to the notification's Reply-To (Ann <x@evil>, Bob). Cleaned.
    • Permission tiers had gaps: newsletter staff could flip the site's tracking-consent mode and log retention (admin-only screens), inbox staff could purge submissions for good, any logged-in account could write one plugin preference. Aligned with what each grant promises. Custom CSS printed on the public site is now reserved to administrators.
    • Smaller: a re-signup could rewrite an unconfirmed subscriber's data before any confirmation (now staged until the link is clicked); the "form filled too fast" trap could be skipped; deleting stored files compared paths loosely; a few PHP handler extensions were missing from the upload denylist.

    ✨ New

    Popups that are just forms

    The one-popup-per-site setting is gone. Any subscribe form can show itself as a popup: a Popup section on the form's card, with a switch. So a popup has everything a form has — fields, style, custom CSS, defaults, captcha, consent box — and you edit it like any other form.

    • Where: kinds of pages (front page, posts, pages, archives, search, custom types), plus specific pages, page sections (a page and everything under it), post categories, and URL patterns.
    • Several popups can coexist, each with its own targeting. Where two would meet, the more specific one shows, and the Forms view tells you about the overlap.
    • When: after scrolling, after a delay (never under three seconds — a popup is never instantaneous), when the mouse leaves, or only on click; three presets; a warning when the combination would annoy visitors.
    • Preview here: the real popup over the admin page, without leaving the editor. Preview on the site still exists.
    • A page or category deleted after being picked is shown as such and simply never matches.
    • Your existing popup migrated onto the form it showed.

    The Newsletters dashboard is the statistics

    The old tiles page and the separate Statistics tab are one screen: at a glance, month by month, newsletter by newsletter, by list — and every section ends with the buttons to what it draws from (subscribers, newsletters in preparation, forms, lists, deliverability). Nothing sent yet? It says so and offers to write the first one. The toolkit's own dashboard shows the Newsletters panel even before the first send.

    Cards or list

    Newsletters and SMTP identities get a two-icon switch top right. List shows one row per item — status, name, the essentials, a date — and a click unfolds the full card. Remembered per person and per section.

    A default for every setting

    Every setting a form or a newsletter can carry now has a site-wide default. Subscribe forms get a Defaults button: success message, the messages for people already subscribed or who never confirmed, the page after confirming, custom CSS for every form. New newsletters are stamped with defaults from the settings: lists, signature, tracking, logging — changing a default never rewrites existing newsletters.

    Say it when someone is already subscribed

    A polite built-in message for people who sign up while already on the list (check the spam folder, add us to your contacts), and another for those who signed up but never confirmed (the confirmation email is sent again). Both customisable per form or for the whole site.

    About page

    Version, requirements, licence, links to the guides, the changelog, the source and the issue tracker — and credits. With thanks to Benoît for valuable feedback during the beta test.

    🐛 Fixed

    • Unticking Track opens or Track clicks on a newsletter did not hold.
    • A captcha next to the submit button now lines up with it, and hosted captchas (Turnstile, hCaptcha, reCAPTCHA) are never clipped when the column is narrow.
    • A send interrupted mid-batch (timeout, server error) left recipients stuck and the newsletter marked "sent" without them; they are picked up again. Two senders starting the same newsletter at once could zero its recipient count.
    • The privacy erasure silently failed for the second person erased on the same newsletter.
    • The Plugin data screen was unreadable in the dark theme.
    • The French now fits its fields — shorter labels, placeholders that end in "…" instead of being cut.
    • Deleting the plugin with "wipe" left user data, the plugin's posts and the upload folders behind; "archive" deleted definitions the kept tables needed.

    🧹 Under the hood

    Inline scripts moved to files, styles on design tokens, one shared delete dialog for forms, one escaper in the admin scripts, schema v24 with the indexes the send loop was missing, the popup and dashboard code documented in docs/.

    Downloads
  • v0.9.1 d8e34af15c

    v0.9.1 Stable

    Robin released this 2026-09-25 16:18:23 +02:00 | 16 commits to main since this release

    The big catch-up before 1.0: everything still owed from the earlier cycles, every request collected since 0.8, and a long list of fixes. It's a large release — the headlines come first, the full list follows.

    ✨ New

    Newsletter statistics

    • A Statistics view next to Newsletters: pick a period (30 days, 90 days, 12 months, everything) and read the tiles — newsletters sent, emails delivered, open rate, click rate, unsubscribe rate, cold subscribers.
    • Month by month: a chart of emails sent with open and click rates on top.
    • Newsletter by newsletter: a sortable table with gauges; click a row for the drill-down — time to first open, opens and clicks over the first two weeks, most-clicked links, results by recipient domain, the latest activity.
    • By list: how each list performs.
    • On the dashboard: a Newsletters row — last 30 days, the latest newsletter, cold subscribers, sends in progress.
    • On every sent newsletter: opened / clicked gauges, unsubscribes, time to first open and the top three links.
    • For each subscriber: the last twelve newsletters they received, with Opened, N clicks, no sign or Failed.
    • Rates only count the people who agreed to be measured, so they stay honest under the consent mode. No charting library: the charts are drawn on the server.

    A signup popup

    • One popup for the whole site, showing any of your subscribe forms — as a centered window, a corner card or a bottom bar.
    • When it shows up: after scrolling, after a delay, when the mouse leaves the page, or only when a visitor clicks a link or button you mark.
    • Polite by design: at most once per visit, not again for N days once closed, never again once subscribed. Three presets — Gentle, Balanced, Insistent — and a warning when a setting would nag.
    • Where: pick the kinds of pages (posts, pages, front page, archives, your custom post types, WooCommerce products…) and add or exclude URL patterns.
    • Preview on the site before switching it on.

    Contact-form conversations

    • Close a conversation once it's settled — from the inbox, in bulk, or with Send and close straight from the reply box. The visitor's page then says the conversation is closed and points to the form for anything new. Reopen any time.
    • A formatting toolbar for visitors (bold, italic, link, lists), switchable per form. What they format is kept everywhere — the inbox, the notification email, the conversation — through the same safe filter as your own replies.

    Deliverability

    • SPF, DKIM and DMARC proven by a real send: one click mails an identity to itself and reads what the receiving server actually verified — no more guessing from DNS.
    • A return address per recipient (VERP), optional per identity: every bounce then names the exact person, even when the reporting server quotes nothing.
    • Watch the pace before sending: a Pace button on every draft plays the send as a dry run under your real limits — who goes when, and why it pauses ("waiting on orange.fr").
    • Bounce reading rewritten and checked against 796 real bounce reports from about 90 mail systems (Orange, Wanadoo and La Poste included): a full mailbox or a spam-policy refusal no longer retires an address for good, delay notices are no longer counted as bounces, and spam complaints are now recognised.

    Subscribers and lists

    • Merge one list into another: members, exclusions, newsletter audiences and signup-form defaults all move across.
    • One person, one recipient: an address that belongs to a WordPress user now joins your lists as that user when imported, instead of becoming a duplicate subscriber.
    • Who downloaded which file: a Downloaded column in the recipients of a newsletter, and each file's download count opens the list of people who fetched it.
    • "Not measured" filter in the recipients of a newsletter.
    • A Measurement column in the subscribers table, and a Consent section in each subscriber's details: their answer, when and where they gave it, what that means under your current setting, and their whole consent history with the exact wording they saw.
    • Your own wording for the consent box, site-wide — and per form, by clicking the text in the form editor. The box is never pre-ticked: under the GDPR a pre-ticked box is not consent.
    • The consent box can be added back to a signup form if it was removed (Tracking consent in the field list).
    • Where the confirmation link leads: each signup form sends new subscribers to the built-in confirmation page — whose title and message you can now word — or to any page of your site.

    Forms

    • Custom CSS for every form, and for all contact forms at once — tucked under Customize → Add custom CSS, with the form's class shown ready to copy and a guide with copy-ready examples (en français).
    • Three more style settings: button colour on hover, form maximum width, helper and error text size.
    • Preset swatches: every style preset now shows its colours, corners or spacing at a glance.
    • "Used on" for the preferences page: see which pages carry the Newsletter preferences block.

    Permissions

    • Give access to one person, from their profile: Newsletters or Inbox for an author or a subscriber account, without changing their role.
    • Finer grants: Subscribers only (subscribers, lists, forms, imports — no sending) and Inbox, read-only (follow conversations without answering or deleting).

    💅 Improved

    • Calmer screens everywhere: explanations moved behind the ⓘ hints, labels that say the unit and what 0 means — 35 paragraphs gone from the admin, none of the knowledge lost.
    • One empty state for every "nothing here yet" and "nothing matches these filters" across the admin.
    • Styled dialogs replace the browser's own alert boxes that were left in a few places.
    • Checkboxes in your forms now follow the size of their text and resist themes that restyle checkboxes; labels no longer follow a theme's text alignment.

    🐛 Fixed

    • A single checkbox in a contact form lost its label after the editor was reopened and saved.
    • The consent box's text could not be edited — the editor saved it empty and the default came back.
    • Add list read Ajouter une identité in French.
    • The preferences page showed an empty Mailing lists section when no list was open to subscribers.

    🔧 Under the hood

    Much of this release is invisible, and makes the next ones faster to build and safer to change:

    • About 2 650 lines of inline scripts and duplicated markup removed from the admin page classes: the Lists and Subscribers scripts became proper files, the two storage modals share one renderer, the inbox uses the shared detail and confirm dialogs, one asset-versioning helper replaces six copies, and the Subscribers page hands its three modals to their own classes.
    • A shared settings-row component replaces the newsletter-only one, on design tokens instead of hard-coded sizes.
    • A readme.txt for wordpress.org, with the required list of external services (the three hosted captchas, the update check of this build) — the first step toward the 1.0 publication. The license header now reads GPLv2 or later.
    • Automated browser checks against the test site before each change is handed over.
    • Database update (schema 9: closed conversations, per-recipient return address) runs by itself on update.

    Requirements

    PHP 8.2+ and WordPress 6.8+.


    Existing installs update automatically from the WordPress Updates screen. Full history: docs/done.md.

    Downloads
  • v0.9.0 ee38f337e7

    v0.9.0 Stable

    Robin released this 2026-09-18 23:26:03 +02:00 | 45 commits to main since this release

    An early 0.9 with three things worth having right away. Please update.

    Fixed — importing from the Newsletter plugin twice doubled every list

    Running the Newsletter plugin import a second time created a copy of each list (Customers and Customers-2) instead of completing the one already there — the import screen preselected Create it here for every list, and its "attach to the existing list" safety net could never fire. The screen even said running it twice was safe. It is now: a list of the same name is preselected and merged, and a new list is created only when none exists.

    If your site already has -2 copies, don't delete them by hand: the copies may hold people the originals lack. Ask for the merge script, or wait for the built-in merge planned for 0.9.x.

    Fixed — "change this" at the bottom of your emails led nowhere

    Measured recipients see We measure opens and clicks to improve our emails — change this. Under the Everyone tracking mode, the page it linked to offered nothing to change. It now does: a Measurement box, ticked by default, that anyone can untick. A refusal is honoured — at most the deliverability pixel remains — and only a refusal is recorded, never a box someone left ticked. Under the Consent mode nothing changes: the box is the consent, off until given.

    New — the preferences page speaks your words

    The page behind Manage preferences and Unsubscribe can now be worded by you: its title, the notice shown when someone arrives from the unsubscribe link, and the title and message shown once they have unsubscribed. Find them in Newsletters → Settings → Newsletter footer and preferences page, next to the footer texts that were already there. {{site_name}}, {{site_url}} and {{email}} work in all of them; empty a field to get the default back, translated in the site's language.

    Requires PHP 8.2+ and WordPress 6.8+.

    Downloads
  • v0.8.9 d18b4efb16

    v0.8.9 Stable

    Robin released this 2026-09-17 17:18:02 +02:00 | 46 commits to main since this release

    Two fixes

    • Newsletter privacy — "Who is measured". Your choice was saved, but after a reload the page showed the setting as unset. The same rows exist twice on that screen (on the page and in the Settings dialog) and shared one radio group; each copy is now its own group, and changing one updates the other on the spot.
    • Form editor — the "Required" box. Since 0.8.8, ticking Required on a field saved correctly but the box itself looked unticked until it lost focus. The style that keeps form fields consistent across themes was also repainting the checkbox; checkboxes and radios are now left to the browser.

    Requires PHP 8.2+ and WordPress 6.8+.

    Downloads
  • v0.8.8 36b6af67d2

    v0.8.8 Stable

    Robin released this 2026-09-16 19:13:10 +02:00 | 47 commits to main since this release

    Your forms look the same in every theme

    Some themes style every label, input and button on the site, and a form dropped into them picked up whatever the theme decided: 30 px of empty space under each field, the anti-spam hint drifting away from its box, a submit button that never took the colour you chose, a sending spinner pushed onto its own line. Elementor sites were the most affected.

    The form now settles every one of those properties itself. What the page decides — its font, its text colour, its palette — still flows into the form, and any style you set in the form's own settings wins over the theme, every time.

    If you had shrunk the spacing or the button padding to compensate, put them back: the space they were fighting is gone.

    Elementor widgets

    Contact form (LRob) and Newsletter signup (LRob) now sit in the Elementor panel, in an LRob category: drop one in, pick the form, and it renders live in the editor — the same form, the same styles, the same anti-spam as the block.

    Shortcode for the contact form

    [lrob_etk_contact_form id="12"] places a contact form anywhere a shortcode works — Classic Editor, page builders, widgets. The ⓘ next to Used on shows the three ways to place each form, with the shortcode ready to copy.

    Small things

    • Used on now lists the pages carrying a form through Elementor, not just through the block editor — and a conversation opened from such a page renders in the form's place there too.

    Requires PHP 8.2+ and WordPress 6.8+.

    Downloads
  • v0.8.7 d183396ec4

    v0.8.7 Stable

    Robin released this 2026-09-11 21:18:47 +02:00 | 48 commits to main since this release

    How long will this send take?

    A sending limit raises a question it never answered: and so when does it arrive? Now it tells you. On the Deliverability settings, under the limit itself — 1 000 emails per hour at best; 1 000 recipients would take about 1 h — and on each newsletter, for its own audience: at the current sending pace, about 2 h 24. Change the limit and the sentence follows, straight away.

    Deliverability, readable at last

    The coloured badges were all grey. SPF, DKIM, DMARC, your bounce and complaint rates, subscriber statuses: everything that was supposed to be green, amber or red rendered the same neutral grey, so none of it meant anything. Colours are back everywhere.

    And the authentication badges no longer over-promise. They read what your domain publishes in DNS — that is all a DNS lookup can decide — so they now say exactly that, and print what they found: your SPF record, your DMARC record, and for DKIM the selector together with the fact that it was guessed from the usual names. Two situations that DNS really does settle turn red: an SPF record that authorises nobody, and a revoked DKIM key. A DMARC record set to p=none shows amber — it collects reports and protects nothing.

    Hovering any badge now explains it, including when a record is missing — which used to be the one case that said nothing at all.

    The page reads like a settings page again, not a manual: explicit titles with the explanation one hover away, instead of a paragraph under every field.

    Elsewhere

    • Unread messages in the admin bar, on every screen, so a contact-form message doesn't wait for you to visit the right page.
    • The success message of a form can now be sized like everything else around it.

    Requires PHP 8.2+ and WordPress 6.8+.

    Downloads
  • v0.8.6 f62222d16b

    v0.8.6 Stable

    Robin released this 2026-09-11 13:12:58 +02:00 | 53 commits to main since this release

    Three irritations gone

    The dashboard chart was empty. On Since the beginning — the range it opens on — you saw a flat red line and nothing else, on any site with more than a couple of months of history. The counting was right; the chart was reading it off a grid that didn't line up, so every bar came back at zero. Fixed, at every zoom level.

    A newsletter you just created went to the bottom of the list. It now appears first, where you are looking for it.

    Deleting a newsletter no longer sends you to the Trash. The card disappears where it is, the tab counters update, and you stay exactly where you were — so going through a list and clearing out what you don't need is a few clicks instead of a page reload and a trip back each time.

    Requires PHP 8.2+ and WordPress 6.8+.

    Downloads
  • v0.8.5 7389a81966

    v0.8.5 Stable

    Robin released this 2026-09-11 12:53:03 +02:00 | 54 commits to main since this release

    Your signature, on your replies too

    The signature you set on your profile was only for newsletters. It now signs the replies you send from the submissions inbox as well — if you want it to.

    • One switch on your profile: Add my signature to the replies I send to contact-form messages.
    • Before you send, the reply box shows you exactly what is about to be attached, with a link to edit it or switch it off. You never discover your own signature in the recipient's copy.
    • It goes out with the email; the reply kept in the conversation stays what you typed, the way your mail client already works.

    The section is now simply Email signature, and anyone who answers the inbox can have one — not just the people who send newsletters.

    The preview follows what you type

    Editing a signature no longer means saving to find out what it looks like. The preview updates as you type, at the true width of an email, and an unfinished tag half-way through a paste is not a problem. It stays perfectly inert while it does it: nothing in a signature can run, which matters when an administrator opens someone else's profile.

    And the Edit signature links now land on the signature itself instead of the top of a long profile page.

    The subscribers table gains a Consent asked on column (pick it in the columns chooser): the date of the last consent entry recorded for that address — the confirmation of their subscription, their choice about measurement, an unsubscribe, whichever came last. Useful when you need to show, address by address, that a consent exists and when it was given.

    A signature set during 0.8.4 needs pasting again — it moved house.

    Requires PHP 8.2+ and WordPress 6.8+.

    Downloads