prefix . 'rl_mailwarmer_messages'; $messages = $wpdb->get_results( $wpdb->prepare( "SELECT * FROM $table_name WHERE status = %s AND scheduled_for_timestamp < %s ORDER BY scheduled_for_timestamp ASC LIMIT 1", 'pending', current_time('mysql') ), ARRAY_A ); if (empty($messages)) { // log_to_file("process_pending_messages - messages empty"); return; } foreach ($messages as $message) { // log_to_file("=========================================================="); try { if (!empty($message['first_message']) && $message['first_message']) { // log_to_file("process_pending_messages - trying send_message"); $result = self::send_message($message); } else { // log_to_file("process_pending_messages - trying reply_message"); $result = self::reply_message($message); } // Update message status to 'completed' on success if ($result) { self::update_message_status($message['id'], 'completed'); } else { self::update_message_status($message['id'], 'failed'); } } catch (Exception $e) { // Handle errors gracefully and log them log_to_file('Error processing message ID ' . $message['id'] . ': ' . $e->getMessage()); self::update_message_status($message['id'], 'failed'); } sleep(3); } } /** * Prepare email data and fetch connection info. * * @param array $message The message data. * @return array Prepared email data and connection info. * @throws Exception If required data is missing. */ private static function prepare_email_data($message) { // log_to_file("prepare_email_data - Running"); // log_to_file("prepare_email_data - Message: ", $message); // Ensure 'from' is valid and fetch connection info if (empty($message['from_email'])) { throw new Exception(__('Missing "from" address in the message.', 'rl-mailwarmer')); } // Find the email-account post matching the 'from' address $from_post_id = self::find_email_account_by_address($message['from_email']); if (!$from_post_id) { throw new Exception(__('No matching email account found for "from" address.', 'rl-mailwarmer')); } // Fetch connection details // log_to_file("prepare_email_data - Getting connection info"); $mail_password = get_post_meta($from_post_id, 'mail_password', true); $email_provider_id = get_post_meta($from_post_id, 'email_provider', true); $connection_info = RL_MailWarmer_Email_Helper::get_provider_defaults($email_provider_id); $connection_info['username'] = $message['from_email']; $connection_info['mail_password'] = $mail_password; // log_to_file("prepare_email_data - Connection Info: ", $connection_info); // Override provider defaults with account-specific settings foreach (['smtp_server', 'smtp_port', 'smtp_password', 'imap_server', 'imap_port', 'imap_password'] as $key) { $meta_value = get_post_meta($from_post_id, $key, true); if (!empty($meta_value)) { $connection_info[$key] = $meta_value; } } // Handle recipients // log_to_file("prepare_email_data - Handling recipients"); $to_emails = is_array($message['to_email']) ? $message['to_email'] : explode(',', $message['to_email']); $cc_emails = is_array($message['cc']) ? $message['cc'] : explode(',', $message['cc']); return [ 'connection_info' => $connection_info, 'to' => array_filter(array_map('trim', $to_emails)), 'cc' => array_filter(array_map('trim', $cc_emails)), 'subject' => $message['subject'], 'body' => $message['body'], 'from' => $message['from_email'], ]; } /** * Find the email-account post ID by email address. * * @param string $email_address The email address to search for. * @return int|null The post ID if found, or null. */ private static function find_email_account_by_address($email_address) { // log_to_file("find_email_account_by_address - Searching for: $email_address"); $query = new WP_Query([ 'post_type' => 'email-account', 'post_status' => 'publish', 'title' => $email_address, 'fields' => 'ids', ]); if (!empty($query->posts)) { return $query->posts[0]; } else { return new WP_Error('find_email_account_by_address - Unable to find a matching email address'); } } /** * Update the status of a message. * * @param int $message_id The ID of the message to update. * @param string $status The new status ('completed', 'failed', etc.). * @return void */ private static function update_message_status($message_id, $status) { global $wpdb; $table_name = $wpdb->prefix . 'rl_mailwarmer_messages'; $wpdb->update( $table_name, ['status' => $status], ['id' => $message_id], ['%s'], ['%d'] ); } /** * Send the first message in a conversation. * * @param array $message The message details. * @return bool True if the message is sent successfully, false otherwise. * @throws Exception If required fields are missing or an error occurs during sending. */ public static function send_message($message) { // log_to_file("send_message - Running"); // log_to_file("send_message - Message: ", $message); // Prepare email data and connection info $email_data = self::prepare_email_data($message); // log_to_file("send_message - Email Data: ", $email_data); // Extract connection info $connection_info = $email_data['connection_info']; // Check required fields if (empty($email_data['to']) || empty($email_data['from']) || empty($email_data['subject']) || empty($email_data['body'])) { throw new Exception(__('Missing required fields for sending the email.', 'rl-mailwarmer')); } // Create the SMTP transport // log_to_file("send_message - Creating Transport"); $transport = new Symfony\Component\Mailer\Transport\Smtp\EsmtpTransport( $connection_info['smtp_server'], $connection_info['smtp_port'] ); // Set authentication details $transport->setUsername($connection_info['username']); $transport->setPassword($connection_info['mail_password']); // Create the mailer $mailer = new Symfony\Component\Mailer\Mailer($transport); // Build the email // log_to_file("send_message - Building Mail"); $email = (new Email()) ->from($email_data['from']) ->to(...$email_data['to']) ->subject($email_data['subject']) ->html($email_data['body']); // Add CCs if present if (!empty($email_data['cc'])) { $email->cc(...$email_data['cc']); } // Attempt to send the email // log_to_file("send_message - Trying to send"); try { $mailer->send($email); log_to_file("send_message - Successfully sent SMTP mail from ", $email_data['from']); return true; // Email sent successfully } catch (TransportExceptionInterface $e) { error_log('Error sending email: ' . $e->getMessage()); return false; // Sending failed } } /** * Reply to an email message. * * @param array $message The message details. * @return bool True if the message is replied to successfully, false otherwise. * @throws Exception If required fields are missing or an error occurs. */ public static function reply_message($message) { // Prepare email data and connection info $email_data = self::prepare_email_data($message); // log_to_file("reply_message - Email Data: ", $email_data); // Extract connection info $connection_info = $email_data['connection_info']; // Validate required fields if (empty($email_data['to']) || empty($email_data['from']) || empty($email_data['subject']) || empty($email_data['body'])) { throw new Exception(__('Missing required fields for replying to the email.', 'rl-mailwarmer')); } // Attempt to find the original email via IMAP // // Attempt to find the original email and reply via IMAP // try { // $imap = new \PhpImap\Mailbox( // sprintf('{%s:%d/imap/ssl}', $connection_info['imap_server'], $connection_info['imap_port']), // $connection_info['imap_username'], // $connection_info['imap_password'], // null, // 'UTF-8' // ); // // Search for the email with the matching subject // $emails = $imap->searchMailbox('SUBJECT "' . addslashes($email_data['subject']) . '"'); // if (!empty($emails)) { // // Fetch the email data // $original_email = $imap->getMail($emails[0]); // // Prepare and send the reply via IMAP // $imap->reply( // $emails[0], // $email_data['body'], // ['from' => $email_data['from'], 'cc' => $email_data['cc']] // ); // return true; // Reply sent successfully via IMAP // } try { // log_to_file("reply_message - Trying to reply via IMAP"); $imap = new \PhpImap\Mailbox( sprintf('{%s:%d/imap/ssl}', $connection_info['imap_server'], $connection_info['imap_port']), $connection_info['username'], $connection_info['mail_password'], null, 'UTF-8' ); // Search for the email with the matching subject // log_to_file("reply_message - Searching for message"); $emails = $imap->searchMailbox('SUBJECT "' . addslashes($email_data['subject']) . '"'); if (!empty($emails)) { // Fetch the email data $original_email = $imap->getMail($emails[0]); // log_to_file("reply_message - Message found!"); // log_to_file("reply_message - IMAP Message: ", $original_email); // Step 2: Send the reply via SMTP $transport = new Symfony\Component\Mailer\Transport\Smtp\EsmtpTransport( $connection_info['smtp_server'], $connection_info['smtp_port'] ); // Set authentication details $transport->setUsername($connection_info['username']); $transport->setPassword($connection_info['mail_password']); $mailer = new Mailer($transport); $reply_email = (new Email()) ->from($email_data['from']) ->to(...$email_data['to']) ->subject('Re: ' . $original_email->subject) ->html($email_data['body']); // Add headers for threading $headers = $reply_email->getHeaders(); $headers->addTextHeader('In-Reply-To', $original_email->messageId); $headers->addTextHeader('References', trim($original_email->headers->references . ' ' . $original_email->messageId)); // ->addHeader('In-Reply-To', $original_email->messageId) // ->addHeader('References', trim($original_email->headers->references . ' ' . $original_email->messageId)); if (!empty($email_data['cc'])) { $reply_email->cc(...$email_data['cc']); } $mailer->send($reply_email); log_to_file("reply_message - Successfully sent IMAP/SMTP reply from ", $email_data['from']); // Step 3: Upload the reply to the Sent folder $imap_stream = imap_open( sprintf('{%s:%d/imap/ssl}', $connection_info['imap_server'], $connection_info['imap_port']), $connection_info['username'], $connection_info['mail_password'] ); $raw_message = $reply_email->toString(); // Convert the Email object to raw MIME format imap_append($imap_stream, sprintf('{%s}/Sent', $connection_info['imap_server']), $raw_message); imap_close($imap_stream); // Create the reply headers // $reply_headers = [ // 'In-Reply-To' => $original_email->messageId, // 'References' => trim($original_email->headers->references . ' ' . $original_email->messageId), // ]; // // Construct the reply body // $reply_body = $email_data['body'] . "\n\n" . // 'On ' . $original_email->date . ', ' . $original_email->fromName . ' <' . $original_email->fromAddress . '> wrote:' . "\n" . // $original_email->textPlain; // // Send the reply via IMAP // log_to_file("reply_message - Sending message via IMAP"); // $imap->addMessageToSentFolder( // 'To: ' . implode(', ', $email_data['to']) . "\r\n" . // 'Cc: ' . implode(', ', $email_data['cc']) . "\r\n" . // 'Subject: Re: ' . $original_email->subject . "\r\n" . // 'From: ' . $email_data['from'] . "\r\n" . // 'In-Reply-To: ' . $reply_headers['In-Reply-To'] . "\r\n" . // 'References: ' . $reply_headers['References'] . "\r\n" . // "\r\n" . // $reply_body // ); // log_to_file("reply_message - Done message via IMAP"); // $mailer = new Mailer($transport); // $mailer->send($reply); return true; // Reply sent successfully } } catch (Exception $e) { log_to_file('IMAP Error: ' . $e->getMessage()); } // Fallback to SMTP if IMAP fails try { // log_to_file("reply_message - Falling back to SMTP"); $smtp_reply = (new Email()) ->from($email_data['from']) ->to(...$email_data['to']) ->subject($email_data['subject']) ->html($email_data['body']); // Add CCs if present if (!empty($email_data['cc'])) { $smtp_reply->cc(...$email_data['cc']); } // Create the SMTP transport $transport = new Symfony\Component\Mailer\Transport\Smtp\EsmtpTransport( $connection_info['smtp_server'], $connection_info['smtp_port'] ); // Set authentication details $transport->setUsername($connection_info['username']); $transport->setPassword($connection_info['mail_password']); // Create the mailer $mailer = new Symfony\Component\Mailer\Mailer($transport); $mailer->send($smtp_reply); log_to_file("reply_message - Sent reply via fallback SMTP from ", $email_data['from']); // log_to_file('reply_message - SMTP Send Success (?)'); return true; // Fallback SMTP reply sent successfully } catch (Exception $e) { log_to_file('reply_message - SMTP Error: ' . $e->getMessage()); return false; // Reply failed } } } /** * Add a metabox to the WP dashboard for monitoring and processing the message queue. */ add_action('wp_dashboard_setup', function () { wp_add_dashboard_widget( 'rl_mailwarmer_message_queue', __('Message Queue', 'rl-mailwarmer'), 'rl_mailwarmer_render_message_queue_widget' ); }); /** * Render the Message Queue dashboard widget. */ function rl_mailwarmer_render_message_queue_widget() { global $wpdb; // Count past-due messages $table_name = $wpdb->prefix . 'rl_mailwarmer_messages'; $past_due_count = $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM $table_name WHERE status = %s AND scheduled_for_timestamp < %s", 'pending', current_time('mysql') ) ); ?>

__('Invalid nonce.', 'rl-mailwarmer')]); } // Ensure the user has permission if (!current_user_can('manage_options')) { wp_send_json_error(['message' => __('Permission denied.', 'rl-mailwarmer')]); } try { // Process pending messages // log_to_file("wp_ajax_rl_mailwarmer_process_message_queue - Trying process_pending_messages()"); $processed_count = RL_MailWarmer_Message_Handler::process_pending_messages(); wp_send_json_success(['processed_count' => $processed_count]); } catch (Exception $e) { wp_send_json_error(['message' => $e->getMessage()]); } });