]> _ Git - fluidbook-toolbox.git/commitdiff
wip #7382 @6
authorVincent Vanwaelscappel <vincent@cubedesigners.com>
Tue, 11 Mar 2025 13:05:19 +0000 (14:05 +0100)
committerVincent Vanwaelscappel <vincent@cubedesigners.com>
Tue, 11 Mar 2025 13:05:19 +0000 (14:05 +0100)
.env.alpha
.env.dev
.env.prod
app/Console/Kernel.php
app/Jobs/ListmonkSyncList.php [new file with mode: 0644]
app/Jobs/MailjetSyncList.php [deleted file]
composer.json
composer.lock
config/listmonk.php [new file with mode: 0644]

index 97a06427cc25c982fb1d1d5eb1520e821df5f894..376d1cf18cb3f6cbadb61be41b7d47407e1f8a9a 100644 (file)
@@ -41,11 +41,11 @@ REDIS_PASSWORD=null
 REDIS_PORT=6379
 
 MAIL_DRIVER=smtp
-MAIL_HOST=mail2.cubedesigners.com
-MAIL_PORT=587
-MAIL_USERNAME=workshop@cubedesigners.com
-MAIL_PASSWORD=4zrmk4Hu9HH97n7UiW5
-MAIL_ENCRYPTION=tls
+MAIL_HOST=postal.cubedesigners.com
+MAIL_PORT=25
+MAIL_USERNAME=cubedesigners/fluidbook-toolbox-dev
+MAIL_PASSWORD=jgqyC9RxBxAM40C5zoJj0WVT
+MAIL_ENCRYPTION=false
 MAIL_FROM_ADDRESS=toolbox+alpha@fluidbook.com
 MAIL_FROM_NAME="[ALPHA] Fluidbook Toolbox"
 MAIL_BCC_ALL=test+toolboxalpha@cubedesigners.com
index a516e6c68a093b6585e21d8807f9cf1be3101992..703841f9fcb59a59f085d7145d85e7f6e5a2a9e6 100644 (file)
--- a/.env.dev
+++ b/.env.dev
@@ -41,18 +41,19 @@ REDIS_PASSWORD=null
 REDIS_PORT=6379
 
 MAIL_DRIVER=smtp
-MAIL_HOST=mail2.cubedesigners.com
-MAIL_PORT=587
-MAIL_USERNAME=workshop@cubedesigners.com
-MAIL_PASSWORD=4zrmk4Hu9HH97n7UiW5
-MAIL_ENCRYPTION=tls
+MAIL_HOST=postal.cubedesigners.com
+MAIL_PORT=25
+MAIL_USERNAME=cubedesigners/fluidbook-toolbox-dev
+MAIL_PASSWORD=jgqyC9RxBxAM40C5zoJj0WVT
+MAIL_ENCRYPTION=false
 MAIL_FROM_ADDRESS=toolbox+dev@fluidbook.com
 MAIL_FROM_NAME="[DEV] Fluidbook Toolbox"
 MAIL_BCC_ALL=test+toolboxdev@cubedesigners.com
 MAIL_TEAM_NAME=Fluidbook
 
-MAILJET_API_KEY=ca110b35f8735c223d69c9987c2ac47d
-MAILJET_API_SECRET=b289d0acb08e0fe56ce98ccf0dd1ed8b
+LISTMONK_BASE_URL=https://listmonk.cubedesigners.com
+LISTMONK_API_USER=fluidbooktoolbox
+LISTMONK_API_TOKEN=b289d0acb08e0fe56ce98ccf0dd1ed8b
 
 MIX_PUSHER_APP_KEY="${PUSHER_APP_KEY}"
 MIX_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}"
index 9f1e45ffabee5a99c9acd8a8190c621081e4aea6..7ee634b55241b5c94a8983ca6f5fa5d7b4e2016b 100644 (file)
--- a/.env.prod
+++ b/.env.prod
@@ -41,11 +41,11 @@ REDIS_PASSWORD=null
 REDIS_PORT=6379
 
 MAIL_DRIVER=smtp
