From 11beebf74d394792cfe2cde50290f45e56ce89e1 Mon Sep 17 00:00:00 2001 From: Rostislav Wolny Date: Wed, 7 Dec 2022 09:25:41 +0100 Subject: [PATCH] Add MailerLog method for handling transactional emails errors The new method allows processing sending errors that happen when sending from other places of plugin then from the Sending Queue worker. After three failed attempts it pauses the sending and admin user will see an notice. [MAILPOET-4736] --- mailpoet/lib/Mailer/MailerLog.php | 38 +++++++++++- .../integration/Mailer/MailerLogTest.php | 58 +++++++++++++++++++ 2 files changed, 95 insertions(+), 1 deletion(-) diff --git a/mailpoet/lib/Mailer/MailerLog.php b/mailpoet/lib/Mailer/MailerLog.php index 0886909206..146e4f9e8f 100644 --- a/mailpoet/lib/Mailer/MailerLog.php +++ b/mailpoet/lib/Mailer/MailerLog.php @@ -16,7 +16,9 @@ use MailPoet\Settings\SettingsController; * "status": ?string, * "retry_attempt": ?int, * "retry_at": ?int, - * "error": ?MailerLogError + * "error": ?MailerLogError, + * "transactional_email_last_error_at": ?int, + * "transactional_email_error_count": ?int, * } */ @@ -56,6 +58,8 @@ class MailerLog { 'retry_attempt' => null, 'retry_at' => null, 'error' => null, + 'transactional_email_last_error_at' => null, + 'transactional_email_error_count' => null, ]; $settings = SettingsController::getInstance(); $settings->set(self::SETTING_NAME, $mailerLog); @@ -115,6 +119,8 @@ class MailerLog { $mailerLog['status'] = self::STATUS_PAUSED; $mailerLog['retry_attempt'] = null; $mailerLog['retry_at'] = null; + $mailerLog['transactional_email_last_error_at'] = null; + $mailerLog['transactional_email_error_count'] = null; return self::updateMailerLog($mailerLog); } @@ -172,6 +178,34 @@ class MailerLog { self::enforceExecutionRequirements(); } + /** + * Process error, increase transactional_email_error_count and pauses sending if it reaches retry limit + * This method is meant to be used for processing errors when sending transactional emails + * like: Confirmation Email, Preview email, Stats Notification etc. + * + * @throws \Exception + */ + public static function processTransactionalEmailError( + string $operation, + string $errorMessage, + ?string $errorCode = null + ): void { + $mailerLog = self::getMailerLog(); + $lastErrorTime = $mailerLog['transactional_email_last_error_at'] ?? null; + $ignoreErrorThreshold = time() - (2 * 60); // 2 minutes ago + // We want to log the error max one time per 2 minutes + if ($lastErrorTime && $lastErrorTime > $ignoreErrorThreshold) { + return; + } + $mailerLog = self::setError($mailerLog, $operation, $errorMessage, $errorCode); + $mailerLog['transactional_email_last_error_at'] = time(); + $mailerLog['transactional_email_error_count'] = ($mailerLog['transactional_email_error_count'] ?? 0) + 1; + self::updateMailerLog($mailerLog); + if ($mailerLog['transactional_email_error_count'] > self::RETRY_ATTEMPTS_LIMIT) { + self::pauseSending($mailerLog); + } + } + /** * @param MailerLogData $mailerLog * @param string $operation @@ -242,6 +276,8 @@ class MailerLog { $mailerLog['retry_attempt'] = null; $mailerLog['retry_at'] = null; $mailerLog['error'] = null; + $mailerLog['transactional_email_last_error_at'] = null; + $mailerLog['transactional_email_error_count'] = null; return self::updateMailerLog($mailerLog); } diff --git a/mailpoet/tests/integration/Mailer/MailerLogTest.php b/mailpoet/tests/integration/Mailer/MailerLogTest.php index 2193e2f65a..47dc23f430 100644 --- a/mailpoet/tests/integration/Mailer/MailerLogTest.php +++ b/mailpoet/tests/integration/Mailer/MailerLogTest.php @@ -3,6 +3,7 @@ namespace MailPoet\Test\Mailer; use MailPoet\Mailer\Mailer; +use MailPoet\Mailer\MailerError; use MailPoet\Mailer\MailerLog; use MailPoet\Settings\SettingsController; use MailPoet\Settings\SettingsRepository; @@ -266,6 +267,59 @@ class MailerLogTest extends \MailPoetTest { ); } + public function testItProcessesTransactionalEmailSendingError() { + $mailerLog = MailerLog::getMailerLog(); + expect($mailerLog['transactional_email_last_error_at'])->null(); + expect($mailerLog['transactional_email_error_count'])->null(); + MailerLog::processTransactionalEmailError(MailerError::OPERATION_SEND, 'email rejected'); + $mailerLog = MailerLog::getMailerLog(); + expect($mailerLog['transactional_email_last_error_at'])->equals(time(), 1); + expect($mailerLog['transactional_email_error_count'])->equals(1); + expect($mailerLog['error'])->equals( + [ + 'operation' => MailerError::OPERATION_SEND, + 'error_message' => 'email rejected', + ] + ); + } + + public function testItSkipsTransactionalEmailSendingErrorWhenLastLoggedIsWithinIgnoreThreshold() { + $mailerLog = MailerLog::createMailerLog(); + $almostTwoMinutesAgo = time() - 110; + $mailerLog['transactional_email_last_error_at'] = $almostTwoMinutesAgo; + $mailerLog['transactional_email_error_count'] = 1; + MailerLog::updateMailerLog($mailerLog); + MailerLog::processTransactionalEmailError(MailerError::OPERATION_SEND, 'email rejected'); + $mailerLog = MailerLog::getMailerLog(); + expect($mailerLog['transactional_email_last_error_at'])->equals($almostTwoMinutesAgo); + expect($mailerLog['transactional_email_error_count'])->equals(1); + } + + public function testItIncreaseCounterOfTransactionalEmailSendingErrorWhenLastLoggedOlderThanIgnoreThreshold() { + $mailerLog = MailerLog::createMailerLog(); + $moreThanTwoMinutesAgo = time() - 130; + $mailerLog['transactional_email_last_error_at'] = $moreThanTwoMinutesAgo; + $mailerLog['transactional_email_error_count'] = 1; + MailerLog::updateMailerLog($mailerLog); + MailerLog::processTransactionalEmailError(MailerError::OPERATION_SEND, 'email rejected'); + $mailerLog = MailerLog::getMailerLog(); + expect($mailerLog['transactional_email_last_error_at'])->equals(time(), 1); + expect($mailerLog['transactional_email_error_count'])->equals(2); + } + + public function testItPausesSendingWhenTransactionalEmailSendingErrorCountReachesLimit() { + $mailerLog = MailerLog::createMailerLog(); + $moreThanTwoMinutesAgo = time() - 130; + $mailerLog['transactional_email_last_error_at'] = $moreThanTwoMinutesAgo; + $mailerLog['transactional_email_error_count'] = MailerLog::RETRY_ATTEMPTS_LIMIT; + MailerLog::updateMailerLog($mailerLog); + MailerLog::processTransactionalEmailError(MailerError::OPERATION_SEND, 'email rejected'); + $mailerLog = MailerLog::getMailerLog(); + expect($mailerLog['transactional_email_last_error_at'])->null(); + expect($mailerLog['transactional_email_error_count'])->null(); + expect(MailerLog::isSendingPaused())->true(); + } + public function testItEnforcesSendingLimit() { $mailerConfig = [ 'frequency' => [ @@ -368,12 +422,16 @@ class MailerLogTest extends \MailPoetTest { 'operation' => 'operation', 'error_code' => 'error_code', 'error_message' => 'error_message', + 'transactional_email_last_error_at' => time(), + 'transactional_email_error_count' => 1, ]; $mailerLog['status'] = 'status'; $mailerLog = MailerLog::clearSendingErrorLog($mailerLog); expect($mailerLog['retry_attempt'])->null(); expect($mailerLog['retry_at'])->null(); expect($mailerLog['error'])->null(); + expect($mailerLog['transactional_email_last_error_at'])->null(); + expect($mailerLog['transactional_email_error_count'])->null(); expect($mailerLog['status'])->equals('status'); }