• v0.3.2 22fc58a689

    UltimateByte released this 2026-05-23 23:54:14 +02:00 | 67 commits to main since this release

    Newsletter alpha (still alpha — needs field testing)

    Step 9 — tracking pipeline. Every outbound newsletter gets a per-recipient rewrite pass before send: image URLs and link hrefs are routed through HMAC-signed REST endpoints (/wp-json/lrob-etk/v1/nl/track/{img,click}/<token>). Newsletters without any <img> get a 1×1 open pixel appended. Test sends (separate code path) bypass the pipeline entirely.

    Counters. Open + click events bump per-recipient (newsletter_recipients.opens/clicks) + per-newsletter aggregate (opens_count/unique, clicks_count/unique) + per-subscriber lifetime stats (total_opened/clicked, last_engagement_at, sends_since_engagement). Mirror keys on wp_users user_meta for WP-user recipients.

    Cold-subscribers sub-tab in Subscribers admin filters by sends_since_engagement >= threshold (default 5). Settings page exposes the threshold + an opt-in to count opens as engagement (off by default — Apple MPP server-side image loads inflate opens to ~100%). Daily retention cron prunes tracking_events past lrob_etk_nl_tracking_retention_days (default 365).

    Also in this release

    • Newsletter list polish from v0.3.1: sub-tabs (In preparation / Sent / Trash) with live counts, trash system (wp_trash_post + Restore + Empty-trash), recipients drawer (paginated newsletter_recipients view with status-filter chips + email/name search + cross-link to Logging entries on failed rows). Newly-created newsletters sort to the top.
    • Display fix: NewsletterRepository::list_all now selects the opens/clicks counter columns so the card actually shows what the tracking endpoint records.
    • fr_FR translations complete (1142 strings).

    Still alpha — please report any issue on GitHub.

    Downloads
  • v0.3.0 17a9906137

    UltimateByte released this 2026-05-23 12:00:01 +02:00 | 69 commits to main since this release

    🚧 Alpha — do not use in production yet. The Newsletter module is feature-complete enough to test end-to-end but hasn't seen real-world send loads. Field testing welcome; feedback / bug reports via GitHub issues.

    What's in this release

    The Newsletter module ships with everything needed to actually run a campaign on a small list:

    • Subscribe forms with double-opt-in confirmation, RFC 8058 one-click unsubscribe, captcha + honeypot + time-trap, customisable templates.
    • Categories + Lists: opt-out by category at the recipient level; manual lists for targeting.
    • Per-recipient preferences page (public token URL + WP-profile section + Gutenberg block + shortcode).
    • Onboarding templates (confirmation / reminder / refuse-acknowledgment) edited in the block editor with a token registry.
    • Trash + refuse polish for subscribers.
    • Newsletter authoring via inline cards on the Newsletters admin view — every newsletter's settings, audience, sender identity, schedule, and send actions live on its card. Gutenberg is content-only. Send button is state-aware: "Send now" ⇄ "Schedule" ⇄ "Unschedule" (red).
    • Send pipeline with chunked recipient materialization, claim-based send loop (race-safe between AJAX and Cron), per-recipient status snapshot, real-time progress bar.
    • WP-Cron safety-net + pause/resume/abort + scheduled-send cron handoff.
    • SMTP circuit-breaker: consecutive-failure threshold trips the newsletter to `paused` with a red banner explaining "fix SMTP then Resume". "Retry failed (N)" bulk action re-queues failed recipients for another attempt.
    • Logging integration: failures are kept in the logs table, successes are pruned by default (data already lives in `newsletter_recipients`), per-newsletter "Log every send" override. Logs page has a tri-state Newsletter filter. Recipients drawer cross-links to each failed row's log entry.
    • WP-Cron health diagnostic at the bottom of the Newsletters view: last observed tick, next scheduled tick, `DISABLE_WP_CRON` flag, colour-coded verdict, manual Refresh button + opt-in 10s auto-refresh toggle. Tooltip explains what a cron is for non-technical users.
    • Live clock-tick + adaptive server-poll on the cards: relative-time displays update every second when a deadline is imminent, fall back to minute/hour cadence further out. Server-poll runs every 10s only when an interesting card exists; pauses when the tab is hidden; localStorage dedup so multiple tabs don't multiply server load.
    • Inline LRob tip when the site relies on pseudo-cron, suggesting a server cron for reliability.

    Other changes

    • Logging module schema migration v1→v2 (renamed reserved `campaign_id` → `newsletter_id`).
    • French translation sweep — 1113 entries, only the .po file header left untranslated.
    • `release.sh` polish: JS syntax check via `node --check`, dead-CSS scan with peel-once heuristic, per-language msgfmt stats, file-type + LoC stats.

    Coming next

    • Step 9 — Tracking: image-rewriter (primary open signal via media URLs) + GIF fallback for image-less emails + link rewriter for clicks + per-subscriber lifetime stats + "Cold subscribers" sub-tab with optional auto-cleanup.
    • Step 10 — Bounce handling.
    • Step 11 — Import wizard (CSV / JSON full-restore / Newsletter-plugin importer).

    Install

    1. Download `lrob-email-toolkit-0.3.0.zip` below.
    2. Upload via WP admin → Plugins → Add New → Upload Plugin.
    3. Enable the Newsletter module under Email Toolkit. (Requires SMTP module to be enabled for actual sending.)

    The auto-updater (shipped in 0.2.0) will pick this release up automatically on existing installs within an hour.

    Requires

    • WordPress 6.0+
    • PHP 8.1+
    Downloads
  • v0.2.2 2ce7e72dfe

    UltimateByte released this 2026-05-22 02:20:38 +02:00 | 92 commits to main since this release

    Foundation work for the upcoming Newsletter module: the Contact Form's form-builder (drag-and-drop editor, structure normalisation, field renderers, anti-spam wiring) has been extracted from src/Modules/ContactForm/ into a new shared src/Forms/ namespace. Newsletter (landing in v0.3.0) will mount the same editor with its own field types and CPT — zero JS duplication.

    Plus the small WP 7.0 cosmetic fix that was sitting on main between v0.2.0 and now.

    Form-builder refactor

    • New LRob\EmailToolkit\Forms\ namespace under src/Forms/: FormStructure, FormContext, FormEditorRenderer, FieldRenderHelpers, FieldTypeInterface, FieldTypeRegistry, plus the 10 stock field types (Text, Email, Textarea, Number, Phone, Date, Select, Radio, Checkbox, Submit) under Fields/.
    • FieldTypeRegistry is keyed by CPT slug — each consumer module declares its allowed types for its own form CPT. Contact Form keeps all 11 of its types (including its module-specific captcha); Newsletter will register a smaller set plus its own picker types.
    • Plugin::boot() wires the registry into the container before module boot, so modules can register types immediately.
    • FormContext is host-neutral: start($form_id, $instance, $name_prefix, $id_prefix, $editor) accepts module-specific prefixes. Contact Form passes lrob_etk_cf / lrob-etk-cf; Newsletter will pass lrob_etk_nl / lrob-etk-nl.
    • Contact Form's captcha field is now src/Modules/ContactForm/Fields/CaptchaField.php — captcha routing stays tied to its module's meta key + Captcha-module context.

    Asset rename + CSS namespace flip

    • Editor JS renamed admin/js/contact-form-fields-editor.jsadmin/js/form-fields-editor.js (handle: lrob-etk-form-fields-editor).
    • Form-builder DOM classes renamed lrob-etk-cf-*lrob-etk-form-* (form wrapper, row/col/field, label/helper/error, overlay/insert/edit-shell, options, submit, required toggles, inline-settings strip, type picker, preset modifier — about 35 classes across CSS/JS/PHP).
    • Module-specific admin chrome keeps its own prefix: Contact Form's form cards, recipients picker, modals, submissions panel etc. stay lrob-etk-cf-*; Newsletter's admin chrome will be lrob-etk-nl-*. Per-module captcha field types keep their own CSS namespace too (lrob-etk-cf-captcha-* for Contact Form).

    Other

    • WP 7.0 button line-height (the 0.2.1 fix bundled into this release): override the new core rule .button .dashicons { line-height: 1.9 } so toolkit buttons with leading icons keep their vertical centering.

    Design spec

    newsletter.md at the repo root documents the full Newsletter module design — data model, send pipeline, tracking, import/export, system email templates as a CPT, scale targets, implementation slicing. Referenced from CLAUDE.md. Worth reading if you want a preview of what v0.3.0 will land.

    No behavior change

    Existing Contact Forms continue to work unchanged. No DB migration. CSS rename is a like-for-like flip with no functional impact. Form-builder editor, frontend rendering, submissions, anti-spam, captcha — all behaviorally identical.

    Downloads
  • v0.2.0 7b48ee5a27

    UltimateByte released this 2026-05-21 03:06:05 +02:00 | 96 commits to main since this release

    First release with self-hosted plugin updates: install once, get future versions through the WordPress Updates page like any other plugin.

    This release also closes out the Contact Form story with a full submissions inbox, privacy/retention controls, and the cascade-delete modal.

    Headline

    • Self-hosted GitHub-release updates. src/AutoUpdate/Updater.php hooks pre_set_site_transient_update_plugins + plugins_api to surface new releases of this plugin as standard WordPress updates. 1-hour cache, with the cache bypassed when the admin is on the Updates page or clicks "Check again" — so explicit update checks always hit GitHub directly. Mirrors wp-lrob-calendar's pattern.

    Contact Form submissions inbox

    • Inbox view at Contact Forms → "View submissions" — filter by form / status (delivered / received / spam-blocked / failed) / captcha outcome / date range / free text. Read-only detail view with humanized field labels and a collapsed technical metadata section (IP, UA, referer, log link).
    • Cross-links both ways with Email Logs. Log rows whose source is a contact form now expose a "View submission" icon; submission detail links back to the outbound email log.
    • Form card stat pill ("N received · M blocked →") and a global stats panel at the bottom of the Forms page.
    • Dashboard tiles: Delivered (30d) and Spam blocked (30d), the latter split into honeypot vs captcha.

    Privacy & retention (new "Storage" header button)

    • Save submissions toggle — global default + per-form tri-state override. When off, the notification email still goes out; captcha and honeypot still run; no row is written and the form drops out of the inbox.
    • IP storage — hashed by default for privacy/GDPR friendliness, with an opt-in "Store raw IP" mode for sites that need it for abuse investigation.
    • Retention — separate windows for delivered submissions (default: forever) and spam-blocked rows (default: 90 days). Daily cron purges older rows.
    • Form delete cascade modal — replaces the bare confirm() with three explicit choices: cancel / delete form only (orphaned submissions stay in the inbox labelled "Deleted form #N") / delete form + N submissions.

    Captcha analytics

    • Per-route counters. CaptchaService::verify() upserts a small (day, route_key, outcome) stats table on every check. Surfaces as 30-day "blocked · passed" pills next to each challenge and identity on the Captcha settings page.

    Architecture

    • View-routing for hidden admin pages. Both the Submissions inbox and the Plugin Data page are now sub-views of their natural parent (?page=lrob-etk-cform&view=submissions and ?page=lrob-etk&view=data) — no separate submenu, no CSS-hide. Replaces the add_submenu_page + remove_submenu_page pattern which broke direct-URL access on a 403 (get_admin_page_cap() walks $submenu for the cap lookup).
    • French translations filled in for all the new strings.

    Upgrade

    Drop-in. The Contact Form schema gains an additive ip_address column (default empty string — privacy-first); existing rows untouched. Captcha schema gains a small aggregate stats table. Both migrations are idempotent dbDelta + safe to roll back.

    If you're using the toolkit in production, this is the first release where you can stop manually re-uploading the zip — the next bump will arrive through Dashboard → Updates.

    Downloads
  • v0.1.2 6003d894ec

    UltimateByte released this 2026-05-20 17:51:20 +02:00 | 101 commits to main since this release

    Pre-release. Hardening follow-ups from the post-public-release security audit. No data migrations, no schema changes — safe to upgrade in place.

    What changed

    • Captcha fails closed on misconfiguration. When a configured captcha route can't resolve (deleted identity, inactive identity, AUTH_KEY rotated since credentials were saved), submissions are now rejected with a clear error message instead of silently passing — the previous behaviour let bots bypass a captcha the admin thought was active.
    • Admin notice for broken captcha routes. A persistent warning banner now appears on every admin page when any routing-map entry can't resolve, with a one-click link to the Captcha settings page. Admins learn about misconfiguration immediately instead of via support tickets.
    • Defense-in-depth CR/LF stripping on email resend. Resender::build_headers() now sanitises every stored header component before reconstructing outbound headers. Not exploitable today (PHPMailer normalises on the way in) but the same code path will be reused by the upcoming IMAP-save / mail-receive features which will introduce attacker-controlled data into those columns — closing the vector now.

    Upgrade

    Drop-in. No DB changes, no settings changes, no feature toggles.

    If the upgrade surfaces a "Captcha misconfiguration" admin notice immediately after activation, that's the new health check doing its job — open the Captcha settings page and either re-pick a working challenge or re-enter the affected provider's credentials.

    Downloads
  • v0.1.1 9d7db33e91

    UltimateByte released this 2026-05-20 16:24:37 +02:00 | 102 commits to main since this release

    First public pre-release. Three modules production-usable today, schema may still evolve before 1.0.

    Shipped

    • SMTP — multi-identity routing, per-source rules, encrypted credentials, native mail() fallback, default TLS/465.
    • Email Logging — log/search/filter/resend, retention, dashboard activity charts.
    • Contact Form — from-scratch WYSIWYG editor (drag/drop, columns, inline settings, autosave, undo/redo), starter templates, stacked anti-spam (honeypot + time-trap + rate-limit + captcha), submission logging.
    • Captcha (service module, always on) — per-context routing with multi-identity per provider. Built-in: math + picture-recognition. Hosted: hCaptcha. Turnstile / reCAPTCHA designed to plug in.

    Languages

    • 🇬🇧 English
    • 🇫🇷 French — 100% coverage

    Install

    1. Download lrob-email-toolkit-0.1.1.zip below.
    2. WordPress admin → Plugins → Add New → Upload Plugin → Activate.
    3. Email Toolkit → enable the modules you need (all off by default).

    Coming next

    • IMAP "Save to Sent" archive (v0.2)
    • Newsletter module (v0.3)
    • Cross-feature captcha for comments / newsletter signup / lost password / registration (v0.5)
    • Webhook Integrations module (v0.6)
    • Public auto-update via GitHub releases (v1.0)

    Notes

    • Pre-1.0 — migrations between versions are idempotent, downgrades are not supported.
    • SMTP / captcha credentials encrypted at rest with AES-256-GCM (derived from AUTH_KEY). Configure AUTH_KEY in wp-config.php before saving any secret.
    • Requires PHP 8.1+ / WordPress 6.0+.
    Downloads