-MAIL_HOST=mail2.cubedesigners.com
-MAIL_PORT=587
-MAIL_USERNAME=workshop@cubedesigners.com
-MAIL_PASSWORD=4zrmk4Hu9HH97n7UiW5
-MAIL_ENCRYPTION=tls
+MAIL_HOST=postal.cubedesigners.com
+MAIL_PORT=25
+MAIL_USERNAME=cubedesigners/fluidbook-toolbox
+MAIL_PASSWORD=JzKxZdtN730yXwKRorTVDhaK
+MAIL_ENCRYPTION=false
 MAIL_FROM_ADDRESS=toolbox@fluidbook.com
 MAIL_FROM_NAME="Fluidbook Toolbox"
 MAIL_BCC_ALL=test+toolbox@cubedesigners.com
@@ -54,8 +54,9 @@ MAIL_TEAM_NAME=Fluidbook
 RATE_PEOPLE_DAY=630
 RATE_PEOPLE_HOUR=100
 
-MAILJET_API_KEY=ca110b35f8735c223d69c9987c2ac47d
-MAILJET_API_SECRET=b289d0acb08e0fe56ce98ccf0dd1ed8b
+LISTMONK_BASE_URL=https://listmonk.cubedesigners.com
+LISTMONK_API_USER=fluidbooktoolbox
+LISTMONK_API_TOKEN=f60zrw7kvialdQFlMCrMKLl49nlQSkEe
 
 MIX_PUSHER_APP_KEY="${PUSHER_APP_KEY}"
 MIX_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}"
index 208c3d6ff70acad3bcdca06c67dedd4c1a2131e9..2386845be1963c0475024566bd4218e6956e5a60 100644 (file)
@@ -45,7 +45,7 @@ class Kernel extends \Cubist\Backpack\Console\Kernel
             // Quotes
             $schedule->command('fluidbook:quote --reminder')->weekdays()->at('10:00');
             // Mailjet
-            $schedule->command('job:dispatchNow MailjetSyncList')->dailyAt('5:00');
+            $schedule->command('job:dispatchNow ListmonkSyncList')->dailyAt('5:00');
         }
 
         $schedule->command('job:dispatchNow ProcessTotals')->everyTwoHours();
