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

2.2.5 - 2026-02-16

2.2.4 - 2026-02-16

2.2.3 - 2026-02-16

2.2.2 - 2026-02-16

2.2.1 - 2026-02-16

2.2.0 - 2026-02-16

2.1.0 - 2026-02-09

2.0.1 - 2026-01-29

2.0.0 - 2026-01-29

1.0.0 - 2026-01-29

", + "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

2.2.6 - 2026-02-16

2.2.5 - 2026-02-16

2.2.4 - 2026-02-16

2.2.3 - 2026-02-16

2.2.2 - 2026-02-16

2.2.1 - 2026-02-16

2.2.0 - 2026-02-16

2.1.0 - 2026-02-09

2.0.1 - 2026-01-29

2.0.0 - 2026-01-29

1.0.0 - 2026-01-29

", "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

2.2.5 - 2026-02-16

2.2.4 - 2026-02-16

2.2.3 - 2026-02-16

2.2.2 - 2026-02-16

2.2.1 - 2026-02-16

2.2.0 - 2026-02-16

" + "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

2.2.6 - 2026-02-16

2.2.5 - 2026-02-16

2.2.4 - 2026-02-16

2.2.3 - 2026-02-16

2.2.2 - 2026-02-16

2.2.1 - 2026-02-16

2.2.0 - 2026-02-16

" }, "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 );