In this article, we will walk you through the process of creating basic newsletter APIs using AWS Lambda, SNS (Simple Notification Service), IAM (Identity and Access Management), and API Gateway. The APIs will include functionalities for subscribing, unsubscribing, and publishing messages. The Lambda functions are written in Node.js, and API Gateway is used to expose these functions as RESTful APIs.
Prerequisites
Before you begin, ensure you have set up the following AWS services:
- AWS Lambda using NodeJS
- Amazon SNS
- IAM (Identity and Access Management)
- AWS Cloudwatch
- API Gateway
Additionally, ensure your Lambda function has the necessary permissions to interact with CloudWatch Logs and SNS. The IAM policy provided in the code examples covers these permissions.
Creating the AWS SNS Topic for the Newsletter
To leverage the power of AWS Simple Notification Service (SNS) for your newsletter, follow these step-by-step instructions to create a new SNS topic using the AWS Management Console:
- Sign in to the AWS Management Console: Open your web browser and navigate to the AWS Management Console. Sign in with your AWS account credentials.
- Navigate to Simple Notification Service (SNS): Once logged in, find and click on the “Services” dropdown at the top of the page. In the “Messaging” section, select “Simple Notification Service (SNS).”
- Access SNS Topics: In the SNS dashboard, locate “Topics” on the left-hand side and click on it. This will display the list of existing topics.
- Create a New Topic: Click the “Create topic” button.
- Configure Topic Details:
- Name: Provide a name for your topic (e.g.,
newsletterTopic
). - Display name (Optional): Add an optional name for better identification.
- Access Policy (Optional): Configure additional access policies if needed.
- Name: Provide a name for your topic (e.g.,
- Create Topic: Click the “Create topic” button after configuring the details.
- Note Topic ARN: Once the topic is created, note the Topic ARN. We will use it in our Lambda functions and other configurations.
Now, you’ve successfully created an SNS topic for your newsletter. This topic will serve as the central hub for publishing and distributing newsletter updates to all subscribed endpoints. Proceed to integrate this topic into your Lambda functions and API Gateway as outlined in the next section.
Setting up the AWS Lambda Functions
Now that the SNS topic is created, let’s set up the Lambda functions to handle subscription, unsubscription, and message publication. Before delving into the code, ensure that the Lambda function has the necessary access permissions to interact with both CloudWatch Logs and SNS.
Lambda Function Permissions
Grant the Lambda function the required permissions with the following IAM policy to communicate with CloudWatch and SNS. You can manage permission from Permissions inside the Configuration tab under the lambda function. We can edit policies inside the role associated with the lambda function.
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "logs:CreateLogGroup",
"Resource": "arn:aws:logs:us-east-1:842551175243:*"
},
{
"Effect": "Allow",
"Action": [
"logs:CreateLogStream",
"logs:PutLogEvents"
],
"Resource": [
"arn:aws:logs:us-east-1:842551175243:log-group:/aws/lambda/lambda-node-contact-form:*"
]
},
{
"Sid": "PublishSNSMessage",
"Effect": "Allow",
"Action": "sns:*",
"Resource": "arn:aws:sns:us-east-1:842551175243:contactFromTopic"
}
]
}
Let’s Code the Lambda Function
import { SNSClient } from "@aws-sdk/client-sns";
import {
SubscribeCommand,
UnsubscribeCommand,
PublishCommand,
ListSubscriptionsByTopicCommand
} from "@aws-sdk/client-sns";
These lines import the necessary modules from the @aws-sdk/client-sns
library. It includes the SNSClient
class and several command classes (SubscribeCommand
, UnsubscribeCommand
, PublishCommand
, and ListSubscriptionsByTopicCommand
) that represent the actions you can perform with Amazon SNS.
const REGION = "us-east-1";
const snsClient = new SNSClient({ region: REGION });
const SNS_TOPIC_ARN = 'arn:aws:sns:us-east-1:842551175243:contactFromTopic'; // a default topic
Here, the code sets the AWS region, creates an instance SNSClient
with the specified region, and defines the ARN (Amazon Resource Name) for the default SNS topic. Adjust the region and ARN according to your AWS environment and configurations.
const subscribtionPath = '/subscribe';
const unsubscribtionPath = '/unsubscribe';
const publishMessagePath = '/publish';
These variables define the API paths for the subscription, unsubscription, and message publishing endpoints.
export const handler = async (event) => {
let payload = null;
try{
payload = JSON.parse(event.body);
} catch(err) {
return setResponse(401, {msg: 'Opps! Invalid payload supplied!'});
}
if(event.httpMethod === 'POST' && event.path === subscribtionPath){
return await subscribe(payload);
} else if( event.httpMethod === 'POST' && event.path === unsubscribtionPath ){
return await unsubscribe(payload);
} else if( event.httpMethod === 'POST' && event.path === publishMessagePath ){
return await publishMessage(payload);
}
};
This function, handler
, is the main Lambda function. It parses the incoming event to extract the payload from the request body. It then determines the API endpoint (subscribe, unsubscribe, or publish) based on the HTTP method and path and calls the respective function.
async function _findSubscriptions(email) {
try{
const params = { TopicArn: SNS_TOPIC_ARN };
const data = await snsClient.send(new ListSubscriptionsByTopicCommand(params));
let subscriptions = data.Subscriptions.filter((el) => {
return el.Endpoint === email;
})
return subscriptions;
} catch(err) {
return false;
}
}
This function, _findSubscriptions
, queries the list of subscriptions for the specified email address within the given SNS topic.
async function subscribe(payload) {
const params = {
Protocol: "email",
TopicArn: SNS_TOPIC_ARN,
Endpoint: payload.email // valid email id
};
try {
let checkExisingSubs = await _findSubscriptions(params.Endpoint);
if(checkExisingSubs.length) {
return setResponse(200, checkExisingSubs[0]);
}
let data = await snsClient.send(new SubscribeCommand(params));
return setResponse(200, data);
} catch (err) {
return setResponse(403, err);
}
};
This function, subscribe
, handles the subscription process. It first checks if the email address is already subscribed. If yes, it returns the existing subscription details. If not, it subscribes the email address to the SNS topic and returns the result.
async function unsubscribe(payload) {
let params = {
Protocol: "email",
TopicArn: SNS_TOPIC_ARN,
Endpoint: payload.email // valid email id
};
try {
let checkExisingSubs = await _findSubscriptions(params.Endpoint);
if(!checkExisingSubs.length) return setResponse(200, checkExisingSubs);
params = {
SubscriptionArn: checkExisingSubs[0]['SubscriptionArn']
};
let data = await snsClient.send(new UnsubscribeCommand(params));
return setResponse(200, data);
} catch (err) {
return setResponse(403, err);
}
};
This function, unsubscribe
, handles the unsubscription process. It checks if the email address is subscribed. If yes, it extracts the subscription ARN and unsubscribes the email address from the SNS topic.
async function publishMessage(payload) {
var params = {
Message: payload.message.trim(),
TopicArn: SNS_TOPIC_ARN
};
try {
const data = await snsClient.send(new PublishCommand(params));
return setResponse(200, data);
} catch (err) {
return setResponse(403, err);
}
};
This function, publishMessage
, publishes a message to the SNS topic. It takes the message from the payload and sends it to all subscribed endpoints.
function setResponse(statusCode, body) {
return {
statusCode: statusCode,
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(body)
}
};
The setResponse
function is a utility function that creates a standardized response object with the provided status code and body. This response format is used throughout the Lambda functions to communicate results and errors.
These Lambda functions, combined with the SNS topic and API Gateway configuration, form a complete system for managing newsletter subscriptions, unsubscriptions, and message publishing. Adjust the code as needed for your specific use case and environment.
API Gateway Configuration
- Create API: In the AWS Management Console, navigate to API Gateway and create a new REST API.
- Create Resources: Add three resources under the API for
subscribe
,unsubscribe
, andpublish
.
- Create Methods: For each resource, create a
POST
method and link it to the corresponding Lambda function.
- Enable CORS: If needed, enable CORS for your API to allow cross-origin requests.
- Deploy API: Deploy your API to make it accessible through the provided endpoint.
Model Validation for Incoming Requests
We will use API Gateway Model validations for incoming requests using Resources Method request settings. Below are the two model schemas:
1. NewsletterPublishModel
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "Newsletter Schema",
"description": "Newsletter Schema",
"type": "object",
"properties": {
"message": {
"description": "Message subject",
"type": "string"
}
},
"required": [ "message" ]
}
2. NewsletterSubscribeModel
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "Newsletter Schema",
"description": "Newsletter Schema",
"type": "object",
"properties": {
"email": {
"description": "Email of the requester!",
"type": "string"
}
},
"required": [ "email" ]
}
Configure these model schemas in API Gateway to define the structure and validation rules for incoming requests.
Implementing Model Validation in API Gateway
- Navigate to API Gateway Console: Open the AWS Management Console, go to API Gateway, and select your API.
- Configure Method Request:
- Go to the Resources section and select the method (e.g., POST) under the desired resource (e.g., /publish).
- In the Method Execution pane, click on “Method Request.”
- Set Request Validator:
- Under “Settings,” set the Request Validator to “Validate query string parameters and headers.”
- Add Model:
- Click on the “+” sign to add a model.
- Enter a Model Name (e.g.,
NewsletterPublishModel
). - Enter the Content-Type (e.g.,
application/json
). - Copy and paste the
NewsletterPublishModel
schema into the Model Definition.
- Repeat for Subscribe Endpoint:
- Repeat the process for the subscribe endpoint by configuring the method request for the /subscribe resource and adding the
NewsletterSubscribeModel
schema.
- Repeat the process for the subscribe endpoint by configuring the method request for the /subscribe resource and adding the
Integrating Lambda Functions with API Gateway
Now that we have configured our API, let’s integrate the Lambda functions using Lambda integration.
- Configure Integration:
- In the API Gateway Console, go to the Resources section and select the desired resource (e.g., /publish).
- Click on the “Integration Request.”
- Lambda Function Integration:
- Set the Integration type to “Lambda Function.“
- We will select the ARN of our Lambda function.
- Deploy API:
- Deploy your API to make the changes live.
Now, your API Gateway is not only configured to validate incoming requests but also integrated with the respective Lambda functions for seamless execution. This ensures that validated data is processed by your Lambda functions, enhancing the reliability and security of your newsletter APIs.
Let’s test the APIs
Now that your API is set up and integrated with Lambda functions, you can use tools like curl
or, Postman to test the endpoints. Here are sample requests:
curl --location 'https://t5995ewoll.execute-api.us-west-2.amazonaws.com/test/subscribe' \
--header 'Content-Type: application/json' \
--data-raw '{
"email": "johndoe@gmail.com"
}'
curl --location 'https://t5995ewoll.execute-api.us-west-2.amazonaws.com/test/unsubscribe' \
--header 'Content-Type: application/json' \
--data-raw '{
"email": "johndoe@gmail.com"
}'
curl --location 'https://t5995ewoll.execute-api.us-west-2.amazonaws.com/test/publish' \
--header 'Content-Type: application/json' \
--data '{
"message": "This is a sample message"
}'
Conclusion
In this guide, we’ve successfully implemented basic newsletter APIs using AWS Lambda, SNS, and API Gateway. The integration of these services allows for seamless subscription, unsubscription, and message publishing functionalities. Leveraging the power of AWS, we’ve created a robust system for managing newsletter interactions.
It’s crucial to highlight the importance of securing APIs in real-life applications. While we’ve covered the fundamental implementation of the newsletter APIs, in real-world scenarios, protecting APIs from unauthorized access is paramount. In an upcoming article, we will delve into securing these APIs using Lambda authorizers.
Lambda authorizers provide a scalable and serverless way to control access to your APIs. By validating incoming requests before they reach your Lambda functions, you can ensure that only authorized users or systems interact with your newsletter APIs. Stay tuned for a detailed exploration of Lambda authorizers and their role in enhancing the security of your serverless applications.
For a hands-on experience and to explore the codebase further, you can access the GitHub repository: Newsletter APIs GitHub Repository.
As you continue to develop and enhance your applications, always prioritize security best practices to safeguard your data and provide a secure experience for your users.