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'); }