Skip to content

Commit

Permalink
Merge branch '1277-unsubscriba-inactive-users-command' into 'master'
Browse files Browse the repository at this point in the history
Added `mail:unsubscribe-inactive-users` command to mailer module

See merge request remp/remp!787
  • Loading branch information
zoldia committed Jul 24, 2023
2 parents 5d169d0 + ea63834 commit 9ed614b
Show file tree
Hide file tree
Showing 7 changed files with 434 additions and 0 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p
- Added hermes handler to notify CRM about refreshing user's data. You can enable the feature in `config.local.neon` (see example file for reference). remp/web#2061
- Fixed typo in the `package.json` definition for `moment-timezone` causing Yarn3 installation issues. [remp2020/mailer-module#2](https://github.com/remp2020/mailer-module/pull/2)
- Added new emit of `user-subscribed-variant` and `user-unsubscribed-variant` hermes event when user subscribe or unsubscribes from the mail type variant. remp/web#2061
- Added `mail:unsubscribe-inactive-users` command to mailer module. remp/remp#1277

### [Sso]

Expand Down
3 changes: 3 additions & 0 deletions Mailer/app/config/config.test.neon
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,10 @@ services:
setup:
- addDomain('expresso.pt')

- Remp\MailerModule\Commands\UnsubscribeInactiveUsersCommand

- Tests\Unit\NullTracker
- Tests\Feature\TestSegmentProvider

config:
setup:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
<?php

namespace Remp\MailerModule\Commands;

use Nette\Utils\DateTime;
use Remp\MailerModule\Hermes\HermesMessage;
use Remp\MailerModule\Models\RedisClientFactory;
use Remp\MailerModule\Models\RedisClientTrait;
use Remp\MailerModule\Models\Segment\Aggregator;
use Remp\MailerModule\Models\Segment\Crm;
use Remp\MailerModule\Models\Tracker\EventOptions;
use Remp\MailerModule\Models\Tracker\ITracker;
use Remp\MailerModule\Models\Tracker\User;
use Remp\MailerModule\Repositories\LogsRepository;
use Remp\MailerModule\Repositories\TemplatesRepository;
use Remp\MailerModule\Repositories\UserSubscriptionsRepository;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Tomaj\Hermes\Emitter;

class UnsubscribeInactiveUsersCommand extends Command
{
use DecoratedCommandTrait, RedisClientTrait;

public const COMMAND_NAME = 'mail:unsubscribe-inactive-users';
private const CRM_SEGMENT_NAME = 'unsubscribe_inactive_users_from_newsletters_list';
private const APPLE_BOT_EMAILS = 'apple_bot_emails';

private array $omitMailTypeCodes = ['system', 'system_optional'];

public function __construct(
private Aggregator $segmentAggregator,
private UserSubscriptionsRepository $userSubscriptionsRepository,
private TemplatesRepository $templatesRepository,
private LogsRepository $logsRepository,
private Emitter $hermesEmitter,
RedisClientFactory $redisClientFactory,
private ITracker|null $tracker = null
) {
parent::__construct();

$this->redisClientFactory = $redisClientFactory;
}

protected function configure(): void
{
$this->setName(self::COMMAND_NAME)
->setDescription('Unsubscribe inactive users from newsletters')
->addOption('segment-provider', 'sp', InputOption::VALUE_REQUIRED, 'Segment provider code.', Crm::PROVIDER_ALIAS)
->addOption('segment', 's', InputOption::VALUE_REQUIRED, 'Crm segment with list of users to unsubscribe.', self::CRM_SEGMENT_NAME)
->addOption('days', 'd', InputOption::VALUE_REQUIRED, 'Days limit for check opened emails.', 45)
->addOption('email', 'e', InputOption::VALUE_REQUIRED, 'Email template code of notification email, sent after unsubscribing.')
->addOption('dry-run', null, InputOption::VALUE_NONE, 'Run command without unsubscribing users and sending notifications.')
->addOption('omit-mail-type-code', 'o', InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Omit mail type (code), from email delivered and opened check and unsubscribing.');
}

protected function execute(InputInterface $input, OutputInterface $output): int
{
$segmentProvider = $input->getOption('segment-provider');
$segment = $input->getOption('segment');
$dayLimit = $input->getOption('days');
$dryRun = $input->getOption('dry-run');
$notificationEmailCode = $input->getOption('email');
$omitMailTypeCodes = array_merge($this->omitMailTypeCodes, $input->getOption('omit-mail-type-code'));

if ($notificationEmailCode) {
$notificationEmailTemplate = $this->templatesRepository->getByCode($input->getOption('email'));
if (!$notificationEmailTemplate) {
$this->error("Notification email template: '{$input->getOption('email')}' doesn't exist.");
return Command::FAILURE;
}
}

$userIds = $this->segmentAggregator->users(['provider' => $segmentProvider, 'code' => $segment]);

foreach ($userIds as $userId) {
$output->write("* Checking user <info>{$userId}</info>: ");
$subscribed = $this->userSubscriptionsRepository->getTable()
->where('user_id', $userId)
->where('mail_type.code NOT IN', $omitMailTypeCodes)
->where('subscribed', 1)
->fetch();

if ($subscribed) {
$userEmail = $subscribed->user_email;
$logs = $this->logsRepository->getTable()
->where('user_id', $userId)
->where('delivered_at > ', DateTime::from("-{$dayLimit} days"))
->where('mail_template.mail_type.code NOT IN', $omitMailTypeCodes)
->fetchAll();

$logCount = count($logs);
if (count($logs) >= 5) {
$columnToCheck = 'opened_at';
if ($this->redis()->sismember(self::APPLE_BOT_EMAILS, $userEmail)) {
$columnToCheck = 'clicked_at';
}

foreach ($logs as $log) {
if ($log->{$columnToCheck}) {
$output->writeln("Skipping, user is active.");
continue 2;
}
}

if (!$dryRun) {
$output->write("<comment>Unsubscribing...</comment> ");
$this->userSubscriptionsRepository->unsubscribeUserFromAll($userId, $userEmail, $omitMailTypeCodes);

$eventOptions = new EventOptions();
$eventOptions->setUser(new User(['id' => $userId]));
$this->tracker?->trackEvent(
new DateTime(),
'mail-type',
'auto-unsubscribe',
$eventOptions
);

if (isset($notificationEmailTemplate)) {
$today = new DateTime();
$this->hermesEmitter->emit(new HermesMessage('send-email', [
'mail_template_code' => $notificationEmailTemplate->code,
'email' => $userEmail,
'context' => "nl_goodbye_all_email.{$userId}.{$notificationEmailTemplate->mail_type->id}.{$today->format('Ymd')}",
]));
}
} else {
$output->write("<comment>Unsubscribing (dry run)...</comment> ");
}

$output->writeln("OK!");
} else {
$output->writeln("Skipping, not enough delivered emails within the {$dayLimit} days period ({$logCount}).");
}
} else {
$output->writeln("Skipping, not subscribed to anything relevant.");
}
}

return Command::SUCCESS;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,25 @@ public function unsubscribeUser(
}
}

public function unsubscribeUserFromAll(
int $userId,
string $email,
array $filterOutMailTypeCodes = []
) {
$subscriptions = $this->getTable()
->where('user_email', $email)
->where('user_id', $userId)
->where('mail_type.code NOT IN', $filterOutMailTypeCodes)
->where('subscribed', 1);

foreach ($subscriptions as $subscription) {
$this->update($subscription, [
'subscribed' => false,
]);
$this->userSubscriptionVariantsRepository->removeSubscribedVariants($subscription);
}
}

public function unsubscribeEmail(ActiveRow $mailType, string $email, array $rtmParams = []): void
{
/** @var ActiveRow $actual */
Expand Down
Loading

0 comments on commit 9ed614b

Please sign in to comment.