brianhenryie / bh-wp-mailboxes
A library for importing emails into WordPress for further processing.
Requires
- php: >=8.4
- composer-runtime-api: ^2.2
- ext-json: *
- directorytree/imapengine: ^1.25
- psr/log: *
- wordpress/secrets-api: dev-main#ffe647beff839024722eac1964d310125388c355
- wptrt/admin-notices: ^1.0
Requires (Dev)
- ext-dom: *
- 10up/wp_mock: *
- alleyinteractive/wordpress-autoloader: ^1.2
- antecedent/patchwork: ^2.2
- brianhenryie/bh-wp-logger: >=0.4
- brianhenryie/bh-wp-private-uploads: >=0.5.1
- brianhenryie/color-logger: *
- dealerdirect/phpcodesniffer-composer-installer: *
- google/apiclient: ^2.19
- johnpbloch/wordpress: *
- lucatume/wp-browser: *
- phpcompatibility/phpcompatibility-wp: *
- phpstan/extension-installer: *
- phpstan/phpstan-mockery: ^2.0
- phpstan/phpstan-phpunit: ^2.0
- phpunit/phpcov: *
- rector/rector: ^2.6
- squizlabs/php_codesniffer: *
- szepeviktor/phpstan-wordpress: *
- vlucas/phpdotenv: *
- wordpress/wordpress: 7.1.0
- wp-coding-standards/wpcs: *
- wpackagist-plugin/wp-crontrol: ^1.21
- wpackagist-theme/twentytwenty: *
Suggests
- brianhenryie/bh-wp-private-uploads: Enables attachments.
- google/apiclient: Enables Gmail API support via console.cloud.google.com (Google Developers console).
Provides
None
Conflicts
None
Replaces
None
This package is auto-updated.
Last update: 2026-09-11 11:11:13 UTC
README
BH WP Mailboxes
A library to download emails into WordPress plugins.
e.g.
- Order payment receipts (Zelle, Venmo etc.)
- Newsletter unsubscribe emails
- Helpdesk
- Post by email
A plugin user should be able to configure an inbox in the plugin settings, the library will download emails on a cron schedule, the library will filter emails to a predicate (e.g. only emails sent by @venmo.com, or a negative filter excluding known irrelevant subjects), the emails are saved to log, then the library fires an action for each new email downloaded. The parent plugin listens for that and acts appropriately, e.g. processes an unsubscribe request, creates a helpdesk ticket, etc.
The core library this is built around is zbateson/mail-mime-parser – mail-mime-parser.org.
Goals
- Handle bad credentials – servers block IPs that have too many bad login attempts, so delay a few hours after each failed attempt, admin_notice to alert admins of problem (warning -> error)
- Support multiple mailboxes
- Save emails to cpt after filtering
- Autodelete email cpts locally.
- Optionally delete emails from the server after downloading (some email services are still size limited).
- Handle delayed emails. Maybe emails would only be delayed if the IMAP server is down. I just know email has an auto-retry mechanism to keep trying until delivered / 48 hours.
It's almost supposed to be a log of emails fetched whose data is used in plugins, for debugging when downloaded emails don't trigger plugins as expected, e.g. regex no longer matches after email body changes.
Anti-goals:
- User-facing UI – the WP_List_Table (conventional, extensible) UI is intended for debugging, to allow site admins (shop managers etc.) to see the original emails and to test account settings etc.
- Sending email – use WP core functions for that, i.e.
wp_mail()with an SMTP plugin. I recommend sending via AWS SES using WP SES plugin
Implementation
Your implementation first needs the BH_WP_Mailboxes_Settings_Interface configuration which sets the custom post type names that mailboxes and emails are saved to, and the cron schedules mailboxes will be checked on.
$mailboxes = BH_WP_Mailboxes::make( $mailboxes_settings, $this->logger );
Somewhere in your plugin's settings you'll want to add a section for email account settings, e.g. IMAP server, username, password, etc. Some settings will probably be configured by you as a plugin developer, e.g. the number of days before emails are deleted.
Using API::save_new_mailbox(), save the account configuration to a wp_post. When saving you will set the connection_type_class, e.g. ImapEngine_Imap_Email_Connection
Credentials are saved by the library, encrypted, using the WordPress Secrets API (currently the wordpress/secrets-api feature plugin, which this library depends on via Composer and always uses as its own copy: on the first credential read or write it loads the API's secrets.php and classes from vendor/ and talks to WP_Secrets_Libsodium_Provider directly, never calling wp_get_secret(), and consumers prefix the copy's class, function and constant names at build time so it never interacts with core's or an activated plugin's implementation). Save them with API::save_account_credentials( $account, $credentials ) (e.g. an Imap_Credentials or Imap_Credentials_Env); the accounts table's add/edit modal does this for you. Read them back with API::get_account_credentials( $account ); they are discarded with API::delete_email_account().
The Secrets API derives its encryption key from LOGGED_IN_KEY/LOGGED_IN_SALT, or from a WP_SECRETS_KEY constant (base64-encoded 32 bytes) when defined; a site whose wp-config.php still has the sample placeholders (e.g. WordPress Playground) must define WP_SECRETS_KEY or saving fails with a logged error.
Saved mailboxes are checked on a cron job for new emails. When a new email is downloaded, the library fires bh_wp_mailboxes_new_email for you to listen for.
Use the methods on New_Email_Interface to read the email, log any action taken, and maybe mark it to be saved
Connection Types
IMAP
IMAP_Credentials_Interface requires the server, username, password, encryption type, and whether to validate the server's TLS certificate (should_validate_cert(), true unless the server uses a self-signed or otherwise untrusted certificate). Port defaults to 993 for TLS and 143 for STARTTLS or none, and can be overridden by specifying it with the server name.
An Imap_Credentials_Env class exists that reads from environmental variables IMAP_SERVER, IMAP_USERNAME, IMAP_PASSWORD, IMAP_ENCRYPTION, IMAP_VALIDATE_CERT (only false, 0, no or off turns validation off), and those env variable names can be specified in the constructor.
Cloudflare Email Routing
A Cloudflare Worker is available that forwards all mail received to a WordPress REST endpoint (example.com/wp-json/.../email-cpt/new).
Enable the REST endpoint by setting BH_WP_Mailboxes_Settings_Interface::get_rest_namespace().
Install the Worker with:
Then visit the Worker URL provided when Cloudflare finishes installing. There you will:
- Set a password for the Worker config page for future access
- Enter the website URL for WordPress and "Continue to WordPress authorization" to grant the Worker an Application Password.
- Set a Cloudflare API token (unsaved) to configure a domain to forward emails to the Worker
- Optionally set an email address to receive failure alerts
The main limit is that Cloudflare Email Routing must be enabled on the entire domain.
Gmail
Gmail can use regular IMAP via application passwords when the account has 2FA enabled.
To use the Gmail API, see includes/connections/gmail-api/README-GMAIL.md for configuring a Google Developer Console project. I think supporting this in distributed plugins is probably too much work!
WP-CLI
The library registers WP-CLI commands for each mailbox it's used to create. They are namespaced under that mailbox's CLI base — BH_WP_Mailboxes_Settings_Interface::get_cli_base(), which defaults to the plugin slug. Return null from get_cli_base() to disable registering CLI commands. Replace <cli-base> below with that value.
wp <cli-base> mailboxes list
List every configured mailbox. A "mailbox" is one instance of the library — an emails post type plus its accounts post type — and may contain many email accounts. The row shows the slug, post-type names, friendly name, and account count. Unlike accounts list (which is scoped to one mailbox), this spans every registered mailbox.
wp <cli-base> mailboxes list [--format=<table|csv|json|yaml|count>]
wp <cli-base> accounts list
List the email accounts configured for this mailbox — id, email, display name, connection, active state, and last-checked time.
wp <cli-base> accounts list [--format=<table|csv|json|yaml|count>]
Privacy / GDPR
The default setting is to delete emails after 7 days. NB: if you're using a shared inbox for your plugin's purpose (e.g. Venmo receipt emails go to treasurer@company.com rather than payments@company.com) this library will download and save all emails (that match the Email_Account_Settings_Interface::get_from_email_regex() and ::get_body_identifier_regex()). You can immediately delete each emails that you know is not relevant, but that is not the default. Emails that are downloaded are saved for debugging, e.g. the format of the Venmo emails changes and regexes that used to work to extract the relevant data no longer work, so you can see the original email in the WP List Table UI. Be aware of this and inform your company's data controller. I am not a lawyer, but I think this is ok!
Managing accounts from your own screen
The emails list screen has an accounts table with an "Add account" button that opens the add/edit IMAP account modal (name, address, server, username, password, encryption, and a "Validate the server's certificate" checkbox, with a "Test connection" button). The modal is reusable: print it on any admin screen (e.g. a WooCommerce payment gateway settings page) together with an "Add account" button, and enqueue its assets there.
use BrianHenryIE\WP_Mailboxes\Admin\Email_Account_Modal; $modal = new Email_Account_Modal( $settings ); add_action( 'admin_enqueue_scripts', fn() => $modal->enqueue_assets() ); // Only on your screen. add_action( 'admin_footer', fn() => $modal->print_modal() ); $modal->print_add_button(); // Where the button should appear.
The library saves the account and its credentials (encrypted, in the WordPress Secrets API): there is nothing for you to persist.
The result is reported in an admin notice inserted after your page's <hr class="wp-header-end">
(or after its first heading when there is none). The development plugin's settings page
(development-plugin/admin/class-settings.php) is a working example, including saving credentials
from its own forms with API::save_account_credentials().
Extensibility
Filters
-
bh_wp_mailboxes_imap_mailbox_config–( array $config, string $plugin_slug, IMAP_Credentials_Interface $credentials, Email_Account_Settings_Interface $account ): the ImapEngine mailbox configuration (host,port,username,password,encryption,validate_cert) just before the IMAP connection is created. Add any other key ImapEngine supports, e.g. to log the IMAP conversation while debugging:add_filter( 'bh_wp_mailboxes_imap_mailbox_config', function ( array $config, string $plugin_slug ): array { if ( 'my-plugin' === $plugin_slug ) { $config['debug'] = WP_CONTENT_DIR . '/imap.log'; // Or `true` to echo it. } return $config; }, 10, 2 );
-
bh_wp_mailboxes_connection_for_account–( ?Email_Connection_Interface $connection, string $plugin_slug, string $emails_cpt, BH_Email_Account $account ): supply a custom connection for an account. -
bh_wp_mailboxes_max_message_size_bytes–( int $bytes, string $plugin_slug, BH_WP_Mailboxes_Settings_Interface $settings ): the largest raw email the REST ingress accepts (defaults to PHP'spost_max_size). -
bh_wp_mailboxes_registered_mailboxes–( API_Interface[] $mailboxes, string $plugin_slug ): every library instance registers its API here, so tooling (WP-CLI, the development plugin) can find them.
Contributing
See CONTRIBUTING.md for details on contributing to the project. It's easy.
TODO:
- AWS SES inbound SMTP via SNS
- All exceptions should be caught and displayed as admin_notices, never thrown (never expect the plugin developer to handle exceptions from the library).
More Information
See github.com/BrianHenryIE/WordPress-Plugin-Boilerplate for initial setup rationale.