diff --git a/app/Jobs/ListmonkSyncList.php b/app/Jobs/ListmonkSyncList.php
new file mode 100644 (file)
index 0000000..913e8cc
--- /dev/null
@@ -0,0 +1,253 @@
+<?php
+
+namespace App\Jobs;
+
+use App\Models\Company;
+use App\Models\User;
+use Cubedesigners\UserDatabase\Permissions;
+use DateTimeInterface;
+use Egulias\EmailValidator\EmailValidator;
+use Egulias\EmailValidator\Validation\DNSCheckValidation;
+use Egulias\EmailValidator\Validation\MultipleValidationWithAnd;
+use Egulias\EmailValidator\Validation\RFCValidation;
+use Illuminate\Support\Facades\Cache;
+use Illuminate\Support\Facades\DB;
+use Mailjet\Client;
+use Mailjet\Resources;
+use Theafolayan\ListmonkLaravel\Facades\Listmonk;
+
+class ListmonkSyncList extends Base
+{
+    const LIST_ID = '1';
+
+    const ENABLED = true;
+
+    const BATCH = 500;
+
+    /**
+     * @throws \Exception
+     */
+    public function handle()
+    {
+        //$this->syncStatusFromMailjet();
+        //dump('Synced status from mailjet');
+
+        dd(Listmonk::getSubscribers(['per_page' => 100000, 'page' => 1, 'list_id' => [self::LIST_ID]]));
+
+        $skippedCompanies = [];
+        $companies = [];
+        $contacts = ['addnoforce' => [], 'unsub' => [], 'remove' => []];
+
+        $validator = new EmailValidator();
+        $multipleValidations = new MultipleValidationWithAnd([
+            new RFCValidation(),
+            new DNSCheckValidation()
+        ]);
+
+        foreach (Company::withoutGlobalScopes()->get() as $company) {
+            if (!$company->created_ok) {
+                $skippedCompanies[] = $company->id;
+                continue;
+            }
+            /** @var Company $company */
+            $adminCompany = Permissions::getCompanyByUser($company->admin);
+            // Skip companies not directly managed by Cubedesigners
+            if ($adminCompany != 7 && !$company->marketing_force) {
+                $skippedCompanies[] = $company->id;
+                continue;
+            }
+            $companies[$company->id] = $company;
+        }
+
+        foreach (User::withoutGlobalScopes()->where('created_ok', '1')->where('enabled', '1')->get() as $user) {
+            if (in_array($user->company, $skippedCompanies) || !isset($companies[$user->company])) {
+                continue;
+            }
+
+            $c = $companies[$user->company];
+            $address = $user->address;
+            if (is_string($address)) {
+                $address = json_decode($address);
+            }
+            if (null === $address) {
+                $country = '';
+            } else {
+                $country = is_array($address) ? $address['country'] : $address->country;
+            }
+
+            $action = 'addnoforce';
+
+            if (str_ends_with($user->email, '@extranet.cubedesigners.com')) {
+                $action = 'remove';
+            }
+
+            if (!Cache::rememberForever('email_valid_' . $user->email, function () use ($user, $validator, $multipleValidations) {
+                return $validator->isValid($user->email, $multipleValidations);
+            })) {
+                continue;
+            }
+
+            if (!$user->marketing) {
+                $action = 'unsub';
+            }
+
+            $contact = [
+                'email' => $user->email,
+                'status' => 'enabled',
+                'name' => trim($user->firstname . ' ' . $user->lastname),
+                'preconfirm_subscriptions' => true,
+                'lists' => [self::LIST_ID],
+                'attribs' => [
+                    'toolbox_id' => $user->id,
+                    'firstname' => trim($user->firstname),
+                    'lastname' => trim($user->lastname),
+                    'company' => trim($c->name),
+                    'fluidbook' => $c->e1_ws_grade > 0 && $c->toolbox_access,
+                    'elearning' => (bool)$c->permissions_elearning,
+                    'locale' => $user->locale,
+                    'country' => $country,
+                    'last_project' => $c->c_last_project_date ? (new \DateTime($c->c_last_project_date))->format(DateTimeInterface::RFC3339) : '',
+                ]
+            ];
+
+            $contacts[$action][] = $contact;
+        }
+
+
+        foreach ($contacts as $action => $list) {
+            $this->addContactsToList($list, $action);
+        }
+
+    }
+
+    protected function deleteContacts()
+    {
+        $mj = static::_api();
+        $i = 0;
+        $remove = $this->getRecipientsList();
+        $emails = $this->getEmailsFromContactIDs($remove);
+        $contacts = [];
+        foreach ($emails as $email) {
+            $contacts[] = ['Email' => $email];
+        }
+        $this->addContactsToList($contacts, 'remove');
+    }
+
+    protected function getRecipientsList($filter = null)
+    {
+        $i = 0;
+        $res = [];
+        $mj = static::_api();
+
+
+        $filters = ['ContactsList' => static::LIST_ID,
+            'Limit' => self::BATCH,
+            'Offset' => 0];
+        if (null !== $filter) {
+            $filters = array_merge($filters, $filter);
+        }
+
+        while (true) {
+            $filters['Offset'] = $i * self::BATCH;
+            $response = $mj->get(Resources::$Listrecipient, ['filters' => $filters]);
+            foreach ($response->getData() as $contact) {
+                $res[] = $contact['ContactID'];
+            }
+
+            if ($response->getCount() < self::BATCH) {
+                break;
+            }
+
+            $i++;
+        }
+        return $res;
+    }
+
+    protected function getEmailsFromContactIDs($contactIDs)
+    {
+        $i = 0;
+        $res = [];
+        $mj = static::_api();
+
+        while (true) {
+            $response = $mj->get(Resources::$Contact, ['filters' => ['ContactsList' => static::LIST_ID,
+                'Limit' => self::BATCH,
+                'Offset' => $i * self::BATCH,
+            ]]);
+
+            foreach ($response->getData() as $contact) {
+                if (in_array($contact['ID'], $contactIDs)) {
+                    $res[] = $contact['Email'];
+                }
+            }
+            if ($response->getCount() < self::BATCH) {
+                break;
+            }
+            $i++;
+        }
+        return $res;
+    }
+
+    protected function addContactsToList($contacts, $action = 'addnoforce')
+    {
+        if (empty($contacts)) {
+            return;
+        }
+        $body = [
+            'Action' => $action,
+            'Contacts' => $contacts
+        ];
+        $response = $mj->post(Resources::$ContactslistManagemanycontacts, ['id' => self::LIST_ID, 'body' => $body]);
+        $jobId = $response->getData()[0]['JobID'];
+
+        while (true) {
+            $check = $mj->get(Resources::$ContactslistManagemanycontacts, ['id' => self::LIST_ID, 'actionid' => (string)$jobId]);
+            $status = ($check->getData()[0]['Status']);
+            if ($status === 'Error') {
+                dd($check->getData()[0]);
+            } else if ($status === 'Completed') {
+                return;
+            }
+            dump($status);
+            sleep(2);
+        }
+    }
+
+    protected function syncStatusFromMailjet()
+    {
+        $mj = static::_api();
+        $i = 0;
+        $unsub = [];
+
+        $timestamp = time() - (3600 * 24 * 90); // 90 days in the past
+
+        // Check bounces
+        while (true) {
+            $response = $mj->get(Resources::$Bouncestatistics, ['filters' => [
+                'Limit' => self::BATCH,
+                'Offset' => $i * self::BATCH,
+                'FromTS' => $timestamp,
+            ]]);
+
+            foreach ($response->getData() as $contact) {
+                if ($contact['IsBlocked'] || $contact['IsStatePermanent']) {
+                    $unsub[] = $contact['ContactID'];
+                }
+            }
+
+            if ($response->getCount() < self::BATCH) {
+                break;
+            }
+            $i++;
+        }
+
+        // Check unsubscribed users
+        $filters = ['Unsub' => 'true', 'IsExcludedFromCampaigns' => 'true'];
+        foreach ($filters as $filter => $value) {
+            $unsub = array_merge($unsub, $this->getRecipientsList([$filter => $value]));
+        }
+
+        $unsubEmails = $this->getEmailsFromContactIDs($unsub);
+        DB::table('extranet_users.user')->whereIn('email', $unsubEmails)->update(['marketing' => 0]);
+    }
+}
diff --git a/app/Jobs/MailjetSyncList.php b/app/Jobs/MailjetSyncList.php
deleted file mode 100644 (file)
index d2754ad..0000000
+++ /dev/null
@@ -1,262 +0,0 @@
-<?php
-
-namespace App\Jobs;
-
-use App\Models\Company;
-use App\Models\User;
-use Cubedesigners\UserDatabase\Permissions;
-use DateTimeInterface;
-use Egulias\EmailValidator\EmailValidator;
-use Egulias\EmailValidator\Validation\DNSCheckValidation;
-use Egulias\EmailValidator\Validation\MultipleValidationWithAnd;
-use Egulias\EmailValidator\Validation\RFCValidation;
-use Illuminate\Support\Facades\Cache;
-use Illuminate\Support\Facades\DB;
-use Mailjet\Client;
-use Mailjet\Resources;
-
-class MailjetSyncList extends Base
-{
-    const LIST_ID = '10296433';
-
-    const ENABLED = false;
-
-    const BATCH = 500;
-
-    /**
-     * @throws \Exception
-     */
-    public function handle()
-    {
-        if (!self::ENABLED) {
-            $this->deleteContacts();
-            return;
-        }
-
-        $this->syncStatusFromMailjet();
-        dump('Synced status from mailjet');
-
-        $skippedCompanies = [];
-        $companies = [];
-        $contacts = ['addnoforce' => [], 'unsub' => [], 'remove' => []];
-
-        $validator = new EmailValidator();
-        $multipleValidations = new MultipleValidationWithAnd([
-            new RFCValidation(),
-            new DNSCheckValidation()
-        ]);
-
-        foreach (Company::withoutGlobalScopes()->get() as $company) {
-            if (!$company->created_ok) {
-                $skippedCompanies[] = $company->id;
-                continue;
-            }
-            /** @var Company $company */
-            $adminCompany = Permissions::getCompanyByUser($company->admin);
-            // Skip companies not directly managed by Cubedesigners
-            if ($adminCompany != 7 && !$company->marketing_force) {
-                $skippedCompanies[] = $company->id;
-                continue;
-            }
-            $companies[$company->id] = $company;
-        }
-
-        foreach (User::withoutGlobalScopes()->where('created_ok', '1')->where('enabled', '1')->get() as $user) {
-            if (in_array($user->company, $skippedCompanies) || !isset($companies[$user->company])) {
-                continue;
-            }
-
-            $c = $companies[$user->company];
-            $address = $user->address;
-            if (is_string($address)) {
-                $address = json_decode($address);
-            }
-            if (null === $address) {
-                $country = '';
-            } else {
-                $country = is_array($address) ? $address['country'] : $address->country;
-            }
-
-            $action = 'addnoforce';
-
-            if (str_ends_with($user->email, '@extranet.cubedesigners.com')) {
-                $action = 'remove';
-            }
-
-            if (!Cache::rememberForever('email_valid_' . $user->email, function () use ($user, $validator, $multipleValidations) {
-                return $validator->isValid($user->email, $multipleValidations);
-            })) {
-                continue;
-            }
-
-            if (!$user->marketing) {
-                $action = 'unsub';
-            }
-
-            $contact = [
-                'Email' => $user->email,
-                'IsExcludedFromCampaigns' => 'false',
-                'Name' => trim($user->firstname . ' ' . $user->lastname),
-                'Properties' => [
-                    'toolbox_id' => $user->id,
-                    'firstname' => trim($user->firstname),
-                    'lastname' => trim($user->lastname),
-                    'company' => trim($c->name),
-                    'fluidbook' => $c->e1_ws_grade > 0 && $c->toolbox_access,
-                    'elearning' => (bool)$c->permissions_elearning,
-                    'locale' => $user->locale,
-                    'country' => $country,
-                    'last_project' => $c->c_last_project_date ? (new \DateTime($c->c_last_project_date))->format(DateTimeInterface::RFC3339) : '',
-                ]
-            ];
-
-            $contacts[$action][] = $contact;
-        }
-
-
-        foreach ($contacts as $action => $list) {
-            $this->addContactsToList($list, $action);
-        }
-
-    }
-
-    protected function deleteContacts()
-    {
-        $mj = static::_api();
-        $i = 0;
-        $remove = $this->getRecipientsList();
-        $emails=$this->getEmailsFromContactIDs($remove);
-        $contacts = [];
-        foreach ($emails as $email) {
-            $contacts[] = ['Email' => $email];
-        }
-        $this->addContactsToList($contacts, 'remove');
-    }
-
-    protected function getRecipientsList($filter = null)
-    {
-        $i = 0;
-        $res = [];
-        $mj = static::_api();
-
-
-        $filters = ['ContactsList' => static::LIST_ID,
-            'Limit' => self::BATCH,
-            'Offset' => 0];
-        if (null !== $filter) {
-            $filters = array_merge($filters, $filter);
-        }
-
-        while (true) {
-            $filters['Offset'] = $i * self::BATCH;
-            $response = $mj->get(Resources::$Listrecipient, ['filters' => $filters]);
-            foreach ($response->getData() as $contact) {
-                $res[] = $contact['ContactID'];
-            }
-
-            if ($response->getCount() < self::BATCH) {
-                break;
-            }
-
-            $i++;
-        }
-        return $res;
-    }
-
-    protected function getEmailsFromContactIDs($contactIDs)
-    {
-        $i = 0;
-        $res = [];
-        $mj = static::_api();
-
-        while (true) {
-            $response = $mj->get(Resources::$Contact, ['filters' => ['ContactsList' => static::LIST_ID,
-                'Limit' => self::BATCH,
-                'Offset' => $i * self::BATCH,
-            ]]);
-
-            foreach ($response->getData() as $contact) {
-                if (in_array($contact['ID'], $contactIDs)) {
-                    $res[] = $contact['Email'];
-                }
-            }
-            if ($response->getCount() < self::BATCH) {
-                break;
-            }
-            $i++;
-        }
-        return $res;
-    }
-
-    protected function addContactsToList($contacts, $action = 'addnoforce')
-    {
-        if (empty($contacts)) {
-            return;
-        }
-        $mj = static::_api();
-        $body = [
-            'Action' => $action,
-            'Contacts' => $contacts
-        ];
-        $response = $mj->post(Resources::$ContactslistManagemanycontacts, ['id' => self::LIST_ID, 'body' => $body]);
-        $jobId = $response->getData()[0]['JobID'];
-
-        while (true) {
-            $check = $mj->get(Resources::$ContactslistManagemanycontacts, ['id' => self::LIST_ID, 'actionid' => (string)$jobId]);
-            $status = ($check->getData()[0]['Status']);
-            if ($status === 'Error') {
-                dd($check->getData()[0]);
-            } else if ($status === 'Completed') {
-                return;
-            }
-            dump($status);
-            sleep(2);
-        }
-    }
-
-    protected function syncStatusFromMailjet()
-    {
-        $mj = static::_api();
-        $i = 0;
-        $unsub = [];
-
-        $timestamp = time() - (3600 * 24 * 90); // 90 days in the past
-
-        // Check bounces
-        while (true) {
-            $response = $mj->get(Resources::$Bouncestatistics, ['filters' => [
-                'Limit' => self::BATCH,
-                'Offset' => $i * self::BATCH,
-                'FromTS' => $timestamp,
-            ]]);
-
-            foreach ($response->getData() as $contact) {
-                if ($contact['IsBlocked'] || $contact['IsStatePermanent']) {
-                    $unsub[] = $contact['ContactID'];
-                }
-            }
-
-            if ($response->getCount() < self::BATCH) {
-                break;
-            }
-            $i++;
-        }
-
-        // Check unsubscribed users
-        $filters = ['Unsub' => 'true', 'IsExcludedFromCampaigns' => 'true'];
-        foreach ($filters as $filter => $value) {
-            $unsub = array_merge($unsub, $this->getRecipientsList([$filter => $value]));
-        }
-
-        $unsubEmails = $this->getEmailsFromContactIDs($unsub);
-        DB::table('extranet_users.user')->whereIn('email', $unsubEmails)->update(['marketing' => 0]);
-    }
-
-    /**
-     * @return Client
-     */
-    protected static function _api($call = true, $version = 'v3')
-    {
-        return new \Mailjet\Client(env('MAILJET_API_KEY'), env('MAILJET_API_SECRET'), $call, ['version' => $version]);
-    }
-}
index e5fa2e68bde27a2d1ccf2e4a37523381ee8790dd..b4040faec90cf8b7e1f7ef892f7ce84af555a161 100644 (file)
@@ -68,6 +68,7 @@
         "rustici-software/scormcloud-api-v2-client-php": "^2.1.0",
         "simplesoftwareio/simple-qrcode": "^4.2",
         "symfony/http-client": "^v6.4.0",
+        "theafolayan/listmonk-laravel": "^1.3",
         "voku/simple_html_dom": "^4.8"
     },
     "require-dev": {
index 44dc2871cfc40fb5c113101f2c17fe0dbc5f93c4..54cde6611c434791b885c05b228592b9ef5727a0 100644 (file)
@@ -4,7 +4,7 @@
         "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
         "This file is @generated automatically"
     ],
-    "content-hash": "86f3969e7c9517948cd163134578cf66",
+    "content-hash": "674f394b07cd9069af87322406565da9",
     "packages": [
         {
             "name": "archtechx/enums",
             "source": {
                 "type": "git",
                 "url": "git://git.cubedesigners.com/fluidbook_tools.git",
-                "reference": "4847a6faf6184e235b290babce1d1c72ad54ff22"
+                "reference": "c06d81f8c352dbf896da52c6168aaf9b625da7b1"
             },
             "dist": {
                 "type": "tar",
-                "url": "https://composer.cubedesigners.com/dist/fluidbook/tools/fluidbook-tools-dev-master-135a28.tar",
-                "reference": "4847a6faf6184e235b290babce1d1c72ad54ff22",
-                "shasum": "22a9e68c5c14e5d6c51b4ef205accbdd6fe2f765"
+                "url": "https://composer.cubedesigners.com/dist/fluidbook/tools/fluidbook-tools-dev-master-acf208.tar",
+                "reference": "c06d81f8c352dbf896da52c6168aaf9b625da7b1",
+                "shasum": "f2c41ec290bbb23ce83084fe6d724f3b88ace0cd"
             },
             "require": {
                 "barryvdh/laravel-debugbar": "*",
                 }
             ],
             "description": "Fluidbook Tools",
-            "time": "2025-02-28T16:32:27+00:00"
+            "time": "2025-03-04T16:42:05+00:00"
         },
         {
             "name": "fpdf/fpdf",
         },
         {
             "name": "graham-campbell/markdown",
-            "version": "v15.2.0",
+            "version": "v15.3.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/GrahamCampbell/Laravel-Markdown.git",
-                "reference": "d594fc197b9068de5e234a890be361807a1ab34f"
+                "reference": "042c23ddc5bde61ed09b77a8a17119d70c8e8419"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/GrahamCampbell/Laravel-Markdown/zipball/d594fc197b9068de5e234a890be361807a1ab34f",
-                "reference": "d594fc197b9068de5e234a890be361807a1ab34f",
+                "url": "https://api.github.com/repos/GrahamCampbell/Laravel-Markdown/zipball/042c23ddc5bde61ed09b77a8a17119d70c8e8419",
+                "reference": "042c23ddc5bde61ed09b77a8a17119d70c8e8419",
                 "shasum": ""
             },
             "require": {
                 "illuminate/filesystem": "^8.75 || ^9.0 || ^10.0 || ^11.0",
                 "illuminate/support": "^8.75 || ^9.0 || ^10.0 || ^11.0",
                 "illuminate/view": "^8.75 || ^9.0 || ^10.0 || ^11.0",
-                "league/commonmark": "^2.4.2",
+                "league/commonmark": "^2.6.1",
                 "php": "^7.4.15 || ^8.0.2"
             },
             "require-dev": {
-                "graham-campbell/analyzer": "^4.1",
-                "graham-campbell/testbench": "^6.1",
+                "graham-campbell/analyzer": "^4.2.1",
+                "graham-campbell/testbench": "^6.2",
                 "mockery/mockery": "^1.6.6",
                 "phpunit/phpunit": "^9.6.17 || ^10.5.13"
             },
             ],
             "support": {
                 "issues": "https://github.com/GrahamCampbell/Laravel-Markdown/issues",
-                "source": "https://github.com/GrahamCampbell/Laravel-Markdown/tree/v15.2.0"
+                "source": "https://github.com/GrahamCampbell/Laravel-Markdown/tree/v15.3.0"
             },
             "funding": [
                 {
                     "type": "tidelift"
                 }
             ],
