Increase number of checks if the email was sent in automation
[MAILPOET-6175]
This commit is contained in:
@ -6,7 +6,6 @@ use MailPoet\AutomaticEmails\WooCommerce\Events\AbandonedCart;
|
||||
use MailPoet\Automation\Engine\Control\AutomationController;
|
||||
use MailPoet\Automation\Engine\Control\StepRunController;
|
||||
use MailPoet\Automation\Engine\Data\Automation;
|
||||
use MailPoet\Automation\Engine\Data\AutomationRun;
|
||||
use MailPoet\Automation\Engine\Data\Step;
|
||||
use MailPoet\Automation\Engine\Data\StepRunArgs;
|
||||
use MailPoet\Automation\Engine\Data\StepValidationArgs;
|
||||
@ -36,6 +35,18 @@ use Throwable;
|
||||
class SendEmailAction implements Action {
|
||||
const KEY = 'mailpoet:send-email';
|
||||
|
||||
// Intervals to poll for email status after sending. These are only
|
||||
// used when immediate status sync fails or the email is never sent.
|
||||
private const POLL_INTERVALS = [
|
||||
5 * MINUTE_IN_SECONDS, // ~5 minutes
|
||||
10 * MINUTE_IN_SECONDS, // ~15 minutes
|
||||
45 * MINUTE_IN_SECONDS, // ~1 hour
|
||||
4 * HOUR_IN_SECONDS, // ~5 hours ...from email scheduling
|
||||
19 * HOUR_IN_SECONDS, // ~1 day
|
||||
4 * DAY_IN_SECONDS, // ~5 days
|
||||
25 * DAY_IN_SECONDS, // ~1 month
|
||||
];
|
||||
|
||||
private const TRANSACTIONAL_TRIGGERS = [
|
||||
'woocommerce:order-status-changed',
|
||||
'woocommerce:order-created',
|
||||
@ -158,33 +169,38 @@ class SendEmailAction implements Action {
|
||||
public function run(StepRunArgs $args, StepRunController $controller): void {
|
||||
$newsletter = $this->getEmailForStep($args->getStep());
|
||||
$subscriber = $this->getSubscriber($args);
|
||||
$run = $args->getAutomationRun();
|
||||
|
||||
// sync sending status with the automation step
|
||||
if (!$args->isFirstRun()) {
|
||||
$this->checkSendingStatus($newsletter, $subscriber, $run);
|
||||
return;
|
||||
if ($args->isFirstRun()) {
|
||||
// run #1: schedule email sending
|
||||
$subscriberStatus = $subscriber->getStatus();
|
||||
if ($newsletter->getType() !== NewsletterEntity::TYPE_AUTOMATION_TRANSACTIONAL && $subscriberStatus !== SubscriberEntity::STATUS_SUBSCRIBED) {
|
||||
throw InvalidStateException::create()->withMessage(sprintf("Cannot schedule a newsletter for subscriber ID '%s' because their status is '%s'.", $subscriber->getId(), $subscriberStatus));
|
||||
}
|
||||
|
||||
if ($subscriberStatus === SubscriberEntity::STATUS_BOUNCED) {
|
||||
throw InvalidStateException::create()->withMessage(sprintf("Cannot schedule an email for subscriber ID '%s' because their status is '%s'.", $subscriber->getId(), $subscriberStatus));
|
||||
}
|
||||
|
||||
$meta = $this->getNewsletterMeta($args);
|
||||
try {
|
||||
$this->automationEmailScheduler->createSendingTask($newsletter, $subscriber, $meta);
|
||||
} catch (Throwable $e) {
|
||||
throw InvalidStateException::create()->withMessage('Could not create sending task.');
|
||||
}
|
||||
|
||||
} else {
|
||||
// run #N: check/sync sending status with the automation step
|
||||
$success = $this->checkSendingStatus($args, $newsletter, $subscriber);
|
||||
if ($success) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
$subscriberStatus = $subscriber->getStatus();
|
||||
if ($newsletter->getType() !== NewsletterEntity::TYPE_AUTOMATION_TRANSACTIONAL && $subscriberStatus !== SubscriberEntity::STATUS_SUBSCRIBED) {
|
||||
throw InvalidStateException::create()->withMessage(sprintf("Cannot schedule a newsletter for subscriber ID '%s' because their status is '%s'.", $subscriber->getId(), $subscriberStatus));
|
||||
}
|
||||
|
||||
if ($subscriberStatus === SubscriberEntity::STATUS_BOUNCED) {
|
||||
throw InvalidStateException::create()->withMessage(sprintf("Cannot schedule an email for subscriber ID '%s' because their status is '%s'.", $subscriber->getId(), $subscriberStatus));
|
||||
}
|
||||
|
||||
$meta = $this->getNewsletterMeta($args);
|
||||
try {
|
||||
$this->automationEmailScheduler->createSendingTask($newsletter, $subscriber, $meta);
|
||||
} catch (Throwable $e) {
|
||||
throw InvalidStateException::create()->withMessage('Could not create sending task.');
|
||||
}
|
||||
|
||||
// schedule a progress run to sync email sending status to the automation step
|
||||
// (1 month is a timout, the progress will normally be executed after sending)
|
||||
$controller->scheduleProgress(time() + MONTH_IN_SECONDS);
|
||||
// Schedule a progress run to sync the email sending status to the automation step.
|
||||
// Normally, a progress run is executed immediately after sending; we're scheduling
|
||||
// these runs to poll for the status if sync fails or email never sends (timeout).
|
||||
$nextInterval = self::POLL_INTERVALS[$args->getRunNumber() - 1] ?? 0;
|
||||
$controller->scheduleProgress(time() + $nextInterval);
|
||||
}
|
||||
|
||||
/** @param mixed $data */
|
||||
@ -212,8 +228,8 @@ class SendEmailAction implements Action {
|
||||
$this->automationController->enqueueProgress($runId, $stepId);
|
||||
}
|
||||
|
||||
private function checkSendingStatus(NewsletterEntity $newsletter, SubscriberEntity $subscriber, AutomationRun $run): void {
|
||||
$scheduledTaskSubscriber = $this->automationEmailScheduler->getScheduledTaskSubscriber($newsletter, $subscriber, $run);
|
||||
private function checkSendingStatus(StepRunArgs $args, NewsletterEntity $newsletter, SubscriberEntity $subscriber): bool {
|
||||
$scheduledTaskSubscriber = $this->automationEmailScheduler->getScheduledTaskSubscriber($newsletter, $subscriber, $args->getAutomationRun());
|
||||
if (!$scheduledTaskSubscriber) {
|
||||
throw InvalidStateException::create()->withMessage('Email failed to schedule.');
|
||||
}
|
||||
@ -225,14 +241,17 @@ class SendEmailAction implements Action {
|
||||
);
|
||||
}
|
||||
|
||||
$wasSent = $scheduledTaskSubscriber->getProcessed() === ScheduledTaskSubscriberEntity::STATUS_PROCESSED;
|
||||
$isLastRun = $args->getRunNumber() >= count(self::POLL_INTERVALS);
|
||||
|
||||
// email was never sent
|
||||
if ($scheduledTaskSubscriber->getProcessed() !== ScheduledTaskSubscriberEntity::STATUS_PROCESSED) {
|
||||
if (!$wasSent && $isLastRun) {
|
||||
$error = 'Email sending process timed out.';
|
||||
$this->automationEmailScheduler->saveError($scheduledTaskSubscriber, $error);
|
||||
throw InvalidStateException::create()->withMessage($error);
|
||||
}
|
||||
|
||||
// email was sent, complete the run
|
||||
return $wasSent;
|
||||
}
|
||||
|
||||
private function getNewsletterMeta(StepRunArgs $args): array {
|
||||
|
@ -4,6 +4,7 @@ namespace MailPoet\Test\Automation\Integrations\MailPoet\Actions;
|
||||
|
||||
use Codeception\Stub\Expected;
|
||||
use MailPoet\Automation\Engine\Builder\UpdateAutomationController;
|
||||
use MailPoet\Automation\Engine\Control\ActionScheduler;
|
||||
use MailPoet\Automation\Engine\Control\AutomationController;
|
||||
use MailPoet\Automation\Engine\Control\StepRunControllerFactory;
|
||||
use MailPoet\Automation\Engine\Data\Automation;
|
||||
@ -63,6 +64,7 @@ class SendEmailActionTest extends \MailPoetTest {
|
||||
|
||||
public function _before() {
|
||||
parent::_before();
|
||||
$this->cleanup();
|
||||
|
||||
$this->scheduledTasksRepository = $this->diContainer->get(ScheduledTasksRepository::class);
|
||||
$this->segmentsRepository = $this->diContainer->get(SegmentsRepository::class);
|
||||
@ -74,6 +76,11 @@ class SendEmailActionTest extends \MailPoetTest {
|
||||
$this->automation = new Automation('test-automation', [], new \WP_User());
|
||||
}
|
||||
|
||||
public function _after() {
|
||||
parent::_after();
|
||||
$this->cleanup();
|
||||
}
|
||||
|
||||
public function testItReturnsRequiredSubjects() {
|
||||
$this->assertSame(['mailpoet:subscriber'], $this->action->getSubjectKeys());
|
||||
}
|
||||
@ -191,7 +198,17 @@ class SendEmailActionTest extends \MailPoetTest {
|
||||
$scheduledTaskSubscriber->setFailed(ScheduledTaskSubscriberEntity::FAIL_STATUS_OK);
|
||||
$scheduledTaskSubscriber->setError(null);
|
||||
|
||||
// email was never sent
|
||||
// email was not sent yet, scheduling next progress (no exception)
|
||||
$actionScheduler = $this->diContainer->get(ActionScheduler::class);
|
||||
$this->assertCount(0, $actionScheduler->getScheduledActions());
|
||||
$this->action->run($args, $controller);
|
||||
$actions = array_values($actionScheduler->getScheduledActions());
|
||||
$this->assertCount(1, $actions);
|
||||
$this->assertSame('mailpoet/automation/step', $actions[0]->get_hook());
|
||||
$this->assertSame([['automation_run_id' => $run->getId(), 'step_id' => 'step-id', 'run_number' => 3]], $actions[0]->get_args());
|
||||
|
||||
// email was never sent (7th run is the last check after ~1 month)
|
||||
$args = new StepRunArgs($automation, $run, $step, $this->getSubjectEntries($subjects), 7);
|
||||
$this->assertThrowsExceptionWithMessage(
|
||||
'Email sending process timed out.',
|
||||
function() use ($args, $controller) {
|
||||
@ -513,4 +530,10 @@ class SendEmailActionTest extends \MailPoetTest {
|
||||
}
|
||||
$this->assertSame($expectedMessage, $error);
|
||||
}
|
||||
|
||||
private function cleanup(): void {
|
||||
global $wpdb;
|
||||
$wpdb->query('TRUNCATE ' . $wpdb->prefix . 'actionscheduler_actions');
|
||||
$wpdb->query('TRUNCATE ' . $wpdb->prefix . 'actionscheduler_claims');
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user