├── .gitignore ├── README.md ├── _config.yml ├── composer.json ├── config └── mail-tracker.php ├── migrations ├── 2016_03_01_193027_create_sent_emails_table.php ├── 2016_09_07_193027_create_sent_emails_Url_Clicked_table.php └── 2016_11_10_213551_add-message-id-to-sent-emails-table.php └── src ├── Events ├── EmailSentEvent.php ├── LinkClickedEvent.php ├── PermanentBouncedMessageEvent.php └── ViewEmailEvent.php ├── Facades └── MailTracker.php ├── MailTracker.php ├── MailTrackerController.php ├── MailTrackerServiceProvider.php ├── Model ├── SentEmail.php └── SentEmailUrlClicked.php └── SNSController.php /.gitignore: -------------------------------------------------------------------------------- 1 | /vendor 2 | .idea -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MailTracker 2 | 3 | MailTracker will hook into all outgoing emails from Laravel and inject a tracking code into it. It will also store the rendered email in the database. There is also an interface to view sent emails. 4 | 5 | ## NOTE: For Laravel < 5.3.23 6 | 7 | ## Install (Laravel) 8 | 9 | Via Composer 10 | 11 | ``` bash 12 | $ composer require jeylabs/mail-tracker 13 | ``` 14 | 15 | Add the following to the providers array in config/app.php: 16 | 17 | ``` php 18 | Jeylabs\MailTracker\MailTrackerServiceProvider::class, 19 | ``` 20 | 21 | Publish the config file and migration 22 | ``` bash 23 | $ php artisan vendor:publish 24 | ``` 25 | 26 | Run the migration 27 | ``` bash 28 | $ php artisan migrate 29 | ``` 30 | 31 | ## Usage 32 | 33 | Once installed, all outgoing mail will be logged to the database. The following config options are available in config/mail-tracker.php: 34 | 35 | * **track-open**: set to true to inject a tracking pixel into all outgoing html emails. 36 | * **track-click**: set to true to rewrite all anchor href links to include a tracking link. The link will take the user back to your website which will then redirect them to the final destination after logging the click. 37 | * **expire-days**: How long in days that an email should be retained in your database. If you are sending a lot of mail, you probably want it to eventually expire. Set it to zero to never purge old emails from the database. 38 | * **route**: The route information for the tracking URLs. Set the prefix and middlware as desired. 39 | * **date-format**: You can define the format to show dates in the Admin Panel. 40 | 41 | ## Events 42 | 43 | When an email is sent, viewed, or a link is clicked, its tracking information is counted in the database using the Jeylabs\MailTracker\Model\SentEmail model. You may want to do additional processing on these events, so an event is fired in these cases: 44 | 45 | * Jeylabs\MailTracker\Events\EmailSentEvent 46 | * Jeylabs\MailTracker\Events\ViewEmailEvent 47 | * Jeylabs\MailTracker\Events\LinkClickedEvent 48 | 49 | If you are using the Amazon SNS notification system, an event is fired when you receive a permanent bounce. You may want to mark the email as bad or remove it from your database. 50 | 51 | * Jeylabs\MailTracker\Events\PermanentBouncedMessageEvent 52 | 53 | To install an event listener, you will want to create a file like the following: 54 | 55 | ``` php 56 | sent_email... 83 | } 84 | } 85 | ``` 86 | 87 | ``` php 88 | email_address... 115 | } 116 | } 117 | ``` 118 | 119 | Then you must register the event in your \App\Providers\EventServiceProvider $listen array: 120 | 121 | ``` php 122 | /** 123 | * The event listener mappings for the application. 124 | * 125 | * @var array 126 | */ 127 | protected $listen = [ 128 | 'Jeylabs\MailTracker\Events\ViewEmailEvent' => [ 129 | 'App\Listeners\EmailViewed', 130 | ], 131 | 'Jeylabs\MailTracker\Events\PermanentBouncedMessageEvent' => [ 132 | 'App\Listeners\BouncedEmail', 133 | ], 134 | ]; 135 | ``` 136 | 137 | ### Passing data to the event listeners 138 | 139 | Often times you may need to link a sent email to another model. The best way to handle this is to add a header to your outgoing email that you can retrieve in your event listener. Here is an example: 140 | 141 | ``` php 142 | /** 143 | * Send an email and do processing on a model with the email 144 | */ 145 | \Mail::send('email.test', [], function ($message) use($email, $subject, $name, $model) { 146 | $message->from('info@jeylabs.com', 'From Name'); 147 | $message->sender('info@jeylabs.com', 'Sender Name'); 148 | $message->to($email, $name); 149 | $message->subject($subject); 150 | 151 | // Create a custom header that we can later retrieve 152 | $message->getHeaders()->addTextHeader('X-Model-ID',$model->id); 153 | }); 154 | ``` 155 | 156 | and then in your event listener: 157 | 158 | ``` 159 | public function handle(EmailSentEvent $event) 160 | { 161 | $tracker = $event->sent_email; 162 | $model_id = $event->getHeader('X-Model-ID'); 163 | $model = Model::find($model_id); 164 | // Perform your tracking/linking tasks on $model knowing the SentEmail object 165 | } 166 | ``` 167 | 168 | Note that the headers you are attaching to the email are actually going out with the message, so do not store any data that you wouldn't want to expose to your email recipients. 169 | 170 | ## Amazon SES features 171 | 172 | If you use Amazon SES, you can add some additional information to your tracking. To set up the SES callbacks, first set up SES notifications under your domain in the SES control panel. Then subscribe to the topic by going to the admin panel of the notification topic and creating a subscription for the URL you copied from the admin page. The system should immediately respond to the subscription request. If you like, you can use multiple subscriptions (i.e. one for delivery, one for bounces). See above for events that are fired on a failed message. 173 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-cayman -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jeylabs/mail-tracker", 3 | "description": "MailTracker will hook into all outgoing emails from Laravel and inject a tracking code into it. It will also store the rendered email in the database. There is also an interface to view sent emails.", 4 | "keywords": ["jeylabs", "laravel", "mail-tracker"], 5 | "license": "MIT", 6 | "type": "Package", 7 | "authors": [ 8 | { 9 | "name": "jeylabs", 10 | "email": "ratheep.ceymplon@jeylabs.com", 11 | "homepage": "https://jeylabs.com", 12 | "role": "Developer" 13 | } 14 | ], 15 | "require": { 16 | "laravel/framework": ">=5.3.23", 17 | "guzzlehttp/guzzle": "^5.3.1|^6.2.1", 18 | "illuminate/support": "~5.1", 19 | "aws/aws-sdk-php-laravel": "~3.0", 20 | "aws/aws-php-sns-message-validator": "^1.1", 21 | "php" : "~5.5|~7.0" 22 | }, 23 | 24 | "autoload": { 25 | "psr-4": { 26 | "Jeylabs\\MailTracker\\": "src" 27 | } 28 | }, 29 | "minimum-stability": "stable", 30 | "prefer-stable": true 31 | } 32 | -------------------------------------------------------------------------------- /config/mail-tracker.php: -------------------------------------------------------------------------------- 1 | true, 4 | 'track-click' => true, 5 | 'expire-days' => 60, 6 | 'emails-per-page' => 30, 7 | 'date-format' => 'm/d/Y g:i a', 8 | 'route' => [ 9 | 'prefix' => 'mail-track', 10 | 'middleware' => ['web'], 11 | ], 12 | 'custom-inject' => false, 13 | 'custom-inject-html' => '' 14 | ]; -------------------------------------------------------------------------------- /migrations/2016_03_01_193027_create_sent_emails_table.php: -------------------------------------------------------------------------------- 1 | increments('id'); 17 | $table->char('hash',32)->unique(); 18 | $table->text('headers'); 19 | $table->string('sender'); 20 | $table->string('recipient'); 21 | $table->string('subject'); 22 | $table->integer('opens'); 23 | $table->integer('clicks'); 24 | $table->timestamps(); 25 | }); 26 | } 27 | 28 | /** 29 | * Reverse the migrations. 30 | * 31 | * @return void 32 | */ 33 | public function down() 34 | { 35 | Schema::drop('sent_emails'); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /migrations/2016_09_07_193027_create_sent_emails_Url_Clicked_table.php: -------------------------------------------------------------------------------- 1 | increments('id'); 17 | $table->integer('sent_email_id')->unsigned(); 18 | $table->foreign('sent_email_id')->references('id')->on('sent_emails')->onDelete('cascade'); 19 | $table->string('url'); 20 | $table->char('hash',32); 21 | $table->integer('clicks')->default('1'); 22 | $table->timestamps(); 23 | }); 24 | } 25 | 26 | /** 27 | * Reverse the migrations. 28 | * 29 | * @return void 30 | */ 31 | public function down() 32 | { 33 | Schema::drop('sent_emails_url_clicked'); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /migrations/2016_11_10_213551_add-message-id-to-sent-emails-table.php: -------------------------------------------------------------------------------- 1 | string('message_id')->nullable()->unique(); 18 | $table->text('meta'); 19 | }); 20 | } 21 | 22 | /** 23 | * Reverse the migrations. 24 | * 25 | * @return void 26 | */ 27 | public function down() 28 | { 29 | Schema::table('sent_emails', function(Blueprint $table) { 30 | $table->dropColumn('message_id'); 31 | $table->dropColumn('meta'); 32 | }); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/Events/EmailSentEvent.php: -------------------------------------------------------------------------------- 1 | sent_email = $sent_email; 23 | } 24 | } -------------------------------------------------------------------------------- /src/Events/LinkClickedEvent.php: -------------------------------------------------------------------------------- 1 | sent_email = $sent_email; 23 | } 24 | } -------------------------------------------------------------------------------- /src/Events/PermanentBouncedMessageEvent.php: -------------------------------------------------------------------------------- 1 | email_address = $email_address; 22 | } 23 | } -------------------------------------------------------------------------------- /src/Events/ViewEmailEvent.php: -------------------------------------------------------------------------------- 1 | sent_email = $sent_email; 22 | } 23 | } -------------------------------------------------------------------------------- /src/Facades/MailTracker.php: -------------------------------------------------------------------------------- 1 | getMessage(); 20 | 21 | // Create the trackers 22 | $this->createTrackers($message); 23 | 24 | // Purge old records 25 | $this->purgeOldRecords(); 26 | } 27 | 28 | public function sendPerformed(\Swift_Events_SendEvent $event) 29 | { 30 | // If this was sent through SES, retrieve the data 31 | if(config('mail.driver') == 'ses') { 32 | $message = $event->getMessage(); 33 | $this->updateSesMessageId($message); 34 | } 35 | } 36 | 37 | protected function updateSesMessageId($message) 38 | { 39 | // Get the SentEmail object 40 | $headers = $message->getHeaders(); 41 | $hash = $headers->get('X-Mailer-Hash')->getFieldBody(); 42 | $sent_email = SentEmail::where('hash',$hash)->first(); 43 | 44 | // Get info about the 45 | $sent_email->message_id = $headers->get('X-SES-Message-ID')->getFieldBody(); 46 | $sent_email->save(); 47 | } 48 | 49 | protected function addTrackers($html, $hash) 50 | { 51 | if(config('mail-tracker.track-open')) { 52 | $html = $this->injectTrackingPixel($html, $hash); 53 | } 54 | if(config('mail-tracker.track-click')) { 55 | $html = $this->injectLinkTracker($html, $hash); 56 | } 57 | if (config('mail-tracker.custom-inject')){ 58 | $customInject = config('mail-tracker.custom-inject-html'); 59 | $html = $this->customInject($html, $customInject); 60 | } 61 | return $html; 62 | } 63 | 64 | protected function injectTrackingPixel($html, $hash) 65 | { 66 | // Append the tracking url 67 | $tracking_pixel = ''; 68 | 69 | $linebreak = str_random(32); 70 | $html = str_replace("\n",$linebreak,$html); 71 | 72 | if(preg_match("/^(.*]*>)(.*)$/", $html, $matches)) { 73 | $html = $matches[1].$tracking_pixel.$matches[2]; 74 | } else { 75 | $html = $html . $tracking_pixel; 76 | } 77 | $html = str_replace($linebreak,"\n",$html); 78 | 79 | return $html; 80 | } 81 | 82 | protected function customInject($html, $injectHtml){ 83 | // Append the custom html 84 | $linebreak = str_random(32); 85 | $html = str_replace("\n",$linebreak,$html); 86 | 87 | if(preg_match("/^(.*]*>)(.*)$/", $html, $matches)) { 88 | $html = $matches[1].$injectHtml.$matches[2]; 89 | } else { 90 | $html = $html . $injectHtml; 91 | } 92 | $html = str_replace($linebreak,"\n",$html); 93 | return $html; 94 | } 95 | 96 | protected function injectLinkTracker($html, $hash) 97 | { 98 | $this->hash = $hash; 99 | 100 | $html = preg_replace_callback("/(]*href=['\"])([^'\"]*)/", 101 | array($this, 'inject_link_callback'), 102 | $html); 103 | 104 | return $html; 105 | } 106 | 107 | protected function inject_link_callback($matches) 108 | { 109 | if (empty($matches[2])) { 110 | $url = app()->make('url')->to('/'); 111 | } else { 112 | $url = $matches[2]; 113 | } 114 | 115 | return $matches[1].route('mailTracker_l', 116 | [ 117 | MailTracker::hash_url($url), 118 | $this->hash 119 | ]); 120 | } 121 | 122 | static public function hash_url($url) 123 | { 124 | // Replace "/" with "$" 125 | return str_replace("/","$",base64_encode($url)); 126 | } 127 | 128 | /** 129 | * Create the trackers 130 | * 131 | * @param Swift_Mime_Message $message 132 | * @return void 133 | */ 134 | protected function createTrackers($message) 135 | { 136 | foreach($message->getTo() as $to_email=>$to_name) { 137 | foreach($message->getFrom() as $from_email=>$from_name) { 138 | $headers = $message->getHeaders(); 139 | $hash = str_random(32); 140 | $headers->addTextHeader('X-Mailer-Hash',$hash); 141 | $subject = $message->getSubject(); 142 | 143 | // $original_content = $message->getBody(); 144 | 145 | if ($message->getContentType() === 'text/html' || 146 | $message->getContentType() === 'multipart/mixed' || 147 | ($message->getContentType() === 'multipart/alternative' && $message->getBody()) 148 | ) { 149 | $message->setBody($this->addTrackers($message->getBody(), $hash)); 150 | } 151 | 152 | foreach ($message->getChildren() as $part) { 153 | if (strpos($part->getContentType(), 'text/html') === 0) { 154 | // $converter->setHTML($part->getBody()); 155 | $part->setBody($this->addTrackers($message->getBody(), $hash)); 156 | } 157 | } 158 | 159 | $tracker = SentEmail::create([ 160 | 'hash'=>$hash, 161 | 'headers'=>$headers->toString(), 162 | 'sender'=>$from_name." <".$from_email.">", 163 | 'recipient'=>$to_name.' <'.$to_email.'>', 164 | 'subject'=>$subject, 165 | 'opens'=>0, 166 | 'clicks'=>0, 167 | 'message_id'=>$message->getId(), 168 | 'meta'=>[], 169 | ]); 170 | 171 | Event::fire(new EmailSentEvent($tracker)); 172 | } 173 | } 174 | } 175 | 176 | /** 177 | * Purge old records in the database 178 | * 179 | * @return void 180 | */ 181 | protected function purgeOldRecords() 182 | { 183 | if(config('mail-tracker.expire-days') > 0) { 184 | $emails = SentEmail::where('created_at','<',\Carbon\Carbon::now() 185 | ->subDays(config('mail-tracker.expire-days'))) 186 | ->select('id') 187 | ->get(); 188 | SentEmailUrlClicked::whereIn('sent_email_id',$emails->pluck('id'))->delete(); 189 | SentEmail::whereIn('id',$emails->pluck('id'))->delete(); 190 | } 191 | } 192 | } 193 | -------------------------------------------------------------------------------- /src/MailTrackerController.php: -------------------------------------------------------------------------------- 1 | header('Content-type','image/gif'); 23 | $response->header('Content-Length',42); 24 | $response->header('Cache-Control','private, no-cache, no-cache=Set-Cookie, proxy-revalidate'); 25 | $response->header('Expires','Wed, 11 Jan 2000 12:59:00 GMT'); 26 | $response->header('Last-Modified','Wed, 11 Jan 2006 12:59:00 GMT'); 27 | $response->header('Pragma','no-cache'); 28 | 29 | $tracker = Model\SentEmail::where('hash',$hash) 30 | ->first(); 31 | if($tracker) { 32 | $tracker->opens++; 33 | $tracker->save(); 34 | Event::fire(new ViewEmailEvent($tracker)); 35 | } 36 | 37 | return $response; 38 | } 39 | 40 | public function getL($url, $hash) 41 | { 42 | $url = base64_decode(str_replace("$","/",$url)); 43 | $tracker = Model\SentEmail::where('hash',$hash) 44 | ->first(); 45 | if($tracker) { 46 | $tracker->clicks++; 47 | if(!$tracker->opens) $tracker->opens = 1; 48 | $tracker->save(); 49 | $url_clicked = Model\SentEmailUrlClicked::where('url',$url)->where('hash', $hash)->first(); 50 | if ($url_clicked) { 51 | $url_clicked->clicks++; 52 | $url_clicked->save(); 53 | } else { 54 | $url_clicked = Model\SentEmailUrlClicked::create([ 55 | 'sent_email_id' => $tracker->id, 56 | 'url' => $url, 57 | 'hash' => $tracker->hash, 58 | ]); 59 | } 60 | Event::fire(new LinkClickedEvent($tracker)); 61 | } 62 | 63 | return redirect($url); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/MailTrackerServiceProvider.php: -------------------------------------------------------------------------------- 1 | publishConfig(); 18 | $this->publishMigrations(); 19 | 20 | $this->registerSwiftPlugin(); 21 | 22 | $this->installRoutes(); 23 | } 24 | 25 | /** 26 | * Register the application services. 27 | * 28 | * @return void 29 | */ 30 | public function register() 31 | { 32 | $this->registerBindings($this->app); 33 | } 34 | 35 | protected function registerBindings(Application $app) 36 | { 37 | $app->alias('MailTracker', MailTracker::class); 38 | } 39 | 40 | protected function registerSwiftPlugin() 41 | { 42 | $this->app['mailer']->getSwiftMailer()->registerPlugin(new MailTracker()); 43 | } 44 | 45 | protected function publishConfig(){ 46 | $source = __DIR__ . '/../config/mail-tracker.php'; 47 | $this->publishes([$source => config_path('mail-tracker.php')]); 48 | $this->mergeConfigFrom($source, 'mail-tracker'); 49 | } 50 | 51 | protected function publishMigrations() 52 | { 53 | $this->loadMigrationsFrom(__DIR__.'/../migrations'); 54 | $this->publishes([ 55 | __DIR__.'/../migrations/2016_03_01_193027_create_sent_emails_table.php' => database_path('migrations/2016_03_01_193027_create_sent_emails_table.php') 56 | ], 'config'); 57 | $this->publishes([ 58 | __DIR__.'/../migrations/2016_09_07_193027_create_sent_emails_Url_Clicked_table.php' => database_path('migrations/2016_09_07_193027_create_sent_emails_Url_Clicked_table.php') 59 | ], 'config'); 60 | $this->publishes([ 61 | __DIR__.'/../migrations/2016_11_10_213551_add-message-id-to-sent-emails-table.php' => database_path('migrations/2016_11_10_213551_add-message-id-to-sent-emails-table.php') 62 | ], 'config'); 63 | 64 | } 65 | 66 | protected function installRoutes() 67 | { 68 | $config = $this->app['config']->get('mail-tracker.route', []); 69 | $config['namespace'] = 'Jeylabs\MailTracker'; 70 | Route::group($config, function() 71 | { 72 | Route::get('t/{hash}', 'MailTrackerController@getT')->name('mailTracker_t'); 73 | Route::get('l/{url}/{hash}', 'MailTrackerController@getL')->name('mailTracker_l'); 74 | Route::post('sns', 'SNSController@callback')->name('mailTracker_SNS'); 75 | }); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/Model/SentEmail.php: -------------------------------------------------------------------------------- 1 | 'collection', 23 | ]; 24 | 25 | /** 26 | * Returns a bootstrap class about the success/failure of the message 27 | * @return [type] [description] 28 | */ 29 | public function getReportClassAttribute() 30 | { 31 | if($this->meta->has('success')) { 32 | if($this->meta->get('success')) { 33 | return 'success'; 34 | } else { 35 | return 'danger'; 36 | } 37 | } else { 38 | return ''; 39 | } 40 | } 41 | 42 | /** 43 | * Returns the smtp detail for this message () 44 | * @return [type] [description] 45 | */ 46 | public function getSmtpInfoAttribute() 47 | { 48 | $meta = $this->meta; 49 | $responses = []; 50 | if($meta->has('smtpResponse')) { 51 | $response = $meta->get('smtpResponse'); 52 | $delivered_at = $meta->get('delivered_at'); 53 | $responses[] = $response.' - Delivered '.$delivered_at; 54 | } 55 | if($meta->has('failures')) { 56 | foreach($meta->get('failures') as $failure) { 57 | $responses[] = $failure['status'].' ('.$failure['action'].'): '.$failure['diagnosticCode'].' ('.$failure['emailAddress'].')'; 58 | } 59 | } else if($meta->has('complaint')) { 60 | $complaint_time = $meta->get('complaint_time'); 61 | if($meta->get('complaint_type')) { 62 | $responses[] = 'Complaint: '.$meta->get('complaint_type').' at '.$complaint_time; 63 | } else { 64 | $responses[] = 'Complaint at '.$complaint_time->format("n/d/y g:i a"); 65 | } 66 | } 67 | return implode(" | ",$responses); 68 | } 69 | 70 | /** 71 | * Returns the header requested from our stored header info 72 | */ 73 | public function getHeader($key) 74 | { 75 | $headers = collect(preg_split("/\r\n|\n|\r/", $this->headers)) 76 | ->transform(function($header) { 77 | list($key,$value) = explode(":",$header.":"); 78 | return collect([ 79 | 'key'=>trim($key), 80 | 'value'=>trim($value) 81 | ]); 82 | })->filter(function($header) { 83 | return $header->get('key'); 84 | })->keyBy('key') 85 | ->transform(function($header) { 86 | return $header->get('value'); 87 | }); 88 | return $headers->get($key); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/Model/SentEmailUrlClicked.php: -------------------------------------------------------------------------------- 1 | belongsTo(SentEmail::class,'sent_email_id'); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/SNSController.php: -------------------------------------------------------------------------------- 1 | message) { 21 | // phpunit cannot mock static methods so without making a facade 22 | // for SNSMessage we have to pass the json data in $request->message 23 | $message = new SNSMessage(json_decode($request->message, true)); 24 | } else { 25 | $message = SNSMessage::fromRawPostData(); 26 | $validator = new SNSMessageValidator(); 27 | $validator->validate($message); 28 | } 29 | 30 | switch($message->offsetGet('Type')) { 31 | case 'SubscriptionConfirmation': 32 | return $this->confirm_subscription($message); 33 | case 'Notification': 34 | return $this->process_notification($message); 35 | } 36 | } 37 | 38 | protected function confirm_subscription($message) 39 | { 40 | $client = new Guzzle(); 41 | $client->get($message->offsetGet('SubscribeURL')); 42 | return 'subscription confirmed'; 43 | } 44 | 45 | protected function process_notification($message) 46 | { 47 | $message = json_decode($message->offsetGet('Message')); 48 | switch($message->notificationType) { 49 | case 'Delivery': 50 | $this->process_delivery($message); 51 | break; 52 | case 'Bounce': 53 | $this->process_bounce($message); 54 | if($message->bounce->bounceType == 'Permanent') { 55 | foreach($message->bounce->bouncedRecipients as $recipient) { 56 | Event::fire(new PermanentBouncedMessageEvent($recipient)); 57 | } 58 | } 59 | break; 60 | case 'Complaint': 61 | $this->process_complaint($message); 62 | foreach($message->complaint->complainedRecipients as $recipient) { 63 | Event::fire(new PermanentBouncedMessageEvent($recipient)); 64 | } 65 | break; 66 | } 67 | return 'notification processed'; 68 | } 69 | 70 | protected function process_delivery($message) 71 | { 72 | $sent_email = SentEmail::where('message_id',$message->mail->messageId)->first(); 73 | if($sent_email) { 74 | $meta = $sent_email->meta; 75 | $meta->put('smtpResponse',$message->delivery->smtpResponse); 76 | $meta->put('success',true); 77 | $meta->put('delivered_at',$message->delivery->timestamp); 78 | $sent_email->meta = $meta; 79 | $sent_email->save(); 80 | } 81 | } 82 | 83 | public function process_bounce($message) 84 | { 85 | $sent_email = SentEmail::where('message_id',$message->mail->messageId)->first(); 86 | if($sent_email) { 87 | $meta = $sent_email->meta; 88 | $current_codes = []; 89 | if($meta->has('failures')) { 90 | $current_codes = $meta->get('failures'); 91 | } 92 | foreach($message->bounce->bouncedRecipients as $failure_details) { 93 | $current_codes[] = $failure_details; 94 | } 95 | $meta->put('failures',$current_codes); 96 | $meta->put('success',false); 97 | $sent_email->meta = $meta; 98 | $sent_email->save(); 99 | } 100 | } 101 | 102 | public function process_complaint($message) 103 | { 104 | $message_id = $message->mail->messageId; 105 | $sent_email = SentEmail::where('message_id',$message_id)->first(); 106 | if($sent_email) { 107 | $meta = $sent_email->meta; 108 | $meta->put('complaint',true); 109 | $meta->put('success',false); 110 | $meta->put('complaint_time',$message->complaint->timestamp); 111 | if(!empty($message->complaint->complaintFeedbackType)) { 112 | $meta->put('complaint_type',$message->complaint->complaintFeedbackType); 113 | } 114 | $sent_email->meta = $meta; 115 | $sent_email->save(); 116 | } 117 | } 118 | } 119 | --------------------------------------------------------------------------------