URL Shortener + Tracking solution using Nexmo Messaging and AWS Serverless

Enrico Portolan
Jun 23 · 7 min read

In this walkthrough, we are going to build a Serverless URL shortener and tracking solution, using Nexmo Messages API and AWS Serverless Components.

Nexmo Messages API allows you to send messages through SMS, MMS, and social chat apps with a single API. One of the most reliable ways to promote your products is with an SMS. However, what can you do if your URL is too long?

The goal of this post is to build an API which receives, as an input, a text message with a long URL. The second step is to shorten it and then send the message. Finally, track the users’ click-through rates.

Nexmo Messages API

Implementing and managing multi-channel solutions is simple, quick, and organized using Messages API. In this demo, we will use it to send SMS. Furthermore, Nexmo offers the delivery receipt webhook which can be used to receive notifications when the delivery status of an SMS that you have sent changes ().

AWS Serverless components

The following are the AWS Services I used to build the solution: S3, CloudFront, Lambda, API Gateway, DynamoDB and Lambda@Edge. We’ll be using the to interact with AWS. The Serverless Framework provides a simple way to develop infinitely scaleable, pay-per-execution APIs. A single configuration file allows you to list your functions and define the endpoints that they’re subscribed to. If you want to learn more before we get started, you should have a read of their .

Note: at the moment Lambda@Edge is not natively supported by Serverless Framework so we need to upload the function using the AWS console.

Architecture

The whole architecture is divided into three parts:

  1. URL Shortener: Lambda function responsible for sending the SMS (using Nexmo NodeJS library), putting the object in S3 and saving the item into DynamoDB table.
  2. Click Tracking: Lambda@Edge responsible for tracking the users’ click of the shortened URL.
  3. SMS Delivery Receipt: Lambda function responsible for handling the delivery receipt callback sent by

Generate unique short URL

The URL Shortener function needs to be able to generate unique URL shortcodes, which will be represented as filenames in the S3 bucket. As a shortcode is just a filename, it’s possible to build a random alphanumeric string. Below is a sample code:

function generatePath(path = ‘’) { 
const characters = ‘abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789’;
const position = Math.floor(Math.random() * characters.length);
const character = characters.charAt(position);
if (path.length === 7) {
return path;
}
return generatePath(path + character);
}

Once the unique shortcode is generated, we put the object to an S3 bucket leveraging a rarely used feature of S3: Website-Redirect-Location metadata.

Using the redirection engine of S3

By simply adding a Website-Redirect-Location value to the metadata of the S3 object, browsers will redirect through an HTTP 301 response and the Location header ().
Now that we have set up the redirection engine, we still need to configure S3 to host our website. S3 gives us the opportunity to host only static websites (no server-side code). We just need to activate “website hosting” on the S3 bucket ().

The URL of an S3 object is composed of the S3 bucket address followed by the object’s name:

http://[bucket-name].s3-website-eu-west-1.amazonaws.com/[object-name]

For example, given a bucket with name “nexmo-shortener-url” in London (eu-west-2) and an object created with the “T80MeI4” shortcode, the URL will be:

http://nexmo-shortener-url.s3-website-eu-west-2.amazonaws.com/T80MeI4

If the user will visit the above URL, he will receive an HTTP 301 Response with Location header with the value of the Long URL.

Using Amazon CloudFront to wrap everything together

Since our final goal is to have a short URL, we use CloudFront to serve the request from the S3 bucket. CloudFront will give us a lot of advantages, for example:

  • HTTPS
  • Friendly domain name
  • Use AWS Edge location to serve the content and speed up the delivery of the content

The last step to obtain a short URL is to buy a short domain using Route 53 (or any other DNS provider). Then, we can route the traffic to the CloudFront distribution following these steps: .

At this point, we built a complete Serverless URL shortener solution. What if we want to track the users’ click?

Lambda + CloudFront = Lambda@Edge

Lambda@Edge lets you run Node.js Lambda functions to customize content that CloudFront delivers, executing the functions in AWS locations closer to the viewer. The functions run in response to CloudFront events, without provisioning or managing servers. You can use Lambda functions to change CloudFront requests and responses at the following points ():

  • After CloudFront receives a request from a viewer (viewer request)
  • Before CloudFront forwards the request to the origin (origin request)
  • After CloudFront receives the response from the origin (origin response)
  • Before CloudFront forwards the response to the viewer (viewer response)

In our use case, we will intercept the origin request event to acknowledge the click event in the link. When a user visits the link for the first time, the CloudFront distribution will send a request to the origin (S3 bucket) to retrieve the object. All the following requests will not hit the S3 bucket, so we will just track the first click.

Clarification: the S3 bucket is hit by CloudFront distribution once per Edge location till the cache expires.

Example: if the user clicks on the link for the first time, CloudFront will forward the request to the S3 bucket so that the following requests will have the object in CloudFront Edge Location cache. If a user from London clicks on the link for the first time, the Edge Location in London will have the object in cache BUT if another user clicks on the link from Sydney the Edge location will have to forward the request to the S3 object.

Now we know when a user clicks our URL for the very first time. We need to save this data in some kind of database. DynamoDB will be our choice.

URL tracking

The URL Shortener Lambda should save some details regarding the short URL created such as:

URL Shortener Lambda

For every request, the URL Shortener Lambda will create a unique string, send the SMS with the shortURL, put the object in S3 with the metadata and save the information above in DynamoDB.

Lambda@Edge

Once a user clicks on the link, the CloudFront distribution will trigger the Lambda@Edge function that will update the item in DynamoDB setting the ClickDate field with the current timestamp. The diagram above shows also a webhook feature that is possible to implement using Lambda@Edge function. Let’s say you want to notify your users using a webhook when the click event is triggered. You can do so by adding the code directly in the Lambda@Edge function.

Delivery Receipt Callback

It’s possible to track the status of the SMS using the delivery receipt callback offered by Nexmo Messages API. We can simply create an API Gateway endpoint backed by a Lambda function and link it to Delivery Receipt Webhook using Nexmo Dashboard (). When the Nexmo platform fires the webhook, our Lambda function gets the item from the DynamoDB by his MessageID and set the message status with the value given by the webhook (e.g. delivered), s.

What next?

Using the Nexmo API and AWS services gives us a lot of flexibility. For example, one of the main advantages of using Nexmo Messaging API is that we can use this URL shortener and tracking solution not only with the SMS but also with Whatsapp, Viber and Facebook Messenger using the same API.

We can also change the Cloudfront trigger to viewer request if we want to log every click on the link. Using the viewer request trigger, the Lambda@Edge will be fired every time a user requests the object to CloudFront (either in cache or not), so we can log the number of clicks on the link.

Lastly, if we would like to add TTL to the link created, we can leverage the DynamoDB TTL + DynamoDB Streams feature. Specifying a TTL to the items in Dynamo, we can trigger a Lambda function using DynamoDB Streams which delete the objects from S3 and the link will be automatically disabled.

Conclusion

I hope you find this article useful. If you have comments, suggestions and ideas, please leave them below in the comments section.

All the code is available here:

Resources: