2023-12-21 18:49:04 +01:00
< ? php
/**
* Invoice Ninja ( https :// invoiceninja . com ) .
*
* @ link https :// github . com / invoiceninja / invoiceninja source repository
*
* @ copyright Copyright ( c ) 2023. Invoice Ninja LLC ( https :// invoiceninja . com )
*
* @ 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 ;
use App\Models\CreditInvitation ;
use App\Models\InvoiceInvitation ;
use App\Models\PurchaseOrderInvitation ;
use App\Models\QuoteInvitation ;
use App\Models\RecurringInvoiceInvitation ;
use App\Models\SystemLog ;
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
{
use Dispatchable , InteractsWithQueue , Queueable , SerializesModels ;
public $tries = 1 ;
public $invitation ;
private $entity ;
private array $default_response = [
'recipients' => '' ,
'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' :
2023-12-22 07:39:49 +01:00
case 'blocked' :
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-21 18:49:04 +01:00
return $this -> processOpen ();
default :
# code...
break ;
}
}
// {
2023-12-22 07:39:49 +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"
// }
// {
// "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"
// }
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 ;
}
( new SystemLogger (
$data ,
SystemLog :: CATEGORY_MAIL ,
SystemLog :: EVENT_MAIL_OPENED ,
SystemLog :: TYPE_WEBHOOK_RESPONSE ,
$this -> invitation -> contact -> client ,
$this -> invitation -> company
)) -> handle ();
}
// {
2023-12-22 07:39:49 +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 ;
}
( new SystemLogger (
$data ,
SystemLog :: CATEGORY_MAIL ,
SystemLog :: EVENT_MAIL_DELIVERY ,
SystemLog :: TYPE_WEBHOOK_RESPONSE ,
$this -> invitation -> contact -> client ,
$this -> invitation -> company
)) -> handle ();
}
// {
2023-12-22 07:39:49 +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"
// }
// {
// "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-21 18:49:04 +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",
2023-12-22 07:39:49 +01:00
// "tag" : "<defined-tag>",//json of array
// "tags": [
// "company_key"
// ],
2023-12-21 18:49:04 +01:00
// "sending_ip" : "xxx.xx.xxx.xx",
// "ts_epoch" : 1534486682000,
2023-12-22 07:39:49 +01:00
// "template_id" : 1,
// "sender_email": "user@example.com",
2023-12-21 18:49:04 +01:00
// }
2023-12-22 07:39:49 +01:00
// {
// "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();
}
// {
// "event" : "spam",
// "email" : "example@example.com",
// "id" : 1,
// "date" : "yyyy-mm-dd hh:i:s",
// "message-id" : "<xxx@msgid.domain>",
2023-12-22 07:39:49 +01:00
// "tag" : "<defined-tag>",//json of array
// "tags": [
// "company_key"
// ],
2023-12-21 18:49:04 +01:00
// "sending_ip" : "xxx.xx.xxx.xx",
2023-12-22 07:39:49 +01:00
// "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 ();
}
}
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 )
{
2023-12-21 19:05:21 +01:00
$Brevo = new TransactionalEmailsApi ( null , Configuration :: getDefaultConfiguration () -> setApiKey ( 'api-key' , config ( 'services.brevo.key' )));
$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
2023-12-22 07:39:49 +01:00
$recipients = $this -> request [ " email " ];
2023-12-21 19:05:21 +01:00
$subject = $messageDetail -> getSubject () ? ? '' ;
2023-12-21 18:49:04 +01:00
2023-12-22 07:39:49 +01:00
$events = collect ( $messageDetail -> getEvents ()) -> map ( function ( GetTransacEmailContentEvents $event ) { // @turbo124 event does only contain name & time property, how to handle transformation?!
2023-12-21 18:49:04 +01:00
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 ;
}
}
}