diff --git a/changelog.txt b/changelog.txt
index 1b038ee..8cecb95 100644
--- a/changelog.txt
+++ b/changelog.txt
@@ -1,3 +1,10 @@
+= 2.2.7 =
+
+* IMPROVED: Worker now runs as a continuous loop, draining the queue completely before exiting
+* IMPROVED: Statistics are synced after each batch (not only at the end)
+* IMPROVED: Worker output includes worker ID on each line for clearer parallel logs
+* FIXED: Uneven work distribution between parallel workers
+
= 2.2.6 =
* CRITICAL FIX: Worker now suppresses plugin header warnings in MultiSite environments
diff --git a/readme.txt b/readme.txt
index b023cba..ce00668 100644
--- a/readme.txt
+++ b/readme.txt
@@ -4,7 +4,7 @@ Tags: newsletter, smtp, email, bulk, queue, worker, amazonses, statistics
Requires at least: 4.7
Tested up to: 6.9
Requires PHP: 7.2
-Stable tag: 2.2.6
+Stable tag: 2.2.7
License: GPLv2 or later
License URI: https://www.gnu.org/licenses/gpl-2.0.html
@@ -141,6 +141,12 @@ Yes. The plugin integrates with ROBOTSTXT SMTP rate limiting system to ensure yo
== Changelog ==
+= 2.2.7 =
+* IMPROVED: Worker now runs as a continuous loop, draining the queue completely before exiting
+* IMPROVED: Statistics are synced after each batch
+* IMPROVED: Worker output includes worker ID on each line for clearer parallel logs
+* FIXED: Uneven work distribution between parallel workers
+
= 2.2.6 =
* CRITICAL FIX: Worker now suppresses plugin header warnings in MultiSite environments
* CRITICAL FIX: Fixed Newsletter base class dependencies not loading in DOING_CRON context
@@ -208,6 +214,9 @@ Yes. The plugin integrates with ROBOTSTXT SMTP rate limiting system to ensure yo
== Upgrade Notice ==
+= 2.2.7 =
+Workers now drain the full queue per invocation. Recommended upgrade for better parallel distribution.
+
= 2.2.6 =
CRITICAL: Fixed multiple worker issues in MultiSite environments including header warnings, class loading, and subdirectory site detection. Essential for reliable worker operation.
diff --git a/robotstxt-smtp-newsletter.php b/robotstxt-smtp-newsletter.php
index 055ea44..1169fd8 100644
--- a/robotstxt-smtp-newsletter.php
+++ b/robotstxt-smtp-newsletter.php
@@ -3,7 +3,7 @@
* Plugin Name: Newsletter - SMTP (by ROBOTSTXT)
* Plugin URI: https://git.robotstxt.es/ROBOTSTXT/robotstxt-smtp-newsletter
* Description: Integrates ROBOTSTXT SMTP configuration with Newsletter plugin for efficient bulk email delivery with database-backed queue and external workers.
- * Version: 2.2.6
+ * Version: 2.2.7
* Requires at least: 4.7
* Requires PHP: 7.2
* Security: robotstxt@robotstxt.es
@@ -25,7 +25,7 @@ if ( ! defined( 'ABSPATH' ) ) {
}
// Plugin constants.
-define( 'ROBOTSTXT_SMTP_NEWSLETTER_VERSION', '2.2.6' );
+define( 'ROBOTSTXT_SMTP_NEWSLETTER_VERSION', '2.2.7' );
define( 'ROBOTSTXT_SMTP_NEWSLETTER_FILE', __FILE__ );
define( 'ROBOTSTXT_SMTP_NEWSLETTER_PATH', plugin_dir_path( __FILE__ ) );
define( 'ROBOTSTXT_SMTP_NEWSLETTER_URL', plugin_dir_url( __FILE__ ) );
diff --git a/update.json b/update.json
index 4bfb4f5..1ef8fbc 100644
--- a/update.json
+++ b/update.json
@@ -1,19 +1,19 @@
{
"name": "Newsletter - SMTP (by ROBOTSTXT)",
"slug": "robotstxt-smtp-newsletter",
- "version": "2.2.6",
+ "version": "2.2.7",
"author": "ROBOTSTXT",
"homepage": "https://git.robotstxt.es/ROBOTSTXT/robotstxt-smtp-newsletter",
- "download_url": "https://git.robotstxt.es/ROBOTSTXT/robotstxt-smtp-newsletter/releases/download/2.2.6/robotstxt-smtp-newsletter-2.2.6.zip",
+ "download_url": "https://git.robotstxt.es/ROBOTSTXT/robotstxt-smtp-newsletter/releases/download/2.2.7/robotstxt-smtp-newsletter-2.2.7.zip",
"requires": "4.7",
"tested": "6.9",
"requires_php": "7.2",
- "last_updated": "2026-02-16",
- "description": "Integrates ROBOTSTXT SMTP configuration with Newsletter plugin for reliable bulk email delivery with database-backed queue and external workers. Provides persistent email queue, parallel processing, automatic retry, and comprehensive campaign statistics.",
- "changelog": "
2.2.6 - 2026-02-16
- CRITICAL: Worker now suppresses plugin header warnings in MultiSite environments
- CRITICAL: Fixed Newsletter base class dependencies not loading in DOING_CRON context
- CRITICAL: Worker now manually initializes plugin when newsletter_loaded hook doesn't fire
- CRITICAL: Fixed MultiSite subdirectory site detection - worker correctly loads site from --url path
- CRITICAL: Removed WP_CLI constant - caused fatal errors with multisite-clone-duplicator and similar plugins
- Fixed: Headers already sent warnings from Redirection plugin and other plugins during WordPress init
- Improved: Worker loads NewsletterAddon and NewsletterMailerAddon before plugin
- Improved: Worker calls Plugin::instance_init() manually for proper initialization
- Improved: REQUEST_URI parsed from URL path for correct MultiSite site selection
- Technical: Output buffering prevents header-related warnings from breaking worker execution
2.2.5 - 2026-02-16
- CRITICAL: Worker now loads plugin manually in DOING_CRON context
- Fixed: Newsletter SMTP plugin is not active error in worker script
- Improved: Worker compatibility with all WordPress environments including MultiSite
- Technical: Worker attempts to load plugin file directly if not already loaded via hooks
2.2.4 - 2026-02-16
- CRITICAL: Speed calculation now uses actual NEWSLETTER_CRON_INTERVAL value
- Fixed: Batch_size was being divided by 5 due to incorrect cron interval assumption
- Improved: Plugin now dynamically calculates speed based on actual Newsletter cron interval
- Technical: Loads Newsletter's cron.php to detect actual interval, supports any cron interval without hardcoded assumptions
2.2.3 - 2026-02-16
- CRITICAL: Overridden get_emails_per_run() to bypass Newsletter's internal calculation
- Fixed: Newsletter was calculating batch = speed / 12, resulting in only 8-16 emails per execution
- Fixed: Plugin now directly returns configured batch_size for proper queue loading
- Technical: Added get_emails_per_run() override in Plugin class
2.2.2 - 2026-02-16
- Fixed: Newsletter now correctly respects configured batch_size when enqueuing emails
- Fixed: Emails are now enqueued all at once to database instead of gradually
- Improved: Added public $batch property that Newsletter reads directly
- Improved: Added get_batch_size() method for Newsletter compatibility
- Technical: Mailers expose batch_size through multiple interfaces for Newsletter compatibility
2.2.1 - 2026-02-16
- Added: MultiSite detection in settings page with tailored worker commands
- Added: Worker script now supports --url parameter for MultiSite environments
- Improved: Settings page automatically displays installation-specific worker setup instructions
- Improved: Worker documentation with MultiSite usage examples
- Improved: README with dedicated MultiSite Installations section
- Technical: Worker script parses --url parameter and sets appropriate $_SERVER variables
- Technical: Settings page uses is_multisite() and get_site_url() for detection and URL generation
2.2.0 - 2026-02-16
- MAJOR: Database-backed email queue for persistent, reliable email delivery
- MAJOR: External CLI worker script for asynchronous email processing
- Added: Queue Status widget with real-time statistics (pending, processing, sent, failed)
- Added: Recent Campaigns section with key metrics in settings page
- Added: Email Queue admin page to view all queued and sent emails
- Added: Statistics admin page with campaign performance metrics
- Added: Parallel worker support for high-volume sending (10+ workers simultaneously)
- Added: Automatic retry with exponential backoff (up to 3 attempts: 5min → 15min → 30min)
- Added: Campaign statistics tracking (start time, duration, speed, success rate)
- Changed: Emails are now queued to database instead of sent immediately
- Changed: Settings page reorganized with Queue Status, Recent Campaigns, and Worker Configuration sections
- Changed: Campaign statistics now grouped by unique Newsletter ID instead of campaign name
- Removed: max_per_connection setting (no longer needed with worker architecture)
- Removed: Direct PHPMailer usage (replaced with wp_mail() in workers)
- Fixed: Duplicate campaign statistics for campaigns with same name but different IDs
- Fixed: Worker command documentation (corrected parallel worker syntax)
- BREAKING: Manual worker setup required - emails no longer send automatically
- BREAKING: System cron or process supervisor required for production use
- Technical: New database tables for queue and statistics with automatic creation on activation
- Technical: Microsecond precision timestamps for accurate performance metrics
- Security: CLI-only worker script with SQL injection prevention and capability checks
2.1.0 - 2026-02-09
- Changed: Uninstall behavior: plugin no longer performs cleanup on uninstall, all data cleanup is now handled by the core SMTP plugin
- Changed: This ensures Newsletter configuration is preserved when only the add-on is removed, and properly cleaned when the core plugin is uninstalled
- Technical: Simplified uninstall.php to delegate all cleanup responsibility to core SMTP plugin
2.0.1 - 2026-01-29
- Changed: Removed Network requirement from plugin headers to allow single-site activation
2.0.0 - 2026-01-29
- Changed: Updated compatibility with SMTP (by ROBOTSTXT) plugin 2.0.0
- Changed: Enhanced integration with core SMTP plugin security improvements
- Improved: Better error handling and validation
1.0.0 - 2026-01-29
- Added: Initial release with SMTPKeepAlive support
- Added: Amazon SES API integration for optimized delivery
- Performance: 20-30x faster bulk email sending compared to standard wp_mail()
",
+ "last_updated": "2026-02-18",
+ "description": "Integrates ROBOTSTXT SMTP configuration with Newsletter plugin for reliable bulk email delivery with database-backed queue and external workers. Workers now run as continuous loops draining the full queue, with balanced distribution across parallel workers.",
+ "changelog": "2.2.7 - 2026-02-18
- Improved: Worker now runs as a continuous loop, draining the queue completely before exiting
- Improved: Statistics are synced after each batch
- Improved: Worker output includes worker ID on each line for clearer parallel logs
- Fixed: Uneven work distribution between parallel workers
2.2.6 - 2026-02-16
- CRITICAL: Worker now suppresses plugin header warnings in MultiSite environments
- CRITICAL: Fixed Newsletter base class dependencies not loading in DOING_CRON context
- CRITICAL: Worker now manually initializes plugin when newsletter_loaded hook doesn't fire
- CRITICAL: Fixed MultiSite subdirectory site detection - worker correctly loads site from --url path
- CRITICAL: Removed WP_CLI constant - caused fatal errors with multisite-clone-duplicator and similar plugins
- Fixed: Headers already sent warnings from Redirection plugin and other plugins during WordPress init
- Improved: Worker loads NewsletterAddon and NewsletterMailerAddon before plugin
- Improved: Worker calls Plugin::instance_init() manually for proper initialization
- Improved: REQUEST_URI parsed from URL path for correct MultiSite site selection
- Technical: Output buffering prevents header-related warnings from breaking worker execution
2.2.5 - 2026-02-16
- CRITICAL: Worker now loads plugin manually in DOING_CRON context
- Fixed: Newsletter SMTP plugin is not active error in worker script
- Improved: Worker compatibility with all WordPress environments including MultiSite
- Technical: Worker attempts to load plugin file directly if not already loaded via hooks
2.2.4 - 2026-02-16
- CRITICAL: Speed calculation now uses actual NEWSLETTER_CRON_INTERVAL value
- Fixed: Batch_size was being divided by 5 due to incorrect cron interval assumption
- Improved: Plugin now dynamically calculates speed based on actual Newsletter cron interval
- Technical: Loads Newsletter's cron.php to detect actual interval, supports any cron interval without hardcoded assumptions
2.2.3 - 2026-02-16
- CRITICAL: Overridden get_emails_per_run() to bypass Newsletter's internal calculation
- Fixed: Newsletter was calculating batch = speed / 12, resulting in only 8-16 emails per execution
- Fixed: Plugin now directly returns configured batch_size for proper queue loading
- Technical: Added get_emails_per_run() override in Plugin class
2.2.2 - 2026-02-16
- Fixed: Newsletter now correctly respects configured batch_size when enqueuing emails
- Fixed: Emails are now enqueued all at once to database instead of gradually
- Improved: Added public $batch property that Newsletter reads directly
- Improved: Added get_batch_size() method for Newsletter compatibility
- Technical: Mailers expose batch_size through multiple interfaces for Newsletter compatibility
2.2.1 - 2026-02-16
- Added: MultiSite detection in settings page with tailored worker commands
- Added: Worker script now supports --url parameter for MultiSite environments
- Improved: Settings page automatically displays installation-specific worker setup instructions
- Improved: Worker documentation with MultiSite usage examples
- Improved: README with dedicated MultiSite Installations section
- Technical: Worker script parses --url parameter and sets appropriate $_SERVER variables
- Technical: Settings page uses is_multisite() and get_site_url() for detection and URL generation
2.2.0 - 2026-02-16
- MAJOR: Database-backed email queue for persistent, reliable email delivery
- MAJOR: External CLI worker script for asynchronous email processing
- Added: Queue Status widget with real-time statistics (pending, processing, sent, failed)
- Added: Recent Campaigns section with key metrics in settings page
- Added: Email Queue admin page to view all queued and sent emails
- Added: Statistics admin page with campaign performance metrics
- Added: Parallel worker support for high-volume sending (10+ workers simultaneously)
- Added: Automatic retry with exponential backoff (up to 3 attempts: 5min → 15min → 30min)
- Added: Campaign statistics tracking (start time, duration, speed, success rate)
- Changed: Emails are now queued to database instead of sent immediately
- Changed: Settings page reorganized with Queue Status, Recent Campaigns, and Worker Configuration sections
- Changed: Campaign statistics now grouped by unique Newsletter ID instead of campaign name
- Removed: max_per_connection setting (no longer needed with worker architecture)
- Removed: Direct PHPMailer usage (replaced with wp_mail() in workers)
- Fixed: Duplicate campaign statistics for campaigns with same name but different IDs
- Fixed: Worker command documentation (corrected parallel worker syntax)
- BREAKING: Manual worker setup required - emails no longer send automatically
- BREAKING: System cron or process supervisor required for production use
- Technical: New database tables for queue and statistics with automatic creation on activation
- Technical: Microsecond precision timestamps for accurate performance metrics
- Security: CLI-only worker script with SQL injection prevention and capability checks
2.1.0 - 2026-02-09
- Changed: Uninstall behavior: plugin no longer performs cleanup on uninstall, all data cleanup is now handled by the core SMTP plugin
- Changed: This ensures Newsletter configuration is preserved when only the add-on is removed, and properly cleaned when the core plugin is uninstalled
- Technical: Simplified uninstall.php to delegate all cleanup responsibility to core SMTP plugin
2.0.1 - 2026-01-29
- Changed: Removed Network requirement from plugin headers to allow single-site activation
2.0.0 - 2026-01-29
- Changed: Updated compatibility with SMTP (by ROBOTSTXT) plugin 2.0.0
- Changed: Enhanced integration with core SMTP plugin security improvements
- Improved: Better error handling and validation
1.0.0 - 2026-01-29
- Added: Initial release with SMTPKeepAlive support
- Added: Amazon SES API integration for optimized delivery
- Performance: 20-30x faster bulk email sending compared to standard wp_mail()
",
"sections": {
- "description": "Integrates ROBOTSTXT SMTP configuration with Newsletter plugin for reliable bulk email delivery with database-backed queue and external workers. Version 2.2.6 fixes header warnings in MultiSite environments with active plugins like Redirection. Emails are queued to database and processed by external CLI workers, providing reliability, scalability, and detailed monitoring.",
- "changelog": "2.2.6 - 2026-02-16
- CRITICAL: Fixed header warnings in MultiSite with Redirection plugin
- CRITICAL: Fixed Newsletter class dependencies in DOING_CRON context
- CRITICAL: Worker manually initializes plugin when hooks don't fire
- CRITICAL: Fixed MultiSite subdirectory site detection via --url path
- CRITICAL: Removed WP_CLI constant causing fatal errors with some plugins
- Improved: Output buffering prevents header warnings
- Improved: Worker loads NewsletterAddon and NewsletterMailerAddon
- Improved: REQUEST_URI parsed from URL for correct site selection
2.2.5 - 2026-02-16
- CRITICAL: Fixed worker plugin loading in DOING_CRON context
- Improved: Worker compatibility with MultiSite and all WordPress environments
2.2.4 - 2026-02-16
- CRITICAL: Fixed speed calculation for any Newsletter cron interval
- Fixed: Batch_size division issue resolved by dynamic interval detection
2.2.3 - 2026-02-16
- CRITICAL: Fixed Newsletter loading only 8-16 emails per execution
- Fixed: Overridden get_emails_per_run() to return configured batch_size directly
2.2.2 - 2026-02-16
- Fixed: Batch size enforcement - emails now enqueue correctly all at once
- Improved: Newsletter compatibility with $batch property and get_batch_size() method
2.2.1 - 2026-02-16
- Added: MultiSite detection with tailored worker commands
- Added: Worker --url parameter support for MultiSite
- Improved: Installation-specific worker setup instructions
2.2.0 - 2026-02-16
- MAJOR: Database-backed email queue for persistent, reliable email delivery
- MAJOR: External CLI worker script for asynchronous email processing
- Added: Queue Status widget with real-time statistics
- Added: Recent Campaigns section with key metrics
- Added: Email Queue admin page
- Added: Statistics admin page with campaign metrics
- Added: Parallel worker support (10+ workers)
- Added: Automatic retry with exponential backoff
- Changed: Emails queued to database instead of sent immediately
- Changed: Campaign statistics grouped by Newsletter ID
- Removed: max_per_connection setting
- Fixed: Duplicate campaign statistics
- BREAKING: Manual worker setup required
"
+ "description": "Integrates ROBOTSTXT SMTP configuration with Newsletter plugin for reliable bulk email delivery with database-backed queue and external workers. Workers now run as continuous loops draining the full queue, with balanced distribution across parallel workers.",
+ "changelog": "2.2.7 - 2026-02-18
- Improved: Worker now runs as a continuous loop, draining the queue completely
- Fixed: Uneven work distribution between parallel workers
2.2.6 - 2026-02-16
- CRITICAL: Fixed header warnings in MultiSite with Redirection plugin
- CRITICAL: Fixed Newsletter class dependencies in DOING_CRON context
- CRITICAL: Worker manually initializes plugin when hooks don't fire
- CRITICAL: Fixed MultiSite subdirectory site detection via --url path
- CRITICAL: Removed WP_CLI constant causing fatal errors with some plugins
- Improved: Output buffering prevents header warnings
- Improved: Worker loads NewsletterAddon and NewsletterMailerAddon
- Improved: REQUEST_URI parsed from URL for correct site selection
2.2.5 - 2026-02-16
- CRITICAL: Fixed worker plugin loading in DOING_CRON context
- Improved: Worker compatibility with MultiSite and all WordPress environments
2.2.4 - 2026-02-16
- CRITICAL: Fixed speed calculation for any Newsletter cron interval
- Fixed: Batch_size division issue resolved by dynamic interval detection
2.2.3 - 2026-02-16
- CRITICAL: Fixed Newsletter loading only 8-16 emails per execution
- Fixed: Overridden get_emails_per_run() to return configured batch_size directly
2.2.2 - 2026-02-16
- Fixed: Batch size enforcement - emails now enqueue correctly all at once
- Improved: Newsletter compatibility with $batch property and get_batch_size() method
2.2.1 - 2026-02-16
- Added: MultiSite detection with tailored worker commands
- Added: Worker --url parameter support for MultiSite
- Improved: Installation-specific worker setup instructions
2.2.0 - 2026-02-16
- MAJOR: Database-backed email queue for persistent, reliable email delivery
- MAJOR: External CLI worker script for asynchronous email processing
- Added: Queue Status widget with real-time statistics
- Added: Recent Campaigns section with key metrics
- Added: Email Queue admin page
- Added: Statistics admin page with campaign metrics
- Added: Parallel worker support (10+ workers)
- Added: Automatic retry with exponential backoff
- Changed: Emails queued to database instead of sent immediately
- Changed: Campaign statistics grouped by Newsletter ID
- Removed: max_per_connection setting
- Fixed: Duplicate campaign statistics
- BREAKING: Manual worker setup required
"
},
"banners": {
"low": "",
diff --git a/worker.php b/worker.php
index 52c0e13..9cb5b24 100644
--- a/worker.php
+++ b/worker.php
@@ -130,92 +130,94 @@ if ( $plugin_manually_loaded ) {
$queue = \Robotstxt_SMTP_Newsletter\Queue::get_instance();
$stats = \Robotstxt_SMTP_Newsletter\Statistics::get_instance();
-// Process batch.
-$items = $queue->get_and_lock_batch( $batch_size, $worker_id );
+$total_success = 0;
+$total_failure = 0;
-if ( empty( $items ) ) {
- echo "No items to process\n";
- exit( 0 );
-}
+// Continuous loop: keep processing batches until the queue is empty.
+while ( true ) {
+ $items = $queue->get_and_lock_batch( $batch_size, $worker_id );
-echo sprintf( "Worker %s: Processing %d emails...\n", $worker_id, count( $items ) );
+ if ( empty( $items ) ) {
+ break;
+ }
-$success_count = 0;
-$failure_count = 0;
+ foreach ( $items as $item ) {
+ try {
+ // Build headers array.
+ $headers = array();
-foreach ( $items as $item ) {
- try {
- // Build headers array.
- $headers = array();
+ // Add From header if specified.
+ if ( ! empty( $item->from_email ) ) {
+ $from = ! empty( $item->from_name )
+ ? sprintf( '%s <%s>', $item->from_name, $item->from_email )
+ : $item->from_email;
+ $headers[] = 'From: ' . $from;
+ }
- // Add From header if specified.
- if ( ! empty( $item->from_email ) ) {
- $from = ! empty( $item->from_name )
- ? sprintf( '%s <%s>', $item->from_name, $item->from_email )
- : $item->from_email;
- $headers[] = 'From: ' . $from;
- }
+ // Add Reply-To header if specified.
+ if ( ! empty( $item->reply_to ) ) {
+ $headers[] = 'Reply-To: ' . $item->reply_to;
+ }
- // Add Reply-To header if specified.
- if ( ! empty( $item->reply_to ) ) {
- $headers[] = 'Reply-To: ' . $item->reply_to;
- }
+ // Add Content-Type header (prefer HTML if available).
+ if ( ! empty( $item->body_html ) ) {
+ $headers[] = 'Content-Type: text/html; charset=UTF-8';
+ }
- // Add Content-Type header (prefer HTML if available).
- if ( ! empty( $item->body_html ) ) {
- $headers[] = 'Content-Type: text/html; charset=UTF-8';
- }
-
- // Decode and add custom headers from JSON.
- if ( ! empty( $item->headers_json ) ) {
- $custom = json_decode( $item->headers_json, true );
- if ( is_array( $custom ) ) {
- foreach ( $custom as $key => $value ) {
- $headers[] = sprintf( '%s: %s', $key, $value );
+ // Decode and add custom headers from JSON.
+ if ( ! empty( $item->headers_json ) ) {
+ $custom = json_decode( $item->headers_json, true );
+ if ( is_array( $custom ) ) {
+ foreach ( $custom as $key => $value ) {
+ $headers[] = sprintf( '%s: %s', $key, $value );
+ }
}
}
+
+ // Select body (prefer HTML over text).
+ $body = ! empty( $item->body_html ) ? $item->body_html : $item->body_text;
+
+ if ( empty( $body ) ) {
+ throw new \Exception( 'Email body is empty' );
+ }
+
+ // Send via wp_mail (uses core SMTP configuration).
+ $result = wp_mail(
+ $item->to_email,
+ $item->subject,
+ $body,
+ $headers
+ );
+
+ if ( $result ) {
+ $queue->mark_sent( $item->id );
+ $total_success++;
+ echo sprintf( " ✓ [%s] Sent: %s\n", $worker_id, $item->to_email );
+ } else {
+ $queue->mark_failed( $item->id, 'wp_mail returned false' );
+ $total_failure++;
+ echo sprintf( " ✗ [%s] Failed: %s (wp_mail returned false)\n", $worker_id, $item->to_email );
+ }
+ } catch ( \Exception $e ) {
+ $queue->mark_failed( $item->id, $e->getMessage() );
+ $total_failure++;
+ echo sprintf( " ✗ [%s] Error: %s - %s\n", $worker_id, $item->to_email, $e->getMessage() );
}
-
- // Select body (prefer HTML over text).
- $body = ! empty( $item->body_html ) ? $item->body_html : $item->body_text;
-
- if ( empty( $body ) ) {
- throw new \Exception( 'Email body is empty' );
- }
-
- // Send via wp_mail (uses core SMTP configuration).
- $result = wp_mail(
- $item->to_email,
- $item->subject,
- $body,
- $headers
- );
-
- if ( $result ) {
- $queue->mark_sent( $item->id );
- $success_count++;
- echo sprintf( " ✓ Sent: %s\n", $item->to_email );
- } else {
- $queue->mark_failed( $item->id, 'wp_mail returned false' );
- $failure_count++;
- echo sprintf( " ✗ Failed: %s (wp_mail returned false)\n", $item->to_email );
- }
-
- } catch ( \Exception $e ) {
- $queue->mark_failed( $item->id, $e->getMessage() );
- $failure_count++;
- echo sprintf( " ✗ Error: %s - %s\n", $item->to_email, $e->getMessage() );
}
+
+ // Sync statistics after each batch.
+ $stats->sync_from_queue();
}
-// Sync statistics after batch processing.
-$stats->sync_from_queue();
-
-echo sprintf(
- "Worker %s: Done. Success: %d, Failed: %d\n",
- $worker_id,
- $success_count,
- $failure_count
-);
+if ( 0 === $total_success && 0 === $total_failure ) {
+ echo sprintf( "Worker %s: No items to process\n", $worker_id );
+} else {
+ echo sprintf(
+ "Worker %s: Done. Success: %d, Failed: %d\n",
+ $worker_id,
+ $total_success,
+ $total_failure
+ );
+}
exit( 0 );