diff --git a/app/Http/Controllers/BrevoController.php b/app/Http/Controllers/BrevoController.php new file mode 100644 index 000000000000..25c328484363 --- /dev/null +++ b/app/Http/Controllers/BrevoController.php @@ -0,0 +1,68 @@ +all())->delay(10); + + return response()->json(['message' => 'Success'], 200); + } +} diff --git a/app/Jobs/Brevo/ProcessBrevoWebhook.php b/app/Jobs/Brevo/ProcessBrevoWebhook.php new file mode 100644 index 000000000000..b8d8f705d216 --- /dev/null +++ b/app/Jobs/Brevo/ProcessBrevoWebhook.php @@ -0,0 +1,405 @@ + '', + 'subject' => 'Message not found.', + 'entity' => '', + 'entity_id' => '', + 'events' => [], + ]; + + /** + * Create a new job instance. + * + */ + public function __construct(private array $request) + { + } + + private function getSystemLog(string $message_id): ?SystemLog + { + return SystemLog::query() + ->where('company_id', $this->invitation->company_id) + ->where('type_id', SystemLog::TYPE_WEBHOOK_RESPONSE) + ->whereJsonContains('log', ['message-id' => $message_id]) + ->orderBy('id', 'desc') + ->first(); + + } + + private function updateSystemLog(SystemLog $system_log, array $data): void + { + $system_log->log = $data; + $system_log->save(); + } + + /** + * Execute the job. + * + * + * @return void + */ + public function handle() + { + MultiDB::findAndSetDbByCompanyKey($this->request['tag']); + + $this->invitation = $this->discoverInvitation($this->request['message-id']); + + if (!$this->invitation) { + return; + } + + // if (array_key_exists('Details', $this->request)) { + // $this->invitation->email_error = $this->request['Details']; + // } // no details, when error occured + + switch ($this->request['event']) { + case 'delivered': + return $this->processDelivery(); + case 'soft_bounce': + case 'hard_bounce': + case 'invalid_email': + return $this->processBounce(); + case 'spam': + return $this->processSpamComplaint(); + case 'Open': + return $this->processOpen(); + default: + # code... + break; + } + } + + // { + // "Metadata": { + // "example": "value", + // "example_2": "value" + // }, + // "RecordType": "Open", + // "FirstOpen": true, + // "Client": { + // "Name": "Chrome 35.0.1916.153", + // "Company": "Google", + // "Family": "Chrome" + // }, + // "OS": { + // "Name": "OS X 10.7 Lion", + // "Company": "Apple Computer, Inc.", + // "Family": "OS X 10" + // }, + // "Platform": "WebMail", + // "UserAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.1916.153 Safari/537.36", + // "ReadSeconds": 5, + // "Geo": { + // "CountryISOCode": "RS", + // "Country": "Serbia", + // "RegionISOCode": "VO", + // "Region": "Autonomna Pokrajina Vojvodina", + // "City": "Novi Sad", + // "Zip": "21000", + // "Coords": "45.2517,19.8369", + // "IP": "188.2.95.4" + // }, + // "MessageID": "00000000-0000-0000-0000-000000000000", + // "MessageStream": "outbound", + // "ReceivedAt": "2022-02-06T06:37:48Z", + // "Tag": "welcome-email", + // "Recipient": "john@example.com" + // } + + private function processOpen() + { + $this->invitation->opened_date = now(); + $this->invitation->save(); + + $data = array_merge($this->request, ['history' => $this->fetchMessage()]); + + $sl = $this->getSystemLog($this->request['message-id']); + + if ($sl) { + $this->updateSystemLog($sl, $data); + return; + } + + (new SystemLogger( + $data, + SystemLog::CATEGORY_MAIL, + SystemLog::EVENT_MAIL_OPENED, + SystemLog::TYPE_WEBHOOK_RESPONSE, + $this->invitation->contact->client, + $this->invitation->company + ))->handle(); + } + + // { + // "event" : "delivered", + // "email" : "example@example.com", + // "id" : 1, + // "date" : "yyyy-m-d h:i:s", + // "message-id" : "", + // "subject" : "Test subject", + // "tag" : "", + // "sending_ip" : "xxx.xx.xxx.xx", + // "ts_epoch" : 1534486682000, + // "template_id" : 1 + // } + private function processDelivery() + { + $this->invitation->email_status = 'delivered'; + $this->invitation->save(); + + $data = array_merge($this->request, ['history' => $this->fetchMessage()]); + + $sl = $this->getSystemLog($this->request['message-id']); + + if ($sl) { + $this->updateSystemLog($sl, $data); + return; + } + + (new SystemLogger( + $data, + SystemLog::CATEGORY_MAIL, + SystemLog::EVENT_MAIL_DELIVERY, + SystemLog::TYPE_WEBHOOK_RESPONSE, + $this->invitation->contact->client, + $this->invitation->company + ))->handle(); + } + + // { + // "event" : "soft_bounce", + // "email" : "example@example.com", + // "id" : 1, + // "date" : "yyyy-mm-dd hh:i:s", + // "message-id" : "", + // "reason" : "", + // "tag" : "", + // "sending_ip" : "xxx.xx.xxx.xx", + // "ts_epoch" : 1534486682000, + // "template_id" : 1 + // } + // { + // "event" : "hard_bounce", + // "email" : "example@example.com", + // "id" : 1, + // "date" : "yyyy-mm-dd hh:i:s", + // "message-id" : "", + // "reason" : "", + // "tag" : "", + // "sending_ip" : "xxx.xx.xxx.xx", + // "ts_epoch" : 1534486682000, + // "template_id" : 1 + // } + // { + // "event" : "invalid_email", + // "email" : "example@example.com", + // "id" : 1, + // "date" : "yyyy-mm-dd hh:i:s", + // "message-id" : "", + // "subject" : "Test subject", + // "tag" : "", + // "sending_ip" : "xxx.xx.xxx.xx", + // "ts_epoch" : 1534486682000, + // "template_id" : 1 + // } + + private function processBounce() + { + $this->invitation->email_status = 'bounced'; + $this->invitation->save(); + + $bounce = new EmailBounce( + $this->request['tag'], + $this->request['From'], + $this->request['message-id'] + ); + + LightLogs::create($bounce)->send(); + + $data = array_merge($this->request, ['history' => $this->fetchMessage()]); + + $sl = $this->getSystemLog($this->request['message-id']); + + if ($sl) { + $this->updateSystemLog($sl, $data); + return; + } + + (new SystemLogger($data, SystemLog::CATEGORY_MAIL, SystemLog::EVENT_MAIL_BOUNCED, SystemLog::TYPE_WEBHOOK_RESPONSE, $this->invitation->contact->client, $this->invitation->company))->handle(); + + // if(config('ninja.notification.slack')) + // $this->invitation->company->notification(new EmailBounceNotification($this->invitation->company->account))->ninja(); + } + + // { + // "event" : "spam", + // "email" : "example@example.com", + // "id" : 1, + // "date" : "yyyy-mm-dd hh:i:s", + // "message-id" : "", + // "tag" : "", + // "sending_ip" : "xxx.xx.xxx.xx", + // } + private function processSpamComplaint() + { + $this->invitation->email_status = 'spam'; + $this->invitation->save(); + + $spam = new EmailSpam( + $this->request['tag'], + $this->request['From'], + $this->request['message-id'] + ); + + LightLogs::create($spam)->send(); + + $data = array_merge($this->request, ['history' => $this->fetchMessage()]); + + $sl = $this->getSystemLog($this->request['message-id']); + + if ($sl) { + $this->updateSystemLog($sl, $data); + return; + } + + (new SystemLogger($data, SystemLog::CATEGORY_MAIL, SystemLog::EVENT_MAIL_SPAM_COMPLAINT, SystemLog::TYPE_WEBHOOK_RESPONSE, $this->invitation->contact->client, $this->invitation->company))->handle(); + + if (config('ninja.notification.slack')) { + $this->invitation->company->notification(new EmailSpamNotification($this->invitation->company->account))->ninja(); + } + } + + private function discoverInvitation($message_id) + { + $invitation = false; + + if ($invitation = InvoiceInvitation::where('message_id', $message_id)->first()) { + $this->entity = 'invoice'; + return $invitation; + } elseif ($invitation = QuoteInvitation::where('message_id', $message_id)->first()) { + $this->entity = 'quote'; + return $invitation; + } elseif ($invitation = RecurringInvoiceInvitation::where('message_id', $message_id)->first()) { + $this->entity = 'recurring_invoice'; + return $invitation; + } elseif ($invitation = CreditInvitation::where('message_id', $message_id)->first()) { + $this->entity = 'credit'; + return $invitation; + } elseif ($invitation = PurchaseOrderInvitation::where('message_id', $message_id)->first()) { + $this->entity = 'purchase_order'; + return $invitation; + } else { + return $invitation; + } + } + + public function getRawMessage(string $message_id) + { + + $Brevo = new BrevoClient(config('services.brevo.key')); + $messageDetail = $Brevo->getOutboundMessageDetails($message_id); + return $messageDetail; + + } + + + public function getBounceId(string $message_id): ?int + { + + $messageDetail = $this->getRawMessage($message_id); + + + $event = collect($messageDetail->messageevents)->first(function ($event) { + + return $event?->Details?->BounceID ?? false; + + }); + + return $event?->Details?->BounceID ?? null; + + } + + private function fetchMessage(): array + { + if (strlen($this->request['message-id']) < 1) { + return $this->default_response; + } + + try { + + $Brevo = new BrevoClient(config('services.brevo.key')); + $messageDetail = $Brevo->getOutboundMessageDetails($this->request['message-id']); + + $recipients = collect($messageDetail['recipients'])->flatten()->implode(','); + $subject = $messageDetail->subject ?? ''; + + $events = collect($messageDetail->messageevents)->map(function ($event) { + + return [ + 'bounce_id' => $event?->Details?->BounceID ?? '', + 'recipient' => $event->Recipient ?? '', + 'status' => $event->Type ?? '', + 'delivery_message' => $event->Details->DeliveryMessage ?? $event->Details->Summary ?? '', + 'server' => $event->Details->DestinationServer ?? '', + 'server_ip' => $event->Details->DestinationIP ?? '', + 'date' => \Carbon\Carbon::parse($event->ReceivedAt)->format('Y-m-d H:i:s') ?? '', + ]; + + })->toArray(); + + return [ + 'recipients' => $recipients, + 'subject' => $subject, + 'entity' => $this->entity ?? '', + 'entity_id' => $this->invitation->{$this->entity}->hashed_id ?? '', + 'events' => $events, + ]; + + } catch (\Exception $e) { + + return $this->default_response; + + } + } +} diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index 1767ac11b1fb..d1d55daf8473 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -124,18 +124,18 @@ class AppServiceProvider extends ServiceProvider ) ); }); - Mailer::macro('brevo_config', function (string $key) { - // @phpstan-ignore /** @phpstan-ignore-next-line **/ - Mail::setSymfonyTransport((new BrevoTransportFactory)->create( - new Dsn( - 'brevo+api', - 'default', - $key - ) - )); + // Mailer::macro('brevo_config', function (string $key) { + // // @phpstan-ignore /** @phpstan-ignore-next-line **/ + // Mail::setSymfonyTransport((new BrevoTransportFactory)->create( + // new Dsn( + // 'brevo+api', + // 'default', + // $key + // ) + // )); - return $this; - }); + // return $this; + // }); } diff --git a/app/Services/Email/Email.php b/app/Services/Email/Email.php index 5da97944e91e..a87a853d280d 100644 --- a/app/Services/Email/Email.php +++ b/app/Services/Email/Email.php @@ -235,8 +235,6 @@ class Email implements ShouldQueue public function email() { // $this->setMailDriver(); - Log::info("mail(): " . $this->mailer); - Log::info($this->client_brevo_secret); /* Init the mailer*/ $mailer = Mail::mailer($this->mailer); @@ -461,8 +459,6 @@ class Email implements ShouldQueue */ private function setMailDriver(): self { - Log::info("E-Mail Sending Method (setMailDriver): " . $this->email_object->settings->email_sending_method); - Log::info(json_encode($this->email_object->settings)); switch ($this->email_object->settings->email_sending_method) { case 'default': $this->mailer = config('mail.default'); diff --git a/routes/api.php b/routes/api.php index 4d648054e298..0d60406a0b75 100644 --- a/routes/api.php +++ b/routes/api.php @@ -20,6 +20,7 @@ use App\Http\Controllers\BankIntegrationController; use App\Http\Controllers\BankTransactionController; use App\Http\Controllers\BankTransactionRuleController; use App\Http\Controllers\BaseController; +use App\Http\Controllers\BrevoController; use App\Http\Controllers\ChartController; use App\Http\Controllers\ClientController; use App\Http\Controllers\ClientGatewayTokenController; @@ -120,7 +121,7 @@ Route::group(['middleware' => ['throttle:api', 'api_secret_check']], function () Route::post('api/v1/oauth_login', [LoginController::class, 'oauthApiLogin']); }); -Route::group(['middleware' => ['throttle:login','api_secret_check','email_db']], function () { +Route::group(['middleware' => ['throttle:login', 'api_secret_check', 'email_db']], function () { Route::post('api/v1/login', [LoginController::class, 'apiLogin'])->name('login.submit'); Route::post('api/v1/reset_password', [ForgotPasswordController::class, 'sendResetLinkEmail']); }); @@ -324,7 +325,7 @@ Route::group(['middleware' => ['throttle:api', 'api_db', 'token_auth', 'locale'] Route::post('reports/user_sales_report', UserSalesReportController::class); Route::post('reports/preview/{hash}', ReportPreviewController::class); Route::post('exports/preview/{hash}', ReportExportController::class); - + Route::post('templates/preview/{hash}', TemplatePreviewController::class); Route::post('search', SearchController::class); @@ -414,6 +415,7 @@ Route::match(['get', 'post'], 'payment_notification_webhook/{company_key}/{compa ->name('payment_notification_webhook'); Route::post('api/v1/postmark_webhook', [PostMarkController::class, 'webhook'])->middleware('throttle:1000,1'); +Route::post('api/v1/brevo_webhook', [BrevoController::class, 'webhook'])->middleware('throttle:1000,1'); Route::get('token_hash_router', [OneTimeTokenController::class, 'router'])->middleware('throttle:500,1'); Route::get('webcron', [WebCronController::class, 'index'])->middleware('throttle:100,1'); Route::post('api/v1/get_migration_account', [HostedMigrationController::class, 'getAccount'])->middleware('guest')->middleware('throttle:100,1');