- 3.11.0: Inbound mail can now be filed once it has been dealt with (lh-tasks #153109). Three new abilities change message state in an IMAP mailbox; the existing IMAP abilities stay read-only. archive-imap-message takes a handled message out of the inbox without deleting it: on Gmail it removes the Inbox label, as Gmail’s own Archive and Done do, leaving the message in All Mail; on other hosts it moves it to the archive folder (archive_folder, else the server’s special-use Archive folder, else “Archive”, filter lh_email_imap_archive_folder) with UID MOVE, or with UIDPLUS a copy then an expunge of that one UID; a server with neither is refused rather than risk expunging other deleted messages. set-imap-flags marks messages read or unread and starred or unstarred (seen, flagged). set-gmail-labels adds and removes Gmail labels with X-GM-LABELS, refusing a missing label unless create_missing is set, and never adding Trash or Spam. All three take uid or uids (up to 50) in a folder of a site’s connection, report each message’s flags and labels before and after, and are idempotent: a message already in the asked-for state is reported unchanged. On Gmail, state is read and changed in All Mail, found by X-GM-MSGID, and each result gives its All Mail folder and UID. Nothing is ever deleted. Requires edit_posts, like the other IMAP abilities.
- 3.10.0: Compose emails can now carry attachments, from private media only (lh-tasks #153091). create-draft and update-draft take attachments, an array of attachment IDs (update-draft replaces the list; an empty array clears it). Each must be a private file held by LH Private Uploads on the same site that the caller can open, at most 10 files and 10 MB per email (filters lh_email_attachment_max_count and lh_email_attachment_max_total_bytes); make one from a sandbox file or an IMAP attachment with lh-private-uploads/create-private-media. get-compose lists a draft’s attachments (file name, size, access-checked URL, purpose, and any problem), and the edit screen shows them in an Attachments box, so they can be reviewed before sending. At dispatch the files are checked again and read from disk; single emails attach them to the one message and broadcasts send the same stored file to every recipient. If a file is missing, the email is not sent: publishing is refused in the editor, a scheduled send goes back to draft, and a queued message whose file has gone is marked failed rather than sent without it. Files made only for an email (purpose email) are deleted 30 days after it is sent, or 30 days after they were made if never sent, but never while a draft still holds them or a message is still queued (filter lh_email_attachment_retention_days); this runs on each site’s hourly wakeup. Requires LH Private Uploads 1.6.0 for attaching; without it, emails without attachments are unaffected.
- 3.9.0: Broadcasts now reach every address a member has. When a broadcast is sent, each recipient address is matched to its account (primary email or verified LH User Identity alias) and the account gets one message addressed To its primary email and all its verified aliases; two addresses of the same account in a draft are merged into that one message, merge fields come from the account, unverified aliases are never added, and addresses with no account still get one message each. A new per-draft switch, “Send to each member’s other addresses” (on by default for broadcasts), turns the extra addresses off; it is in the Envelope panel and on lh-email/create-draft and lh-email/update-draft as expand_aliases. The compose’s recipient count is per account, with the number of addresses shown alongside. Fixes: CC and BCC recipients were recorded in the log but never actually sent; they are now. A message can now have several To recipients, so single-mode emails keep every To address in To instead of moving the extras to CC, and wp_mail() calls with more than one To address are no longer rejected. A recipient’s display name is now added without replacing an alias with the primary address.
- 3.8.6: Mail sent very early in a request, before Action Scheduler has initialised (for example a notification sent from the fatal-error screen while plugins are still loading), is now scheduled with WP-Cron instead of Action Scheduler, which stops the “as_schedule_single_action() was called before the Action Scheduler data store was initialised” notice. Unscheduling skips Action Scheduler in the same situation. The email is stored in the queue first either way, so the hourly wakeup still sends it. This release also includes the earlier guard that stops cron being spawned, or mail sent, at shutdown on a request that died before init.
- 3.8.5: Fixed a second fatal on requests that die before WordPress has finished loading (#152798). On such a request the shutdown cron spawn called spawn_cron() before WP_CRON_LOCK_TIMEOUT was defined, and the send-after-response path could try to send mail with WordPress half-loaded. Both now stand down unless the request reached init; mail queued on such a request stays queued and goes out on the next hourly wakeup.
- 3.8.4: Removed the class_exists() guards around LH Email’s own classes, LH_Email_plugin and LH_Email_sender_class (lh-tasks #152795). On this server PHP/OPcache can early-bind a class with no extends clause at compile time, so the guard could report the class as already defined and silently skip the whole plugin: no hooks, no abilities and no mail queue, with nothing in the debug log. As LH Email handles all outgoing mail on the network, that failure would stop email quietly. WordPress already loads each plugin file once, so the guards protected nothing. No change in behaviour.
- 3.8.3: Agents can now move a superseded compose draft to the trash with lh-mcp-general-abilities/trash-post, and restore it with untrash-post (lh-tasks #152762). Compose drafts get a trash-only rule there: only drafts can be trashed, so a sent email never can, and compose posts still cannot be created or edited through create-post or update-post, since publishing one sends it. Drafting and revising emails stays with this plugin’s own abilities.
- 3.8.2: IMAP errors now say what actually went wrong, and a dropped connection is retried once (lh-tasks #152727). The IMAP library wraps the real failure, such as a timeout or a refused connection, inside a bare “connection failed”, so abilities like search-imap-messages reported only that. Every IMAP error now includes the underlying cause. Connecting to the mail server is retried once on a connection failure, since a dropped Gmail connection usually succeeds on a second try; a login failure is not retried.
- 3.8.1: get-imap-quota no longer reports a misleading percentage when the mail server’s quota figures aren’t real. Gmail answers the IMAP quota query with a placeholder (0 KB used against a limit of about 7 PiB), because Gmail storage is measured across the whole Google account and isn’t exposed over IMAP. Each quota entry now carries reliable. For a Gmail connection, or any limit above a plausibility ceiling (1 TB by default, filterable through lh_email_imap_quota_max_plausible_kb), reliable is false, percent is null and a note explains why. The raw usage and limit figures are still returned. Results from servers that report a real quota are unchanged apart from reliable: true.
- 3.8.0: copy-imap-attachment now takes site_id, like the other eight IMAP abilities, so an attachment can be copied from another site’s mailbox through any connector. With site_id the whole call runs on that site: the connection slug is looked up there, and a private or media copy is filed into that site’s media library, with upload_files, parent_id editing and the already-copied check all decided on that site. A sandbox copy still goes to your own sandbox, which belongs to you rather than to a site. The returned attachment source now carries the site’s ID on a multisite network, so lh-mcp-file-abilities (0.4.0 or later) reads it from the right media library whichever connector passes it on.
- 3.7.1: Email drafts created or updated through MCP are now saved as block markup. create-draft, create-reply-draft and update-draft convert an HTML or plain-text body to blocks before saving, using the same conversion as lh-mcp-content-transformer/convert-to-blocks, so the draft opens as blocks in the editor. A body that already contains block markup is stored unchanged, and the quoted original on a reply is converted on its own. If LH Content Converter is not available the body is saved as before. Sent emails look the same.
- 3.7.0: Other plugins can now open a new compose screen with the recipients filled in by posting them, instead of putting them in the page address. Long recipient lists now work, and members’ addresses stay out of the address bar and server logs. LH Export Emails uses this for its Send Email button.
- 3.6.1: Shorter MCP ability descriptions. The opening sentence of copy-imap-attachment now says what the ability does and returns in 120 characters, because discover-abilities lists only that sentence; the detail follows unchanged. search-messages now ends with its permission sentence (Requires manage_options on each site searched, or install_plugins to see every site), saying the same as before. Wording only: no change to behaviour, inputs or outputs.
- 3.6.0: Search Sent Messages now searches the whole network’s outbound email log by default instead of only the current site. Network administrators (install_plugins) see every site, and other users see the sites where they can manage options. An optional site_id narrows the search to one of those sites, and every result now says which site it came from.
- 3.5.0: Failed sends now say why. The error detail on a failed email records which SMTP connection was used and how it was chosen (from address or site default), its host, port, encryption value and username, whether the password is set with its length and any whitespace, and the SMTP conversation with the server, with everything sent during AUTH redacted. If no connection matched it says so and that PHP mail() was used. Fixed error details accumulating across a queue run: each failure used to inherit every earlier failure’s errors from the same request, so the detail now covers only that message’s own attempt. A retry that succeeds clears the old error. The All Emails screen shows a Last attempt column (sortable, with the attempt count after more than one) beside Created, and the single email view shows Last attempt too, so a retried email no longer looks untouched. search-messages returns last_attempt_at and attempts. Schema 2.6.0 adds last_attempt_at and attempts to the messages table; existing rows are backfilled with their dispatch time (sent) or last update (failed), with the attempt count 0 meaning unknown.
- 3.4.4: Compose, outbound log and IMAP abilities are marked as working on one site’s content (meta site_scoped). Where LH MCP Adapter Extensions is active they take an optional site_id to work on another site you belong to, with permissions checked on that site. copy-imap-attachment stays on the current site only, because it depends on LH Private Uploads being loaded for the request. Calls without site_id are unchanged.
- 3.4.3: Schema upgrades can no longer run twice at once. When several admin requests arrived together just after an update (a page load and its admin-ajax calls, for example), each could see the old schema and run the same upgrade routine; on 3.4.0 that made a second request try to add the message_id index the first had just created, logging a “Duplicate key name” database error. Upgrades now run only in the request holding a network-wide database lock, which re-checks the stored schema version once it has the lock; any other request carries on and finds the upgrade done afterwards. No schema change.
- 3.4.2: msgno is removed from IMAP message summaries; use uid. list-imap-messages, search-imap-messages, get-imap-message and get-imap-headers no longer return it. A message’s sequence number changes whenever earlier messages in the folder are deleted or moved, so it could not safely identify a message beyond one connection, and since 3.4.1 list and search returned it as 0. Every ability that acts on a message already takes its uid. Opening a single message or its headers also no longer makes an extra request to the mail server to look the number up.
- 3.4.1: IMAP listing and search no longer time out on mailboxes with unread mail. Pages are now built straight from batched server responses (flags, headers, UID and size) instead of through the IMAP library’s message objects, which sent a needless write to the mailbox for every unread message just to keep it unread, slow enough on Gmail to time out a page of five. Messages are fetched 25 at a time (filterable with lh_email_imap_fetch_chunk_size), and the time limit is checked between batches, so a slow server returns a partial page instead of an empty one. Results, fields and ordering are unchanged, and listing still never marks anything as read.
- 3.4.0: Faster IMAP listing and search. list-imap-messages and search-imap-messages now fetch headers only instead of every message’s full body, search the folder once instead of twice, and look up the UID and size of the whole page in one request instead of two round trips per message. Pages also come back in the order asked for. Both stop building summaries when a request nears the time limit (20 seconds, filterable with lh_email_imap_time_budget) and return what they have with partial true and a returned count, instead of timing out with a 502. The outbound messages table gains an index on message_id (schema 2.5.0), so matching a reply to the email it answers is an index lookup rather than a scan of the whole log.
- 3.3.2: Reply drafts quoting the original message now attribute it the way mail clients do, for example “On Sun, 18 Oct 2026 at 8:59 pm, Peter Shaw wrote:”. The date is shown in a readable form, and the sender’s address is included alongside their name. get-compose and list-composes now report a real created_at for drafts, which previously came back as 0000-00-00 00:00:00 because WordPress does not set a GMT post date until a post is published.
- 3.3.1: Email to a verified email alias (LH User Identity) is now linked to the account that owns the alias. Previously a recipient whose address was only an alias was not recognised, so sending to it created a new, duplicate user account.
- 3.3.0: Compose emails in single mode now accept several To addresses: the first is the To recipient and the others receive the same message as CC, so everyone still sees who it went to. A compose email with no usable recipient or with an invalid address can no longer be published or scheduled from the editor, which shows the reason instead. If one still reaches published by another route, or its message cannot be queued, it is put back to draft rather than left showing as published when nothing was sent. Dispatch now runs once the whole save has finished, so envelope changes saved in the same request as publishing are used. The recipient count recorded at dispatch now includes CC and BCC recipients.
- 3.2.0: Emails to one person now go out straight after the page has been sent, instead of waiting for the queue, which on some sites took over an hour. Every email is still written to the queue table and queued in Action Scheduler first; the send happens once the server has closed the connection to the visitor, so no page is slowed, and if it does not happen the queue sends it as before, with the existing claim stopping any double send. An email addressed to one recipient (To, Cc and Bcc together) is sent this way by default, as is a single-mode compose; anything to several people, and every broadcast, stays staged in the queue. A sender can force either behaviour with an X-LH-Email-Delivery: immediate or staged header, which is not passed on, and the lh_email_send_immediately filter can override the choice. At most five emails are sent after any one response (lh_email_max_sends_after_response); any more are left to the queue. On servers that cannot close the connection early, nothing is sent after the response and the queue handles everything.
- 3.1.1: copy-imap-attachment now declares an output schema describing what it returns: the source reference to pass to lh-mcp-file-abilities, and the details of the attachment or sandbox file it copied to. This completes LH Email’s MCP declarations; every ability in the plugin now declares its annotations, input schema and output schema. No behaviour changes.
- 3.1.0: LH Email’s MCP abilities now each live in their own class under includes/abilities/, as copy-imap-attachment already did. The thirteen abilities that were registered in one 1,000-line trait (create-draft, create-reply-draft, update-draft, get-compose, list-composes, search-messages, list-imap-connections, list-imap-folders, list-imap-messages, search-imap-messages, get-imap-message, get-imap-headers, get-imap-quota) have moved into one class each, extending a shared LH_Email_Ability_Base, and the trait now only registers the category and loads each class. Their behaviour, names, inputs and permissions are unchanged. Every one of them now declares readonly, destructive and idempotent annotations, so MCP clients can tell reads from writes: the eleven readers are read-only, create-draft and create-reply-draft are non-destructive writes, and update-draft is marked destructive because a supplied field replaces the stored value. Each also declares an output schema describing what it returns, and an input schema that rejects unknown parameters. list-imap-messages’ description now says that only date sorting spans the whole folder, and each description states its required capability.
- 3.0.2: The email log’s checkbox column reads each row as an object, as the other columns already do, since WP_List_Table allows a row to be an array or an object. No visible change: rows are always objects in practice. This clears the last static-analysis finding in LH Email’s own code.
- 3.0.1: Coding-standards and small correctness fixes across LH Email, with no change to what it sends or stores. In the email log, a recipient linked to a user account now shows as their name followed by their address in angle brackets; it was double-escaped before, so the address appeared as literal HTML entities. The sort order parameter is now unslashed and sanitised, the list’s pagination figures are passed as integers, and a zero per-page screen option can no longer divide by zero. The inbox message view escapes its header fields where they are printed rather than earlier. WP-Cron is spawned at shutdown through a small void wrapper, spawn_cron_on_shutdown(), after a queued, retried or dispatched email, instead of hooking spawn_cron() directly as an action. An unused debug array that copied all request data on the email log screen is removed, as is a duplicate write_log() in the sender class; logging goes through LH_Email_plugin::write_log() as elsewhere. Translator comments are added or moved onto every translatable string with a placeholder. The connection slug is read and sanitised in one step. Two checks that could never fail are removed from attachment lookup, update-draft only calls wp_update_post() when a subject or body was actually supplied, clean_html() drops an unreachable branch, parse_url() is replaced by wp_parse_url(), and the activation and deactivation hooks are registered with the two arguments WordPress accepts.
- 3.0.0: LH Email no longer reads attachments: it now only copies them out of the mailbox, and reading any file, from an email or anywhere else, is done by LH MCP File Abilities. This is a breaking change to the MCP interface. Three abilities are removed: get-imap-attachment (the bytes as base64), get-imap-attachment-as-resource (the same bytes as an MCP resource block) and get-imap-attachment-text (text extraction from .docx, plain text, PDF and spreadsheets). Their replacements are lh-mcp-file-abilities get-file-as-resource and get-file-text, which also add describe-file, get-file-rows, search-file and get-file-image, the last of which renders scanned documents and photos that no text extractor can read. save-imap-attachment is renamed copy-imap-attachment, and its visibility input is replaced by destination: private (the default, as before) and media (formerly visibility public) file the attachment into the media library as before, and the new sandbox destination writes it to the caller’s own LH MCP Sandbox directory for reading without keeping it, needing LH MCP Sandbox 1.4.0 or later. Every destination now returns a source reference that LH MCP File Abilities accepts directly, so an attachment is read in two calls: copy-imap-attachment, then for example get-file-text with the returned source. The attachment’s provenance is now returned as origin rather than source, and its title no longer carries WordPress’s Private: prefix. Attachments saved by earlier versions are still recognised, so a repeat copy returns them rather than a duplicate. get-imap-message’s description now points to copy-imap-attachment. The attachment and spreadsheet extraction classes are deleted, and the PDF and spreadsheet libraries (smalot/pdfparser, prinsfrank/pdfparser, phpoffice/phpspreadsheet) are removed from Composer, leaving webklex/php-imap as the only dependency. Sending, queuing, the email log and IMAP reading are unchanged.
- 2.13.2: lh-email no longer loads its Composer dependencies on every request. LH_Email_IMAP and LH_Email_Attachment_Extract each required vendor/autoload.php as soon as their files were parsed, so every request on every site in the network loaded the 500 KB Composer classmap and ran its eager files autoloads: the Symfony polyfills and the Illuminate global helper functions such as collect() and data_get() that webklex/php-imap depends on. That cost OPcache memory and per-request work, and defined those global functions for every other plugin, where a different vendored Illuminate version could have collided with them. The autoloader is now loaded on first use only: LH_Email_IMAP::is_available() loads it before the IMAP client is created, and LH_Email_Attachment_Extract loads it before PDF text extraction. Nothing else in the network calls those helpers or the PHP 8.5 polyfilled functions, which was checked before this change. The IMAP abilities, the Inbox screen, attachment text extraction and save-imap-attachment work as before. This release also records a change made on 20 September 2026 without a version: the on-disk readme.txt, readme.md and CHANGELOG.md were removed after the reconstruction check showed that their content is covered by the portfolio entry and its changelog entries, since both readmes are now compiled from the portfolio when the plugin is pushed or zipped.
- 2.13.1: Shows the sending status introduced in 2.13.0 everywhere statuses are listed. The email log list table has a Sending filter view and shows Sending as the status label, the single email view shows Sending as its status, and the search-messages MCP ability accepts lh_email_sending as a status filter (it was previously rejected by the schema and ignored by the query). A record normally only holds this status for the few seconds while it is being sent; one that stays in it longer was interrupted mid-send and is marked failed on the next hourly wakeup.
- 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.)
Changelog
If you like this plugin please consider making a donation to further development