array( $this, 'sanitize_options' ),
)
);
add_settings_section(
'robotstxt_smtp_newsletter_main',
__( 'SMTP (by ROBOTSTXT) Integration', 'robotstxt-smtp-newsletter' ),
array( $this, 'render_section' ),
'robotstxt_smtp_newsletter'
);
add_settings_field(
'enabled',
__( 'Enable Integration', 'robotstxt-smtp-newsletter' ),
array( $this, 'render_enabled_field' ),
'robotstxt_smtp_newsletter',
'robotstxt_smtp_newsletter_main'
);
add_settings_field(
'batch_size',
__( 'Batch Size', 'robotstxt-smtp-newsletter' ),
array( $this, 'render_batch_size_field' ),
'robotstxt_smtp_newsletter',
'robotstxt_smtp_newsletter_main'
);
// Queue Status section.
add_settings_section(
'robotstxt_smtp_newsletter_queue_status',
__( 'Queue Status', 'robotstxt-smtp-newsletter' ),
array( $this, 'render_queue_status_section' ),
'robotstxt_smtp_newsletter'
);
// Recent Campaigns section.
add_settings_section(
'robotstxt_smtp_newsletter_recent_campaigns',
__( 'Recent Campaigns', 'robotstxt-smtp-newsletter' ),
array( $this, 'render_recent_campaigns_section' ),
'robotstxt_smtp_newsletter'
);
// Worker configuration section (at the end).
add_settings_section(
'robotstxt_smtp_newsletter_worker',
__( 'Queue Worker Configuration', 'robotstxt-smtp-newsletter' ),
array( $this, 'render_worker_section' ),
'robotstxt_smtp_newsletter'
);
}
/**
* Add settings page to admin menu.
*
* @return void
*/
public function add_settings_page() {
add_submenu_page(
'newsletter_main_index',
__( 'SMTP (by ROBOTSTXT)', 'robotstxt-smtp-newsletter' ),
__( 'SMTP (by ROBOTSTXT)', 'robotstxt-smtp-newsletter' ),
'manage_options',
'robotstxt-smtp-newsletter',
array( $this, 'render_page' )
);
}
/**
* Render settings page.
*
* @return void
*/
public function render_page() {
if ( ! current_user_can( 'manage_options' ) ) {
wp_die( esc_html__( 'You do not have sufficient permissions to access this page.', 'robotstxt-smtp-newsletter' ) );
}
?>
';
esc_html_e( 'Configure how Newsletter uses SMTP (by ROBOTSTXT) for email delivery.', 'robotstxt-smtp-newsletter' );
echo '';
// Show current SMTP configuration.
if ( ! class_exists( '\Robotstxt_SMTP\Plugin' ) ) {
echo '';
esc_html_e( 'ROBOTSTXT SMTP plugin is not active. Please activate it first.', 'robotstxt-smtp-newsletter' );
echo '
';
return;
}
$plugin = \Robotstxt_SMTP_Newsletter\Plugin::get_instance();
$settings = $plugin->get_smtp_settings();
echo '';
echo '' . esc_html__( 'Current SMTP Configuration:', 'robotstxt-smtp-newsletter' ) . '
';
echo '
';
echo '- ' . sprintf(
esc_html__( 'Host: %s:%d', 'robotstxt-smtp-newsletter' ),
esc_html( $settings['host'] ?? '' ),
absint( $settings['port'] ?? 0 )
) . '
';
$encryption = $settings['encryption'] ?? '';
if ( ! empty( $encryption ) ) {
echo '- ' . sprintf(
esc_html__( 'Encryption: %s', 'robotstxt-smtp-newsletter' ),
esc_html( strtoupper( $encryption ) )
) . '
';
}
if ( \Robotstxt_SMTP\Plugin::is_amazon_ses_integration_active() ) {
echo '- ' . esc_html__( 'Amazon SES: Active', 'robotstxt-smtp-newsletter' ) . '
';
}
echo '
';
echo '
';
// Show optimal batch size recommendations.
$this->render_recommendations();
}
/**
* Render settings recommendations.
*
* @return void
*/
private function render_recommendations() {
$optimal = $this->calculate_optimal_settings();
$current_options = \Robotstxt_SMTP_Newsletter\Plugin::get_instance()->get_settings();
$current_batch = absint( $current_options['batch_size'] ?? 10 );
// Determine batch size status.
$batch_status = 'optimal';
if ( $current_batch < $optimal['batch_size'] * 0.5 ) {
$batch_status = 'too_low';
} elseif ( $current_batch > $optimal['batch_size'] * 1.5 ) {
$batch_status = 'too_high';
} elseif ( abs( $current_batch - $optimal['batch_size'] ) > 5 ) {
$batch_status = 'suboptimal';
}
// Status styling.
$notice_class = 'notice-info';
$status_icon = 'ⓘ'; // Info icon.
if ( 'optimal' === $batch_status ) {
$notice_class = 'notice-success';
$status_icon = '✔'; // Checkmark.
} elseif ( in_array( $batch_status, array( 'too_low', 'too_high' ), true ) ) {
$notice_class = 'notice-warning';
$status_icon = '⚠'; // Warning icon.
}
echo '';
echo '' . $status_icon . ' ' . esc_html__( 'Settings Recommendations', 'robotstxt-smtp-newsletter' ) . '
';
echo '
';
// Show delivery method.
$delivery_method = $optimal['is_ses']
? __( 'Amazon SES API', 'robotstxt-smtp-newsletter' )
: __( 'SMTP', 'robotstxt-smtp-newsletter' );
echo '- ' . sprintf(
/* translators: %s: delivery method */
esc_html__( 'Delivery method: %s', 'robotstxt-smtp-newsletter' ),
esc_html( $delivery_method )
) . '
';
// Show calculation reasoning.
if ( ! empty( $optimal['reasoning'] ) ) {
foreach ( $optimal['reasoning'] as $reason ) {
echo '- ' . esc_html( $reason ) . '
';
}
}
echo '
';
// Show recommendation.
echo '
';
if ( 'optimal' === $batch_status ) {
echo '✔ ' . sprintf(
/* translators: %d: batch size */
esc_html__( 'Batch size: Your current setting (%d) is optimal!', 'robotstxt-smtp-newsletter' ),
$current_batch
);
} else {
echo '► ' . sprintf(
/* translators: %d: recommended batch size */
esc_html__( 'Recommended batch size: %d emails/batch', 'robotstxt-smtp-newsletter' ),
$optimal['batch_size']
);
if ( 'too_low' === $batch_status ) {
echo '
' . sprintf(
/* translators: %d: current batch size */
esc_html__( 'Current (%d) is significantly lower than optimal. Increase it to enqueue faster.', 'robotstxt-smtp-newsletter' ),
$current_batch
);
} elseif ( 'too_high' === $batch_status ) {
echo '
' . sprintf(
/* translators: %d: current batch size */
esc_html__( 'Current (%d) is too high and may trigger rate limits. Reduce it to avoid errors.', 'robotstxt-smtp-newsletter' ),
$current_batch
);
} elseif ( 'suboptimal' === $batch_status ) {
echo '
' . sprintf(
/* translators: 1: current batch size, 2: recommended batch size */
esc_html__( 'Current (%1$d) could be optimized. Consider changing to %2$d.', 'robotstxt-smtp-newsletter' ),
$current_batch,
$optimal['batch_size']
);
}
}
echo '
';
echo '
';
}
/**
* Render enabled field.
*
* @return void
*/
public function render_enabled_field() {
$options = get_option( self::OPTION_NAME, array() );
$enabled = ! empty( $options['enabled'] );
?>
sync_from_queue();
// Get recent campaigns (last 5).
$campaigns = $stats->get_recent_campaigns( 5 );
?>
|
|
|
|
|
|
|
|
total_emails > 0
? ( $campaign->sent_emails / $campaign->total_emails ) * 100
: 0;
$row_style = '';
if ( $success_rate < 95 ) {
$row_style = 'background-color: #fff3cd;'; // Warning yellow for low success rate.
}
?>
|
campaign_name ); ?>
finished_at ) : ?>
⚠
|
started_at ) {
echo esc_html( mysql2date( 'Y-m-d H:i', $campaign->started_at ) );
} else {
echo '-';
}
?>
|
finished_at ) {
echo esc_html( mysql2date( 'Y-m-d H:i', $campaign->finished_at ) );
} else {
echo '' . esc_html__( 'In progress', 'robotstxt-smtp-newsletter' ) . '';
}
?>
|
total_emails ); ?>
|
sent_emails ); ?>
(%)
|
failed_emails > 0 ) : ?>
failed_emails ); ?>
0
|
duration_seconds ) {
$hours = floor( $campaign->duration_seconds / 3600 );
$minutes = floor( ( $campaign->duration_seconds % 3600 ) / 60 );
$seconds = $campaign->duration_seconds % 60;
if ( $hours > 0 ) {
echo sprintf( '%dh %dm', $hours, $minutes );
} elseif ( $minutes > 0 ) {
echo sprintf( '%dm %ds', $minutes, $seconds );
} else {
echo sprintf( '%ds', $seconds );
}
} else {
echo '-';
}
?>
|
emails_per_hour ) {
echo '' . number_format_i18n( $campaign->emails_per_hour, 0 ) . ' /h';
if ( $campaign->emails_per_minute ) {
echo ' ' . number_format_i18n( $campaign->emails_per_minute, 1 ) . ' /min';
}
} else {
echo '-';
}
?>
|
php "w1" --url=""
seq 1 10 | xargs -P10 -I{} php "w{}" --url=""
' . esc_html( $site_url ) . ''
);
?>
php "w1"
seq 1 10 | xargs -P10 -I{} php "w{}"
get_queue_stats();
?>
Array with 'batch_size', 'reasoning', and 'is_ses'.
*/
private function calculate_optimal_settings() {
$plugin = \Robotstxt_SMTP_Newsletter\Plugin::get_instance();
$settings = $plugin->get_smtp_settings();
// Get rate limits.
$rate_per_second = absint( $settings['rate_limit_per_second'] ?? 0 );
$rate_per_hour = absint( $settings['rate_limit_per_hour'] ?? 0 );
$rate_per_day = absint( $settings['rate_limit_per_day'] ?? 0 );
// Check if Amazon SES is active.
$is_ses = \Robotstxt_SMTP\Plugin::is_amazon_ses_integration_active();
$optimal_batch = 10; // Default fallback.
$reasoning = array();
$limiting_factor = '';
// If no rate limits are set.
if ( 0 === $rate_per_second && 0 === $rate_per_hour && 0 === $rate_per_day ) {
$optimal_batch = $is_ses ? 50 : 20;
$reasoning[] = __( 'No rate limits configured.', 'robotstxt-smtp-newsletter' );
$reasoning[] = $is_ses
? __( 'Using default for Amazon SES (50 emails/batch).', 'robotstxt-smtp-newsletter' )
: __( 'Using default for SMTP (20 emails/batch).', 'robotstxt-smtp-newsletter' );
$limiting_factor = 'default';
} else {
// Calculate based on rate limits - use conservative values for queuing.
$candidates = array();
if ( $rate_per_second > 0 ) {
// For per-second: batch should be reasonable for burst processing.
$max_emails = min( 100, $rate_per_second * 30 ); // 30 seconds worth.
$candidates['per_second'] = $max_emails;
$reasoning[] = sprintf(
/* translators: 1: emails per second, 2: calculated max */
__( 'Per-second limit: %1$d emails/sec → batch of %2$d', 'robotstxt-smtp-newsletter' ),
$rate_per_second,
$max_emails
);
}
if ( $rate_per_hour > 0 ) {
// For per-hour: assume worker processes every 5 minutes.
$batches_per_hour = 12;
$max_emails = floor( $rate_per_hour / $batches_per_hour );
$candidates['per_hour'] = $max_emails;
$reasoning[] = sprintf(
/* translators: 1: emails per hour, 2: calculated max */
__( 'Per-hour limit: %1$d emails/hour → batch of %2$d', 'robotstxt-smtp-newsletter' ),
$rate_per_hour,
$max_emails
);
}
if ( $rate_per_day > 0 ) {
// For per-day: assume 288 worker runs per day (every 5 minutes).
$batches_per_day = 288;
$max_emails = floor( $rate_per_day / $batches_per_day );
$candidates['per_day'] = $max_emails;
$reasoning[] = sprintf(
/* translators: 1: emails per day, 2: calculated max */
__( 'Per-day limit: %1$d emails/day → batch of %2$d', 'robotstxt-smtp-newsletter' ),
$rate_per_day,
$max_emails
);
}
// The optimal batch size is the minimum of all candidates.
if ( ! empty( $candidates ) ) {
$optimal_batch = min( $candidates );
$limiting_factor = array_search( $optimal_batch, $candidates, true );
// Apply safety margin (90%).
$optimal_batch = max( 1, floor( $optimal_batch * 0.9 ) );
$reasoning[] = sprintf(
/* translators: 1: limiting factor, 2: final batch size */
__( 'Most restrictive: %1$s. Recommended batch (with 10%% margin): %2$d', 'robotstxt-smtp-newsletter' ),
$limiting_factor,
$optimal_batch
);
}
}
return array(
'batch_size' => $optimal_batch,
'reasoning' => $reasoning,
'limiting_factor' => $limiting_factor,
'is_ses' => $is_ses,
);
}
/**
* Sanitize options.
*
* @param array $options Raw options.
*
* @return array Sanitized options.
*/
public function sanitize_options( $options ) {
$clean = array();
$clean['enabled'] = ! empty( $options['enabled'] );
// Batch size: Allow up to 10,000 for high-throughput scenarios (SES, high rate limits).
$clean['batch_size'] = max( 1, min( 10000, (int) ( $options['batch_size'] ?? 10 ) ) );
return $clean;
}
}