In this tutorial, we will see how to track emails in Laravel using AWS SES and SNS.
Introduction
In general, we attach some data to emails to track if the email was delivered or if the user opened it or the email failed (bounce).
We are going to use the SES and SNS to notify you whenever the above actions are performed.
Step 1 – Configure Access Key
In the .env file, update the below variables with your credentials,
AWS_ACCESS_KEY_ID='XYZ'
AWS_SECRET_ACCESS_KEY='**********'
AWS_DEFAULT_REGION='your-region'
If you don’t know how to create an AWS Access key in the AWS console, see here.
And then change default mailer to ses in env
MAIL_MAILER=ses
Step 2 – Create SNS Topic and Subscription
Go to the SNS dashboard and create a topic with standard configuration.
After creating a topic, go to the subscription panel and select the topic you created. Then select the HTTP protocol and set your site URL as the endpoint.
Step 3 – Configuration Set
Create a configuration set in the AWS console and link the SNS topic you created.
After creating a configuration set in AWS, update it to your config/services.php file
'ses' => [
'key' => env('AWS_ACCESS_KEY_ID'),
'secret' => env('AWS_SECRET_ACCESS_KEY'),
'region' => env('AWS_DEFAULT_REGION'),
'options' => [
'ConfigurationSetName' => env('AWS_SES_CONFIG_SET', 'Test'),
],
],
Step 4 – Verify Subscription – Routes
The most exclusive part is endpoint verification. You can only get notifications from AWS SNS after the subscription is verified. For that, you have to add the following code to your routes/api.php file.
Route::group([ 'middleware' => 'api' ], function ($router) {
Route::post('sns', 'App\Http\Controllers\YourController@sns_notifications');
});
CSRF error – When the request comes from outside of the Laravel, it will throw an unauthenticated error like the CSRF token is missing.
You have to edit the except variable with the below code to exclude the SNS route from the CSRF Verification.
Middleware/VerifyCsrfToken.php
protected $except = [
'api/sns'
];
Step 5 – Verify Subscription – Controller
Here is the main part, the SNS request will come to your sns_notifications function in your controller after setting up your route
Once you got the request, you have to verify the subscription by using the file_get_contents function.
public function sns_notifications(Request $request)
{
$data = $request->json()->all();
//Start verification
if ($data['Type'] == 'SubscriptionConfirmation'){
file_get_contents($data['SubscribeURL']); // To verify. (When the first request comes from AWS SNS)
}
//End verification
//Start tracking emails
elseif ($data['Type'] == 'Notification'){
// In this section, we will see how to track emails after a while.
}
//End tracking emails
return response('OK', 200);
}
Step 6 – Sending Emails
public function send_emails(Request $request)
{
$email_history = EmailHistory::create([
'from_address'=>$request->from_address,
'to_address' => $request->to_address,
'subject'=>$request->subject,
'body'=>$request->body,
]);
Mail::to('no-reply@yoursite.com')->queue(new TrackingEmail($request->body, $request->subject, $email_history->id));
}
EmailHistory is just a model which can use a different name — up to you.
Here I’ve attached email_histories migration for your reference.
Schema::create('email_histories', function (Blueprint $table) {
$table->id();
$table->string('to_address')->nullable();
$table->string('from_address')->nullable();
$table->text('subject')->nullable();
$table->longText('body')->nullable();
$table->text('message_id')->nullable();
$table->timestamp('opened')->nullable();
$table->timestamp('delivered')->nullable();
$table->timestamp('complaint')->nullable();
$table->timestamp('bounced')->nullable();
$table->timestamps();
});
Step 7 – Create a mailable – TrackingEmail
php artisan make:mailable TrackingEmail
<?php
namespace App\Mail;
use Illuminate\Bus\Queueable;
use Illuminate\Mail\Mailable;
use Illuminate\Queue\SerializesModels;
class TrackingEmail extends Mailable
{
use Queueable, SerializesModels;
/**
* Create a new message instance.
*
* @return void
*/
protected $email_body;
protected $email_subject;
protected $email_history_id;
public function __construct($email_body, $email_subject, $email_history_id)
{
$this->email_body = $email_body;
$this->email_subject = $email_subject;
$this->email_history_id = $email_history_id;
}
/**
* Build the message.
*
* @return $this
*/
public function build()
{
return $this->view('mails.tracking')
->subject($this->email_subject)
->with([
'email_body' => $this->email_body,
])
->withSwiftMessage(function ($message) {
$message->getHeaders()->addTextHeader(
'Custom-Message-ID', $this->email_history_id //Custom-Message-ID is the tracking id
);
});
}
}
Step 8 – Tracking Emails – Controller
We have come to the last part. Let’s finish the unfinished function. – sns_notifications()
public function sns_notifications(Request $request)
{
$data = $request->json()->all();
//Start verification
if ($data['Type'] == 'SubscriptionConfirmation'){
file_get_contents($data['SubscribeURL']); // To verify. (When the first request comes from AWS SNS)
}
//End verification
//Start tracking emails
elseif ($data['Type'] == 'Notification'){
$message = json_decode($data['Message'], true);
$message_id = null;
foreach($message['mail']['headers'] as $key => $newV){
if($newV['name'] == 'Custom-Message-ID'){ // This is a tracking variable that is attached when sending emails.
$message_id = $newV['value'];
}
}
try {
switch($message['eventType']){
case 'Bounce':
$bounce = $message['bounce'];
$email = EmailHistory::where('id', $message_id)->first();
$email->bounced = Carbon::now()->toDateTimeString();
$email->save();
break;
case 'Complaint':
$complaint = $message['complaint'];
$email = EmailHistory::where('id', $message_id)->first();
$email->complaint = Carbon::now()->toDateTimeString();
$email->save();
break;
case 'Open':
$open = $message['open'];
$email = EmailHistory::where('id', $message_id)->first();
$email->opened = Carbon::now()->toDateTimeString();
$email->save();
break;
case 'Delivery':
$delivery = $message['delivery'];
$email = EmailHistory::where('id', $message_id)->first();
$email->delivered = Carbon::now()->toDateTimeString();
$email->save();
break;
default:
// Do Nothing
break;
}
}catch (\Exception $e) {
Log::info($e->getMessage());
}
}
//End tracking emails
return response('OK', 200);
}
Conclusion
I hope it helped you with how to track emails in Laravel using AWS SES and SNS along with the Queue process. If you have any doubts, feel free to ask in the comments section down below.
Cheers!