loading...
Cover image for Build an email tracking service using Laravel, AWS SES & SNS services.

Build an email tracking service using Laravel, AWS SES & SNS services.

faisalshaikh8433 profile image Shaikh Faisal ・4 min read

Introduction:

AWS setup:

  1. Go to the SNS dashboard and create a new topic of which we want to receive notifications. This topic will serve as a bridge between SNS and SES. For example, I have created an email-notifications topic.
  2. Create a subscription for this topic where we will define our web app's public URL and process the notification data.
  3. Select the topic we created. The protocol can be https. The endpoint will be our web app's public route. We want a notification in JSON format so no need to enable raw message delivery. As soon as we create the subscription SNS will send a notification for confirmation of the link. We have to visit a subscription URL for confirming the subscription after that we will start receiving the notification. The code for confirming the subscribe URL is done below in the Laravel setup. If we miss confirming the subscription we can request a reconfirm.
  4. Set up the SES configuration set from the SES dashboard. Add a new configuration set, name it, and then edit it for defining the destination of email notifications like delivery, opened, etc. Select SNS from the add destination option, type the name, select the event types we want SES to send it to SNS, and the topic which we created in the first step. Now set up for SES and SNS is done.

Laravel setup:

  1. To use the Amazon SES driver for sending mail we have to install the Amazon AWS SDK for PHP, by adding the "aws/aws-sdk-php": "~3.0" to your composer.json file's require section and running the composer update command.

  2. Set the default option in your config/mail.php configuration file to SES and in config/services.php add key, secret, region and in options, array add configuration set name which we created in AWS setup.

'ses' => [
       'key' => env('AWS_ACCESS_KEY_ID'),
       'secret' => env('AWS_SECRET_ACCESS_KEY'),
       'region' => env('AWS_DEFAULT_REGION', 'ap-south-1'),
       'options' => [
           'ConfigurationSetName' => env('AWS_SES_CONFIG_SET', 'email-notifications'),
       ],
   ],
Enter fullscreen mode Exit fullscreen mode
  1. Whenever we send an email we will be getting a unique message-id through an X-SES-message-ID header which is added by AWS SES, we have to store this id in the database table for matching it with SNS notification messageId. So I have created an Amazon Controller and sendEmail method for sending the email and persisting the message-id and email details in the sent_emails table.

  2. Add a public route that we define as an endpoint for our SNS subscription in web.php. We will receive notifications as a post request. I have created an emailNotifications method in Amazon controller for handling this post request.
    Route::post(‘amazon-sns/email-notifications’, ‘AmazonController@emailNotifications’);

  3. As the data comes as a post request we have to tell our verify csrf token middleware to exclude this route. So in VerifyCsrfToken.php add ‘/amazon-sns/email-notifications’ in the except array.

  4. We receive 2 types of notifications, a subscription confirmation which is for confirming the URL endpoint when we create a subscription for the first time from the SNS dashboard and the other one is just normal notification with SNS events.
    So in the emailNotifications method, we take all the JSON from the request, then check the value of the ‘Type’ key if it is ‘SubscriptionConfirmation’ then we get the value of ‘SubscribeURL’ key and then using file_get_contents() method we visit the URL and confirm the subscription.

  5. If the ‘Type’ key is ‘Notification’ then we take the value of ‘Message’ key and decode it to an array, then check the value of ‘eventType’ key which can be ‘Bounce’, ‘Open’, ‘Delivery’, etc. Now we take the ‘Mail’ key whose value is an array and take the value of ‘messageId’ key from it and update the sent_emails table records according to the event by using this messageId and persisted messageId.

JSON data for Subscription confirmation example:

array (
 'Type' => 'SubscriptionConfirmation',
 'MessageId' => 'c9f90bc3-6b7a-4ab9-8156-5b27b17797f8',
 'Token' => '2336412f37fb687f5d51e6e2425f004aee4aa0c75a56575973dba0d4ca2fa9f2d12a4ee88495d74faebbdda39e034e91fc520e03aa05796e452ab5c12cc81a666e25fe99529b3975bb34a500d84d692dbcace3d64cc36273504da5b0464a954b7d48239c61198e2eb161f6cfcfa6304b566c685c407b9c65b7c07d7d2d11da32',
 'TopicArn' => 'arn:aws:sns:ap-south-1:499826649778:email-notifications',
'Message' => 'You have chosen to subscribe to the topic arn:aws:sns:ap-south-1:499826649778:email-notifications.
To confirm the subscription, visit the SubscribeURL included in this message.',
 'SubscribeURL' => 'https://sns.ap-south-1.amazonaws.com/?Action=ConfirmSubscription&TopicArn=arn:aws:sns:ap-south-1:499826649778:email-notifications&Token=2336412f37fb687f5d51e6e2425f004aee4aa0c75a56575973dba0d4ca2fa9f2d12a4ee88495d74faebbdda39e034e91fc520e03aa05796e452ab5c12cc81a666e25fe99529b3975bb34a500d84d692dbcace3d64cc36273504da5b0464a954b7d48239c61198e2eb161f6cfcfa6304b566c685c407b9c65b7c07d7d2d11da32',
 'Timestamp' => '2020-07-21T11:26:03.334Z',
 'SignatureVersion' => '1',
 'Signature' => 'QVIAlzHwf4DkxvtwBpKp9SByF9Z9Yh88t46ksTUkYA3Vr3nqkaKgVDmIE6wjdLn/B3LlkDU27zl9R+O2Sn4X9CSw9TT7B221zFCciEm6lbrQpIAX+KJ81blvaanR45pQY1VH9ageUv+2fk8VJvz1VnXw1+0fNIGx7k67aQbI1OttagbRr9wUIay/bwGq8LlP+rTviU8qg+AWbJgfg0iLhyoYSw5VaVL4aFyu7v5tLACMpRfPRDfIMaejmCJQBRfxWLfSFmALgkWcTIcUcWPllrw1sFEqgP+PzXlFfSfEkCzkcmqdy8++XfzEjME9lHky3kcOqgcaWse8Fs4GTFEsiQ==',
 'SigningCertURL' => 'https://sns.ap-south-1.amazonaws.com/SimpleNotificationService-a86cb10b4e1f29c941702d737128f7b6.pem',
)
Enter fullscreen mode Exit fullscreen mode

JSON data for Notification of delivery event example:

array (
 'Type' => 'Notification',
 'MessageId' => '81d5d58e-7838-5a2a-8fac-821ee35fc2e1',
 'TopicArn' => 'arn:aws:sns:ap-south-1:499826649778:email-notifications',
 'Subject' => 'Amazon SES Email Event Notification',
 'Message' => '{"eventType":"Delivery","mail":{"timestamp":"2020-07-21T07:28:36.625Z","source":"foo@gmail.com","sourceArn":"arn:aws:ses:ap-south-1:499826649778:identity/foo@gmail.com","sendingAccountId":"499826649778","messageId":"0109017370463711-e110c671-cfe4-48d2-9f16-a6ee18b6edad-000000","destination":["bar@gmail.com"],"headersTruncated":false,"headers":[{"name":"Message-ID","value":"<c48ec65d8e3ffcc42020b52f3c28ecb5@127.0.0.1>"},{"name":"Date","value":"Tue, 21 Jul 2020 07:28:36 +0000"},{"name":"Subject","value":"SES TEST"},{"name":"From","value":"Email_Tracker <foo@gmail.com>"},{"name":"To","value":"bar@gmail.com"},{"name":"MIME-Version","value":"1.0"},{"name":"Content-Type","value":"text/html; charset=utf-8"},{"name":"Content-Transfer-Encoding","value":"quoted-printable"}],"commonHeaders":{"from":["Email_Tracker <foo@gmail.com>"],"date":"Tue, 21 Jul 2020 07:28:36 +0000","to":["bar@gmail.com"],"messageId":"0109017370463711-e110c671-cfe4-48d2-9f16-a6ee18b6edad-000000","subject":"SES TEST"},"tags":{"ses:operation":["SendRawEmail"],"ses:configuration-set":["email-notifications"],"ses:source-ip":["103.88.223.122"],"ses:from-domain":["gmail.com"],"ses:caller-identity":["root"],"ses:outgoing-ip":["76.223.180.12"]}},"delivery":{"timestamp":"2020-07-21T07:28:39.465Z","processingTimeMillis":2840,"recipients":["bar@gmail.com"],"smtpResponse":"250 2.0.0 OK  1595316519 r188si12628852pfc.330 - gsmtp","reportingMTA":"c180-12.smtp-out.ap-south-1.amazonses.com"}}
',
 'Timestamp' => '2020-07-21T07:28:39.537Z',
 'SignatureVersion' => '1',
 'Signature' => 'I1OrPbUNXeFM681n6E9K0NlJovNUGTBOpk3OIylVAufwJQCeUF0/Syrn9RlUbDXcfe5z/HaI5T8SGB0Opnh53om7FU7++R5Nte2Zltp/MoC2DCVxGv+ycSlP4Zv4csftHLcrVu/XtXDGRA6JHystxeznY4iUnFY4IKz84d0l+eOAdTi/Z71qb3swMpzhGybLeNBhMUyBzv4FFlSbIYF2z5/gDCCPjDSIkvFE1nm4pl7Gk7YcU3Dyw8zlT05LbRMQRNR8jHzNhTzOXBLCA6Ej+mTZ+F2qWe+7dDRiSSOoVk2nO4NMo+9r0RmdslpRJzAaas/8e8i1uAwz9WskS/aX1A==',
 'SigningCertURL' => 'https://sns.ap-south-1.amazonaws.com/SimpleNotificationService-a86cb10b4e1f29c941702d737128f7b6.pem',
 'UnsubscribeURL' => 'https://sns.ap-south-1.amazonaws.com/?Action=Unsubscribe&SubscriptionArn=arn:aws:sns:ap-south-1:499826649778:email-notifications:c4f4e401-f0c6-4643-9fd4-8ba36fe34c8f',
)
Enter fullscreen mode Exit fullscreen mode

In the above JSON, only ‘eventType’ changes according to the event that happens and the rest remains the same.

Link to the source code: aws-ses-email-app

Discussion

pic
Editor guide