SMTP Mailing Queue queued outbound WordPress mail by replacing the pluggable wp_mail() function, writing each message as a JSON file under wp-content/uploads/smtp-mailing-queue/, and draining the queue on a custom wp-cron schedule. The plugin handled SMTP transport itself by configuring WordPress core’s PHPMailer at phpmailer_init. Dennis Hildenbrand authored it; Antoine Brultet maintained it in its later years.
On 2024-10-09, the wp.org listing was closed at the author’s request. The closure banner reads: “This plugin has been closed as of October 9, 2024 and is not available for download. This closure is permanent. Reason: Author Request.” The plugin is the third member of the queue-only niche in this catalogue, after GD Mail Queue and Mail Queue; for the case for queueing in WordPress, see Understanding email queues in WordPress.
What the wp.org closure changes
Closed listings on wp.org stop receiving auto-updates. Sites that still have SMTP Mailing Queue installed continue to run it (closure does not roll back installs), but any future security or compatibility fix has to come from the GitHub source at
github.com/Birmania/smtp-mailing-queue or from a private fork. The last release published via wp.org was 2.0.1, a security patch escaping admin output in the “Tools” and “Supervisors” tabs to prevent stored XSS. That is the final security update the plugin will receive through the standard channel.
The final wp.org metadata is also stale by a wide margin. The plugin is tested up to WordPress 5.9.3; current WordPress is on the 7.x line. The gap spans the PHPMailer 6.x replacement in 5.5, the pre_wp_mail short-circuit introduced in 5.7, and several rounds of block-editor mailer work since. The minimum requirements (WordPress 3.9, PHP 5.4) belong to a different era of the project entirely.
How it intercepts outbound mail
SMTP Mailing Queue is the only plugin in the queue-only trio that intercepts at the pluggable-function layer rather than via a filter. The main plugin file (smtp-mailing-queue.php) defines wp_mail() directly, inside the if (!function_exists('wp_mail')) guard that WordPress’s pluggable mechanism provides:
if (!function_exists('wp_mail') && !isset($_GET['smqProcessQueue'])) {
function wp_mail($to, $subject, $message, $headers = '', $attachments = array())
{
global $smtpMailingQueue;
return $smtpMailingQueue->wp_mail($to, $subject, $message, $headers, $attachments);
}
}
The handler delegates to SMTPMailingQueue::wp_mail() in classes/SMTPMailingQueue.php, which applies the min_recipients threshold (default 1) and, for messages with fewer recipients than the threshold, falls back to the original pluggable wp_mail via the plugin’s own OriginalPluggeable wrapper at classes/OriginalPluggeable/OriginalPluggeable.php. Messages at or above the threshold are routed into the queue via storeMail().
This is the structural difference against the two siblings. GD Mail Queue intercepts at phpmailer_init and swaps the PHPMailer instance for a stub. Mail Queue intercepts at pre_wp_mail, the short-circuit filter introduced in WordPress 5.7. SMTP Mailing Queue intercepts at the pluggable-function layer that WordPress 5.7 explicitly made redundant for this purpose. The interception works, but it owns more of the surface than it needs to.
The pluggable-function rule and what it implies
WordPress’s pluggable system loads the first plugin that defines wp_mail() and treats subsequent function_exists checks as a no-op. Load order is the order plugins appear in the active_plugins option, which is the order they were activated. There is no admin control over this. Operators who add a second plugin that also redefines wp_mail to a site running SMTP Mailing Queue get one of two outcomes: either the second plugin’s wp_mail definition is skipped because SMTP Mailing Queue already owns the function, or SMTP Mailing Queue is the one whose definition is skipped because the other plugin loaded first. Whichever loses, loses silently. There is no warning in the admin.
The modern integration path for an outbound-mail plugin is the wp_mail filter chain plus the phpmailer_init action. Hooks on those two surfaces compose with each other and with pre_wp_mail-based queue layers like Mail Queue. SMTP Mailing Queue’s pluggable-function replacement does not compose: a plugin that owns wp_mail() cannot share it with another plugin that wants the same function name.
storeMail, filter handling, and on-disk storage
When the queue path is taken, SMTPMailingQueue::storeMail() runs the wp_mail filter and the pre_wp_mail filter at enqueue time. The 2.0.0 changelog records this behaviour as a fix: earlier versions deferred the filters until send, which broke plugins that registered transient filters during the calling request. The handler serializes recipients, subject, message, headers, and attachments into a JSON file under wp-content/uploads/smtp-mailing-queue/, with attachments stored separately under attachments/ via SMTPMailingQueueAttachments::storeAttachments(). The directory is created on first use; a .htaccess file with DENY FROM ALL is written at the same time. Sites on nginx or other non-Apache servers must add the equivalent deny rule by hand. The plugin readme says so; the plugin does nothing automatic about it.
The on-disk layout is the operational distinction against the siblings, both of which queue to the database. Filesystem-level backups capture the queue. So does anything else with filesystem read access to the uploads directory. Storing the queue on disk also means a fast filesystem helps and a slow shared filesystem hurts; a database-backed queue inherits the database’s performance characteristics instead.
The send path
When the queue drains, processQueue() reads each JSON file, decodes it, and calls sendMail($data). sendMail() re-enters wp_mail() with the wp_mail, pre_wp_mail, wp_mail_from, wp_mail_from_name, wp_mail_content_type, and wp_mail_charset filters temporarily removed via a withoutFilters() helper. Because SMTP Mailing Queue’s wp_mail is the pluggable definition, the re-entry hits its own handler again, but the _GET['smqProcessQueue'] flag is set during processing, which causes the pluggable definition to be skipped (the guard condition above includes !isset($_GET['smqProcessQueue'])). Control falls through to WordPress core’s wp_mail(), which constructs the core PHPMailer.
SMTP Mailing Queue does not ship a PHPMailer fork. The file at classes/PHPMailer/class-phpmailer.php is a compatibility shim that loads the core PHPMailer for whichever WordPress version is running: the pre-5.5 class-phpmailer.php, or the post-5.5 namespaced PHPMailer/PHPMailer.php plus class_alias calls that map the namespaced classes onto the global names the plugin uses. The shim is included by storeMail() so that the class names are available at enqueue time.
The SMTP transport is configured by initMailer() on the phpmailer_init action, from the saved settings: host, port, encryption (SSL or TLS), and credentials. The SMTP password is stored encrypted in wp_options using openssl_encrypt with an AES-256-CBC key derived from AUTH_SALT, falling back to base64 if openssl is unavailable on the server. The encrypted blob sits in the same database as the application’s own AUTH_SALT-derived key material; the standard “secrets in wp_options” trade-off applies, and is not specific to this plugin.
Queue draining: the cron pings itself
The custom cron schedule smq is registered via the cron_schedules filter with an interval from wpcron_interval (default 300 seconds). The action smq_start_queue is hooked to callProcessQueue(), which does not drain the queue directly. It calls wp_remote_get() against ?smqProcessQueue&key=<process_key> on the site’s own URL, with a timeout of WP_CRON_LOCK_TIMEOUT / 2 (30 seconds on a stock WordPress install). The target request triggers processQueue() via an init hook gated on the same _GET['smqProcessQueue'] parameter. That second request is the one that takes a batch of up to queue_limit (default 10) messages, calls sendMail() on each, increments a per-message failure counter on failure, moves messages exceeding max_retry (default 10) into invalid/, and moves successful sends into sent/ for later purging (controlled by sent_storage_size, default 0 = purge immediately).
The loopback-HTTP design is unusual. Most queue plugins drain the queue inside the cron callback itself. SMTP Mailing Queue does it this way because the pluggable wp_mail definition needs a clean request context to be safely bypassed via the _GET flag. The cost is sensitivity to anything that blocks loopback HTTP on the host: split DNS, host-level loopback firewalls, IPv6 misconfiguration, hostnames that require idn_to_ascii punycoding (the plugin handles this case explicitly). The benefit is a clean separation between the request that enqueues the message and the request that sends it.
The dont_use_wpcron option disables the wp-cron registration entirely. Operators then wire a system cron job to call ?smqProcessQueue&key=<process_key> directly at the chosen interval. The process_key is a 16-character generated secret stored in smtp_mailing_queue_advanced.process_key. The endpoint is otherwise public; the secret is the only access control. On a site fronted by a CDN, the cron request has to bypass the cache, which sometimes requires an explicit cache exception rule.
An hourly smq_sanity_checks cron event reschedules smq_start_queue if it has gone missing. This is a workaround for an observed wp-cron bug where the queue event would disappear from the schedule, leaving the queue stuck.
Logging and the Supervisors tab
The plugin exposes three on-disk lists in the admin under “Supervisors”: queued, invalid (retries exhausted), and sent. Each is a directory of JSON files. The admin offers preview, delete, retry, and purge actions. The granularity sits between Mail Queue’s event log and GD Mail Queue’s database grid; the substantial caveat is that messages sit on disk in plain JSON, including the attachment filesystem paths. The sent_storage_size default of 0 is correct for production; the plugin readme notes that retained sent mails are not encrypted on disk and recommends keeping retention off.
Compatibility
The plugin queues anything that calls wp_mail(). Plugins that ship their own mailer or call PHP mail() directly bypass the queue, the same boundary as the two siblings. The pluggable-function conflict with the mainstream SMTP plugins is the operationally important compatibility note (see above). For Contact Form 7, BuddyPress, bbPress, and any other plugin that emits its mail through wp_mail, the integration is transparent. For multisite, the upload path is the site’s uploads directory, which on multisite is shared across subsites unless UPLOADS has been customised in wp-config.php. The plugin is single-site oriented; multisite is best-effort.
Where it sits against alternatives
Two comparison axes.
Versus the queue-only siblings. GD Mail Queue intercepts at phpmailer_init, queues to a database table, and is eighteen months stale but still listed. Mail Queue intercepts at pre_wp_mail, queues to a database table, and is actively maintained. SMTP Mailing Queue replaces the pluggable wp_mail, queues to files on disk, configures SMTP transport itself, and is closed on wp.org. The file-on-disk storage is the architectural delta against the siblings; the closure is the operational one.
Versus the mainstream SMTP plugins with queueing. FluentSMTP and Post SMTP ship queue features in their free tiers; WP Mail SMTP Pro ships a delayed-send queue. All three are actively maintained, all three integrate with the major transactional providers, and all three offer the queue feature inside an SMTP plugin rather than as a separate queue-only product. None of them is a worse choice than running a closed plugin.
Assessment
The recommended position in 2026: do not install SMTP Mailing Queue. Migrate sites that still run it to one of the actively-maintained alternatives. FluentSMTP and Post SMTP are the cleanest swaps for sites that want queueing inside the SMTP plugin. Mail Queue plus a separate SMTP plugin is the cleanest swap for sites that want the queue layer decoupled from the transport. Either path removes the pluggable-function conflict and restores the maintenance channel that wp.org provides.
The narrow case where leaving SMTP Mailing Queue in place is defensible: a long-running site that does not expose its WordPress admin to the public internet, whose operator can mirror the GitHub source and patch in private, and whose mail volume is low enough that the next compatibility break in WordPress core can be fielded as planned work rather than as an outage. Even there, the migration is cheaper than the watch.
For the broader setup the plugin slots into, see how to set up WordPress email.