-            "time": "2024-03-17T23:07:39+00:00"
+            "time": "2025-03-02T22:13:22+00:00"
         },
         {
             "name": "graham-campbell/result-type",
         },
         {
             "name": "ramsey/collection",
-            "version": "2.0.0",
+            "version": "2.1.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/ramsey/collection.git",
-                "reference": "a4b48764bfbb8f3a6a4d1aeb1a35bb5e9ecac4a5"
+                "reference": "3c5990b8a5e0b79cd1cf11c2dc1229e58e93f109"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/ramsey/collection/zipball/a4b48764bfbb8f3a6a4d1aeb1a35bb5e9ecac4a5",
-                "reference": "a4b48764bfbb8f3a6a4d1aeb1a35bb5e9ecac4a5",
+                "url": "https://api.github.com/repos/ramsey/collection/zipball/3c5990b8a5e0b79cd1cf11c2dc1229e58e93f109",
+                "reference": "3c5990b8a5e0b79cd1cf11c2dc1229e58e93f109",
                 "shasum": ""
             },
             "require": {
             },
             "require-dev": {
                 "captainhook/plugin-composer": "^5.3",
-                "ergebnis/composer-normalize": "^2.28.3",
-                "fakerphp/faker": "^1.21",
+                "ergebnis/composer-normalize": "^2.45",
+                "fakerphp/faker": "^1.24",
                 "hamcrest/hamcrest-php": "^2.0",
-                "jangregor/phpstan-prophecy": "^1.0",
-                "mockery/mockery": "^1.5",
+                "jangregor/phpstan-prophecy": "^2.1",
+                "mockery/mockery": "^1.6",
                 "php-parallel-lint/php-console-highlighter": "^1.0",
-                "php-parallel-lint/php-parallel-lint": "^1.3",
-                "phpcsstandards/phpcsutils": "^1.0.0-rc1",
-                "phpspec/prophecy-phpunit": "^2.0",
-                "phpstan/extension-installer": "^1.2",
-                "phpstan/phpstan": "^1.9",
-                "phpstan/phpstan-mockery": "^1.1",
-                "phpstan/phpstan-phpunit": "^1.3",
-                "phpunit/phpunit": "^9.5",
-                "psalm/plugin-mockery": "^1.1",
-                "psalm/plugin-phpunit": "^0.18.4",
-                "ramsey/coding-standard": "^2.0.3",
-                "ramsey/conventional-commits": "^1.3",
-                "vimeo/psalm": "^5.4"
+                "php-parallel-lint/php-parallel-lint": "^1.4",
+                "phpspec/prophecy-phpunit": "^2.3",
+                "phpstan/extension-installer": "^1.4",
+                "phpstan/phpstan": "^2.1",
+                "phpstan/phpstan-mockery": "^2.0",
+                "phpstan/phpstan-phpunit": "^2.0",
+                "phpunit/phpunit": "^10.5",
+                "ramsey/coding-standard": "^2.3",
+                "ramsey/conventional-commits": "^1.6",
+                "roave/security-advisories": "dev-latest"
             },
             "type": "library",
             "extra": {
             ],
             "support": {
                 "issues": "https://github.com/ramsey/collection/issues",
-                "source": "https://github.com/ramsey/collection/tree/2.0.0"
+                "source": "https://github.com/ramsey/collection/tree/2.1.0"
             },
-            "funding": [
-                {
-                    "url": "https://github.com/ramsey",
-                    "type": "github"
-                },
-                {
-                    "url": "https://tidelift.com/funding/github/packagist/ramsey/collection",
-                    "type": "tidelift"
-                }
-            ],
-            "time": "2022-12-31T21:50:55+00:00"
+            "time": "2025-03-02T04:48:29+00:00"
         },
         {
             "name": "ramsey/uuid",
         },
         {
             "name": "rickselby/laravel-gate-cache",
-            "version": "3.8.0",
+            "version": "3.9.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/rickselby/laravel-gate-cache.git",
-                "reference": "d19bc9aaaffa5853c531e76bc19330dca4a5c606"
+                "reference": "6403cffb8d8082ff9b2944d3f481ce53c5aa9426"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/rickselby/laravel-gate-cache/zipball/d19bc9aaaffa5853c531e76bc19330dca4a5c606",
-                "reference": "d19bc9aaaffa5853c531e76bc19330dca4a5c606",
+                "url": "https://api.github.com/repos/rickselby/laravel-gate-cache/zipball/6403cffb8d8082ff9b2944d3f481ce53c5aa9426",
+                "reference": "6403cffb8d8082ff9b2944d3f481ce53c5aa9426",
                 "shasum": ""
             },
             "require": {
-                "illuminate/auth": "10.*|11.*",
-                "illuminate/contracts": "10.*|11.*"
+                "illuminate/auth": "^10.0|^11.0|^12.0",
+                "illuminate/contracts": "^10.0|^11.0|^12.0"
             },
             "require-dev": {
                 "graham-campbell/testbench": "^6.1",
-                "phpunit/phpunit": "^10.0"
+                "phpunit/phpunit": "^10.0|^11.0"
             },
             "type": "library",
             "extra": {
             "description": "Add a per-request caching layer to Laravel's Gate",
             "support": {
                 "issues": "https://github.com/rickselby/laravel-gate-cache/issues",
-                "source": "https://github.com/rickselby/laravel-gate-cache/tree/3.8.0"
+                "source": "https://github.com/rickselby/laravel-gate-cache/tree/3.9.0"
             },
-            "time": "2024-03-14T12:27:24+00:00"
+            "time": "2025-03-04T19:22:49+00:00"
         },
         {
             "name": "rodneyrehm/plist",
             ],
             "time": "2025-01-07T12:55:42+00:00"
         },
+        {
+            "name": "theafolayan/listmonk-laravel",
+            "version": "1.3.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/theafolayan/listmonk-laravel.git",
+                "reference": "80b17cc74e857fa884fc9394d19889b5470dd7ad"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/theafolayan/listmonk-laravel/zipball/80b17cc74e857fa884fc9394d19889b5470dd7ad",
+                "reference": "80b17cc74e857fa884fc9394d19889b5470dd7ad",
+                "shasum": ""
+            },
+            "require": {
+                "guzzlehttp/guzzle": "^7.0",
+                "illuminate/support": "^8.0|^9.0|^10.0",
+                "php": "^8.0"
+            },
+            "type": "library",
+            "extra": {
+                "laravel": {
+                    "aliases": {
+                        "Listmonk": "Theafolayan\\ListmonkLaravel\\Facades\\Listmonk"
+                    },
+                    "providers": [
+                        "Theafolayan\\ListmonkLaravel\\ListmonkServiceProvider"
+                    ]
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Theafolayan\\ListmonkLaravel\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "theafolayan",
+                    "email": "theafolayan@gmail.com"
+                }
+            ],
+            "description": "A Laravel package for interacting with the Listmonk API",
+            "support": {
+                "issues": "https://github.com/theafolayan/listmonk-laravel/issues",
+                "source": "https://github.com/theafolayan/listmonk-laravel/tree/v1.3.0"
+            },
+            "time": "2025-02-04T18:37:57+00:00"
+        },
         {
             "name": "tijsverkoyen/css-to-inline-styles",
             "version": "v2.3.0",
diff --git a/config/listmonk.php b/config/listmonk.php
new file mode 100644 (file)
index 0000000..ad8a1e1
--- /dev/null
@@ -0,0 +1,7 @@
+<?php
+
+return [
+    'base_url' => env('LISTMONK_BASE_URL', 'http://localhost:9000'),
+    'api_user' => env('LISTMONK_API_USER', 'default_user'),
+    'api_token' => env('LISTMONK_API_TOKEN', 'your-token'),
+];