2023-12-21 18:49:04 +01:00
< ? php
/**
* Invoice Ninja ( https :// invoiceninja . com ) .
*
* @ link https :// github . com / invoiceninja / invoiceninja source repository
*
2024-04-12 06:15:41 +02:00
* @ copyright Copyright ( c ) 2024. Invoice Ninja LLC ( https :// invoiceninja . com )
2023-12-21 18:49:04 +01:00
*
* @ license https :// www . elastic . co / licensing / elastic - license
*/
namespace App\Jobs\Brevo ;
use App\DataMapper\Analytics\Mail\EmailBounce ;
use App\DataMapper\Analytics\Mail\EmailSpam ;
use App\Jobs\Util\SystemLogger ;
use App\Libraries\MultiDB ;
2024-01-16 07:01:54 +01:00
use App\Models\Company ;
2023-12-21 18:49:04 +01:00
use App\Models\CreditInvitation ;
use App\Models\InvoiceInvitation ;
use App\Models\PurchaseOrderInvitation ;
use App\Models\QuoteInvitation ;
use App\Models\RecurringInvoiceInvitation ;
use App\Models\SystemLog ;
2024-01-16 07:01:54 +01:00
use App\Notifications\Ninja\EmailBounceNotification ;
2023-12-21 18:49:04 +01:00
use App\Notifications\Ninja\EmailSpamNotification ;
2023-12-21 19:05:21 +01:00
use Brevo\Client\Configuration ;
use Brevo\Client\Model\GetTransacEmailContentEvents ;
2023-12-21 18:49:04 +01:00
use Illuminate\Bus\Queueable ;
use Illuminate\Contracts\Queue\ShouldQueue ;
use Illuminate\Foundation\Bus\Dispatchable ;
use Illuminate\Queue\InteractsWithQueue ;
use Illuminate\Queue\SerializesModels ;
2023-12-21 19:05:21 +01:00
use Brevo\Client\Api\TransactionalEmailsApi ;
2023-12-21 18:49:04 +01:00
use Turbo124\Beacon\Facades\LightLogs ;
class ProcessBrevoWebhook implements ShouldQueue
{
2024-06-14 09:09:44 +02:00
use Dispatchable ;
use InteractsWithQueue ;
use Queueable ;
use SerializesModels ;
2023-12-21 18:49:04 +01:00
public $tries = 1 ;
public $invitation ;
private $entity ;
private array $default_response = [
'recipients' => '' ,
'subject' => 'Message not found.' ,
'entity' => '' ,
'entity_id' => '' ,
'events' => [],
];
2024-08-22 08:45:06 +02:00
2024-06-16 06:35:56 +02:00
/** @var ?\App\Models\Company $company*/
2024-02-18 17:50:33 +01:00
private ? Company $company = null ;
2023-12-21 18:49:04 +01:00
/**
* 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 .
*
*
*/
public function handle ()
{
2023-12-28 09:24:32 +01:00
MultiDB :: findAndSetDbByCompanyKey ( $this -> request [ 'tags' ][ 0 ]);
2024-06-16 06:35:56 +02:00
2024-07-14 13:28:54 +02:00
$this -> company = Company :: query () -> where ( 'company_key' , $this -> request [ 'tags' ][ 0 ]) -> first ();
2023-12-21 18:49:04 +01:00
$this -> invitation = $this -> discoverInvitation ( $this -> request [ 'message-id' ]);
2024-02-18 17:50:33 +01:00
if ( $this -> company && $this -> request [ 'event' ] == 'spam' && config ( 'ninja.notification.slack' )) {
$this -> company -> notification ( new EmailSpamNotification ( $this -> company )) -> ninja ();
2024-01-16 07:01:54 +01:00
}
2023-12-21 18:49:04 +01:00
if ( ! $this -> invitation ) {
return ;
}
2024-01-16 07:01:54 +01:00
if ( array_key_exists ( 'reason' , $this -> request )) {
$this -> invitation -> email_error = $this -> request [ 'reason' ];
}
2023-12-21 18:49:04 +01:00
switch ( $this -> request [ 'event' ]) {
case 'delivered' :
return $this -> processDelivery ();
case 'soft_bounce' :
case 'hard_bounce' :
case 'invalid_email' :
2023-12-22 07:39:49 +01:00
case 'blocked' :
2024-01-16 07:01:54 +01:00
if ( $this -> request [ 'subject' ] == ctrans ( 'texts.confirmation_subject' )) {
2024-02-18 17:50:33 +01:00
$this -> company -> notification ( new EmailBounceNotification ( $this -> request [ 'email' ])) -> ninja ();
2024-01-16 07:01:54 +01:00
}
2023-12-21 18:49:04 +01:00
return $this -> processBounce ();
case 'spam' :
return $this -> processSpamComplaint ();
2023-12-21 19:05:21 +01:00
case 'unique_opened' :
case 'opened' :
2023-12-28 09:24:32 +01:00
case 'click' :
2023-12-21 18:49:04 +01:00
return $this -> processOpen ();
default :
# code...
break ;
}
}
// {
2023-12-28 09:24:32 +01:00
// "id": 948562,
// "email": "test@example.com",
// "message-id": "<202312211546.94160606300@smtp-relay.mailin.fr>",
// "date": "2023-12-21 18:34:42",
// "tags": [
// "gMtwiTIJtJxklXCj1OUFANgY6YYynQxV"
// ],
// "tag": "[\"gMtwiTIJtJxklXCj1OUFANgY6YYynQxV\"]",
// "event": "unique_opened",
// "subject": "Reminder: Invoice 0002 from Untitled Company",
// "sending_ip": "74.125.208.8",
// "ts": 1703180082,
// "ts_epoch": 1703180082286,
// "ts_event": 1703180082,
// "link": "",
// "sender_email": "user@example.com"
// }
2023-12-22 07:39:49 +01:00
// {
2023-12-28 09:24:32 +01:00
// "id": 948562,
// "email": "test@example.com",
// "message-id": "<202312211555.14720890391@smtp-relay.mailin.fr>",
// "date": "2023-12-21 18:34:53",
// "tags": [
// "gMtwiTIJtJxklXCj1OUFANgY6YYynQxV"
// ],
// "tag": "[\"gMtwiTIJtJxklXCj1OUFANgY6YYynQxV\"]",
// "event": "opened",
// "subject": "Reminder: Invoice 0002 from Untitled Company",
// "sending_ip": "74.125.208.8",
// "ts": 1703180093,
// "ts_epoch": 1703180093075,
// "ts_event": 1703180093,
// "link": "",
// "sender_email": "user@example.com"
// }
// {
// "id": 948562,
// "email": "paul@wer-ner.de",
// "message-id": "<202312280812.10968711117@smtp-relay.mailin.fr>",
// "date": "2023-12-28 09:20:18",
// "tags": [
// "gMtwiTIJtJxklXCj1OUFANgY6YYynQxV"
// ],
// "tag": "[\"gMtwiTIJtJxklXCj1OUFANgY6YYynQxV\"]",
// "event": "click",
// "subject": "Reminder: Invoice 0002 from Untitled Company",
// "sending_ip": "79.235.133.157",
// "ts": 1703751618,
// "ts_epoch": 1703751618831,
// "ts_event": 1703751618,
// "link": "http://localhost/client/invoice/CssCvqOcKsenMCgYJ7EUNRZwxSDGUkau",
// "sender_email": "user@example.com"
// }
2023-12-21 18:49:04 +01:00
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 ;
}
2024-01-03 22:44:12 +01:00
(
new SystemLogger (
$data ,
SystemLog :: CATEGORY_MAIL ,
SystemLog :: EVENT_MAIL_OPENED ,
SystemLog :: TYPE_WEBHOOK_RESPONSE ,
$this -> invitation -> contact -> client ,
$this -> invitation -> company
)
) -> handle ();
2023-12-21 18:49:04 +01:00
}
// {
2023-12-28 09:24:32 +01:00
// "id": 948562,
// "email": "test@example",
// "message-id": "<202312211742.12697514322@smtp-relay.mailin.fr>",
// "date": "2023-12-21 18:42:31",
// "tags": [
// "gMtwiTIJtJxklXCj1OUFANgY6YYynQxV"
// ],
// "tag": "[\"gMtwiTIJtJxklXCj1OUFANgY6YYynQxV\"]",
// "event": "delivered",
// "subject": "Reminder: Invoice 0002 from Untitled Company",
// "sending_ip": "77.32.148.26",
// "ts_event": 1703180551,
// "ts": 1703180551,
// "reason": "sent",
// "ts_epoch": 1703180551324,
// "sender_email": "user@example.com"
// }
2023-12-21 18:49:04 +01:00
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 ;
}
2024-01-03 22:44:12 +01:00
(
new SystemLogger (
$data ,
SystemLog :: CATEGORY_MAIL ,
SystemLog :: EVENT_MAIL_DELIVERY ,
SystemLog :: TYPE_WEBHOOK_RESPONSE ,
$this -> invitation -> contact -> client ,
$this -> invitation -> company
)
) -> handle ();
2023-12-21 18:49:04 +01:00
}
// {
2023-12-28 09:24:32 +01:00
// "id": 948562,
// "email": "ryder36@example.net",
// "message-id": "<202312211744.55168080257@smtp-relay.mailin.fr>",
// "date": "2023-12-21 18:44:52",
// "tags": [
// "gMtwiTIJtJxklXCj1OUFANgY6YYynQxV"
// ],
// "tag": "[\"gMtwiTIJtJxklXCj1OUFANgY6YYynQxV\"]",
// "event": "soft_bounce",
// "subject": "Reminder: Invoice 0001 from Untitled Company",
// "sending_ip": "77.32.148.26",
// "ts_event": 1703180692,
// "ts": 1703180692,
// "reason": "Unable to find MX of domain example.net",
// "ts_epoch": 1703180692382,
// "sender_email": "user@example.com"
// }
2023-12-22 07:39:49 +01:00
// {
2023-12-28 09:24:32 +01:00
// "id": 948562,
// "email": "gloria46@example.com",
// "message-id": "<202312211744.57456703957@smtp-relay.mailin.fr>",
// "date": "2023-12-21 18:44:54",
// "tags": [
// "gMtwiTIJtJxklXCj1OUFANgY6YYynQxV"
// ],
// "tag": "[\"gMtwiTIJtJxklXCj1OUFANgY6YYynQxV\"]",
// "event": "hard_bounce",
// "subject": "Reminder: Invoice 0001 from Untitled Company",
// "sending_ip": "77.32.148.25",
// "ts_event": 1703180694,
// "ts": 1703180694,
// "reason": "blocked by Admin",
// "ts_epoch": 1703180694175,
// "sender_email": "user@example.com"
// }
2023-12-22 07:39:49 +01:00
// {
2023-12-28 09:24:32 +01:00
// "event" : "invalid_email",
// "email" : "example@example.com",
// "id" : 1,
// "date" : "yyyy-mm-dd hh:i:s",
// "message-id" : "<xxx@msgid.domain>",
// "subject" : "Test subject",
// "tag" : "<defined-tag>",//json of array
// "tags": [
// "company_key"
// ],
// "sending_ip" : "xxx.xx.xxx.xx",
// "ts_epoch" : 1534486682000,
// "template_id" : 1,
// "sender_email": "user@example.com",
// }
// {
// "id": 948562,
// "email": "neoma.langosh@example.com",
// "message-id": "<202312211745.65538701430@smtp-relay.mailin.fr>",
// "date": "2023-12-21 18:45:48",
// "tags": [
// "gMtwiTIJtJxklXCj1OUFANgY6YYynQxV"
// ],
// "tag": "[\"gMtwiTIJtJxklXCj1OUFANgY6YYynQxV\"]",
// "event": "blocked",
// "subject": "Reminder: Invoice 0001 from Untitled Company",
// "ts_event": 1703180748,
// "ts": 1703180748,
// "reason": "blocked : due to blacklist user",
// "ts_epoch": 1703180748987,
// "sender_email": "user@example.com"
// }
2023-12-21 18:49:04 +01:00
private function processBounce ()
{
$this -> invitation -> email_status = 'bounced' ;
$this -> invitation -> save ();
$bounce = new EmailBounce (
2023-12-22 07:39:49 +01:00
$this -> request [ 'tags' ][ 0 ],
$this -> request [ 'sender_email' ], // TODO: @turbo124 is this the recipent?
2023-12-21 18:49:04 +01:00
$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();
}
// {
2023-12-28 09:24:32 +01:00
// "event" : "spam",
// "email" : "example@example.com",
// "id" : 1,
// "date" : "yyyy-mm-dd hh:i:s",
// "message-id" : "<xxx@msgid.domain>",
// "tag" : "<defined-tag>",//json of array
// "tags": [
// "company_key"
// ],
// "sending_ip" : "xxx.xx.xxx.xx",
// "sender_email": "user@example.com",
2023-12-21 18:49:04 +01:00
// }
private function processSpamComplaint ()
{
$this -> invitation -> email_status = 'spam' ;
$this -> invitation -> save ();
$spam = new EmailSpam (
2023-12-22 07:39:49 +01:00
$this -> request [ 'tags' ][ 0 ],
$this -> request [ 'sender_email' ],
2023-12-21 18:49:04 +01:00
$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 ();
}
}
2023-12-28 09:24:32 +01:00
private function discoverInvitation ( string $message_id )
2023-12-21 18:49:04 +01:00
{
$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 )
{
2024-03-24 10:53:20 +01:00
$brevo_secret = ! empty ( $this -> company -> settings -> brevo_secret ) ? $this -> company -> settings -> brevo_secret : config ( 'services.brevo.secret' );
2024-02-18 17:50:33 +01:00
$brevo = new TransactionalEmailsApi ( null , Configuration :: getDefaultConfiguration () -> setApiKey ( 'api-key' , $brevo_secret ));
$messageDetail = $brevo -> getTransacEmailContent ( $message_id );
2023-12-21 18:49:04 +01:00
return $messageDetail ;
}
public function getBounceId ( string $message_id ) : ? int
{
$messageDetail = $this -> getRawMessage ( $message_id );
2023-12-21 19:05:21 +01:00
$event = collect ( $messageDetail -> getEvents ()) -> first ( function ( $event ) {
2023-12-21 18:49:04 +01:00
return $event ? -> Details ? -> BounceID ? ? false ;
});
return $event ? -> Details ? -> BounceID ? ? null ;
}
2023-12-21 19:05:21 +01:00
// TODO
2023-12-21 18:49:04 +01:00
private function fetchMessage () : array
{
if ( strlen ( $this -> request [ 'message-id' ]) < 1 ) {
return $this -> default_response ;
}
try {
2023-12-21 19:05:21 +01:00
$messageDetail = $this -> getRawMessage ( $this -> request [ 'message-id' ]);
2023-12-21 18:49:04 +01:00
2024-01-15 11:42:08 +01:00
$recipient = array_key_exists ( " email " , $this -> request ) ? $this -> request [ " email " ] : '' ;
$server_ip = array_key_exists ( " sending_ip " , $this -> request ) ? $this -> request [ " sending_ip " ] : '' ;
$delivery_message = array_key_exists ( " reason " , $this -> request ) ? $this -> request [ " reason " ] : '' ;
2023-12-21 19:05:21 +01:00
$subject = $messageDetail -> getSubject () ? ? '' ;
2023-12-21 18:49:04 +01:00
2024-01-15 11:42:08 +01:00
$events = collect ( $messageDetail -> getEvents ()) -> map ( function ( GetTransacEmailContentEvents $event ) use ( $recipient , $server_ip , $delivery_message ) { // @turbo124 event does only contain name & time property, how to handle transformation?!
2023-12-21 18:49:04 +01:00
return [
2024-01-15 11:42:08 +01:00
'bounce_id' => '' ,
'recipient' => $recipient ,
'status' => $event -> name ? ? '' ,
'delivery_message' => $delivery_message , // TODO: @turbo124 this results in all cases for the history in the string, which may be incorrect
'server' => '' ,
'server_ip' => $server_ip ,
2024-01-03 22:44:12 +01:00
'date' => \Carbon\Carbon :: parse ( $event -> getTime ()) -> format ( 'Y-m-d H:i:s' ) ? ? '' ,
2023-12-21 18:49:04 +01:00
];
}) -> toArray ();
return [
2024-01-15 11:42:08 +01:00
'recipients' => $recipient ,
2023-12-21 18:49:04 +01:00
'subject' => $subject ,
'entity' => $this -> entity ? ? '' ,
'entity_id' => $this -> invitation -> { $this -> entity } -> hashed_id ? ? '' ,
'events' => $events ,
];
} catch ( \Exception $e ) {
return $this -> default_response ;
}
}
}