Modular all-in-one WordPress email plugin: SMTP routing, email logs, contact forms with conversations, captcha and newsletters — from your own server, no SaaS. With thanks to Benoît (https://benoua.fr/) for valuable feedback during the beta test. https://www.lrob.fr/wordpress/plugins/lrob-email-toolkit/
  • PHP 71.7%
  • JavaScript 18.2%
  • CSS 9.3%
  • Shell 0.8%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
LRob aa3de637b6 0.9.5: site title decoded for recipients, bounces about mailed addresses act, unsubscribes and forget-me counted per newsletter
- Support\SiteName::get() decodes the HTML-escaped blogname (d'escrime)
  and replaces every get_bloginfo('name') in src/: From names, subjects,
  tokens and page titles show the apostrophe as written.
- BounceProcessor: a standard report without our signed tag is verified
  when the address has a sent recipient row (latest_row_sent_to, new
  email_snapshot key); hard → bounced + suppressed, the log names the
  newsletter. Never-mailed addresses keep the 0.9.2 caution. The Outcome
  column says what the report changed.
- BounceParser: the delivery-status part is unfolded before the groups are
  split (Gmail's four-line diagnostic was cut after "does"); detail 500 chars.
- Prefs / unsubscribe links and List-Unsubscribe carry lrob-etk-nl-from;
  PrefsHandler::mark_left() sets unsubscribed_via_email (never written
  before) or the new forgotten_via_email on that newsletter's row, or on
  the last one received. Stats rows carry unsubscribed / forgotten / left;
  dashboard tiles, tables, detail modal, card insight and Deliverability
  count both. Schema v25.
- Docs, readme changelog, French strings.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HJbtdXcSAfRu6eMJt81zDJ
2026-10-06 10:42:29 +00:00
admin Subscribers bulk menu follows the tab: trash outside the Trash tab, restore / delete inside it 2026-10-01 09:17:07 +00:00
assets Replies never carry the admin's email; visitors and subscribers answered in their language 2026-09-30 16:15:46 +00:00
dev dev/e2e: Chromium never outlives cdp.js (signal handlers + 100 s watchdog) 2026-09-30 10:48:40 +00:00
docs 0.9.5: site title decoded for recipients, bounces about mailed addresses act, unsubscribes and forget-me counted per newsletter 2026-10-06 10:42:29 +00:00
languages 0.9.5: site title decoded for recipients, bounces about mailed addresses act, unsubscribes and forget-me counted per newsletter 2026-10-06 10:42:29 +00:00
src 0.9.5: site title decoded for recipients, bounces about mailed addresses act, unsubscribes and forget-me counted per newsletter 2026-10-06 10:42:29 +00:00
.gitignore Consent you can word and see, custom CSS behind Customize, readme.txt, Subscribers modals as classes 2026-09-25 14:17:46 +00:00
CLAUDE.md 0.9.5: site title decoded for recipients, bounces about mailed addresses act, unsubscribes and forget-me counted per newsletter 2026-10-06 10:42:29 +00:00
deploy.sh v0.8.8 — forms hold their own inside any theme, Elementor widgets, contact-form shortcode 2026-09-16 17:13:05 +00:00
LICENSE Initial scaffold 2026-05-17 12:27:22 +02:00
lrob-email-toolkit.php 0.9.5: site title decoded for recipients, bounces about mailed addresses act, unsubscribes and forget-me counted per newsletter 2026-10-06 10:42:29 +00:00
publish.sh publish.sh: wait for Forgejo to register the pushed tag before creating the release 2026-09-06 08:20:10 +00:00
README.md 0.9.5: site title decoded for recipients, bounces about mailed addresses act, unsubscribes and forget-me counted per newsletter 2026-10-06 10:42:29 +00:00
readme.txt 0.9.5: site title decoded for recipients, bounces about mailed addresses act, unsubscribes and forget-me counted per newsletter 2026-10-06 10:42:29 +00:00
release.sh Consent you can word and see, custom CSS behind Customize, readme.txt, Subscribers modals as classes 2026-09-25 14:17:46 +00:00
uninstall.php Security audit: private REST for the plugin's post types, signup abuse guards, proven WP-user opt-in, captcha tokens without the answer, signed bounce tags, tier fixes 2026-09-28 15:34:17 +00:00

LRob — Email Toolkit

All-in-one modular email plugin for WordPress. SMTP routing, email logging, customizable contact forms, multi-context captcha, a full newsletter platform with tracking + segmentation, and webhook integrations — all from one plugin, no SaaS, no per-subscriber fees.

Status

Public beta — v0.9.1. Five modules ship today: SMTP, Email Logging, Contact Form, Captcha, and Newsletter. The schema and the admin surfaces are stable, and the toolkit drives real email on production sites every day.

0.9.5 (unreleased) renders a site title with an apostrophe correctly for recipients (sender name, subjects, emails), acts on non-delivery reports about any address the site really mailed (dead address → bounced and never mailed again, even when the report quotes none of our headers), and counts the people who unsubscribe or ask to be forgotten from each newsletter in the statistics. 0.9.4 (released 2026-10-01) fixes subscriber imports of more than ~330 rows, which a PHP limit silently cut (the ticked list with them), names the lists an import went to, and gives the bulk menu the right actions per tab. 0.9.3 (released 2026-09-30) adds three things the beta test asked for: an Export CSV of the subscribers (the current tab and filters, the columns the import reads back, so a file can be sorted or corrected in a spreadsheet and imported again), a re-import that updates names but never re-enables someone who unsubscribed unless you say so, and excluded lists on a newsletter — send to "News" minus everyone in "General". It also makes forms safe behind page caches (the built-in anti-spam question is fetched per visitor and renewed in place when it expires; nothing per-visitor is frozen in the cached page) and stops a web application firewall from swallowing a form's own error message. Replies sent from the inbox go out under the site's address with the responder's name only, never the admin's email, and everything a visitor or subscriber reads back — form answers, replies, confirmation emails, newsletter footers — is in the language of the page they used. 0.9.2 (released 2026-09-28) answers people who are already subscribed, gives every per-form and per-newsletter setting a site default, makes the Newsletters dashboard the statistics, lets any signup form become a popup (several popups, each targeting its pages, sections or categories), offers a list view of newsletters and SMTP identities, fixes newsletters whose Track opens / clicks could not be switched off, adds an About page, cleans up the admin code and its French, and closes what a five-pass security audit found (private REST for the plugin's post types, signup rate limits and proven WordPress-user opt-in, captcha tokens without the answer, signed bounce tags, prefetch-safe links, permission tiers).

0.9.1 is the big catch-up before 1.0. Newsletters get a Statistics view (tiles, month-by-month chart, per-newsletter drill-down, each subscriber's history), a site-wide signup popup, list merging, who-downloaded-what and a dry run of the send pace. Contact forms can close a conversation and give visitors a formatting toolbar. Deliverability proves SPF / DKIM / DMARC with a real send, tags the return address per recipient (VERP) and reads bounces far more accurately. Consent becomes visible and editable: the consent box's wording, a Measurement column and each subscriber's consent history, and the page the confirmation link leads to. Forms gain custom CSS (with a guide), three more style settings and checkboxes that hold their own in any theme; permissions can be granted to one person, subscribers-only or inbox read-only. Next: 1.0 — publication on wordpress.org.

Form theming is the headline of the 0.6 cycle: a click-driven style editor on every contact and newsletter signup form — colour scheme, roundness, spacing, fonts, label emphasis, hover and focus effects — with a live preview and no CSS to write. Build a look once, save it as a named preset, and reuse it on any form; set one as the default and every form that hasn't overridden a setting follows it, so changing your brand colour is a single edit. Every built-in colour scheme meets WCAG AA contrast. Forms can also celebrate: the success message replaces the form when sent, with a choice of reveal — a letter flying off, a rocket launch, confetti, or fireworks — all of which respect prefers-reduced-motion. Development also moved to git.lrob.net/WP/email-toolkit, where updates now come from.

0.8.0 brings the two milestones that make large sends safe and lawful. Deliverability: a site-wide sending limit, sending pace per recipient domain (built-in ceilings for the providers known to throttle, worldwide), a cap per SMTP identity, parallel senders, bounces read from each identity's mailbox over IMAP or received through a webhook, a suppression list, a bounce log, sent copies filed in the IMAP Sent folder, and a Deliverability page that shows bounce and complaint rates, SPF / DKIM / DMARC and the worst domains. Privacy: consent-based open and click measurement, a consent log, a subscribers-by-consent browser, one person's data export / erasure, retention settings in one place, generated policy text, and role permissions for Newsletters and the Inbox. Also fixes the subscriber modal losing the first edit of a field.

0.9.0 makes re-running the Newsletter-plugin import safe: lists of the same name are completed, never duplicated. 0.8.9 fixes two checkboxes that lied (the privacy "Who is measured" choice, the editor's Required box); 0.8.8 makes every form hold its own inside any theme — no more spacing, button or spinner surprises from the theme's own CSS — and adds the Elementor widgets and the contact-form shortcode. 0.8.7 puts a number on the sending pace — 1 000 emails per hour at best; your 2 400 recipients would take about 2 h 24 — on the settings and on each newsletter, adds an unread-messages item to the admin bar, and finishes the form success-message styling. 0.8.6 fixes three irritations: the dashboard chart was blank on Since the beginning, a newly created newsletter sorted last instead of first, and deleting one reloaded the page and dropped you into the Trash tab — it now disappears in place and you stay where you were. 0.8.5 extends that signature to the replies you send from the submissions inbox — switched on per person, shown under the reply box before you send — and the signature editor now previews as you type. 0.8.4 lets whoever writes a newsletter sign it: each person pastes their signature once on their WordPress profile — the one their email client already produces, tables and logo included — and ticks Sign this newsletter on any newsletter, signing as themselves or as a colleague. A preview at the real width of an email shows exactly what recipients get. The emails the site sends also joined the privacy tools, completing the toolkit's data export and erasure.

0.8.3 answers a question contact forms couldn't: which page was the visitor on? Every message now records where it came from — useful the moment a page links to your form with "a mistake on this page? Tell us". It shows in the inbox, in the notification email, and can go straight into the subject line. A new Source page field puts it in front of the visitor too — pre-filled, still editable — and a Report a problem starter template wires the whole thing up in one click. Contact-form messages also joined the privacy tools: they are part of the data export and erasure, and of the suggested privacy-policy text.

v0.6.0 fixes a critical bug (a fatal error that could block new-user registration / WooCommerce checkout on sites running the Newsletter module) and rounds out newsletter delivery: each newsletter now sends through the SMTP identity you choose on it (test sends included), and newsletter + contact-form mail can be pointed at a specific identity from the SMTP routing rules. It builds on the v0.5 cycle — a single branded dark admin theme (matching the lrob.fr site), accessibility-hardened public forms (proper labels, announced errors, keyboard support), and v0.4's full captcha lineup (hCaptcha / Turnstile / reCAPTCHA incl. invisible + score-based v3), in-place detail views, and resend-safe saved attachments.

Pre-1.0, so the schema can still change between minor versions — see Versioning. Bug reports + UX feedback welcome on the issue tracker.

For the full feature history, see docs/done.md. For the backlog, see docs/todo.md.

What it is

A single plugin that replaces the typical stack of SMTP plugin + email logger + contact-form plugin + captcha plugin + newsletter plugin (+ Mailchimp/Brevo subscription), with consistent design, shared SMTP routing, and a shared event vocabulary. Each module is independently activatable — only the parts you enable add code to the runtime.

No external libraries at runtime: no Composer, no React, no build pipeline. Plain PHP 8.2+, vanilla JS, server-rendered admin UI. The ~780 KB release zip is the entire plugin.

Highlights

A few things that make it stand out:

  • See every email your site sends. Full logging captures every outgoing message — recipients, subject, body, attachments, status and any error — all searchable and filterable. No more "did that order confirmation actually go out?"; open the log and know.
  • Re-send any email, attachments and all. Switch on Save attachments locally and any logged message can be re-sent later with its files intact, straight from the log — and those files live on your server, not stuck in a mailbox (with an optional copy saved to your IMAP Sent folder too).
  • SMTP that practically sets itself up. You don't need to know your mail-server settings: the host picker suggests your domain's real servers (common presets plus a live MX lookup), shows a green/red check that each one actually resolves, and a one-click connection test confirms login works — before you rely on it.
  • One mailbox per job — add as many as you like. Run multiple SMTP "from" identities and route each source to the right one: WooCommerce through one, your contact form through another, newsletters through a third.
  • Stack as many captchas as you need. Multiple providers and multiple credentials side by side — the privacy-friendly built-in math and picture challenges (zero third-party calls, no cookies), plus hCaptcha, Cloudflare Turnstile and Google reCAPTCHA — each assignable to its own context (forms, comments, login, registration…) with a clear site-wide default.
  • Own your newsletter — no SaaS, no per-subscriber fees. Send to your subscribers and WordPress users from your own server, with open/click tracking, list segmentation and one-click unsubscribe — without Mailchimp/Brevo monthly bills or handing your contact list to a third party.
  • Up and running in minutes, thanks to presets. Ready-made form templates, prebuilt subscribe-form profiles (Email-only, Contact basics, Full profile), and smart defaults everywhere mean most things just work the moment you switch them on — you tweak only what you want.
  • Forms anyone can fill in. A drag-and-drop builder for contact and signup forms, with front-end markup hardened for keyboard and screen-reader users (clear labels, announced errors).
  • Your secrets stay yours. SMTP and captcha credentials are encrypted at rest (AES-256-GCM); tracking IPs are anonymised; ops teams can keep secrets entirely in wp-config.php.
  • Light and self-contained. No Composer, no React, no build step, no external CDN — a ~780 KB zip is the whole plugin, and it auto-updates from git.lrob.net.

Modules

Module Status What it does
SMTP ✅ shipped Route wp_mail() through one or more configured SMTP servers. Multiple "from" identities, per-source routing rules, wp-config.php constant overrides, AES-256-GCM-encrypted passwords at rest, native PHP mail() transport as fallback. Per-identity save attachments locally option so logged emails can be re-sent with their files intact.
Email Logging ✅ shipped Log every outgoing email (headers, body, attachments, status, errors). Browse / search / filter / resend from an in-page detail modal — resend re-attaches files saved via the SMTP save attachments toggle. Configurable retention. Activity charts on the dashboard.
Contact Form ✅ shipped Customizable forms with a from-scratch WYSIWYG editor (drag & drop, columns, inline settings, autosave, undo/redo). Stacked anti-spam: honeypot, time-trap, rate-limit, captcha. Starter templates. Per-form recipients, Reply-To picking, subject templates, success-message templates. Submissions inbox with captcha-outcome tracking + filters + detail view. Conversations: reply to a submission from the inbox with the WordPress editor — the visitor gets your answer by email, sent from the form's own identity and colours, with a link back to the page they wrote from, where the form turns into the conversation and they can answer, no account needed. Inbox with unread badges (menu, dashboard), a trash with automatic cleanup, and a Used on line showing which pages carry each form. Place a form with the Gutenberg block, the Elementor widget or the [lrob_etk_contact_form id="…"] shortcode; its styles hold their own inside any theme, and follow the page's font and colours unless you set your own. A Source page field, pre-filled with the page the visitor came from and still editable by them, plus a matching Report a problem template. Every message also records where it came from — the page the visitor was on before opening the form, the page carrying it, their language, their account if they were signed in — shown in the inbox, named in the notification email, and available as a {source_page} token for the subject.

An Email design (logo, accent, background, font, signature line) set once on the dashboard dresses every email the toolkit writes itself, with a live preview. | Captcha | ✅ shipped (service module) | Shared captcha service consumed by Contact Form + Newsletter sign-up forms + WordPress comments + login + lost-password + registration (+ WooCommerce login). Per-context assignments with the Make as default badge — pick the default site-wide then optionally override per use case. Built-in math + picture-recognition challenges. hCaptcha, Cloudflare Turnstile, and Google reCAPTCHA (v2 + v3 score-based) all shipped — multi-identity, AES-encrypted credentials, per-identity theme / size / invisible mode. | | Newsletter | 🧪 beta (v0.3.x) | Newsletters composed in Gutenberg, sent to your subscribers + WordPress users. Shared files: attach documents to a newsletter and recipients get download buttons — files are hosted on your site (never sent as attachments), download clicks are tracked, files expire after a configurable delay, and deleting a newsletter lets you keep its files online or take them down. Lists: two kinds — Subscribers lists (manual membership) and WP users lists (rule-based — by role, WooCommerce customer status, active WooCommerce subscriptions, or a custom rule plugged in by a developer). Mark lists as Public so subscribers self-join from their prefs page, Private for admin-managed segmentation. Audience picker with per-list member counts, opt-out visibility, excluded lists (send to a list minus the members of another), per-row Send anyway / Exclude overrides, Bypass opt-outs for operational sends (warned at send time); defaults for new newsletters (recipients, signature, tracking, logging). Subscribe forms with a drag-and-drop builder (text / email / phone with country picker / gender / dropdown / list picker / captcha / submit), full-form templates (Email-only, Contact basics, Full profile), per-form default lists, per-form messages for people already subscribed or who never confirmed (with site-wide defaults for every form setting), and per form the page the confirmation link leads to (the built-in one, with your own texts, or any page of your site); placed with the Gutenberg block, the Elementor widget or the [lrob_etk_nl_subscribe id="…"] shortcode. Signup popup: one site-wide popup showing a subscribe form as a centered window, a corner card or a bottom bar — after scrolling, after a delay, on exit intent or on click — capped per visit and per dismissal, never again once subscribed, targeted by kind of page and URL pattern, with non-intrusive presets. Statistics: a Statistics view (period tiles, month-by-month chart, sortable per-newsletter table with a drill-down into links, domains and recipient activity, per-list rollup), Newsletter cards on the toolkit dashboard, gauges and top links on every sent newsletter, and each subscriber's newsletter history — all server-rendered, no charting library. Subscriber self-edit: name / phone / postal address / gender from the prefs page; email change requires click-to-confirm on the new address. Throttled AJAX + Cron send pipeline with SMTP circuit-breaker, open + click tracking via HMAC-signed REST endpoints, per-subscriber lifetime engagement stats, cold-subscriber detection, bulk unsubscribe, RFC 8058 one-click unsubscribe headers. WP-Cron health diagnostic. Deliverability: per-domain sending pace (built-in ceilings for orange / free / laposte / gmail / outlook…, your own rows, a default), per-identity sending limit, bounces via webhook or IMAP mailbox (hard → bounced, soft → counted, complaint → unsubscribed), suppression list. Privacy: consent-based tracking (consent field, prefs page, profile; deliverability-only pixel for the others; withdrawal wipes measurements) with your own wording for the consent box, each subscriber's answer and consent history in the subscribers table and their detail view, consent log, personal-data export / erasure, policy text, WordPress privacy-tools integration. Permissions: grant Newsletters or Inbox to any role. Custom fields: your own subscriber fields (club, company, member number…) as columns, card fields, CSV columns, form targets and {{tokens}}; imported from the Newsletter plugin too. Import / export: paste, CSV or the Newsletter plugin in; a CSV of any tab and filter out, with the columns the import reads back. | | Integrations | ⏳ planned | Outbound webhooks to n8n, Slack, Discord, Matrix and generic endpoints. Each module already emits events from v0.0.1 — devs can hook them today via WordPress actions, no module needed. |

Requirements

  • PHP 8.2+
  • WordPress 6.8+
  • WooCommerce 8.0+ (only required if you'll use WooCommerce-based segmentation in the Newsletter module — HPOS supported, integration planned)
  • A reasonably modern browser for the admin UI — the design system uses CSS color-mix() (Chrome/Edge 111+, Firefox 113+, Safari 16.2+; all released 2023). Older browsers degrade gracefully (tints/glows just don't paint); the public-facing forms have no such requirement.

Languages

  • 🇬🇧 English (source)
  • 🇫🇷 French (fr_FR) — 100% coverage

Install

Two ways:

From a release:

  1. Download lrob-email-toolkit-<version>.zip from the Releases page.
  2. WordPress admin → Plugins → Add New → Upload Plugin → choose the zip → Activate.

From source:

  1. git clone this repo.
  2. ./release.sh — produces ../releases/lrob-email-toolkit-<version>.zip.
  3. Upload the zip as above.

In both cases, all modules are disabled after activation. You opt in to each one explicitly from the Email Toolkit dashboard. Once installed, the plugin auto-updates from the releases on git.lrob.net — a new version is offered as soon as WordPress runs its next update check.

Security notes

  • SMTP and captcha-provider credentials are stored in the database encrypted with AES-256-GCM, using a key derived from your WordPress AUTH_KEY via HKDF-SHA256. Without a valid AUTH_KEY in wp-config.php, the plugin refuses to encrypt secrets.
  • Newsletter tracking tokens are HMAC-SHA256 signatures of the URL parameters — tampering invalidates the token and the endpoint refuses to record / redirect.
  • Recipient IPs in tracking events are anonymised before storage (IPv4 → /24, IPv6 → /48). User-agents are not stored by default; opt-in per newsletter.
  • All credential fields can be overridden by wp-config.php constants for ops teams who want secrets out of the database entirely.

Hooks for developers

Stable since v0.0.1, regardless of which modules are enabled.

// Subscribe to one specific event
add_action('lrob_etk_email_sent', function (array $payload): void {
    // $payload = ['log_id' => ..., 'to' => [...], 'subject' => ..., 'identity_id' => ...]
});

// Or subscribe to every plugin event in one place
add_action('lrob_etk_event', function (string $name, array $payload): void {
    error_log("[lrob-etk] {$name}: " . wp_json_encode($payload));
}, 10, 2);

Live event names: email.{sending,sent,failed,imap_saved,imap_save_failed}, contact_form.{submitted,spam_blocked,delivered}, newsletter.{started,paused,resumed,aborted,completed,test_sent}, newsletter.recipient.{sent,failed}, newsletter.subscriber.{added,confirmed,refused,unsubscribed,trashed,promoted,resubscribed,reminder_sent,email_change_requested}, newsletter.tracking.{opened,clicked,unsubscribed}. Full vocabulary in CLAUDE.md.

Versioning

Two cadences:

  • Patch (+0.0.1) — small adjustments, on demand.
  • Minor (+0.1.0) — a full module shipped.

Migrations between versions are idempotent. Downgrades are not supported. 1.0.0 happens when the plugin is stable enough to declare it so — no specific feature gate.

Roadmap

Priority order, no version commitment — see docs/todo.md for the full backlog with reasoning. Top priorities right now:

  1. Statistics overhaul — a dedicated Newsletter statistics view plus global Email Toolkit dashboard tiles (open/click trends, per-list rollups, per-send drill-down).
  2. Contact-form personalization — per-form colours, hover effects, and send animations.

Next major features on deck: universal email tracking (Opened column on Email Logs), one-shot email composer with templates + attachments, marketing automation workflows (drip campaigns), drag-and-drop email builder, subscriber tags + list rules on custom fields, customize WP default emails.

License

GPL-2.0-or-later. See LICENSE.

Author

Built by LRob, with thanks to Benoît for valuable feedback during the beta test.