Add asynchronous email queue#28
Open
Nimeshan wants to merge 4 commits into
Open
Conversation
Reviewer's GuideImplements an asynchronous email queuing system backed by a new queue table and WP-Cron processor, wires it into existing SMTP/logging flows, and surfaces queue status in admin logs, including schema migration and lifecycle hooks for activation/deactivation and upgrades. Sequence diagram for wp_mail queuing and asynchronous processingsequenceDiagram
actor User
participant WP as WordPress_wp_mail
participant SMTP as Wpfa_Mailconnect_SMTP
participant Queue as Wpfa_Mailconnect_Queue
participant Logger as Wpfa_Mailconnect_Logger
participant DB as WordPress_DB
participant Cron as WP_Cron
User->>WP: wp_mail(to, subject, message, headers, attachments)
WP->>SMTP: pre_wp_mail filter queue_email(pre, args)
SMTP->>SMTP: get_option smtp_options
alt queue_enabled and not Wpfa_Mailconnect_Queue::is_processing
SMTP->>Queue: add_to_queue(args)
Queue->>Queue: normalize_mail_args(args)
Queue->>Logger: generate_mail_hash(args)
Logger-->>Queue: hash
Queue->>Logger: insert_pending(hash, to, subject, message, body_html, headers, status_details)
Logger->>DB: INSERT log_row(status pending)
Queue->>Logger: update_status(hash, queued, "", status_details)
Logger->>DB: UPDATE log_row(status queued)
Queue->>DB: INSERT queue_row(status queued)
DB-->>Queue: insert_id
Queue-->>SMTP: queued_id
SMTP->>Queue: Wpfa_Mailconnect_Queue::schedule_processing()
Queue->>Cron: wp_schedule_event(PROCESS_CRON_HOOK)
SMTP-->>WP: true (short-circuit wp_mail)
else queue disabled or processing
SMTP-->>WP: pre (continue normal wp_mail)
end
Cron->>Queue: process_queue_batch()
Queue->>DB: SELECT queued rows WHERE next_attempt_at <= now LIMIT BATCH_SIZE
DB-->>Queue: queue_items
loop each queue_item
Queue->>DB: UPDATE queue_row SET status processing
Queue->>WP: add_action wp_mail_failed(error_capture)
Queue->>Queue: self::$processing = true
Queue->>WP: wp_mail(to, subject, message, headers, attachments)
WP-->>Queue: sent_flag
Queue->>Queue: self::$processing = false
Queue->>WP: remove_action wp_mail_failed
alt sent_flag true
Queue->>DB: UPDATE queue_row SET status sent
Queue->>Logger: update_status(hash, success, "", details)
Logger->>DB: UPDATE log_row(status success)
else sent_flag false
Queue->>DB: UPDATE retries, status(queued or failed), next_attempt_at
Queue->>Logger: update_status(hash, queued or failed, error_message, details)
Logger->>DB: UPDATE log_row(status queued or failed)
end
end
Entity relationship diagram for email logs and queue tableserDiagram
email_logs {
bigint id PK
varchar hash
longtext to_email
text subject
longtext message
longtext body_html
varchar status
text error_message
longtext status_details
longtext headers
datetime created_at
}
mail_queue {
bigint id PK
varchar status
varchar hash
longtext to_email
text subject
longtext message
longtext headers
longtext attachments
int retries
datetime created_at
datetime next_attempt_at
datetime updated_at
}
email_logs ||--o{ mail_queue : hash_links_queue_items_to_logs
Class diagram for email queue, SMTP, and logger integrationclassDiagram
class Wpfa_Mailconnect {
-Wpfa_Mailconnect_SMTP smtp
-Wpfa_Mailconnect_Logger email_logger_service
-Wpfa_Mailconnect_Queue queue
+load_dependencies()
+define_admin_hooks()
}
class Wpfa_Mailconnect_SMTP {
-Wpfa_Mailconnect_Logger logger
-Wpfa_Mailconnect_Queue queue
-array fields
+__construct(plugin_name, version)
+settings_init()
+logging_section_callback()
+queue_section_callback()
+sanitize_smtp_options(input)
+test_email_form()
+queue_email(pre, args)
+phpmailer_override(phpmailer)
}
class Wpfa_Mailconnect_Queue {
<<service>>
+const TABLE_SUFFIX
+const PROCESS_CRON_HOOK
+const CRON_INTERVAL
+const BATCH_SIZE
+const MAX_RETRIES
-static bool processing
-string table_name
-Wpfa_Mailconnect_Logger logger
+__construct(logger)
+add_cron_schedules(schedules) array
+add_to_queue(args) int
+process_queue_batch() int
+get_status_counts() array
+is_queue_enabled() bool
+schedule_processing() void
+unschedule_processing() void
+is_processing() bool
-create_table() void
-mark_processing(id) void
-mark_sent(item) void
-mark_failed_or_retry(item, error) void
-normalize_mail_args(args) array
-format_recipients(recipients) string
-is_html_content(args) bool
-get_attachment_details(attachments) array
}
class Wpfa_Mailconnect_Logger {
+const ALLOWED_STATUSES
+create_log_table() void
+generate_mail_hash(args) string
-get_attachment_hash_details(attachments) array
+insert_pending(hash, to, subject, message, body_html, headers, status_details) bool
+update_status(hash, status, error_message, status_details) bool
+clear_old_logs(days) int
}
class Wpfa_Mailconnect_Admin {
+render_logs_page() void
-render_queue_status_widget() void
+handle_clear_logs() void
}
class Wpfa_Mailconnect_Activator {
+activate() void
}
class Wpfa_Mailconnect_Deactivator {
+deactivate() void
}
class Wpfa_Mailconnect_Updater {
-run_migrations(installed_version) void
-update_to_1_2_0() void
-update_to_1_3_0() void
-update_to_1_0_1() void
}
Wpfa_Mailconnect --> Wpfa_Mailconnect_SMTP : composes
Wpfa_Mailconnect --> Wpfa_Mailconnect_Queue : composes
Wpfa_Mailconnect_SMTP --> Wpfa_Mailconnect_Logger : uses
Wpfa_Mailconnect_SMTP --> Wpfa_Mailconnect_Queue : composes
Wpfa_Mailconnect_Queue --> Wpfa_Mailconnect_Logger : uses
Wpfa_Mailconnect_Admin --> Wpfa_Mailconnect_Queue : uses
Wpfa_Mailconnect_Activator --> Wpfa_Mailconnect_Logger : uses
Wpfa_Mailconnect_Activator --> Wpfa_Mailconnect_Queue : uses
Wpfa_Mailconnect_Deactivator --> Wpfa_Mailconnect_Queue : uses
Wpfa_Mailconnect_Updater --> Wpfa_Mailconnect_Queue : uses
File-Level Changes
Assessment against linked issues
Possibly linked issues
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
There was a problem hiding this comment.
Hey - I've found 2 issues, and left some high level feedback:
- The
cron_schedulesregistration for the custom 5‑minute interval is split between the activator (anonymous filter) andWpfa_Mailconnect_Queue::add_cron_schedules; consider consolidating to a single location, since adding the filter only during activation is not persisted across requests and may causewp_schedule_eventwith the custom interval to fail. - The attachment metadata logic is implemented twice (
Wpfa_Mailconnect_Logger::get_attachment_hash_detailsandWpfa_Mailconnect_Queue::get_attachment_details) with slightly different outputs; extracting a shared helper would reduce drift and make it easier to keep the logged and hashed attachment information consistent.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- The `cron_schedules` registration for the custom 5‑minute interval is split between the activator (anonymous filter) and `Wpfa_Mailconnect_Queue::add_cron_schedules`; consider consolidating to a single location, since adding the filter only during activation is not persisted across requests and may cause `wp_schedule_event` with the custom interval to fail.
- The attachment metadata logic is implemented twice (`Wpfa_Mailconnect_Logger::get_attachment_hash_details` and `Wpfa_Mailconnect_Queue::get_attachment_details`) with slightly different outputs; extracting a shared helper would reduce drift and make it easier to keep the logged and hashed attachment information consistent.
## Individual Comments
### Comment 1
<location path="includes/class-wpfa-mailconnect-activator.php" line_range="54-62" />
<code_context>
wp_schedule_event( time(), 'daily', $cleanup_hook );
}
+
+ add_filter(
+ 'cron_schedules',
+ function( $schedules ) {
+ $schedules[ Wpfa_Mailconnect_Queue::CRON_INTERVAL ] = array(
+ 'interval' => 5 * MINUTE_IN_SECONDS,
+ 'display' => __( 'Every 5 Minutes', 'wpfa-mailconnect' ),
+ );
+ return $schedules;
+ }
+ );
+ if ( Wpfa_Mailconnect_Queue::is_queue_enabled() ) {
</code_context>
<issue_to_address>
**issue (bug_risk):** Avoid registering cron_schedules filter inside activation, as it won’t persist beyond the activation request and duplicates runtime logic.
Because this filter is added only during activation, the custom schedule won’t exist on normal requests, so the interval isn’t reliably registered. It also duplicates the schedule configuration already handled in Wpfa_Mailconnect_Queue::add_cron_schedules. Please remove this activator filter and rely on the queue class’ add_cron_schedules hook instead.
</issue_to_address>
### Comment 2
<location path="includes/class-wpfa-mailconnect-queue.php" line_range="357-360" />
<code_context>
+ private function mark_failed_or_retry( $item, $error ) {
+ global $wpdb;
+
+ $retries = absint( $item->retries ) + 1;
+ $failed = $retries >= self::MAX_RETRIES;
+ $status = $failed ? 'failed' : 'queued';
+ $delay = min( 60, 5 * $retries );
+
+ $wpdb->update(
</code_context>
<issue_to_address>
**issue (bug_risk):** Align next_attempt_at time calculation with WordPress time helpers to avoid potential timezone inconsistencies.
`current_time( 'timestamp' )` already applies the site timezone, while `date()` uses the server timezone, which may differ and cause scheduling drift. Use a consistent helper chain, for example:
```php
$timestamp = current_time( 'timestamp' );
$next_ts = $timestamp + ( $delay * MINUTE_IN_SECONDS );
$next_attempt = gmdate( 'Y-m-d H:i:s', $next_ts );
```
(or `current_time( 'mysql', true )` plus a manual offset) so this value stays aligned with how `created_at`/`updated_at` are generated.
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
Resolve conflicts: keep 1.0.0 version constants and retain render_queue_status_widget() for the async queue UI on logs. Co-authored-by: Cursor <cursoragent@cursor.com>
Resolve conflicts keeping both async queue (PR fossasia#28) and HTML email templates (PR fossasia#29) features: - Preserve render_queue_status_widget() alongside new format_attachment_log_summary() - Keep queue class require and cron hooks alongside template wp_mail filter - Combine queue and template settings sections/fields in SMTP class - Retain both queue_email() and apply_html_template() methods Co-authored-by: Cursor <cursoragent@cursor.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #10
Adds an asynchronous email queue backed by a dedicated queue table and WP Cron processing.
Summary:
Summary by Sourcery
Introduce an asynchronous email queuing system backed by a new queue table, WP Cron processing, and extended logging to decouple wp_mail calls from immediate delivery.
New Features:
Enhancements: