• 2.13.0: Fixes duplicate sending of queued emails. Recipients of a broadcast could receive the same message two or three times, because nothing stopped more than one queue runner from sending the same record: each runner checked that a record was still queued, then spent several seconds in wp_mail(), and only then marked it sent, so any runner that read the record in that window sent it again. It was made worse on a network: the messages table is network-wide, but the hourly wakeup cron runs on every site and each one drained the whole network's queue, so several sites' wakeups and the per-record async actions could be working through the same broadcast at once. Each record is now claimed before it is sent, with a single conditional database update that moves it from queued to a new sending status; only the runner that makes that change sends the message, and any other runner skips it. Each site's wakeup now only processes its own site's records. A record left in sending for more than 30 minutes (only possible if the process died mid-send, so it may or may not have been delivered) is marked failed with an explanatory error on the next wakeup rather than being retried automatically, so it can be reviewed and retried by hand from the email log without risking another duplicate.
  • 2.12.0: Adds the save-imap-attachment MCP ability, which files one IMAP attachment into the current site's media library server-side and returns the attachment ID, so registration forms, invoices and signed documents can be kept rather than just read, without the file ever passing through the MCP response. Attachments are private by default: the file is written straight into the LH Private Uploads directory above the webroot (never into public uploads first) and is served only through its access-checked proxy URL; visibility public must be passed explicitly, and a private save is refused if LH Private Uploads is not active on the site. The file type is checked the same way the normal uploader checks it, so anything WordPress would refuse is refused. The source (connection, folder, UID, part, Message-ID, sender, date, subject) is stored in attachment meta, and a repeat call for the same message and part returns the existing attachment instead of saving a second copy. Optional title and parent post. Requires upload_files and edit_posts. The ability lives in its own class under the new includes/abilities directory, the first lh-email ability to follow the one-file-per-ability layout, and the get-imap-attachment description now points to it.
  • 2.11.1: Spreadsheet extraction improvements found by testing on a real team registration form. Highlighted cells are now marked: a cell with a solid, patterned or gradient fill other than white gets [highlighted #RRGGBB] after its value (or just [highlighted] when the colour cannot be read), since forms ask for a choice such as the playing day or grade to be marked by highlighting it, and a fill is not part of the value. Cells that only look blank no longer come through as values: non-breaking and other Unicode spaces are treated as ordinary spaces, runs of spaces collapse to one, and a cell holding nothing but date separators (an unfilled date field such as " / / ") counts as empty. Also passes IOFactory::class to ReflectionClass rather than a plain string, which fixes a static-analysis finding.
  • 2.11.0: Adds spreadsheet support to get-imap-attachment-text: .xlsx, .xlsm, .xls and .ods attachments are now read server-side using phpoffice/phpspreadsheet (a new Composer dependency), through a new LH_Email_Spreadsheet_Extract class that plugs into the existing extractor filters. Each sheet becomes a section headed with its name, with one line per non-empty row and each cell written as its reference and value, so form layouts stay readable. Dates are detected from the file's own number formats and written in ISO format, percentages are converted, formulas give their last saved result without recalculation, other numbers are written raw, and merged ranges are listed. Includes a zip-bomb guard and per-sheet row and column caps. The get-imap-attachment and get-imap-attachment-text descriptions now list spreadsheets. Also: plugin_version() now reads the Version header, the upgrade routine is keyed on a new schema_version() rather than the plugin version (existing installs carry their stored version over, so no routine reruns), and Requires PHP is raised to 8.2, which PhpSpreadsheet needs.
  • 2.10.1: Migrate write_log() in lh-email.php and includes/lh-email-sender.php to the canonical LH convention: add return_plugin_debugging_status() (default true) and rewrite write_log($log, $level='info') to emit 'Level: [namespace] message' via wp_json_encode for arrays/objects. lh-email-sender.php's standalone LH_Email_sender_class also gets a new return_plugin_namespace() delegating to LH_Email_plugin::return_plugin_namespace(). Bump plugin version 2.10.0 -> 2.10.1 (header + plugin_version()). No other code changes.
  • 2.10.0: Added the create-reply-draft MCP ability, which creates a compose draft threaded into an existing conversation. It fetches the source message by its IMAP UID and fills in the subject (Re: without doubling an existing prefix), In-Reply-To and References. The recipient defaults to the original sender and can be changed, and the original can optionally be quoted below the reply. Single mode only. If the source message cannot be loaded or has no Message-ID, the draft is refused rather than created unthreaded. Single-mode sending now includes these threading headers; broadcast sending never does.
  • 2.9.9: Added the get-imap-attachment MCP ability, which fetches one attachment from an IMAP message, base64-encoded, by the part number get-imap-message already lists. It leaves the message unread, and refuses attachments over a filterable size limit (default 5 MB) rather than truncating them, checking both the reported and the actual size. Verified against live Gmail data.
  • 2.9.8: Removed the Customize menu item (BuddyPress's email template customiser), which was not used. (Original release date not recorded.)
  • 2.9.7: Menu fixes: Email Templates now sits after LH Email's own screens and before Situations and Customize, instead of at the very bottom, and the Emails menu is now highlighted when viewing the Situations screen. (Original release date not recorded.)
  • 2.9.6: Fixes to the 2.9.5 menu merge: Email Templates appeared at the top of the menu, the Situations screen was missing, and the Customize link led to a missing page. Situations and Customize are now added to the Emails menu directly rather than moved there, and Email Templates is placed at the end. (Original release date not recorded.)
  • 2.9.5: BuddyPress's separate Emails menu is merged into LH Email's own Emails menu. Its list screen is relabelled Email Templates to tell it apart from All Emails, the sent message log, and its Add New item is removed, since templates are made by editing existing ones. (Original release date not recorded.)
  • 2.9.4: BuddyPress emails can now be edited in the block editor. The BuddyPress email post type and its Situations taxonomy are now managed here instead of in lh-crm, with REST support enabled, and Situations gets its own admin screen and a column on the email list. (Original release date not recorded.)
  • 2.9.3: Fix: the IMAP unavailable notice on the Inbox screen still told admins to ask their host to enable PHP's imap extension. It now points to the Composer dependency, which is what is actually missing when IMAP is unavailable. (Original release date not recorded.)
  • 2.9.2: Fix: get-imap-quota returned empty usage and limit figures, because the new IMAP library returns the raw server response. The quota lines are now read correctly, matching the results from before the library change. This completes the IMAP library migration, with every IMAP ability confirmed working against live Gmail data. (Original release date not recorded.)
  • 2.9.1: Fixes after the IMAP library change. Listing and searching messages failed with an empty search command. The unread check always reported messages as read. Browsing marked messages as read on the server; the client now fetches with the peek option so nothing is ever marked read. Gmail search was rejected and now works for date and sender queries. Gmail accounts now also list the [Gmail] container folder, which is cosmetic only. (Original release date not recorded.)
  • 2.9.0: IMAP now runs on the webklex/php-imap library, installed with Composer, instead of PHP's imap extension, which is unmaintained, no longer bundled with PHP 8.4 and was behind the 2.8.4 crash. Gmail search now works. Messages are still left unread. Sorting by date is done by the server; sorting by sender, subject or size now applies to the current page only. The free-text search query is parsed into individual criteria rather than passed through whole. (Original release date not recorded.)
  • 2.8.4: Fix: listing folders crashed against Gmail, in both the list-imap-folders ability and the Inbox screen. Folders are now listed after opening INBOX, the same way every other IMAP call connects. (Original release date not recorded.)
  • 2.8.3: The Connections screen now has SMTP and IMAP tabs. IMAP connections take host, port, encryption (SSL on port 993 by default), username and password. Fixed a nonce check on connection deletion, and delete nonces are now scoped to the connection type so SMTP and IMAP connections with the same slug cannot collide. (Original release date not recorded.)
  • 2.8.2: Fix: BuddyPress emails were sent as plain text with visible HTML markup, because BuddyPress calls wp_mail() without headers. The HTML content type is now forced for BuddyPress emails, and the rendered BuddyPress template is sent as is. (Original release date not recorded.)
  • 2.8.1: The dispatch meta fields on compose emails (dispatched at, recipient count, queued count) are now exposed read-only in the REST API, as collaborative editing needs, while REST writes to them stay blocked. (Original release date not recorded.)
  • 2.8.0: IMAP support. New Emails, Inbox screen with a connection selector, message list with unread indicator, and message view with a reply button. Connections are opened read-only, so browsing never changes the mailbox. New MCP abilities: list-imap-connections, list-imap-folders, list-imap-messages, search-imap-messages (with Gmail search), get-imap-message, get-imap-headers and get-imap-quota. Shows a notice when IMAP is unavailable or no connection is set up. (Original release date not recorded.)
  • 2.7.16: Removed temporary debug logging added in 2.7.13 to 2.7.15. (Original release date not recorded; 2.7.13 to 2.7.15 were debugging builds with no separate notes.)
  • 2.7.12: Fix: the email log's delete, retry and cancel actions are now handled before the page renders, with a proper referer check, and the remaining uses of the old action parameter were renamed. (Original release date not recorded.)
  • 2.7.11: Fix: the email log's row actions clashed with WordPress's reserved action parameter on admin pages; the parameter is now named lh_action. (Original release date not recorded.)
  • 2.7.10: Fix: the delete, retry and cancel row actions on the email log failed because the nonce was altered by sanitisation before it was checked. (Original release date not recorded.)
  • 2.7.9: Backwards compatibility: added the lh_membership_return_site_email_domain filter (previously provided by lh-multiple-email-accounts), which prefixes the site domain with mail. (Original release date not recorded.)
  • 2.7.8: SMTP errors such as a refused connection or failed login are now caught and recorded in the message's error detail. (Original release date not recorded.)
  • 2.7.7: Email is now sent over SMTP using connections from LH Connections: the connection whose slug matches the From address is used, then the site default, then plain mail(). The connection's From name and address always apply when one is found, and a PHPMailer error on an SMTP connection marks the message as failed. (Original release date not recorded.)
  • 2.7.6: Added From Name and From Email Address fields to SMTP connections. (Original release date not recorded.)
  • 2.7.5: Added an Emails, Connections screen for managing site-scoped SMTP connections, and a default-connection setting. All Emails screens now require the promote_users capability (previously edit_users). Shows a notice rather than failing when LH Connections is not active. (Original release date not recorded.)
  • 2.7.4: Added a Settings screen for choosing the default SMTP connection (requires LH Connections). (Original release date not recorded.)
  • 2.7.3: Custom email store tables with an actor registry. New compose post type with single and broadcast modes. MCP abilities: create-draft, update-draft, get-compose, list-composes and search-messages. (Original release date not recorded.)